Index: libraries.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/libraries/libraries.api.php,v retrieving revision 1.6 diff -u -p -r1.6 libraries.api.php --- libraries.api.php 27 Jan 2011 02:22:04 -0000 1.6 +++ libraries.api.php 3 Feb 2011 16:25:37 -0000 @@ -103,6 +103,41 @@ * names and whose values are sets of files to load for the module, using * the same notion as the top-level 'files' property. Each specified file * should contain the path to the file relative to the module it belongs to. + * - callbacks: An associative array whose keys are callback groups and whose + * values are arrays of callbacks to apply to the library in that group. + * Each callback receives the following arguments: + * - $library: An array of library information belonging to the top-level + * library, a specific version, a specific variant or a specific variant + * of a specific version. Because library information such as the 'files' + * property (see above) can be declared in all these different locations + * of the library array, but a callback may have to act on all these + * different parts of the library, it is called recursively for each + * library with a certain part of the libraries array passed as $library + * each time. + * - $version: If the $library array belongs to a certain version (see + * above), a string containing the version. NULL, otherwise. + * - $variant: If the $library array belongs to a certain variant (see + * above), a string containing the variant name. NULL, otherwise. + * Valid callback groups are: + * - prepare: Callbacks registered in this group are applied as soon as the + * library information has been retrieved via hook_libraries_info() or + * info files. + * - detect: Callbacks registered in this group are applied as soon as + * library detection has been completed. At this point the library + * contains the version-specific information, if specified, and following + * additional information is available: + * - $library['installed']: A boolean indicating whether the library is + * installed or not. + * - $library['version']: If it could be detected, a string containing the + * version of the library. + * - $library['variants'][$variant]['installed']: For each specified + * variant, a boolean indicating whether the variant is installed or + * not. + * Note that in this group the 'versions' property is no longer available. + * - load: Callbacks registered in this group are applied directly + * before this library is loaded. At this point the library contains + * variant-specific information, if specified. Note that in this group the + * 'variants' property is no longer available. * Additional top-level properties can be registered as needed. * * @see hook_library() @@ -214,6 +249,14 @@ function hook_libraries_info() { '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. 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 3 Feb 2011 16:25:37 -0000 @@ -145,6 +145,62 @@ function libraries_scan_info_files() { 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) { + if (!empty($library['callbacks'][$group])) { + 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 (!empty($library['versions'])) { + foreach ($library['versions'] as $version_string => &$version) { + $callback($version, $version_string, NULL); + // Versions can include variants as well. + if (!empty($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 (!empty($library['variants'])) { + foreach ($library['variants'] as $variant_name => &$variant) { + $callback($variant, NULL, $variant_name); + } + } +} + /** * Returns information about registered libraries. * @@ -199,11 +255,21 @@ function libraries_info($name = NULL) { 'variants' => array(), 'versions' => array(), 'integration files' => array(), + '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)) { @@ -333,6 +399,10 @@ function libraries_detect_library(&$libr // 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; } @@ -379,6 +449,9 @@ function libraries_load($name, $variant // 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; 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 3 Feb 2011 16:25:37 -0000 @@ -148,6 +148,74 @@ class LibrariesTestCase extends DrupalWe $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'), + ), + 'module' => 'libraries_test', + '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)'; + libraries_detect_library($library); + $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. + $expected['loaded'] = 0; + $expected['load callback'] = 'applied (top-level)'; + $expected['variants']['example_variant']['load callback'] = 'applied (variant example_variant)'; + $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.'); + // Test loading of a simple library with a top-level files property. $this->drupalGet('libraries_test/files'); $this->assertLibraryFiles('example_1', 'File loading'); 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 3 Feb 2011 16:25:37 -0000 @@ -201,6 +201,46 @@ function libraries_test_libraries_info() ), ); + // 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(); @@ -287,6 +327,107 @@ function _libraries_test_return_installe } /** + * 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() {