diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3f10c45..fb039ad 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,43 @@ -// $Id$ -Libraries 6.x-1.x, xxxx-xx-xx +Libraries 7.x-2.x, xxxx-xx-xx ----------------------------- +#961476 by tstoeckler: Changed libraries_get_path() to return FALSE by default. +#958162 by tstoeckler, sun, good_man: Allow to apply callbacks to libraries. +#1125904 by tstoeckler, boombatower: Fix drush libraries-list. +#1050076 by tstoeckler: Re-utilize libraries_detect() and remove libraries_detect_library(). +#466090 by tstoeckler: Add update function. +#466090 by tstoeckler: Allow cache to be flushed. +#466090 by tstoeckler, sun: Cache library information. +#1064008 by tstoeckler, bfroehle: Fix outdated API examples in libraries.api.php. +#1028744 by tstoeckler: Code clean-up. +#1023322 by tstoeckler, sun: Fixed libraries shouldn't be loaded multiple times. +#1024080 by hswong3i, tstoeckler: Fixed installation profile retrieval. +#995988 by good_man: Wrong default install profile. +#975498 by Gábor Hojtsy: Update JS/CSS-loading to new drupal_add_js/css() API. +#958162 by tsteoeckler, sun: Consistent variable naming. +#924130 by aaronbauman: Fixed libraries_get_path() should use drupal_static(). +#958162 by tstoeckler, sun: Code clean-up, tests revamp, more robust loading. +#919632 by tstoeckler, sun: Allow library information to be stored in info files. +by sun: Fixed testbot breaks upon directory name/info file name mismatch. +#864376 by tstoeckler, sun: Code-cleanup, allow hard-coded 'version'. +#939174 by sun, tstoeckler: Rename example.info to libraries_example.info. +by sun: Fixed testbot breaks upon .info file without .module file. +#542940 by tstoeckler, sun: Add libraries-list command. +#919632 by tstoeckler: Add example library info file for testing purposes. +#719896 by tstoeckler, sun: Documentation clean-up and tests improvement. +#542940 by sun: Added initial Drush integration file. +#719896 by tstoeckler, sun: Improved library detection and library loading. +#855050 by Gábor Hojtsy: Avoid call-time pass by reference in libraries_detect(). +#719896 by tstoeckler, sun: Added starting point for hook_libraries_info(). + + +Libraries 7.x-1.x, xxxx-xx-xx +----------------------------- + + +Libraries 7.x-1.0, 2010-01-27 +----------------------------- +#743522 by sun: Ported to D7. Libraries 6.x-1.0, 2010-01-27 diff --git a/README.txt b/README.txt index aa0a0f6..7db9c6f 100644 --- a/README.txt +++ b/README.txt @@ -1,4 +1,3 @@ -/* $Id$ */ -- SUMMARY -- diff --git a/libraries.api.php b/libraries.api.php new file mode 100644 index 0000000..66fa772 --- /dev/null +++ b/libraries.api.php @@ -0,0 +1,395 @@ + 'Example library', + 'vendor url' => 'http://example.com', + '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', + // Optional: Define a custom version detection callback, if required. + 'version callback' => 'mymodule_get_version', + // Specify arguments for the version callback. By default, + // libraries_get_version() takes a named argument array: + 'version arguments' => array( + 'file' => 'docs/CHANGELOG.txt', + 'pattern' => '@version\s+([0-9a-zA-Z\.-]+)@', + 'lines' => 5, + 'cols' => 20, + ), + // Default list of files of the library to load. Important: Only specify + // third-party files belonging to the library here, not integration files of + // your module. + 'files' => array( + // 'js' and 'css' follow the syntax of hook_library(), but file paths are + // relative to the library path. + 'js' => array( + 'exlib.js', + 'gadgets/foo.js', + ), + 'css' => array( + 'lib_style.css', + 'skin/example.css', + ), + // For PHP libraries, specify include files here, still relative to the + // library path. + 'php' => array( + 'exlib.php', + 'exlib.inc', + ), + ), + // Optional: Specify alternative variants of the library, if available. + 'variants' => array( + // All properties defined for 'minified' override top-level properties. + 'minified' => array( + 'files' => array( + 'js' => array( + 'exlib.min.js', + 'gadgets/foo.min.js', + ), + 'css' => array( + 'lib_style.css', + 'skin/example.css', + ), + ), + 'variant callback' => 'mymodule_check_variant', + 'variant arguments' => array( + 'variant' => 'minified', + ), + ), + ), + // Optional, but usually required: Override top-level properties for later + // versions of the library. The properties of the minimum version that is + // matched override the top-level properties. Note: + // - When registering 'versions', it usually does not make sense to register + // 'files', 'variants', and 'integration files' on the top-level, as most + // of those likely need to be different per version and there are no + // defaults. + // - The array keys have to be strings, as PHP does not support floats for + // array keys. + 'versions' => array( + '2' => array( + 'files' => array( + 'js' => array('exlib.js'), + 'css' => array('exlib_style.css'), + ), + ), + '3.0' => array( + 'files' => array( + 'js' => array('exlib.js'), + 'css' => array('lib_style.css'), + ), + ), + '3.2' => array( + 'files' => array( + 'js' => array( + 'exlib.js', + 'gadgets/foo.js', + ), + 'css' => array( + 'lib_style.css', + 'skin/example.css', + ), + ), + ), + ), + // Optional: Register files to auto-load for your module. All files must be + // keyed by module, and follow the syntax of the 'files' property. + 'integration files' => array( + 'mymodule' => array( + 'js' => array('ex_lib.inc'), + ), + ), + // Optionally register callbacks to apply to the library during different + // stages of its lifetime ('callback groups'). Here, a callback is + // registered in the 'detect' group. + 'callbacks' => array( + 'detect' => array( + 'mymodule_example_detect_callback', + ), + ), + ); + + // A very simple library. No changing APIs (hence, no versions), no variants. + // Expected to be extracted into 'sites/all/libraries/simple'. + $libraries['simple'] = array( + 'name' => 'Simple library', + 'vendor url' => 'http://example.com/simple', + 'download url' => 'http://example.com/simple', + 'version arguments' => array( + 'file' => 'readme.txt', + // Best practice: Document the actual version strings for later reference. + // 1.x: Version 1.0 + 'pattern' => '/Version (\d+)/', + 'lines' => 5, + ), + 'files' => array( + 'js' => array('simple.js'), + ), + ); + + // A library that (naturally) evolves over time with API changes. + $libraries['tinymce'] = array( + 'name' => 'TinyMCE', + 'vendor url' => 'http://tinymce.moxiecode.com', + 'download url' => 'http://tinymce.moxiecode.com/download.php', + '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', + 'version arguments' => array( + // It can be easier to parse the first characters of a minified file + // instead of doing a multi-line pattern matching in a source file. See + // 'lines' and 'cols' below. + 'file' => 'jscripts/tiny_mce/tiny_mce.js', + // Best practice: Document the actual version strings for later reference. + // 2.x: this.majorVersion="2";this.minorVersion="1.3" + // 3.x: majorVersion:'3',minorVersion:'2.0.1' + 'pattern' => '@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@', + 'lines' => 1, + 'cols' => 100, + ), + 'versions' => array( + '2.1' => array( + 'files' => array( + 'js' => array('tiny_mce.js'), + ), + 'variants' => array( + 'source' => array( + 'files' => array( + 'js' => array('tiny_mce_src.js'), + ), + ), + ), + 'integration files' => array( + 'wysiwyg' => array( + 'js' => array('editors/js/tinymce-2.js'), + 'css' => array('editors/js/tinymce-2.css'), + ), + ), + ), + // Definition used if 3.1 or above is detected. + '3.1' => array( + // Does not support JS aggregation. + 'files' => array( + 'js' => array( + 'tiny_mce.js' => array('preprocess' => FALSE), + ), + ), + 'variants' => array( + // New variant leveraging jQuery. Not stable yet; therefore not the + // default variant. + 'jquery' => array( + 'files' => array( + 'js' => array( + 'tiny_mce_jquery.js' => array('preprocess' => FALSE), + ), + ), + ), + 'source' => array( + 'files' => array( + 'js' => array( + 'tiny_mce_src.js' => array('preprocess' => FALSE), + ), + ), + ), + ), + 'integration files' => array( + 'wysiwyg' => array( + 'js' => array('editors/js/tinymce-3.js'), + 'css' => array('editors/js/tinymce-3.css'), + ), + ), + ), + ), + ); + return $libraries; +} + +/** + * Alter the library information before detection and caching takes place. + * + * The library definitions are passed by reference. A common use-case is adding + * a module's integration files to the library array, so that the files are + * loaded whenever the library is. As noted above, it is important to declare + * integration files inside of an array, whose key is the module name. + * + * @see hook_libraries_info() + */ +function hook_libraries_info_alter(&$libraries) { + $files = array( + 'php' => array('example_module.php_spellchecker.inc'), + ); + $libraries['php_spellchecker']['integration files']['example_module'] = $files; +} + +/** + * Specify paths to look for library info files. + * + * Libraries API looks in the following directories for library info files by + * default: + * - libraries + * - profiles/$profile/libraries + * - sites/all/libraries + * - sites/$site/libraries + * This hook allows you to specify additional locations to look for library info + * files. This should only be used for modules that declare many libraries. + * Modules that only implement a few libraries should implement + * hook_libraries_info(). + * + * @return + * An array of paths. + */ +function hook_libraries_paths() { + // Taken from the Libraries test module, which needs to specify the path to + // the test library. + return array(drupal_get_path('module', 'libraries_test') . '/example'); +} diff --git a/libraries.drush.inc b/libraries.drush.inc new file mode 100644 index 0000000..512648c --- /dev/null +++ b/libraries.drush.inc @@ -0,0 +1,156 @@ + 'libraries_drush_list', + 'description' => dt('Lists registered library information.'), + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + ); + /**$items['libraries-download'] = array( + 'callback' => 'libraries_drush_download', + 'description' => dt('Downloads a registered library into the libraries directory for the active site.'), + 'arguments' => array( + 'name' => dt('The internal name of the registered library.'), + ), + );*/ + return $items; +} + +/** + * Implements hook_drush_help(). + */ +function libraries_drush_help($section) { + switch ($section) { + case 'drush:libraries-list': + return dt('Lists registered library information.'); + + case 'drush:libraries-download': + return dt('Downloads a registered library into the libraries directory for the active site. + +See libraries-list for a list of registered libraries.'); + } +} + +/** + * Lists registered library information. + */ +function libraries_drush_list() { + $libraries = array(); + foreach (libraries_info() as $name => $info) { + $libraries[$name] = libraries_detect($name); + } + ksort($libraries); + + if (empty($libraries)) { + drush_print('There are no registered libraries.'); + } + + else { + $header = array('Name', 'Status', 'Version', 'Variants'); + $rows = array(); + foreach ($libraries as $name => $library) { + // Status and version + if ($library['installed']) { + $status = 'OK'; + $version = $library['version']; + } + else { + $status = drupal_ucfirst($library['error']); + $version = (empty($library['version']) ? '-' : $library['version']); + } + // Variants + $variants = array(); + foreach ($library['variants'] as $variant_name => $variant) { + if ($variant['installed']) { + $variants[] = $variant_name; + } + } + if (empty($variants)) { + $variants = '-'; + } + else { + $variants = implode(', ', $variants); + } + + $rows[] = array($name, $status, $version, $variants); + } + $table = new Console_Table(); + drush_print($table->fromArray($header, $rows)); + } +} + +/** + * Downloads a library. + * + * @param $name + * The internal name of the library to download. + */ +function libraries_drush_download($name) { + return; + + // @todo Looks wonky? + if (!drush_shell_exec('type unzip')) { + return drush_set_error(dt('Missing dependency: unzip. Install it before using this command.')); + } + + // @todo Simply use current drush site. + $args = func_get_args(); + if ($args[0]) { + $path = $args[0]; + } + else { + $path = 'sites/all/libraries'; + } + + // Create the path if it does not exist. + if (!is_dir($path)) { + drush_op('mkdir', $path); + drush_log(dt('Directory @path was created', array('@path' => $path)), 'notice'); + } + + // Set the directory to the download location. + $olddir = getcwd(); + chdir($path); + + $filename = basename(COLORBOX_DOWNLOAD_URI); + $dirname = basename(COLORBOX_DOWNLOAD_URI, '.zip'); + + // Remove any existing Colorbox plugin directory + if (is_dir($dirname)) { + drush_log(dt('A existing Colorbox plugin was overwritten at @path', array('@path' => $path)), 'notice'); + } + // Remove any existing Colorbox plugin zip archive + if (is_file($filename)) { + drush_op('unlink', $filename); + } + + // Download the zip archive + if (!drush_shell_exec('wget '. COLORBOX_DOWNLOAD_URI)) { + drush_shell_exec('curl -O '. COLORBOX_DOWNLOAD_URI); + } + + if (is_file($filename)) { + // Decompress the zip archive + drush_shell_exec('unzip -qq -o '. $filename); + // Remove the zip archive + drush_op('unlink', $filename); + } + + // Set working directory back to the previous working directory. + chdir($olddir); + + if (is_dir($path .'/'. $dirname)) { + drush_log(dt('Colorbox plugin has been downloaded to @path', array('@path' => $path)), 'success'); + } + else { + drush_log(dt('Drush was unable to download the Colorbox plugin to @path', array('@path' => $path)), 'error'); + } +} diff --git a/libraries.info b/libraries.info index 5a5bd6b..99de57d 100644 --- a/libraries.info +++ b/libraries.info @@ -1,4 +1,3 @@ -; $Id$ name = Libraries description = Allows version dependent and shared usage of external libraries. core = 6.x diff --git a/libraries.install b/libraries.install new file mode 100644 index 0000000..f20e197 --- /dev/null +++ b/libraries.install @@ -0,0 +1,41 @@ + $file) { + $files[basename($filename, '.libraries')] = $file; + unset($files[$filename]); + } + + return $files; +} + +/** + * Invokes library callbacks. + * + * @param $group + * A string containing the group of callbacks that is to be applied. Should be + * either 'prepare', 'detect', or 'load'. + * @param $library + * An array of library information, passed by reference. + */ +function libraries_invoke($group, &$library) { + foreach ($library['callbacks'][$group] as $callback) { + libraries_traverse_library($library, $callback); + } +} + +/** + * Helper function to apply a callback to all parts of a library. + * + * Because library declarations can include variants and versions, and those + * version declarations can in turn include variants, modifying e.g. the 'files' + * property everywhere it is declared can be quite cumbersome, in which case + * this helper function is useful. + * + * @param $library + * An array of library information, passed by reference. + * @param $callback + * A string containing the callback to apply to all parts of a library. + */ +function libraries_traverse_library(&$library, $callback) { + // Always apply the callback to the top-level library. + $callback($library, NULL, NULL); + + // Apply the callback to versions. + if (isset($library['versions'])) { + foreach ($library['versions'] as $version_string => &$version) { + $callback($version, $version_string, NULL); + // Versions can include variants as well. + if (isset($version['variants'])) { + foreach ($version['variants'] as $version_variant_name => &$version_variant) { + $callback($version_variant, $version_string, $version_variant_name); + } + } + } + } + + // Apply the callback to variants. + if (isset($library['variants'])) { + foreach ($library['variants'] as $variant_name => &$variant) { + $callback($variant, NULL, $variant_name); + } + } +} + +/** + * Returns information about registered libraries. + * + * The returned information is unprocessed, i.e. as registered by modules. + * + * @param $name + * (optional) The machine name of a library to return registered information + * for, or FALSE if no library with the given name exists. If omitted, + * information about all libraries is returned. + * + * @return + * An associative array containing registered information for all libraries, + * or the registered information for the library specified by $name. + * + * @see hook_libraries_info() + * + * @todo Re-introduce support for include file plugin system - either by copying + * Wysiwyg's code, or directly switching to CTools. + */ +function libraries_info($name = NULL) { + static $libraries; + if (!isset($libraries)) { + $libraries = array(); + // Gather information from hook_libraries_info(). + foreach (module_implements('libraries_info') as $module) { + foreach (module_invoke($module, 'libraries_info') as $machine_name => $properties) { + $properties['module'] = $module; + $libraries[$machine_name] = $properties; + } + } + // Gather information from .info files. + // .info files override module definitions. + foreach (libraries_scan_info_files() as $machine_name => $file) { + $properties = drupal_parse_info_file($file->uri); + $properties['info file'] = $file->uri; + $libraries[$machine_name] = $properties; + } + + // Provide defaults. + foreach ($libraries as $machine_name => &$properties) { + $properties += array( + 'machine name' => $machine_name, + 'name' => $machine_name, + 'vendor url' => '', + 'download url' => '', + 'path' => '', + 'library path' => NULL, + 'version callback' => 'libraries_get_version', + 'version arguments' => array(), + 'files' => array(), + 'variants' => array(), + 'versions' => array(), + 'integration files' => array(), + 'callbacks' => array(), + ); + $properties['callbacks'] += array( + 'prepare' => array(), + 'detect' => array(), + 'load' => array(), + ); + } + + // Allow modules to alter the registered libraries. + drupal_alter('libraries_info', $libraries); + + // Invoke callbacks in the 'prepare' group. + foreach ($libraries as &$properties) { + libraries_invoke('prepare', $properties); + } + } + + if (isset($name)) { + return !empty($libraries[$name]) ? $libraries[$name] : FALSE; + } + return $libraries; +} + +/** + * Tries to detect a library and its installed version. + * + * @todo We need to figure out whether, and if, how we want to retain the + * processed information. I.e. either use a static cache here, or make + * libraries_info() conditionally invoke libraries_detect($name). D7 only way: + * Re-use drupal_static() of libraries_info() - but would still require to + * update the (DB) cache (there likely will be one soon). Also, we probably do + * not want to ALWAYS parse ALL possible libraries; rather, the + * requesting/consuming module likely wants to know whether a list of + * supported libraries (possibly those registered by itself, or in a certain + * "category") is available... Food for thought. + * + * @param $name + * The machine name of a library to return registered information for. + * + * @return + * An associative array containing registered information for the library + * specified by $name. In addition to the keys returned by libraries_info() + * libraries contain the following keys: + * - installed: A boolean indicating whether the library is installed. Note + * that not only the top-level library, but also each variant contains this + * key. + * - version: If the version could be detected, the full version string. + * - error: If an error occurred during library detection, one of the + * following error statuses: "not found", "not detected", "not supported". + * - error message: If an error occurred during library detection, a detailed + * error message. + * + * @see libraries_info() + */ +function libraries_detect($name) { + $library = libraries_info($name); + + $library['installed'] = FALSE; + + // Check whether the library exists. + if (!isset($library['library path'])) { + $library['library path'] = libraries_get_path($library['machine name']); + } + if ($library['library path'] === FALSE || !file_exists($library['library path'])) { + $library['error'] = 'not found'; + $library['error message'] = t('The %library library could not be found.', array( + '%library' => $library['name'], + )); + return $library; + } + + // Detect library version, if not hardcoded. + if (!isset($library['version'])) { + // We support both a single parameter, which is an associative array, and an + // indexed array of multiple parameters. + if (isset($library['version arguments'][0])) { + // Add the library as the first argument. + $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments'])); + } + else { + $library['version'] = $library['version callback']($library, $library['version arguments']); + } + if (empty($library['version'])) { + $library['error'] = 'not detected'; + $library['error message'] = t('The version of the %library library could not be detected.', array( + '%library' => $library['name'], + )); + return $library; + } + } + + // Determine to which supported version the installed version maps. + if (!empty($library['versions'])) { + ksort($library['versions']); + $version = 0; + foreach ($library['versions'] as $supported_version => $version_properties) { + if (version_compare($library['version'], $supported_version, '>=')) { + $version = $supported_version; + } + } + if (!$version) { + $library['error'] = 'not supported'; + $library['error message'] = t('The installed version %version of the %library library is not supported.', array( + '%version' => $library['version'], + '%library' => $library['name'], + )); + return $library; + } + + // Apply version specific definitions and overrides. + $library = array_merge($library, $library['versions'][$version]); + unset($library['versions']); + } + + // Check each variant if it is installed. + if (!empty($library['variants'])) { + foreach ($library['variants'] as $variant_name => &$variant) { + // If no variant callback has been set, assume the variant to be + // installed. + if (!isset($variant['variant callback'])) { + $variant['installed'] = TRUE; + } + else { + // We support both a single parameter, which is an associative array, + // and an indexed array of multiple parameters. + if (isset($variant['variant arguments'][0])) { + // Add the library as the first argument, and the variant name as the second. + $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments'])); + } + else { + $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']); + } + if (!$variant['installed']) { + $variant['error'] = 'not found'; + $variant['error message'] = t('The %variant variant of the %library library could not be found.', array( + '%variant' => $variant_name, + '%library' => $library['name'], + )); + } + } + } + } + + // If we end up here, the library should be usable. + $library['installed'] = TRUE; + + // Invoke callbacks in the 'detect' group. + libraries_invoke('detect', $library); + + return $library; +} + +/** + * Loads a library. + * + * @param $name + * The name of the library to load. + * @param $variant + * The name of the variant to load. Note that only one variant of a library + * can be loaded within a single request. The variant that has been passed + * first is used; different variant names in subsequent calls are ignored. + * + * @return + * An associative array of the library information as returned from + * libraries_info(). The top-level properties contain the effective definition + * of the library (variant) that has been loaded. Additionally: + * - installed: Whether the library is installed, as determined by + * libraries_detect_library(). + * - loaded: Either the amount of library files that have been loaded, or + * FALSE if the library could not be loaded. + * See hook_libraries_info() for more information. + */ +function libraries_load($name, $variant = NULL) { + static $loaded = array(); + + if (!isset($loaded[$name])) { + $library = cache_get($name, 'cache_libraries'); + if ($library) { + $library = $library->data; + } + else { + $library = libraries_detect($name); + cache_set($name, $library, 'cache_libraries'); + } + + // If a variant was specified, override the top-level properties with the + // variant properties. + if (isset($variant)) { + // Ensure that the $variant key exists, and if it does not, set its + // 'installed' property to FALSE by default. This will prevent the loading + // of the library files below. + $library['variants'] += array($variant => array('installed' => FALSE)); + $library = array_merge($library, $library['variants'][$variant]); + } + // Regardless of whether a specific variant was requested or not, there can + // only be one variant of a library within a single request. + unset($library['variants']); + + // If the library (variant) is installed, load it. + $library['loaded'] = FALSE; + if ($library['installed']) { + // Invoke callbacks in the 'load' group. + libraries_invoke('load', $library); + + $library['loaded'] = libraries_load_files($library); + } + $loaded[$name] = $library; + } + + return $loaded[$name]; +} + +/** + * Loads a library's files. + * + * @param $library + * An array of library information as returned by libraries_info(). + * + * @return + * The number of loaded files. + */ +function libraries_load_files($library) { + // Load integration files. + if (!empty($library['integration files'])) { + foreach ($library['integration files'] as $module => $files) { + libraries_load_files(array( + 'files' => $files, + 'path' => '', + 'library 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); + + // Count the number of loaded files for the return value. + $count = 0; + + // Load both the JavaScript and the CSS files. + // The parameters for drupal_add_js() and drupal_add_css() require special + // handling. + // @see drupal_process_attached() + foreach (array('js', 'css') as $type) { + if (!empty($library['files'][$type])) { + foreach ($library['files'][$type] as $data => $options) { + // 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; + } + // 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 + // array. + if (is_numeric($data)) { + $data = $options['data']; + unset($options['data']); + } + // Apply the default group if the group isn't explicitly given. + if (!isset($options['group'])) { + $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT; + } + call_user_func('drupal_add_' . $type, $data, $options); + $count++; + } + } + } + + // 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; + $count++; + } + } + } + + return $count; +} + +/** + * Gets the version information from an arbitrary library. + * + * @param $library + * An associative array containing all information about the library. + * @param $options + * An associative array containing with the following keys: + * - file: The filename to parse for the version, relative to the library + * path. For example: 'docs/changelog.txt'. + * - pattern: A string containing a regular expression (PCRE) to match the + * library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. + * - lines: (optional) The maximum number of lines to search the pattern in. + * Defaults to 20. + * - cols: (optional) The maximum number of characters per line to take into + * account. Defaults to 200. In case of minified or compressed files, this + * prevents reading the entire file into memory. + * + * @return + * A string containing the version of the library. + * + * @see libraries_get_path() + */ +function libraries_get_version($library, $options) { + // Provide defaults. + $options += array( + 'file' => '', + 'pattern' => '', + 'lines' => 20, + 'cols' => 200, + ); + + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file']; + if (empty($options['file']) || !file_exists($file)) { + return; + } + $file = fopen($file, 'r'); + while ($options['lines'] && $line = fgets($file, $options['cols'])) { + if (preg_match($options['pattern'], $line, $version)) { + fclose($file); + return $version[1]; + } + $options['lines']--; + } + fclose($file); +} + +/** + * Gets the name of the installed profile. + */ +function libraries_get_profile() { + global $profile; + + // When this function is called during Drupal's initial installation process, + // the name of the profile that is about to be installed is stored in the + // global $profile variable. At all other times, the regular system variable + // contains the name of the current profile, and we can call variable_get() + // to determine the profile. + if (!isset($profile)) { + $profile = variable_get('install_profile', 'default'); + } + return $profile; +} diff --git a/tests/example/README.txt b/tests/example/README.txt new file mode 100644 index 0000000..e3582c2 --- /dev/null +++ b/tests/example/README.txt @@ -0,0 +1,42 @@ + +Example library + +Version 1 + +This file is an example file to test version detection. + +The various other files in this directory are to test the loading of JavaScript, +CSS and PHP files. +- JavaScript: The filenames of the JavaScript files are asserted to be in the + raw HTML via SimpleTest. Since the filename could appear, for instance, in an + error message, this is not very robust. Explicit testing of JavaScript, + though, is not yet possible with SimpleTest. To allow for easier debugging, we + place the following text on the page: + "If this text shows up, no JavaScript test file was loaded." + This text is replaced via JavaScript by a text of the form: + "If this text shows up, [[file] was loaded successfully." + [file] is either 'example_1.js', 'example_2.js', 'example_3.js', + 'example_4.js' or 'libraries_test.js'. If you have SimpleTest's verbose mode + enabled and see the above text in one of the debug pages, the noted JavaScript + file was loaded successfully. +- CSS: The filenames of the CSS files are asserted to be in the raw HTML via + SimpleTest. Since the filename could appear, for instance, in an error + message, this is not very robust. Explicit testing of CSS, though, is not yet + possible with SimpleTest. Hence, the CSS files, if loaded, make the following + text a certain color: + "If one of the CSS test files has been loaded, this text will be colored: + - example_1: red + - example_2: green + - example_3: orange + - example_4: blue + - libraries_test: purple" + If you have SimpleTest's verbose mode enabled, and see the above text in a + certain color (i.e. not in black), a CSS file was loaded successfully. Which + file depends on the color as referenced in the text above. +- PHP: The loading of PHP files is tested by defining a dummy function in the + PHP files and then checking whether this function was defined using + function_exists(). This can be checked programatically with SimpleTest. +The loading of integration files is tested with the same method. The integration +files are libraries_test.js, libraries_test.css, libraries_test.inc and are +located in the tests directory alongside libraries_test.module (i.e. they are +not in the same directory as this file). diff --git a/tests/example/example_1.css b/tests/example/example_1.css new file mode 100644 index 0000000..a732bda --- /dev/null +++ b/tests/example/example_1.css @@ -0,0 +1,11 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div red. See README.txt for more information. + */ + +.libraries-test-css { + color: red; +} diff --git a/tests/example/example_1.js b/tests/example/example_1.js new file mode 100644 index 0000000..8a1b9a2 --- /dev/null +++ b/tests/example/example_1.js @@ -0,0 +1,18 @@ + +/** + * @file + * Test JavaScript file for Libraries loading. + * + * Replace the text in the 'libraries-test-javascript' div. See README.txt for + * more information. + */ + +(function ($) { + +Drupal.behaviors.librariesTest = { + attach: function(context, settings) { + $('.libraries-test-javascript').text('If this text shows up, example_1.js was loaded successfully.') + } +}; + +})(jQuery); diff --git a/tests/example/example_1.php b/tests/example/example_1.php new file mode 100644 index 0000000..92e6711 --- /dev/null +++ b/tests/example/example_1.php @@ -0,0 +1,12 @@ + 'Libraries detection and loading', + 'description' => 'Tests detection and loading of libraries.', + 'group' => 'Libraries API', + ); + } + + function setUp() { + parent::setUp('libraries', 'libraries_test'); + } + + /** + * Tests libraries detection and loading. + * + * @todo Better method name(s); split into detection/loading/overloading/etc. + */ + function testLibraries() { + // Test libraries_get_path(). + $this->assertEqual(libraries_get_path('example'), FALSE, 'libraries_get_path() returns FALSE for a missing library.'); + + // Test that library information is found correctly. + $expected = array_merge(libraries_info('example_empty'), array( + 'machine name' => 'example_files', + 'name' => 'Example files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + )); + $library = libraries_info('example_files'); + $this->verbose(var_export($expected, TRUE)); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library, $expected, 'Library information is correctly gathered.'); + + // Test a library specified with an .info file gets detected. + $expected = array_merge(libraries_info('example_empty'), array( + 'machine name' => 'example_info_file', + 'name' => 'Example info file', + 'info file' => drupal_get_path('module', 'libraries_test') . '/example/example_info_file.libraries.info', + )); + unset($expected['module']); + $library = libraries_info('example_info_file'); + $this->verbose(var_export($expected, TRUE)); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library, $expected, 'Library specified with an .info file found'); + + // Test missing library. + $library = libraries_detect('example_missing'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['error'], 'not found', 'Missing library not found.'); + $error_message = t('The %library library could not be found.', array( + '%library' => $library['name'], + )); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a missing library.'); + + // Test unknown library version. + $library = libraries_detect('example_undetected_version'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['error'], 'not detected', 'Undetected version detected as such.'); + $error_message = t('The version of the %library library could not be detected.', array( + '%library' => $library['name'], + )); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an undetected version.'); + + // Test unsupported library version. + $library = libraries_detect('example_unsupported_version'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['error'], 'not supported', 'Unsupported version detected as such.'); + $error_message = t('The installed version %version of the %library library is not supported.', array( + '%version' => $library['version'], + '%library' => $library['name'], + )); + $this->assertEqual($library['error message'], $error_message, 'Correct error message for a library with an unsupported version.'); + + // Test supported library version. + $library = libraries_detect('example_supported_version'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['installed'], TRUE, 'Supported library version found.'); + + // Test libraries_get_version(). + $library = libraries_detect('example_default_version_callback'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['version'], '1', 'Expected version returned by default version callback.'); + + // Test a multiple-parameter version callback. + $library = libraries_detect('example_multiple_parameter_version_callback'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['version'], '1', 'Expected version returned by multiple parameter version callback.'); + + // Test a top-level files property. + $library = libraries_detect('example_files'); + $files = array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['files'], $files, 'Top-level files property works.'); + + // Test version-specific library files. + $library = libraries_detect('example_versions'); + $files = array( + 'js' => array('example_2.js'), + 'css' => array('example_2.css'), + 'php' => array('example_2.php'), + ); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['files'], $files, 'Version-specific library files found.'); + + // Test missing variant. + $library = libraries_detect('example_variant_missing'); + $variants = array_keys($library['variants']); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['variants']['example_variant']['error'], 'not found', 'Missing variant not found'); + $error_message = t('The %variant variant of the %library library could not be found.', array( + '%variant' => $variants[0], + '%library' => $library['name'], + )); + $this->assertEqual($library['variants']['example_variant']['error message'], $error_message, 'Correct error message for a missing variant.'); + + // Test existing variant. + $library = libraries_detect('example_variant'); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library['variants']['example_variant']['installed'], TRUE, 'Existing variant found.'); + + // Test the applying of callbacks. + $expected = array_merge(libraries_info('example_empty'), array( + 'machine name' => 'example_callback', + 'name' => 'Example callback', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'versions' => array( + '1' => array( + 'variants' => array( + 'example_variant' => array( + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ), + ), + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ), + ), + 'variants' => array( + 'example_variant' => array( + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ), + ), + 'callbacks' => array( + 'prepare' => array('_libraries_test_prepare_callback'), + 'detect' => array('_libraries_test_detect_callback'), + 'load' => array('_libraries_test_load_callback'), + ), + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + )); + // Test a callback in the 'prepare' phase. + $expected['prepare callback'] = 'applied (top-level)'; + $expected['versions']['1']['prepare callback'] = 'applied (version 1)'; + $expected['versions']['1']['variants']['example_variant']['prepare callback'] = 'applied (version 1, variant example_variant)'; + $expected['variants']['example_variant']['prepare callback'] = 'applied (variant example_variant)'; + $library = libraries_info('example_callback'); + $this->verbose(var_export($expected, TRUE)); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library, $expected, 'Prepare callback was applied correctly.'); + // Test a callback in the 'detect' phase. + unset($expected['versions']); + $expected['installed'] = TRUE; + $expected['prepare callback'] = 'applied (version 1)'; + $expected['variants']['example_variant']['installed'] = TRUE; + $expected['variants']['example_variant']['prepare callback'] = 'applied (version 1, variant example_variant)'; + $expected['detect callback'] = 'applied (top-level)'; + $expected['variants']['example_variant']['detect callback'] = 'applied (variant example_variant)'; + $library = libraries_detect('example_callback'); + $this->verbose(var_export($expected, TRUE)); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library, $expected, 'Detect callback was applied correctly.'); + + // Test a callback in the 'load' phase. + unset($expected['variants']); + $expected['loaded'] = 0; + $expected['load callback'] = 'applied (top-level)'; + $library = libraries_load('example_callback'); + $this->verbose(var_export($expected, TRUE)); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library, $expected, 'Load callback was applied correctly.'); + // This is not recommended usually and is only used for testing purposes. + drupal_static_reset('libraries_load'); + $expected['prepare callback'] = 'applied (version 1, variant example_variant)'; + $expected['detect callback'] = 'applied (variant example_variant)'; + $library = libraries_load('example_callback', 'example_variant'); + $this->verbose(var_export($expected, TRUE)); + $this->verbose(var_export($library, TRUE)); + $this->assertEqual($library, $expected, 'Load callback was applied correctly to a variant.'); + + // Test loading of a simple library with a top-level files property. + $this->drupalGet('libraries_test/files'); + $this->assertLibraryFiles('example_1', 'File loading'); + + // Test loading of integration files. + $this->drupalGet('libraries_test/integration_files'); + $this->assertRaw('libraries_test.js', 'Integration file loading: libraries_test.js found'); + $this->assertRaw('libraries_test.css', 'Integration file loading: libraries_test.css found'); + $this->assertRaw('libraries_test.inc', 'Integration file loading: libraries_test.inc found'); + + // Test version overloading. + $this->drupalGet('libraries_test/versions'); + $this->assertLibraryFiles('example_2', 'Version overloading'); + + // Test variant loading. + $this->drupalGet('libraries_test/variant'); + $this->assertLibraryFiles('example_3', 'Variant loading'); + + // Test version overloading and variant loading. + $this->drupalGet('libraries_test/versions_and_variants'); + $this->assertLibraryFiles('example_4', 'Concurrent version and variant overloading'); + } + + /** + * Helper function to assert that a library was correctly loaded. + * + * Asserts that all the correct files were loaded and all the incorrect ones + * were not. + * + * @param $name + * The name of the files that should be loaded. The current testing system + * knows of 'example_1', 'example_2', 'example_3' and 'example_4'. Each name + * has an associated JavaScript, CSS and PHP file that will be asserted. All + * 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 $label + * (optional) A label to prepend to the assertion messages, to make them + * less ambiguous. + * @param $extensions + * (optional) The expected file extensions of $name. Defaults to + * array('js', 'css', 'php'). + */ + function assertLibraryFiles($name, $label = '', $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) { + foreach ($extensions as $extension) { + $message = "$filename.$extension not found"; + $message = ($label !== '' ? "$label: $message" : $message); + $this->assertNoRaw("$filename.$extension", $message); + } + } + + // Test that the correct files are loaded. + foreach ($extensions as $extension) { + $message = "$name.$extension found"; + $message = ($label !== '' ? "$label: $message" : $message); + $this->assertRaw("$name.$extension", $message); + } + } + +} + diff --git a/tests/libraries_test.css b/tests/libraries_test.css new file mode 100644 index 0000000..3505281 --- /dev/null +++ b/tests/libraries_test.css @@ -0,0 +1,12 @@ + +/** + * @file + * Test CSS file for Libraries loading. + * + * Color the 'libraries-test-css' div purple. See README.txt for more + * information. + */ + +.libraries-test-css { + color: purple; +} diff --git a/tests/libraries_test.inc b/tests/libraries_test.inc new file mode 100644 index 0000000..c0cf0dc --- /dev/null +++ b/tests/libraries_test.inc @@ -0,0 +1,11 @@ + 'Example missing', + 'library 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', + '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', + 'version callback' => '_libraries_test_return_version', + 'version arguments' => array('1'), + 'versions' => array( + '2' => array(), + ), + ); + + $libraries['example_supported_version'] = array( + 'name' => 'Example supported version', + 'library path' => drupal_get_path('module', 'libraries') . '/tests', + 'version callback' => '_libraries_test_return_version', + 'version arguments' => array('1'), + 'versions' => array( + '1' => array(), + ), + ); + + // 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', + 'version arguments' => array( + 'file' => 'README.txt', + // Version 1 + 'pattern' => '/Version (\d+)/', + 'lines' => 5, + ), + ); + + // 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', + // Version 1 + 'version callback' => '_libraries_test_get_version', + 'version arguments' => array('README.txt', '/Version (\d+)/', 5), + ); + + // Test a top-level files property. + $libraries['example_files'] = array( + 'name' => 'Example files', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + ); + + // Test loading of integration files. + // Normally added by the corresponding module via hook_libraries_info_alter(), + // 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', + 'version' => '1', + 'integration files' => array( + 'libraries_test' => array( + 'js' => array('libraries_test.js'), + 'css' => array('libraries_test.css'), + 'php' => array('libraries_test.inc'), + ), + ), + ); + + // Test version overloading. + $libraries['example_versions'] = array( + 'name' => 'Example versions', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '2', + 'versions' => array( + '1' => array( + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + ), + '2' => array( + 'files' => array( + 'js' => array('example_2.js'), + 'css' => array('example_2.css'), + 'php' => array('example_2.php'), + ), + ), + ), + ); + + // Test variant detection. + $libraries['example_variant_missing'] = array( + 'name' => 'Example variant missing', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'variants' => array( + 'example_variant' => array( + 'files' => array( + 'js' => array('example_3.js'), + 'css' => array('example_3.css'), + 'php' => array('example_3.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(FALSE), + ), + ), + ); + + $libraries['example_variant'] = array( + 'name' => 'Example variant', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'variants' => array( + 'example_variant' => array( + 'files' => array( + 'js' => array('example_3.js'), + 'css' => array('example_3.css'), + 'php' => array('example_3.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + ), + ); + + // 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', + 'version' => '2', + 'versions' => array( + '1' => array( + 'variants' => array( + 'example_variant_1' => array( + 'files' => array( + 'js' => array('example_1.js'), + 'css' => array('example_1.css'), + 'php' => array('example_1.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + 'example_variant_2' => array( + 'files' => array( + 'js' => array('example_2.js'), + 'css' => array('example_2.css'), + 'php' => array('example_2.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + ), + ), + '2' => array( + 'variants' => array( + 'example_variant_1' => array( + 'files' => array( + 'js' => array('example_3.js'), + 'css' => array('example_3.css'), + 'php' => array('example_3.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + 'example_variant_2' => array( + 'files' => array( + 'js' => array('example_4.js'), + 'css' => array('example_4.css'), + 'php' => array('example_4.php'), + ), + 'variant callback' => '_libraries_test_return_installed', + 'variant arguments' => array(TRUE), + ), + ), + ), + ), + ); + + // Test the applying of callbacks. + $libraries['example_callback'] = array( + 'name' => 'Example callback', + 'library path' => drupal_get_path('module', 'libraries') . '/tests/example', + 'version' => '1', + 'versions' => array( + '1' => array( + 'variants' => array( + 'example_variant' => array( + // These keys are for testing purposes only. + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ), + ), + // These keys are for testing purposes only. + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ), + ), + 'variants' => array( + 'example_variant' => array( + // These keys are for testing purposes only. + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ), + ), + 'callbacks' => array( + 'prepare' => array('_libraries_test_prepare_callback'), + 'detect' => array('_libraries_test_detect_callback'), + 'load' => array('_libraries_test_load_callback'), + ), + // These keys are for testing purposes only. + 'prepare callback' => 'not applied', + 'detect callback' => 'not applied', + 'load callback' => 'not applied', + ); + + // This library is used together with libraries_info() to be populated with + // the defaults. + $libraries['example_empty'] = array(); + + return $libraries; +} + +/** + * Implements hook_libraries_info_file_paths() + */ +function libraries_test_libraries_info_file_paths() { + return array(drupal_get_path('module', 'libraries_test') . '/example'); +} + +/** + * Gets the version of an example library. + * + * Returns exactly the version string entered as the $version parameter. This + * function cannot be collapsed with _libraries_test_return_installed(), because + * of the different arguments that are passed automatically. + */ +function _libraries_test_return_version($library, $version) { + return $version; +} + +/** + * Gets the version information from an arbitrary library. + * + * Test function for a version callback with multiple arguments. This is an + * exact copy of libraries_get_version(), which uses a single $option argument, + * except for the fact that it uses multiple arguments. Since we support both + * type of version callbacks, detecting the version of a test library with this + * function ensures that the arguments are passed correctly. This function might + * be a useful reference for a custom version callback that uses multiple + * parameters. + * + * @param $library + * An associative array containing all information about the library. + * @param $file + * The filename to parse for the version, relative to the library path. For + * example: 'docs/changelog.txt'. + * @param pattern + * A string containing a regular expression (PCRE) to match the library + * version. For example: '/@version (\d+)\.(\d+)/'. + * @param lines + * (optional) The maximum number of lines to search the pattern in. Defaults + * to 20. + * @param cols + * (optional) The maximum number of characters per line to take into account. + * Defaults to 200. In case of minified or compressed files, this prevents + * reading the entire file into memory. + * + * @return + * A string containing the version of the library. + * + * @see libraries_get_version() + */ +function _libraries_test_get_version($library, $file, $pattern, $lines = 20, $cols = 200) { + + $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $file; + if (!file_exists($file)) { + return; + } + $file = fopen($file, 'r'); + while ($lines && $line = fgets($file, $cols)) { + if (preg_match($pattern, $line, $version)) { + fclose($file); + return $version[1]; + } + $lines--; + } + fclose($file); +} + +/** + * Detects the variant of an example library. + * + * Returns exactly the value of $installed, either TRUE or FALSE. This function + * cannot be collapsed with _libraries_test_return_version(), because of the + * different arguments that are passed automatically. + */ +function _libraries_test_return_installed($library, $name, $installed) { + return $installed; +} + +/** + * Sets the 'prepare callback' key. + * + * This function is used as a test callback for the 'prepare' callback group. + * + * @param $library + * An array of library information, which may be version- or variant-specific. + * Passed by reference. + * @param $version + * The version the library information passed in $library belongs to, or NULL + * if the passed library information is not version-specific. + * @param $variant + * The variant the library information passed in $library belongs to, or NULL + * if the passed library information is not variant-specific. + * + * @see _libraries_test_callback() + */ +function _libraries_test_prepare_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'prepare'); +} + +/** + * Sets the 'detect callback' key. + * + * This function is used as a test callback for the 'detect' callback group. + * + * @param $library + * An array of library information, which may be version- or variant-specific. + * Passed by reference. + * @param $version + * The version the library information passed in $library belongs to, or NULL + * if the passed library information is not version-specific. + * @param $variant + * The variant the library information passed in $library belongs to, or NULL + * if the passed library information is not variant-specific. + * + * @see _libraries_test_callback() + */ +function _libraries_test_detect_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'detect'); +} + +/** + * Sets the 'load callback' key. + * + * This function is used as a test callback for the 'load' callback group. + * + * @param $library + * An array of library information, which may be version- or variant-specific. + * Passed by reference. + * @param $version + * The version the library information passed in $library belongs to, or NULL + * if the passed library information is not version-specific. + * @param $variant + * The variant the library information passed in $library belongs to, or NULL + * if the passed library information is not variant-specific. + * + * @see _libraries_test_callback() + */ +function _libraries_test_load_callback(&$library, $version, $variant) { + _libraries_test_callback($library, $version, $variant, 'load'); +} + +/** + * Sets the '[group] callback' key, where [group] is prepare, detect, or load. + * + * This function is used as a test callback for the all callback groups. + * + * It sets the '[group] callback' (see above) key to 'applied ([part])' where + * [part] is either 'top-level', 'version x.y' (where x.y is the passed-in + * version string), 'variant example' (where example is the passed-in variant + * name), or 'version x.y, variant example' (see above), depending on the part + * of the library the passed-in library information belongs to. + * + * @param $library + * An array of library information, which may be version- or variant-specific. + * Passed by reference. + * @param $version + * The version the library information passed in $library belongs to, or NULL + * if the passed library information is not version-specific. + * @param $variant + * The variant the library information passed in $library belongs to, or NULL + * if the passed library information is not variant-specific. + */ +function _libraries_test_callback(&$library, $version, $variant, $group) { + $string = 'applied'; + if (isset($version) && isset($variant)) { + $string .= " (version $version, variant $variant)"; + } + elseif (isset($version)) { + $string .= " (version $version)"; + } + elseif (isset($variant)) { + $string .= " (variant $variant)"; + } + else { + $string .= ' (top-level)'; + } + $library["$group callback"] = $string; +} + +/** + * Implements hook_menu(). + */ +function libraries_test_menu() { + $items['libraries_test/files'] = array( + 'title' => 'Test files', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_files'), + 'access callback' => TRUE, + ); + $items['libraries_test/integration_files'] = array( + 'title' => 'Test integration files', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_integration_files'), + 'access callback' => TRUE, + ); + $items['libraries_test/versions'] = array( + 'title' => 'Test version loading', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_versions'), + 'access callback' => TRUE, + ); + $items['libraries_test/variant'] = array( + 'title' => 'Test variant loading', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_variant', 'example_variant'), + 'access callback' => TRUE, + ); + $items['libraries_test/versions_and_variants'] = array( + 'title' => 'Test concurrent version and variant loading', + 'page callback' => '_libraries_test_load', + 'page arguments' => array('example_versions_and_variants', 'example_variant_2'), + 'access callback' => TRUE, + ); + return $items; +} + +/** + * Loads a specified library (variant) for testing. + * + * JavaScript and CSS files can be checked directly by SimpleTest, so we only + * need to manually check for PHP files. We provide information about the loaded + * JavaScript and CSS files for easier debugging. See example/README.txt for + * more information. + */ +function _libraries_test_load($library, $variant = NULL) { + 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 = ''; + + // For easer debugging of JS loading, a text is shown that the JavaScript will + // replace. + $output .= '

JavaScript

'; + $output .= '
'; + $output .= 'If this text shows up, no JavaScript test file was loaded.'; + $output .= '
'; + + // For easier debugging of CSS loading, the loaded CSS files will color the + // following text. + $output .= '

CSS

'; + $output .= '
'; + $output .= 'If one of the CSS test files has been loaded, this text will be colored:'; + $output .= ''; + $output .= '
'; + + $output .= '

PHP

'; + $output .= '
'; + $output .= 'The following is a list of all loaded test PHP files:'; + $output .= ''; + $output .= '
'; + + return $output; +}