comparison sites/all/modules/libraries/libraries.module @ 2:b74b41bb73f0

-- Google analytics module
author danieleb <danielebarchiesi@me.com>
date Thu, 22 Aug 2013 17:22:54 +0100
parents
children b28be78d8160
comparison
equal deleted inserted replaced
1:67ce89da90df 2:b74b41bb73f0
1 <?php
2
3 /**
4 * @file
5 * External library handling for Drupal modules.
6 */
7
8 /**
9 * Implements hook_flush_caches().
10 */
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');
17 }
18 }
19
20 /**
21 * Gets the path of a library.
22 *
23 * @param $name
24 * The machine name of a library to return the path for.
25 * @param $base_path
26 * Whether to prefix the resulting path with base_path().
27 *
28 * @return
29 * The path to the specified library or FALSE if the library wasn't found.
30 *
31 * @ingroup libraries
32 */
33 function libraries_get_path($name, $base_path = FALSE) {
34 $libraries = &drupal_static(__FUNCTION__);
35
36 if (!isset($libraries)) {
37 $libraries = libraries_get_libraries();
38 }
39
40 $path = ($base_path ? base_path() : '');
41 if (!isset($libraries[$name])) {
42 return FALSE;
43 }
44 else {
45 $path .= $libraries[$name];
46 }
47
48 return $path;
49 }
50
51 /**
52 * Returns an array of library directories.
53 *
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.
61 *
62 * @return
63 * A list of library directories.
64 *
65 * @ingroup libraries
66 */
67 function libraries_get_libraries() {
68 $searchdir = array();
69 $profile = drupal_get_path('profile', drupal_get_profile());
70 $config = conf_path();
71
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';
76
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";
81
82 // Always search sites/all/libraries.
83 $searchdir[] = 'sites/all/libraries';
84
85 // Also search sites/<domain>/*.
86 $searchdir[] = "$config/libraries";
87
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";
97 }
98 }
99 }
100 closedir($handle);
101 }
102 }
103
104 return $directories;
105 }
106
107 /**
108 * Looks for library info files.
109 *
110 * This function scans the following directories for info files:
111 * - libraries
112 * - profiles/$profilename/libraries
113 * - sites/all/libraries
114 * - sites/$sitename/libraries
115 * - any directories specified via hook_libraries_info_file_paths()
116 *
117 * @return
118 * An array of info files, keyed by library name. The values are the paths of
119 * the files.
120 */
121 function libraries_scan_info_files() {
122 $profile = drupal_get_path('profile', drupal_get_profile());
123 $config = conf_path();
124
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";
131
132 // Scan for info files.
133 $files = array();
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(
137 'key' => 'name',
138 'recurse' => FALSE,
139 )));
140 }
141 }
142
143 foreach ($files as $filename => $file) {
144 $files[basename($filename, '.libraries')] = $file;
145 unset($files[$filename]);
146 }
147
148 return $files;
149 }
150
151 /**
152 * Invokes library callbacks.
153 *
154 * @param $group
155 * A string containing the group of callbacks that is to be applied. Should be
156 * either 'info', 'pre-detect', 'post-detect', or 'load'.
157 * @param $library
158 * An array of library information, passed by reference.
159 */
160 function libraries_invoke($group, &$library) {
161 foreach ($library['callbacks'][$group] as $callback) {
162 libraries_traverse_library($library, $callback);
163 }
164 }
165
166 /**
167 * Helper function to apply a callback to all parts of a library.
168 *
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.
173 *
174 * @param $library
175 * An array of library information, passed by reference.
176 * @param $callback
177 * A string containing the callback to apply to all parts of a library.
178 */
179 function libraries_traverse_library(&$library, $callback) {
180 // Always apply the callback to the top-level library.
181 $callback($library, NULL, NULL);
182
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);
191 }
192 }
193 }
194 }
195
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);
200 }
201 }
202 }
203
204 /**
205 * Library info callback to make all 'files' properties consistent.
206 *
207 * This turns libraries' file information declared as e.g.
208 * @code
209 * $library['files']['js'] = array('example_1.js', 'example_2.js');
210 * @endcode
211 * into
212 * @code
213 * $library['files']['js'] = array(
214 * 'example_1.js' => array(),
215 * 'example_2.js' => array(),
216 * );
217 * @endcode
218 * It does the same for the 'integration files' property.
219 *
220 * @param $library
221 * An associative array of library information or a part of it, passed by
222 * reference.
223 * @param $version
224 * If the library information belongs to a specific version, the version
225 * string. NULL otherwise.
226 * @param $variant
227 * If the library information belongs to a specific variant, the variant name.
228 * NULL otherwise.
229 *
230 * @see libraries_info()
231 * @see libraries_invoke()
232 */
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'];
239 }
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;
244 }
245 }
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]);
255 }
256 }
257 }
258 }
259 }
260 }
261
262 /**
263 * Library post-detect callback to process and detect dependencies.
264 *
265 * It checks whether each of the dependencies of a library are installed and
266 * available in a compatible version.
267 *
268 * @param $library
269 * An associative array of library information or a part of it, passed by
270 * reference.
271 * @param $version
272 * If the library information belongs to a specific version, the version
273 * string. NULL otherwise.
274 * @param $variant
275 * If the library information belongs to a specific variant, the variant name.
276 * NULL otherwise.
277 *
278 * @see libraries_info()
279 * @see libraries_invoke()
280 */
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'],
292 ));
293 }
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'],
301 ));
302 }
303
304 // Remove the version string from the dependency, so libraries_load() can
305 // load the libraries directly.
306 $dependency_string = $dependency_info['name'];
307 }
308 }
309 }
310
311 /**
312 * Returns information about registered libraries.
313 *
314 * The returned information is unprocessed; i.e., as registered by modules.
315 *
316 * @param $name
317 * (optional) The machine name of a library to return registered information
318 * for. If omitted, information about all registered libraries is returned.
319 *
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.
324 *
325 * @see hook_libraries_info()
326 *
327 * @todo Re-introduce support for include file plugin system - either by copying
328 * Wysiwyg's code, or directly switching to CTools.
329 */
330 function &libraries_info($name = NULL) {
331 // This static cache is re-used by libraries_detect() to save memory.
332 $libraries = &drupal_static(__FUNCTION__);
333
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;
341 }
342 }
343 // Gather information from hook_libraries_info() in enabled themes.
344 // @see drupal_alter()
345 global $theme, $base_theme_info;
346 if (isset($theme)) {
347 $theme_keys = array();
348 foreach ($base_theme_info as $base) {
349 $theme_keys[] = $base->name;
350 }
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;
358 }
359 }
360 }
361 }
362
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;
369 }
370
371 // Provide defaults.
372 foreach ($libraries as $machine_name => &$properties) {
373 libraries_info_defaults($properties, $machine_name);
374 }
375
376 // Allow modules to alter the registered libraries.
377 drupal_alter('libraries_info', $libraries);
378
379 // Invoke callbacks in the 'info' group.
380 foreach ($libraries as &$properties) {
381 libraries_invoke('info', $properties);
382 }
383 }
384
385 if (isset($name)) {
386 if (!empty($libraries[$name])) {
387 return $libraries[$name];
388 }
389 else {
390 $false = FALSE;
391 return $false;
392 }
393 }
394 return $libraries;
395 }
396
397 /**
398 * Applies default properties to a library definition.
399 *
400 * @library
401 * An array of library information, passed by reference.
402 * @name
403 * The machine name of the passed-in library.
404 */
405 function libraries_info_defaults(&$library, $name) {
406 $library += array(
407 'machine name' => $name,
408 'name' => $name,
409 'vendor url' => '',
410 'download url' => '',
411 'path' => '',
412 'library path' => NULL,
413 'version callback' => 'libraries_get_version',
414 'version arguments' => array(),
415 'files' => array(),
416 'dependencies' => array(),
417 'variants' => array(),
418 'versions' => array(),
419 'integration files' => array(),
420 'callbacks' => array(),
421 );
422 $library['callbacks'] += array(
423 'info' => array(),
424 'pre-detect' => array(),
425 'post-detect' => array(),
426 'pre-dependencies-load' => array(),
427 'pre-load' => array(),
428 'post-load' => array(),
429 );
430
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');
434
435 return $library;
436 }
437
438 /**
439 * Tries to detect a library and its installed version.
440 *
441 * @param $name
442 * The machine name of a library to return registered information for.
443 *
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
448 * are contained:
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
451 * key.
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
456 * error message.
457 *
458 * @see libraries_info()
459 */
460 function libraries_detect($name) {
461 // Re-use the statically cached value of libraries_info() to save memory.
462 $library = &libraries_info($name);
463
464 if ($library === FALSE) {
465 return $library;
466 }
467 // If 'installed' is set, library detection ran already.
468 if (isset($library['installed'])) {
469 return $library;
470 }
471
472 $library['installed'] = FALSE;
473
474 // Check whether the library exists.
475 if (!isset($library['library path'])) {
476 $library['library path'] = libraries_get_path($library['machine name']);
477 }
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'],
482 ));
483 return $library;
484 }
485
486 // Invoke callbacks in the 'pre-detect' group.
487 libraries_invoke('pre-detect', $library);
488
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']));
496 }
497 else {
498 $library['version'] = $library['version callback']($library, $library['version arguments']);
499 }
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'],
504 ));
505 return $library;
506 }
507 }
508
509 // Determine to which supported version the installed version maps.
510 if (!empty($library['versions'])) {
511 ksort($library['versions']);
512 $version = 0;
513 foreach ($library['versions'] as $supported_version => $version_properties) {
514 if (version_compare($library['version'], $supported_version, '>=')) {
515 $version = $supported_version;
516 }
517 }
518 if (!$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'],
523 ));
524 return $library;
525 }
526
527 // Apply version specific definitions and overrides.
528 $library = array_merge($library, $library['versions'][$version]);
529 unset($library['versions']);
530 }
531
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
536 // installed.
537 if (!isset($variant['variant callback'])) {
538 $variant['installed'] = TRUE;
539 }
540 else {
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']));
546 }
547 else {
548 $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
549 }
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'],
555 ));
556 }
557 }
558 }
559 }
560
561 // If we end up here, the library should be usable.
562 $library['installed'] = TRUE;
563
564 // Invoke callbacks in the 'post-detect' group.
565 libraries_invoke('post-detect', $library);
566
567 return $library;
568 }
569
570 /**
571 * Loads a library.
572 *
573 * @param $name
574 * The name of the library to load.
575 * @param $variant
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.
579 *
580 * @return
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.
589 */
590 function libraries_load($name, $variant = NULL) {
591 $loaded = &drupal_static(__FUNCTION__, array());
592
593 if (!isset($loaded[$name])) {
594 $library = cache_get($name, 'cache_libraries');
595 if ($library) {
596 $library = $library->data;
597 }
598 else {
599 $library = libraries_detect($name);
600 cache_set($name, $library, 'cache_libraries');
601 }
602
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]);
611 }
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']);
615
616 // Invoke callbacks in the 'pre-dependencies-load' group.
617 libraries_invoke('pre-dependencies-load', $library);
618
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);
626 }
627 }
628
629 // Invoke callbacks in the 'pre-load' group.
630 libraries_invoke('pre-load', $library);
631
632 // Load all the files associated with the library.
633 $library['loaded'] = libraries_load_files($library);
634
635 // Invoke callbacks in the 'post-load' group.
636 libraries_invoke('post-load', $library);
637 }
638 $loaded[$name] = $library;
639 }
640
641 return $loaded[$name];
642 }
643
644 /**
645 * Loads a library's files.
646 *
647 * @param $library
648 * An array of library information as returned by libraries_info().
649 *
650 * @return
651 * The number of loaded files.
652 */
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(
658 'files' => $files,
659 'path' => '',
660 'library path' => drupal_get_path('module', $module),
661 ));
662 }
663 }
664
665 // Construct the full path to the library for later use.
666 $path = $library['library path'];
667 $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
668
669 // Count the number of loaded files for the return value.
670 $count = 0;
671
672 // Load both the JavaScript and the CSS files.
673 // The parameters for drupal_add_js() and drupal_add_css() require special
674 // handling.
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)) {
682 $data = $options;
683 $options = array();
684 }
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
687 // array.
688 if (is_numeric($data)) {
689 $data = $options['data'];
690 unset($options['data']);
691 }
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;
697 }
698 call_user_func('drupal_add_' . $type, $data, $options);
699 $count++;
700 }
701 }
702 }
703
704 // Load PHP files.
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;
710 $count++;
711 }
712 }
713 }
714
715 return $count;
716 }
717
718 /**
719 * Gets the version information from an arbitrary library.
720 *
721 * @param $library
722 * An associative array containing all information about the library.
723 * @param $options
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.
733 * Defaults to 20.
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.
737 *
738 * @return
739 * A string containing the version of the library.
740 *
741 * @see libraries_get_path()
742 */
743 function libraries_get_version($library, $options) {
744 // Provide defaults.
745 $options += array(
746 'file' => '',
747 'pattern' => '',
748 'lines' => 20,
749 'cols' => 200,
750 );
751
752 $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
753 if (empty($options['file']) || !file_exists($file)) {
754 return;
755 }
756 $file = fopen($file, 'r');
757 while ($options['lines'] && $line = fgets($file, $options['cols'])) {
758 if (preg_match($options['pattern'], $line, $version)) {
759 fclose($file);
760 return $version[1];
761 }
762 $options['lines']--;
763 }
764 fclose($file);
765 }