Index: libraries.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v retrieving revision 1.4 diff -u -p -r1.4 libraries.api.php --- libraries.api.php 9 Oct 2010 22:26:03 -0000 1.4 +++ libraries.api.php 15 Oct 2010 17:19:19 -0000 @@ -17,9 +17,21 @@ * - title: The official, human-readable name of the library. * - vendor url: The URL of the homepage of the library. * - download url: The URL of a web page on which the library can be obtained. + * - library path: (optional) The absolute path to the library directory. This + * should not be declared normally, as it is automatically detected, to + * allow for multiple possible library locations. A valid use-case is an + * external library, in which case the full URL to the library should be + * specified here. * - path: (optional) A relative path from the directory of the library to the * actual library. Only required if the extracted download package contains * the actual library files in a sub-directory. + * - version: (optional) The version of the library. This should not be + * declared normally, as it is automatically detected (see 'version + * callback' below) to allow for version changes of libraries without code + * changes of implementing modules and to support different versions of a + * library simultaneously (though only one version can be installed per + * site). A valid use-case is an external library whose version cannot be + * determined programatically. * - version callback: (optional) The name of a function that detects and * returns the full version string of the library. The first argument is * always $library, an array containing all library information as described @@ -311,6 +323,34 @@ function hook_libraries_info() { ), ), ); + // An example library that is external. It has a local and a remote variant. + $libraries['openlayers'] = array( + 'title' => 'OpenLayers', + 'vendor url' => 'http://www.openlayers.org/', + 'download url' => 'http://trac.osgeo.org/openlayers/wiki/HowToDownload', + // This makes Libraries API not scan the filesystem for the library. + 'library path' => 'http://openlayers.org/api/OpenLayers.js', + // This should determine which variant is available, the local or the remote + // one, (with libraries_get_path()) and then conditionally determine the + // version (with libraries_get_version() and the respective parameters). + 'version callback' => 'openlayers_get_version', + // The explicit declaration below is in part redundant, but is used here for + // demonstration purposes. + 'variants' => array( + 'local' => array( + 'library path' => libraries_get_path('openlayers'), + 'files' => array( + 'js' => array('OpenLayers.js'), + ), + ), + 'remote' => array( + 'library path' => 'http://openlayers.org/api', + 'files' => array( + 'js' => array('OpenLayers.js'), + ), + ), + ), + ); return $libraries; } Index: libraries.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.module,v retrieving revision 1.9 diff -u -p -r1.9 libraries.module --- libraries.module 15 Oct 2010 15:15:53 -0000 1.9 +++ libraries.module 15 Oct 2010 18:13:52 -0000 @@ -131,26 +131,25 @@ function libraries_info_files() { if (!isset($profile)) { $profile = variable_get('install_profile', 'default'); } - $config = conf_path(); + $site = conf_path(); // Build a list of directories. $directories = module_invoke_all('libraries_info_file_paths'); $directories[] = 'libraries'; $directories[] = "libraries/$profile/libraries"; $directories[] = 'sites/all/libraries'; - $directories[] = "sites/$config/libraries"; + $directories[] = "sites/$site/libraries"; // Scan for info files. $files = array(); foreach ($directories as $dir) { - $files += file_scan_directory($dir, '/[a-z[a-z0-9_]+.info/', array( + $dir_files = file_scan_directory($dir, '/^[a-zA-Z0-9_-]+\.info$/', array( 'key' => 'name', 'recurse' => FALSE, )); - } - - foreach ($files as &$file) { - $file = $file->uri; + foreach ($dir_files as $name => $file) { + $files[$name] = $file->uri; + } } return $files; @@ -187,8 +186,7 @@ function libraries_info($library = NULL) } } // Gather information from .info files. - foreach (libraries_info_files() as $name => $path) { - $file = "$path/$name.info"; + foreach (libraries_info_files() as $name => $file) { $libraries[$name] = drupal_parse_info_file($file); } @@ -259,7 +257,7 @@ function libraries_detect_library(&$libr if (!isset($library['library path'])) { $library['library path'] = libraries_get_path($name); } - if (!file_exists($library['library path'])) { + if (!libraries_file_exists($library['library path'])) { $library['error'] = 'not found'; $library['error message'] = t('%library could not be found.', array('%library' => $library['title'])); return; @@ -393,8 +391,9 @@ function libraries_load_files($library, // If the value is not an array, it's a filename and passed as first // (and only) argument. if (!is_array($options)) { - // Prepend the library path to the file name. - $data = "$path/$options"; + // Prepend the library path to the file name, if it is not an external + // file. + $data = (url_is_external($options) ? $options : "$path/$options"); $options = NULL; } // In some cases, the first parameter ($data) is an array. Arrays can't @@ -416,9 +415,9 @@ function libraries_load_files($library, // Load PHP files. if (!empty($library['files']['php'])) { foreach ($library['files']['php'] as $file) { - $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file; - if (file_exists($file_path)) { - require_once $file_path; + $filepath = (url_is_external($file) ? $file : DRUPAL_ROOT . '/' . $path . '/' . $file); + if (libraries_file_exists($filepath)) { + include_once $filepath; } } } @@ -455,8 +454,8 @@ function libraries_get_version($library, 'cols' => 200, ); - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; - if (empty($options['file']) || !file_exists($file)) { + $file = (url_is_external($options['file']) ? $options['file'] : DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']); + if (empty($options['file']) || !libraries_file_exists($file)) { return; } $file = fopen($file, 'r'); @@ -470,3 +469,19 @@ function libraries_get_version($library, fclose($file); } +/** + * Wrapper function for file_exists() with support for external files. + * + * For external files, it doesn't actually check their availability, as that + * would have to be done with fopen, which causes significant overhead. Instead, + * it simply assumes all external files to exist. + * + * @param $filepath + * The path to the file. In case of an external file the full URL. + * + * @return + * A boolean indicating whether this file exists or not. + */ +function libraries_file_exists($filepath) { + return url_is_external($filepath) || file_exists($filepath); +} Index: tests/libraries.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries.test,v retrieving revision 1.8 diff -u -p -r1.8 libraries.test --- tests/libraries.test 15 Oct 2010 15:15:53 -0000 1.8 +++ tests/libraries.test 15 Oct 2010 18:17:05 -0000 @@ -31,9 +31,11 @@ class LibrariesTestCase extends DrupalWe */ function testLibraries() { // Test a library specified with an .info file gets detected. - $library = libraries_info('example'); + $library = libraries_info('libraries_info_example'); $expected = array( - 'title' => 'example', + 'name' => 'libraries_info_example', + 'title' => 'Example info file', + 'hidden' => TRUE, 'vendor url' => '', 'download url' => '', 'path' => '', @@ -44,6 +46,7 @@ class LibrariesTestCase extends DrupalWe 'versions' => array(), 'integration files' => array(), ); + $this->verbose(var_export($library, TRUE)); $this->assertEqual($library, $expected, 'Library specified with an .info file found'); // Test missing library. @@ -127,6 +130,16 @@ class LibrariesTestCase extends DrupalWe libraries_detect_library($library); $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.'); + // Test external library. + $library = libraries_info('example_external'); + libraries_detect_library($library); + $this->assertEqual($library['installed'], TRUE, 'External library found.'); + + // Test external library with version callback. + $library = libraries_info('example_external_version'); + libraries_detect_library($library); + $this->assertEqual($library['version'], '2', 'Library version of an external library found.'); + // Test loading of a simple library with a top-level files property. $this->drupalGet('libraries_test/simple'); $this->assertLibraryFiles('example_1'); @@ -148,6 +161,10 @@ class LibrariesTestCase extends DrupalWe // Test version overloading and variant loading. $this->drupalGet('libraries_test/versions_and_variants'); $this->assertLibraryFiles('example_4'); + + // Test loading of an external library. + $this->drupalGet('libraries_test/external'); + $this->assertLibraryFiles('example_1', array('js', 'css')); } /** @@ -163,22 +180,25 @@ class LibrariesTestCase extends DrupalWe * other files will be asserted to not be loaded. See * tests/example/README.txt for more information on how the loading of the * files is tested. + * @param $extensions + * (optional) The expected file extensions of $name. Defaults to + * array('js', 'css', 'php'). */ - function assertLibraryFiles($name) { + function assertLibraryFiles($name, $extensions = array('js', 'css', 'php')) { $names = drupal_map_assoc(array('example_1', 'example_2', 'example_3', 'example_4')); unset($names[$name]); // Test that the wrong files are not loaded. foreach ($names as $filename) { - $this->assertNoRaw("$filename.js", 'A wrong JavaScript file is not loaded.'); - $this->assertNoRaw("$filename.css", 'A wrong CSS file is not loaded.'); - $this->assertNoText("$filename.php", 'A wrong PHP file is not loaded.'); + foreach ($extensions as $extension) { + $this->assertNoRaw("$filename.$extension"); + } } // Test that the correct files are loaded. - $this->assertRaw("$name.js", 'The correct JavaScript file is loaded.'); - $this->assertRaw("$name.css", 'The correct CSS file is loaded.'); - $this->assertText("$name.php", 'The correct PHP file is loaded.'); + foreach ($extensions as $extension) { + $this->assertRaw("$name.$extension"); + } } } Index: tests/libraries_test.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_test.module,v retrieving revision 1.6 diff -u -p -r1.6 libraries_test.module --- tests/libraries_test.module 15 Oct 2010 15:15:53 -0000 1.6 +++ tests/libraries_test.module 15 Oct 2010 17:49:27 -0000 @@ -47,6 +47,43 @@ function libraries_test_libraries_info() ), ); + // Test external libraries. + $local_path = drupal_get_path('module', 'libraries_test') . '/example'; + $remote_path = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'libraries_test') . '/example'; + $libraries['example_external'] = array( + 'library path' => $remote_path, + 'version' => '2', + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + ), + ); + $libraries['example_external_variant'] = array( + 'version' => '2', + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + ), + 'variants' => array( + 'local' => array( + 'library path' => $local_path, + ), + 'remote' => array( + 'library path' => $remote_path, + ), + ), + ); + $libraries['example_external_version'] = array( + 'library path' => $remote_path, + 'version callback' => 'libraries_get_version', + 'version arguments' => array( + 'file' => 'README.txt', + // Version 2 + 'pattern' => '/Version (\d+)/', + 'lines' => 5, + ), + ); + // Test the default version callback. $libraries['example_default_version_callback'] = array( 'title' => 'Example default version callback', @@ -234,7 +271,7 @@ function libraries_test_libraries_info() * Implements hook_libraries_info_file_paths() */ function libraries_test_libraries_info_file_paths() { - return array(drupal_get_path('module', 'libraries_test') . '/example'); + return array(drupal_get_path('module', 'libraries_test') . '/libraries_info_example'); } /** @@ -342,6 +379,12 @@ function libraries_test_menu() { 'page arguments' => array('example_versions_and_variants', 'example_variant_2'), 'access callback' => TRUE, ); + $items['libraries_test/external'] = array( + 'title' => 'Test loading of an external library', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_external'), + 'access callback' => TRUE, + ); return $items; } @@ -352,21 +395,6 @@ function _libraries_test_load($library, libraries_load($library, $variant); // JavaScript and CSS files can be checked directly by SimpleTest, so we only // need to manually check for PHP files. - $output = ''; - if (function_exists('_libraries_test_example_1')) { - $output .= 'example_1.php'; - } - if (function_exists('_libraries_test_example_2')) { - $output .= 'example_2.php'; - } - if (function_exists('_libraries_test_example_3')) { - $output .= 'example_3.php'; - } - if (function_exists('_libraries_test_example_4')) { - $output .= 'example_4.php'; - } - if (function_exists('_libraries_test_integration_file')) { - $output .= 'libraries_test.inc'; - } + $output = implode("
\n", get_included_files()); return $output; } Index: tests/libraries_info_example/libraries_info_example.info =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_info_example/libraries_info_example.info,v retrieving revision 1.1 diff -u -p -r1.1 libraries_info_example.info --- tests/libraries_info_example/libraries_info_example.info 15 Oct 2010 16:11:08 -0000 1.1 +++ tests/libraries_info_example/libraries_info_example.info 15 Oct 2010 18:15:55 -0000 @@ -2,7 +2,7 @@ ; This is an example info file of a library used for testing purposes. ; Do not declare name manually. It is set automatically. -name = example_info_file +name = libraries_info_example title = Example info file ; Because Drupal thinks this is a module's .info file, it is in the 'libraries'