Index: libraries.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v retrieving revision 1.7 diff -u -p -r1.7 libraries.api.php --- libraries.api.php 20 Feb 2011 11:19:55 -0000 1.7 +++ libraries.api.php 21 Feb 2011 18:23:09 -0000 @@ -17,14 +17,33 @@ * - name: 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. - * - 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. - * - library path: (optional) The absolute path to the library directory. This + * - sub 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. + * - 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 callback: (optional) A callback to determine the path of the + * library. The first argument is always $name, a string containing the name + * of the library. There are two ways to declare the path callback's + * additional arguments, either as a single $options parameter or as + * multiple parameters, which correspond to the two ways to specify the + * argument values (see 'path arguments'). Defaults to libraries_get_path(). + * - path arguments: (optional) A list of arguments to pass to the path + * callback. Path arguments can be declared either as an associative array + * whose keys are the argument names or as an indexed array without + * specifying keys. If declared as an associative array, the arguments get + * passed to the location callback as a single $options parameter whose keys + * are the argument names (i.e. $options is identical to the specified + * array). If declared as an indexed array, the array values get passed to + * the version callback as seperate arguments in the order they were + * declared. The default location callback libraries_get_path() expects a + * single, associative array with named keys: + * - fallback: (optional) A fallback path to use in case the library is not + * found. This can be used for libraries that can be downloaded locally, + * but are also available externally. * - 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 @@ -120,7 +139,7 @@ function hook_libraries_info() { 'download url' => 'http://example.com/download', // Optional: If, after extraction, the actual library files are contained in // 'sites/all/libraries/example/lib', specify the relative path here. - 'path' => 'lib', + 'sub path' => 'lib', // Optional: Define a custom version detection callback, if required. 'version callback' => 'mymodule_get_version', // Specify arguments for the version callback. By default, @@ -239,7 +258,7 @@ function hook_libraries_info() { 'name' => 'TinyMCE', 'vendor url' => 'http://tinymce.moxiecode.com', 'download url' => 'http://tinymce.moxiecode.com/download.php', - 'path' => 'jscripts/tiny_mce', + 'sub path' => 'jscripts/tiny_mce', // The regular expression catches two parts (the major and the minor // version), which libraries_get_version() doesn't allow. 'version callback' => 'tinymce_get_version', @@ -309,6 +328,49 @@ function hook_libraries_info() { ), ), ); + + // A simple external library. + $libraries['external'] = array( + 'name' => 'External library', + // This library does not have a download url! + 'vendor url' => 'http://example.com/external', + // It is only availably externally so we must declare the library path. + 'path' => 'http://example.com/external/source', + // The version cannot be detected programmatically, so it must be declared + // upfront. + 'version' => '1.0.1', + // The library only consists of one file, which is located at + // http://example.com/external/source/external.js. We have already declared + // the library path above, so we only need to specify the filename here. + 'files' => array( + 'js' => array( + 'external.js', + ), + ), + ); + + // A library that is both available locally and externally, i.e. you can + // download it to your server for performance or use the online version for + // convenience. + $libraries['openlayers'] = array( + 'name' => 'OpenLayers', + 'vendor url' => 'http://openlayers.org/', + 'download url' => 'http://trac.osgeo.org/openlayers/wiki/HowToDownload', + // Because we want people to be able to use the library locally, but fall + // back to the external library, we declare the optional $remote_path + // argument for libraries_get_path(). The path we specify is then used as a + // fall back, if the library is not found locally. + 'path arguments' => array( + 'fallback' => 'http://openlayers.org/api', + ), + // Regardless of whether it is available locally or externally, the files + // are the same. + '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.20 diff -u -p -r1.20 libraries.module --- libraries.module 29 Jan 2011 18:41:15 -0000 1.20 +++ libraries.module 21 Feb 2011 18:23:10 -0000 @@ -7,102 +7,6 @@ */ /** - * Gets the path of a library. - * - * @param $name - * The machine name of a library to return the path for. - * @param $base_path - * Whether to prefix the resulting path with base_path(). - * - * @return - * The path to the specified library. - * - * @ingroup libraries - */ -function libraries_get_path($name, $base_path = FALSE) { - $libraries = &drupal_static(__FUNCTION__); - - if (!isset($libraries)) { - $libraries = libraries_get_libraries(); - } - - $path = ($base_path ? base_path() : ''); - if (!isset($libraries[$name])) { - // Most often, external libraries can be shared across multiple sites, so - // we return sites/all/libraries as the default path. - $path .= 'sites/all/libraries/' . $name; - } - else { - $path .= $libraries[$name]; - } - - return $path; -} - -/** - * Returns an array of library directories. - * - * Returns an array of library directories from the all-sites directory - * (i.e. sites/all/libraries/), the profiles directory, and site-specific - * directory (i.e. sites/somesite/libraries/). The returned array will be keyed - * by the library name. Site-specific libraries are prioritized over libraries - * in the default directories. That is, if a library with the same name appears - * in both the site-wide directory and site-specific directory, only the - * site-specific version will be listed. - * - * @return - * A list of library directories. - * - * @ingroup libraries - */ -function libraries_get_libraries() { - $directory = 'libraries'; - $searchdir = array(); - $profile = drupal_get_profile(); - $config = conf_path(); - - // Similar to 'modules' and 'themes' directories in the root directory, - // certain distributions may want to place libraries into a 'libraries' - // directory in Drupal's root directory. - $searchdir[] = $directory; - - // The 'profiles' directory contains pristine collections of modules and - // themes as organized by a distribution. It is pristine in the same way - // that /modules is pristine for core; users should avoid changing anything - // there in favor of sites/all or sites/ directories. - if (file_exists("profiles/$profile/$directory")) { - $searchdir[] = "profiles/$profile/$directory"; - } - - // Always search sites/all/*. - $searchdir[] = 'sites/all/' . $directory; - - // Also search sites//*. - if (file_exists("$config/$directory")) { - $searchdir[] = "$config/$directory"; - } - - // Retrieve list of directories. - // @todo Core: Allow to scan for directories. - $directories = array(); - $nomask = array('CVS'); - foreach ($searchdir as $dir) { - if (is_dir($dir) && $handle = opendir($dir)) { - while (FALSE !== ($file = readdir($handle))) { - if (!in_array($file, $nomask) && $file[0] != '.') { - if (is_dir("$dir/$file")) { - $directories[$file] = "$dir/$file"; - } - } - } - closedir($handle); - } - } - - return $directories; -} - -/** * Looks for library info files. * * This function scans the following directories for info files: @@ -191,8 +95,9 @@ function libraries_info($name = NULL) { 'name' => $machine_name, 'vendor url' => '', 'download url' => '', - 'path' => '', - 'library path' => NULL, + 'sub path' => '', + 'path callback' => 'libraries_get_path', + 'path arguments' => array(), 'version callback' => 'libraries_get_version', 'version arguments' => array(), 'files' => array(), @@ -248,10 +153,18 @@ function libraries_detect_library(&$libr $library['installed'] = FALSE; // Check whether the library exists. - if (!isset($library['library path'])) { - $library['library path'] = libraries_get_path($library['machine name']); + if (!isset($library['path'])) { + // We support both a single parameter, which is an associative array, and an + // indexed array of multiple parameters. + if (isset($library['path arguments'][0])) { + // Add the library name as the first argument. + $library['path'] = call_user_func_array($library['path callback'], array_merge(array($name), $library['path arguments'])); + } + else { + $library['path'] = $library['path callback']($name, $library['path arguments']); + } } - if (!file_exists($library['library path'])) { + if (empty($library['path']) || !libraries_file_exists($library['path'])) { $library['error'] = 'not found'; $library['error message'] = t('The %library library could not be found.', array( '%library' => $library['name'], @@ -402,15 +315,15 @@ function libraries_load_files($library) foreach ($library['integration files'] as $module => $files) { libraries_load_files(array( 'files' => $files, - 'path' => '', - 'library path' => drupal_get_path('module', $module), + 'sub path' => '', + 'path' => drupal_get_path('module', $module), )); } } // Construct the full path to the library for later use. - $path = $library['library path']; - $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path); + $path = $library['path']; + $path = ($library['sub path'] !== '' ? $path . '/' . $library['sub path'] : $path); // Count the number of loaded files for the return value. $count = 0; @@ -427,7 +340,9 @@ function libraries_load_files($library) 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 @@ -449,9 +364,10 @@ 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 = "$path/$file"; + $filepath = (url_is_external($filepath) ? $filepath : DRUPAL_ROOT . "/$filepath"); + if (libraries_file_exists($filepath)) { + require_once $filepath; $count++; } } @@ -461,6 +377,119 @@ function libraries_load_files($library) } /** + * 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); +} + +/** + * Gets the path of an arbitrary library. + * + * @param $name + * The machine name of a library to return the path for. + * @param $options + * An associative array of options. Possible options are: + * - fallback: (optional) A fallback path to use in case the library is not + * found. This can be used for libraries that can be downloaded locally, but + * are also available externally. + * + * @return + * The path to the specified library, or FALSE if it was not found. + * + * @ingroup libraries + */ +function libraries_get_path($name, $options = array()) { + $libraries = &drupal_static(__FUNCTION__); + + if (!isset($libraries)) { + $libraries = libraries_get_libraries(); + } + + if (!isset($libraries[$name])) { + $path = (isset($options['fallback']) ? $options['fallback'] : FALSE); + } + else { + $path = $libraries[$name]; + } + + return $path; +} + +/** + * Returns an array of library directories. + * + * Returns an array of library directories from the all-sites directory + * (i.e. sites/all/libraries/), the profiles directory, and site-specific + * directory (i.e. sites/somesite/libraries/). The returned array will be keyed + * by the library name. Site-specific libraries are prioritized over libraries + * in the default directories. That is, if a library with the same name appears + * in both the site-wide directory and site-specific directory, only the + * site-specific version will be listed. + * + * @return + * A list of library directories. + * + * @ingroup libraries + */ +function libraries_get_libraries() { + $directory = 'libraries'; + $searchdir = array(); + $profile = drupal_get_profile(); + $config = conf_path(); + + // Similar to 'modules' and 'themes' directories in the root directory, + // certain distributions may want to place libraries into a 'libraries' + // directory in Drupal's root directory. + $searchdir[] = $directory; + + // The 'profiles' directory contains pristine collections of modules and + // themes as organized by a distribution. It is pristine in the same way + // that /modules is pristine for core; users should avoid changing anything + // there in favor of sites/all or sites/ directories. + if (file_exists("profiles/$profile/$directory")) { + $searchdir[] = "profiles/$profile/$directory"; + } + + // Always search sites/all/*. + $searchdir[] = 'sites/all/' . $directory; + + // Also search sites//*. + if (file_exists("$config/$directory")) { + $searchdir[] = "$config/$directory"; + } + + // Retrieve list of directories. + // @todo Core: Allow to scan for directories. + $directories = array(); + $nomask = array('CVS'); + foreach ($searchdir as $dir) { + if (is_dir($dir) && $handle = opendir($dir)) { + while (FALSE !== ($file = readdir($handle))) { + if (!in_array($file, $nomask) && $file[0] != '.') { + if (is_dir("$dir/$file")) { + $directories[$file] = "$dir/$file"; + } + } + } + closedir($handle); + } + } + + return $directories; +} + +/** * Gets the version information from an arbitrary library. * * @param $library @@ -491,7 +520,7 @@ function libraries_get_version($library, 'cols' => 200, ); - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; + $file = DRUPAL_ROOT . '/' . $library['path'] . '/' . $options['file']; if (empty($options['file']) || !file_exists($file)) { return; } Index: tests/libraries.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries.test,v retrieving revision 1.12 diff -u -p -r1.12 libraries.test --- tests/libraries.test 27 Jan 2011 02:22:04 -0000 1.12 +++ tests/libraries.test 21 Feb 2011 18:23:11 -0000 @@ -34,7 +34,7 @@ class LibrariesTestCase extends DrupalWe $expected = array_merge(libraries_info('example_empty'), array( 'machine name' => 'example_files', 'name' => 'Example files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'files' => array( 'js' => array('example_1.js'), Index: tests/libraries_test.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/tests/libraries_test.module,v retrieving revision 1.8 diff -u -p -r1.8 libraries_test.module --- tests/libraries_test.module 27 Jan 2011 02:22:04 -0000 1.8 +++ tests/libraries_test.module 21 Feb 2011 18:23:12 -0000 @@ -13,17 +13,17 @@ function libraries_test_libraries_info() // Test library detection. $libraries['example_missing'] = array( 'name' => 'Example missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/missing', + 'path' => drupal_get_path('module', 'libraries') . '/tests/missing', ); $libraries['example_undetected_version'] = array( 'name' => 'Example undetected version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'path' => drupal_get_path('module', 'libraries') . '/tests', 'version callback' => '_libraries_test_return_version', 'version arguments' => array(FALSE), ); $libraries['example_unsupported_version'] = array( 'name' => 'Example unsupported version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'path' => drupal_get_path('module', 'libraries') . '/tests', 'version callback' => '_libraries_test_return_version', 'version arguments' => array('1'), 'versions' => array( @@ -33,7 +33,7 @@ function libraries_test_libraries_info() $libraries['example_supported_version'] = array( 'name' => 'Example supported version', - 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'path' => drupal_get_path('module', 'libraries') . '/tests', 'version callback' => '_libraries_test_return_version', 'version arguments' => array('1'), 'versions' => array( @@ -44,7 +44,7 @@ function libraries_test_libraries_info() // Test the default version callback. $libraries['example_default_version_callback'] = array( 'name' => 'Example default version callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version arguments' => array( 'file' => 'README.txt', // Version 1 @@ -56,7 +56,7 @@ function libraries_test_libraries_info() // Test a multiple-parameter version callback. $libraries['example_multiple_parameter_version_callback'] = array( 'name' => 'Example multiple parameter version callback', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', // Version 1 'version callback' => '_libraries_test_get_version', 'version arguments' => array('README.txt', '/Version (\d+)/', 5), @@ -65,7 +65,7 @@ function libraries_test_libraries_info() // Test a top-level files property. $libraries['example_files'] = array( 'name' => 'Example files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'files' => array( 'js' => array('example_1.js'), @@ -79,7 +79,7 @@ function libraries_test_libraries_info() // these files should be automatically loaded when the library is loaded. $libraries['example_integration_files'] = array( 'name' => 'Example integration files', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'integration files' => array( 'libraries_test' => array( @@ -93,7 +93,7 @@ function libraries_test_libraries_info() // Test version overloading. $libraries['example_versions'] = array( 'name' => 'Example versions', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '2', 'versions' => array( '1' => array( @@ -116,7 +116,7 @@ function libraries_test_libraries_info() // Test variant detection. $libraries['example_variant_missing'] = array( 'name' => 'Example variant missing', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'variants' => array( 'example_variant' => array( @@ -133,7 +133,7 @@ function libraries_test_libraries_info() $libraries['example_variant'] = array( 'name' => 'Example variant', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '1', 'variants' => array( 'example_variant' => array( @@ -151,7 +151,7 @@ function libraries_test_libraries_info() // Test correct behaviour with multiple versions and multiple variants. $libraries['example_versions_and_variants'] = array( 'name' => 'Example versions and variants', - 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', 'version' => '2', 'versions' => array( '1' => array( @@ -201,6 +201,17 @@ function libraries_test_libraries_info() ), ); + $libraries['example_external'] = array( + 'name' => 'Example external', + 'path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'files' => array( + 'js' => array(DRUPAL_ROOT . '/' . drupal_get_path('module', 'libraries') . '/tests/example/example_1.js'), + 'css' => array(DRUPAL_ROOT . '/' . drupal_get_path('module', 'libraries') . '/tests/example/example_1.css'), + 'php' => array(DRUPAL_ROOT . '/' . drupal_get_path('module', 'libraries') . '/tests/example/example_1.php'), + ), + ); + // This library is used together with libraries_info() to be populated with // the defaults. $libraries['example_empty'] = array(); @@ -260,7 +271,7 @@ function _libraries_test_return_version( */ function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $cols = 200) { - $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file; + $file = DRUPAL_ROOT . '/' . $library['path'] . '/' . $file; if (!file_exists($file)) { return; } @@ -320,6 +331,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 external file loading', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_external'), + 'access callback' => TRUE, + ); return $items; }