Index: libraries.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v retrieving revision 1.5 diff -u -p -r1.5 libraries.api.php --- libraries.api.php 3 Nov 2010 22:22:42 -0000 1.5 +++ libraries.api.php 3 Nov 2010 22:56:29 -0000 @@ -114,7 +114,7 @@ function hook_libraries_info() { // directory, which should contain the entire, original extracted library. $libraries['example'] = array( // Only used in administrative UI of Libraries API. - 'title' => 'Example library', + 'name' => 'Example library', 'vendor url' => 'http://example.com', 'download url' => 'http://example.com/download', // Optional: If, after extraction, the actual library files are contained in @@ -226,7 +226,7 @@ function hook_libraries_info() { // A very simple library. No changing APIs (hence, no versions), no variants. // Expected to be extracted into 'sites/all/libraries/simple'. $libraries['simple'] = array( - 'title' => 'Simple library', + 'name' => 'Simple library', 'vendor url' => 'http://example.com/simple', 'download url' => 'http://example.com/simple', 'version arguments' => array( @@ -245,7 +245,7 @@ function hook_libraries_info() { // A library that (naturally) evolves over time with API changes. $libraries['tinymce'] = array( - 'title' => 'TinyMCE', + 'name' => 'TinyMCE', 'vendor url' => 'http://tinymce.moxiecode.com', 'download url' => 'http://tinymce.moxiecode.com/download.php', 'path' => 'jscripts/tiny_mce', @@ -323,6 +323,31 @@ function hook_libraries_info() { ), ), ); + // An example library that is external. It has a local and a remote variant. + $libraries['openlayers'] = array( + 'name' => 'OpenLayers', + 'vendor url' => 'http://www.openlayers.org/', + 'download url' => 'http://trac.osgeo.org/openlayers/wiki/HowToDownload', + // 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', + 'files' => array( + 'js' => array('OpenLayers.js'), + ), + // It is usual for an external with a local and a remote variant to differ + // only in the library path. The actual library is usually the same, only + // the location is different. Therefore 'files' is specified on the top + // level and is not repeated. + 'variants' => array( + 'local' => array( + 'library path' => libraries_get_path('openlayers'), + ), + 'remote' => array( + 'library path' => 'http://openlayers.org/api', + ), + ), + ); return $libraries; } Index: libraries.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.module,v retrieving revision 1.11 diff -u -p -r1.11 libraries.module --- libraries.module 3 Nov 2010 22:22:42 -0000 1.11 +++ libraries.module 3 Nov 2010 22:56:30 -0000 @@ -262,10 +262,42 @@ function libraries_detect_library(&$libr $name = $library['name']; // Check whether the library exists. + $found = FALSE; if (!isset($library['library path'])) { + // If the library path hasn't been set Libraries API scans all libraries + // directories for the library. This is the intended usage of the API. $library['library path'] = libraries_get_path($name); } - if (!file_exists($library['library path'])) { + if ($library_exists = libraries_file_exists($library['library path'])) { + $found = TRUE; + } + // Check the variants for library paths as well. This is especially needed for + // libraries which specify the library path in their variant declarations + // (e.g. external libraries). + if (!empty($library['variants'])) { + foreach ($library['variants'] as $name => &$variant) { + // We consider the variant not found if one of the following is true: + // 1. The library itself was not found and the variant did not specify a + // library path. + // 2. The variant specified a library path that could not be found. + if (isset($variant['library path']) && $variant_exists = libraries_file_exists($variant['library path'])) { + $found = TRUE; + } + elseif (!$library_exists || (isset($variant_exists) && !$variant_exists)) { + $variant['installed'] = FALSE; + $variant['error'] = 'not found'; + $variant['error message'] = t('The %variant variant of %library could not be found.', array( + '%variant' => $name, + '%library' => $library['name'], + )); + } + } + } + // We consider the library found if one of the following is true: + // 1. 'library path' was specified in the top-level library or in one of the + // variants and resolves to an existing path. + // 2. libraries_get_path() finds the library in a libraries directory. + if (!$found) { $library['error'] = 'not found'; $library['error message'] = t('%library could not be found.', array('%library' => $library['name'])); return; @@ -315,6 +347,10 @@ function libraries_detect_library(&$libr // Check each variant if it is installed. if (!empty($library['variants'])) { foreach ($library['variants'] as $name => &$variant) { + // If errors have been set already (during path detection), do nothing. + if (isset($variant['error'])) { + break; + } // If no variant callback has been set, assume the variant to be // installed. if (!isset($variant['variant callback'])) { @@ -413,9 +449,10 @@ 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"; - $options = NULL; + // This is needed because drupal_add_css() does not detect external + // paths automatically. See http://drupal.org/node/953340 + $options = (url_is_external($data) ? array('type' => 'external') : NULL); } // In some cases, the first parameter ($data) is an array. Arrays can't // be passed as keys in PHP, so we have to get $data from the value @@ -437,9 +474,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; $count++; } } @@ -479,11 +516,12 @@ function libraries_get_version($library, 'cols' => 200, ); - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; - if (empty($options['file']) || !file_exists($file)) { + $filepath = $library['library path'] . '/' . $options['file']; + $filepath = (url_is_external($filepath) ? $filepath : DRUPAL_ROOT . '/' . $filepath); + if (empty($options['file']) || !libraries_file_exists($filepath)) { return; } - $file = fopen($file, 'r'); + $file = fopen($filepath, 'r'); while ($options['lines'] && $line = fgets($file, $options['cols'])) { if (preg_match($options['pattern'], $line, $version)) { fclose($file); @@ -494,3 +532,20 @@ 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.9 diff -u -p -r1.9 libraries.test --- tests/libraries.test 3 Nov 2010 22:22:42 -0000 1.9 +++ tests/libraries.test 3 Nov 2010 22:56:30 -0000 @@ -149,6 +149,24 @@ class LibrariesTestCase extends DrupalWe $this->verbose(var_export($library, TRUE)); $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.'); + // Test external library. + $library = libraries_info('example_external'); + libraries_detect_library($library); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['installed'], TRUE, 'External library found.'); + + // Test external library with a local and remote variant + $library = libraries_info('example_external_variant'); + libraries_detect_library($library); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['installed'], TRUE, 'External library with local and remote variant found.'); + + // Test external library with version callback. + $library = libraries_info('example_external_version'); + libraries_detect_library($library); + $this->verbose(var_export($library, TRUE)); + $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', 'Simple library loading'); @@ -170,6 +188,18 @@ class LibrariesTestCase extends DrupalWe // Test version overloading and variant loading. $this->drupalGet('libraries_test/versions_and_variants'); $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading'); + + // Test loading of an external library. + $this->drupalGet('libraries_test/external'); + $this->assertLibraryFiles('example_1', 'External library loading', array('js', 'css')); + + // Test the local variant of an external library. + $this->drupalGet('libraries_test/external_variant_local'); + $this->assertLibraryFiles('example_1', 'Loading of a local variant of an external library', array('js', 'css')); + + // Test the remote variant of an external library. + $this->drupalGet('libraries_test/external_variant_remote'); + $this->assertLibraryFiles('example_1', 'Loading of a remote variant of an external library', array('js', 'css')); } /** Index: tests/libraries_test.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_test.module,v retrieving revision 1.7 diff -u -p -r1.7 libraries_test.module --- tests/libraries_test.module 3 Nov 2010 22:22:42 -0000 1.7 +++ tests/libraries_test.module 3 Nov 2010 22:56:31 -0000 @@ -43,6 +43,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( 'name' => 'Example default version callback', @@ -334,6 +371,24 @@ function libraries_test_menu() { 'page arguments' => array('example_versions_and_variants', 'example_variant_2'), 'access callback' => TRUE, ); + $items['libraries_test/external'] = array( + 'title' => 'Test external library', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_external'), + 'access callback' => TRUE, + ); + $items['libraries_test/external_variant_local'] = array( + 'title' => 'Test local variant of an external library', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_external_variant', 'local'), + 'access callback' => TRUE, + ); + $items['libraries_test/external_variant_remote'] = array( + 'title' => 'Test remote variant of an external library', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_external_variant', 'remote'), + 'access callback' => TRUE, + ); return $items; }