5 * External library handling for Drupal modules.
9 * Implements hook_flush_caches().
11 function libraries_flush_caches() {
12 // @todo When upgrading from 1.x, update.php attempts to flush caches before
13 // the cache table has been created.
14 // @see http://drupal.org/node/1477932
15 if (db_table_exists('cache_libraries')) {
16 return array('cache_libraries');
21 * Gets the path of a library.
24 * The machine name of a library to return the path for.
26 * Whether to prefix the resulting path with base_path().
29 * The path to the specified library or FALSE if the library wasn't found.
33 function libraries_get_path($name, $base_path = FALSE
) {
34 $libraries = &drupal_static(__FUNCTION__
);
36 if (!isset($libraries)) {
37 $libraries = libraries_get_libraries();
40 $path = ($base_path ?
base_path() : '');
41 if (!isset($libraries[$name])) {
45 $path .
= $libraries[$name];
52 * Returns an array of library directories.
54 * Returns an array of library directories from the all-sites directory
55 * (i.e. sites/all/libraries/), the profiles directory, and site-specific
56 * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
57 * by the library name. Site-specific libraries are prioritized over libraries
58 * in the default directories. That is, if a library with the same name appears
59 * in both the site-wide directory and site-specific directory, only the
60 * site-specific version will be listed.
63 * A list of library directories.
67 function libraries_get_libraries() {
69 $profile = drupal_get_path('profile', drupal_get_profile());
70 $config = conf_path();
72 // Similar to 'modules' and 'themes' directories in the root directory,
73 // certain distributions may want to place libraries into a 'libraries'
74 // directory in Drupal's root directory.
75 $searchdir[] = 'libraries';
77 // Similar to 'modules' and 'themes' directories inside an installation
78 // profile, installation profiles may want to place libraries into a
79 // 'libraries' directory.
80 $searchdir[] = "$profile/libraries";
82 // Always search sites/all/libraries.
83 $searchdir[] = 'sites/all/libraries';
85 // Also search sites/<domain>/*.
86 $searchdir[] = "$config/libraries";
88 // Retrieve list of directories.
89 $directories = array();
90 $nomask = array('CVS');
91 foreach ($searchdir as
$dir) {
92 if (is_dir($dir) && $handle = opendir($dir)) {
93 while (FALSE
!== ($file = readdir($handle))) {
94 if (!in_array($file, $nomask) && $file[0] != '.') {
95 if (is_dir("$dir/$file")) {
96 $directories[$file] = "$dir/$file";
108 * Looks for library info files.
110 * This function scans the following directories for info files:
112 * - profiles/$profilename/libraries
113 * - sites/all/libraries
114 * - sites/$sitename/libraries
115 * - any directories specified via hook_libraries_info_file_paths()
118 * An array of info files, keyed by library name. The values are the paths of
121 function libraries_scan_info_files() {
122 $profile = drupal_get_path('profile', drupal_get_profile());
123 $config = conf_path();
125 // Build a list of directories.
126 $directories = module_invoke_all('libraries_info_file_paths');
127 $directories[] = 'libraries';
128 $directories[] = "$profile/libraries";
129 $directories[] = 'sites/all/libraries';
130 $directories[] = "$config/libraries";
132 // Scan for info files.
134 foreach ($directories as
$dir) {
135 if (file_exists($dir)) {
136 $files = array_merge($files, file_scan_directory($dir, '@^[a-z0-9._-]+\.libraries\.info$@', array(
143 foreach ($files as
$filename => $file) {
144 $files[basename($filename, '.libraries')] = $file;
145 unset($files[$filename]);
152 * Invokes library callbacks.
155 * A string containing the group of callbacks that is to be applied. Should be
156 * either 'info', 'pre-detect', 'post-detect', or 'load'.
158 * An array of library information, passed by reference.
160 function libraries_invoke($group, &$library) {
161 foreach ($library['callbacks'][$group] as
$callback) {
162 libraries_traverse_library($library, $callback);
167 * Helper function to apply a callback to all parts of a library.
169 * Because library declarations can include variants and versions, and those
170 * version declarations can in turn include variants, modifying e.g. the 'files'
171 * property everywhere it is declared can be quite cumbersome, in which case
172 * this helper function is useful.
175 * An array of library information, passed by reference.
177 * A string containing the callback to apply to all parts of a library.
179 function libraries_traverse_library(&$library, $callback) {
180 // Always apply the callback to the top-level library.
181 $callback($library, NULL
, NULL
);
183 // Apply the callback to versions.
184 if (isset($library['versions'])) {
185 foreach ($library['versions'] as
$version_string => &$version) {
186 $callback($version, $version_string, NULL
);
187 // Versions can include variants as well.
188 if (isset($version['variants'])) {
189 foreach ($version['variants'] as
$version_variant_name => &$version_variant) {
190 $callback($version_variant, $version_string, $version_variant_name);
196 // Apply the callback to variants.
197 if (isset($library['variants'])) {
198 foreach ($library['variants'] as
$variant_name => &$variant) {
199 $callback($variant, NULL
, $variant_name);
205 * Library info callback to make all 'files' properties consistent.
207 * This turns libraries' file information declared as e.g.
209 * $library['files']['js'] = array('example_1.js', 'example_2.js');
213 * $library['files']['js'] = array(
214 * 'example_1.js' => array(),
215 * 'example_2.js' => array(),
218 * It does the same for the 'integration files' property.
221 * An associative array of library information or a part of it, passed by
224 * If the library information belongs to a specific version, the version
225 * string. NULL otherwise.
227 * If the library information belongs to a specific variant, the variant name.
230 * @see libraries_info()
231 * @see libraries_invoke()
233 function libraries_prepare_files(&$library, $version = NULL
, $variant = NULL
) {
234 // Both the 'files' property and the 'integration files' property contain file
235 // declarations, and we want to make both consistent.
236 $file_types = array();
237 if (isset($library['files'])) {
238 $file_types[] = &$library['files'];
240 if (isset($library['integration files'])) {
241 // Integration files are additionally keyed by module.
242 foreach ($library['integration files'] as
&$integration_files) {
243 $file_types[] = &$integration_files;
246 foreach ($file_types as
&$files) {
247 // Go through all supported types of files.
248 foreach (array('js', 'css', 'php') as
$type) {
249 if (isset($files[$type])) {
250 foreach ($files[$type] as
$key => $value) {
251 // Unset numeric keys and turn the respective values into keys.
252 if (is_numeric($key)) {
253 $files[$type][$value] = array();
254 unset($files[$type][$key]);
263 * Library post-detect callback to process and detect dependencies.
265 * It checks whether each of the dependencies of a library are installed and
266 * available in a compatible version.
269 * An associative array of library information or a part of it, passed by
272 * If the library information belongs to a specific version, the version
273 * string. NULL otherwise.
275 * If the library information belongs to a specific variant, the variant name.
278 * @see libraries_info()
279 * @see libraries_invoke()
281 function libraries_detect_dependencies(&$library, $version = NULL
, $variant = NULL
) {
282 if (isset($library['dependencies'])) {
283 foreach ($library['dependencies'] as
&$dependency_string) {
284 $dependency_info = drupal_parse_dependency($dependency_string);
285 $dependency = libraries_detect($dependency_info['name']);
286 if (!$dependency['installed']) {
287 $library['installed'] = FALSE
;
288 $library['error'] = 'missing dependency';
289 $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
290 '%dependency' => $dependency['name'],
291 '%library' => $library['name'],
294 elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
295 $library['installed'] = FALSE
;
296 $library['error'] = 'incompatible dependency';
297 $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
298 '%dependency_version' => $dependency['version'],
299 '%dependency' => $dependency['name'],
300 '%library' => $library['name'],
304 // Remove the version string from the dependency, so libraries_load() can
305 // load the libraries directly.
306 $dependency_string = $dependency_info['name'];
312 * Returns information about registered libraries.
314 * The returned information is unprocessed; i.e., as registered by modules.
317 * (optional) The machine name of a library to return registered information
318 * for. If omitted, information about all registered libraries is returned.
320 * @return array|false
321 * An associative array containing registered information for all libraries,
322 * the registered information for the library specified by $name, or FALSE if
323 * the library $name is not registered.
325 * @see hook_libraries_info()
327 * @todo Re-introduce support for include file plugin system - either by copying
328 * Wysiwyg's code, or directly switching to CTools.
330 function &libraries_info($name = NULL
) {
331 // This static cache is re-used by libraries_detect() to save memory.
332 $libraries = &drupal_static(__FUNCTION__
);
334 if (!isset($libraries)) {
335 $libraries = array();
336 // Gather information from hook_libraries_info().
337 foreach (module_implements('libraries_info') as
$module) {
338 foreach (module_invoke($module, 'libraries_info') as
$machine_name => $properties) {
339 $properties['module'] = $module;
340 $libraries[$machine_name] = $properties;
343 // Gather information from hook_libraries_info() in enabled themes.
344 // @see drupal_alter()
345 global $theme, $base_theme_info;
347 $theme_keys = array();
348 foreach ($base_theme_info as
$base) {
349 $theme_keys[] = $base->name
;
351 $theme_keys[] = $theme;
352 foreach ($theme_keys as
$theme_key) {
353 $function = $theme_key .
'_' .
'libraries_info';
354 if (function_exists($function)) {
355 foreach ($function() as
$machine_name => $properties) {
356 $properties['theme'] = $theme_key;
357 $libraries[$machine_name] = $properties;
363 // Gather information from .info files.
364 // .info files override module definitions.
365 foreach (libraries_scan_info_files() as
$machine_name => $file) {
366 $properties = drupal_parse_info_file($file->uri
);
367 $properties['info file'] = $file->uri
;
368 $libraries[$machine_name] = $properties;
372 foreach ($libraries as
$machine_name => &$properties) {
373 libraries_info_defaults($properties, $machine_name);
376 // Allow modules to alter the registered libraries.
377 drupal_alter('libraries_info', $libraries);
379 // Invoke callbacks in the 'info' group.
380 foreach ($libraries as
&$properties) {
381 libraries_invoke('info', $properties);
386 if (!empty($libraries[$name])) {
387 return $libraries[$name];
398 * Applies default properties to a library definition.
401 * An array of library information, passed by reference.
403 * The machine name of the passed-in library.
405 function libraries_info_defaults(&$library, $name) {
407 'machine name' => $name,
410 'download url' => '',
412 'library path' => NULL
,
413 'version callback' => 'libraries_get_version',
414 'version arguments' => array(),
416 'dependencies' => array(),
417 'variants' => array(),
418 'versions' => array(),
419 'integration files' => array(),
420 'callbacks' => array(),
422 $library['callbacks'] += array(
424 'pre-detect' => array(),
425 'post-detect' => array(),
426 'pre-dependencies-load' => array(),
427 'pre-load' => array(),
428 'post-load' => array(),
431 // Add our own callbacks before any others.
432 array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
433 array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
439 * Tries to detect a library and its installed version.
442 * The machine name of a library to return registered information for.
444 * @return array|false
445 * An associative array containing registered information for the library
446 * specified by $name, or FALSE if the library $name is not registered.
447 * In addition to the keys returned by libraries_info(), the following keys
449 * - installed: A boolean indicating whether the library is installed. Note
450 * that not only the top-level library, but also each variant contains this
452 * - version: If the version could be detected, the full version string.
453 * - error: If an error occurred during library detection, one of the
454 * following error statuses: "not found", "not detected", "not supported".
455 * - error message: If an error occurred during library detection, a detailed
458 * @see libraries_info()
460 function libraries_detect($name) {
461 // Re-use the statically cached value of libraries_info() to save memory.
462 $library = &libraries_info($name);
464 if ($library === FALSE
) {
467 // If 'installed' is set, library detection ran already.
468 if (isset($library['installed'])) {
472 $library['installed'] = FALSE
;
474 // Check whether the library exists.
475 if (!isset($library['library path'])) {
476 $library['library path'] = libraries_get_path($library['machine name']);
478 if ($library['library path'] === FALSE
|| !file_exists($library['library path'])) {
479 $library['error'] = 'not found';
480 $library['error message'] = t('The %library library could not be found.', array(
481 '%library' => $library['name'],
486 // Invoke callbacks in the 'pre-detect' group.
487 libraries_invoke('pre-detect', $library);
489 // Detect library version, if not hardcoded.
490 if (!isset($library['version'])) {
491 // We support both a single parameter, which is an associative array, and an
492 // indexed array of multiple parameters.
493 if (isset($library['version arguments'][0])) {
494 // Add the library as the first argument.
495 $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
498 $library['version'] = $library['version callback']($library, $library['version arguments']);
500 if (empty($library['version'])) {
501 $library['error'] = 'not detected';
502 $library['error message'] = t('The version of the %library library could not be detected.', array(
503 '%library' => $library['name'],
509 // Determine to which supported version the installed version maps.
510 if (!empty($library['versions'])) {
511 ksort($library['versions']);
513 foreach ($library['versions'] as
$supported_version => $version_properties) {
514 if (version_compare($library['version'], $supported_version, '>=')) {
515 $version = $supported_version;
519 $library['error'] = 'not supported';
520 $library['error message'] = t('The installed version %version of the %library library is not supported.', array(
521 '%version' => $library['version'],
522 '%library' => $library['name'],
527 // Apply version specific definitions and overrides.
528 $library = array_merge($library, $library['versions'][$version]);
529 unset($library['versions']);
532 // Check each variant if it is installed.
533 if (!empty($library['variants'])) {
534 foreach ($library['variants'] as
$variant_name => &$variant) {
535 // If no variant callback has been set, assume the variant to be
537 if (!isset($variant['variant callback'])) {
538 $variant['installed'] = TRUE
;
541 // We support both a single parameter, which is an associative array,
542 // and an indexed array of multiple parameters.
543 if (isset($variant['variant arguments'][0])) {
544 // Add the library as the first argument, and the variant name as the second.
545 $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments']));
548 $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
550 if (!$variant['installed']) {
551 $variant['error'] = 'not found';
552 $variant['error message'] = t('The %variant variant of the %library library could not be found.', array(
553 '%variant' => $variant_name,
554 '%library' => $library['name'],
561 // If we end up here, the library should be usable.
562 $library['installed'] = TRUE
;
564 // Invoke callbacks in the 'post-detect' group.
565 libraries_invoke('post-detect', $library);
574 * The name of the library to load.
576 * The name of the variant to load. Note that only one variant of a library
577 * can be loaded within a single request. The variant that has been passed
578 * first is used; different variant names in subsequent calls are ignored.
581 * An associative array of the library information as returned from
582 * libraries_info(). The top-level properties contain the effective definition
583 * of the library (variant) that has been loaded. Additionally:
584 * - installed: Whether the library is installed, as determined by
585 * libraries_detect_library().
586 * - loaded: Either the amount of library files that have been loaded, or
587 * FALSE if the library could not be loaded.
588 * See hook_libraries_info() for more information.
590 function libraries_load($name, $variant = NULL
) {
591 $loaded = &drupal_static(__FUNCTION__
, array());
593 if (!isset($loaded[$name])) {
594 $library = cache_get($name, 'cache_libraries');
596 $library = $library->data
;
599 $library = libraries_detect($name);
600 cache_set($name, $library, 'cache_libraries');
603 // If a variant was specified, override the top-level properties with the
604 // variant properties.
605 if (isset($variant)) {
606 // Ensure that the $variant key exists, and if it does not, set its
607 // 'installed' property to FALSE by default. This will prevent the loading
608 // of the library files below.
609 $library['variants'] += array($variant => array('installed' => FALSE
));
610 $library = array_merge($library, $library['variants'][$variant]);
612 // Regardless of whether a specific variant was requested or not, there can
613 // only be one variant of a library within a single request.
614 unset($library['variants']);
616 // Invoke callbacks in the 'pre-dependencies-load' group.
617 libraries_invoke('pre-dependencies-load', $library);
619 // If the library (variant) is installed, load it.
620 $library['loaded'] = FALSE
;
621 if ($library['installed']) {
622 // Load library dependencies.
623 if (isset($library['dependencies'])) {
624 foreach ($library['dependencies'] as
$dependency) {
625 libraries_load($dependency);
629 // Invoke callbacks in the 'pre-load' group.
630 libraries_invoke('pre-load', $library);
632 // Load all the files associated with the library.
633 $library['loaded'] = libraries_load_files($library);
635 // Invoke callbacks in the 'post-load' group.
636 libraries_invoke('post-load', $library);
638 $loaded[$name] = $library;
641 return $loaded[$name];
645 * Loads a library's files.
648 * An array of library information as returned by libraries_info().
651 * The number of loaded files.
653 function libraries_load_files($library) {
654 // Load integration files.
655 if (!empty($library['integration files'])) {
656 foreach ($library['integration files'] as
$module => $files) {
657 libraries_load_files(array(
660 'library path' => drupal_get_path('module', $module),
665 // Construct the full path to the library for later use.
666 $path = $library['library path'];
667 $path = ($library['path'] !== '' ?
$path .
'/' .
$library['path'] : $path);
669 // Count the number of loaded files for the return value.
672 // Load both the JavaScript and the CSS files.
673 // The parameters for drupal_add_js() and drupal_add_css() require special
675 // @see drupal_process_attached()
676 foreach (array('js', 'css') as
$type) {
677 if (!empty($library['files'][$type])) {
678 foreach ($library['files'][$type] as
$data => $options) {
679 // If the value is not an array, it's a filename and passed as first
680 // (and only) argument.
681 if (!is_array($options)) {
685 // In some cases, the first parameter ($data) is an array. Arrays can't
686 // be passed as keys in PHP, so we have to get $data from the value
688 if (is_numeric($data)) {
689 $data = $options['data'];
690 unset($options['data']);
692 // Prepend the library path to the file name.
693 $data = "$path/$data";
694 // Apply the default group if the group isn't explicitly given.
695 if (!isset($options['group'])) {
696 $options['group'] = ($type == 'js') ? JS_DEFAULT
: CSS_DEFAULT
;
698 call_user_func('drupal_add_' .
$type, $data, $options);
705 if (!empty($library['files']['php'])) {
706 foreach ($library['files']['php'] as
$file => $array) {
707 $file_path = DRUPAL_ROOT .
'/' .
$path .
'/' .
$file;
708 if (file_exists($file_path)) {
709 require_once
$file_path;
719 * Gets the version information from an arbitrary library.
722 * An associative array containing all information about the library.
724 * An associative array containing with the following keys:
725 * - file: The filename to parse for the version, relative to the library
726 * path. For example: 'docs/changelog.txt'.
727 * - pattern: A string containing a regular expression (PCRE) to match the
728 * library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that
729 * the returned version is not the match of the entire pattern (i.e.
730 * '@version 1.2.3' in the above example) but the match of the first
731 * sub-pattern (i.e. '1.2.3' in the above example).
732 * - lines: (optional) The maximum number of lines to search the pattern in.
734 * - cols: (optional) The maximum number of characters per line to take into
735 * account. Defaults to 200. In case of minified or compressed files, this
736 * prevents reading the entire file into memory.
739 * A string containing the version of the library.
741 * @see libraries_get_path()
743 function libraries_get_version($library, $options) {
752 $file = DRUPAL_ROOT .
'/' .
$library['library path'] .
'/' .
$options['file'];
753 if (empty($options['file']) || !file_exists($file)) {
756 $file = fopen($file, 'r');
757 while ($options['lines'] && $line = fgets($file, $options['cols'])) {
758 if (preg_match($options['pattern'], $line, $version)) {