Project

General

Profile

Paste
Download (32.9 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / libraries / libraries.module @ ed912c77

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
  // Clear static caches.
13
  // We don't clear the 'libraries_load' static cache, because that could result
14
  // in libraries that had been loaded before the cache flushing to be loaded
15
  // again afterwards.
16
  foreach (array('libraries_get_path', 'libraries_info') as $name) {
17
    drupal_static_reset($name);
18
  }
19

    
20
  // @todo When upgrading from 1.x, update.php attempts to flush caches before
21
  //   the cache table has been created.
22
  // @see http://drupal.org/node/1477932
23
  if (db_table_exists('cache_libraries')) {
24
    return array('cache_libraries');
25
  }
26
}
27

    
28
/**
29
 * Implements hook_admin_menu_cache_info().
30
 */
31
function libraries_admin_menu_cache_info() {
32
  $caches['libraries'] = array(
33
    'title' => t('Libraries'),
34
    'callback' => 'libraries_cache_clear',
35
  );
36
  return $caches;
37
}
38

    
39
/**
40
 * Clears the cached library information.
41
 */
42
function libraries_cache_clear() {
43
  foreach (libraries_flush_caches() as $bin) {
44
    // Using the wildcard argument leads to DrupalDatabaseCache::clear()
45
    // truncating the libraries cache table which is more performant that
46
    // deleting the rows.
47
    cache_clear_all('*', $bin, TRUE);
48
  }
49
}
50

    
51
/**
52
 * Gets the path of a library.
53
 *
54
 * @param $name
55
 *   The machine name of a library to return the path for.
56
 * @param $base_path
57
 *   Whether to prefix the resulting path with base_path().
58
 *
59
 * @return string
60
 *   The path to the specified library or FALSE if the library wasn't found.
61
 *
62
 * @ingroup libraries
63
 */
64
function libraries_get_path($name, $base_path = FALSE) {
65
  $libraries = &drupal_static(__FUNCTION__);
66

    
67
  if (!isset($libraries)) {
68
    $libraries = libraries_get_libraries();
69
  }
70

    
71
  $path = ($base_path ? base_path() : '');
72
  if (!isset($libraries[$name])) {
73
    return FALSE;
74
  }
75
  else {
76
    $path .= $libraries[$name];
77
  }
78

    
79
  return $path;
80
}
81

    
82
/**
83
 * Returns an array of library directories.
84
 *
85
 * Returns an array of library directories from the all-sites directory
86
 * (i.e. sites/all/libraries/), the profiles directory, and site-specific
87
 * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
88
 * by the library name. Site-specific libraries are prioritized over libraries
89
 * in the default directories. That is, if a library with the same name appears
90
 * in both the site-wide directory and site-specific directory, only the
91
 * site-specific version will be listed.
92
 *
93
 * @return array
94
 *   A list of library directories.
95
 *
96
 * @ingroup libraries
97
 */
98
function libraries_get_libraries() {
99
  $searchdir = array();
100
  $profile = drupal_get_path('profile', drupal_get_profile());
101
  $config = conf_path();
102

    
103
  // $config and $profile should never be empty in a proper Drupal setup.
104
  // However, we should never search into the root filesystem under any
105
  // circumstances, so just bail out in that case.
106
  if (!$profile || !$config) {
107
    return array();
108
  }
109

    
110
  // Similar to 'modules' and 'themes' directories in the root directory,
111
  // certain distributions may want to place libraries into a 'libraries'
112
  // directory in Drupal's root directory.
113
  $searchdir[] = 'libraries';
114

    
115
  // Similar to 'modules' and 'themes' directories inside an installation
116
  // profile, installation profiles may want to place libraries into a
117
  // 'libraries' directory.
118
  $searchdir[] = "$profile/libraries";
119

    
120
  // Always search sites/all/libraries.
121
  $searchdir[] = 'sites/all/libraries';
122

    
123
  // Also search sites/<domain>/*.
124
  $searchdir[] = "$config/libraries";
125

    
126
  // Retrieve list of directories.
127
  $directories = array();
128
  $nomask = array('CVS');
129
  foreach ($searchdir as $dir) {
130
    if (is_dir($dir) && $handle = opendir($dir)) {
131
      while (FALSE !== ($file = readdir($handle))) {
132
        if (!in_array($file, $nomask) && $file[0] != '.') {
133
          if (is_dir("$dir/$file")) {
134
            $directories[$file] = "$dir/$file";
135
          }
136
        }
137
      }
138
      closedir($handle);
139
    }
140
  }
141

    
142
  return $directories;
143
}
144

    
145
/**
146
 * Looks for library info files.
147
 *
148
 * This function scans the following directories for info files:
149
 * - libraries
150
 * - profiles/$profilename/libraries
151
 * - sites/all/libraries
152
 * - sites/$sitename/libraries
153
 * - any directories specified via hook_libraries_info_file_paths()
154
 *
155
 * @return array
156
 *   An array of info files, keyed by library name. The values are the paths of
157
 *   the files.
158
 */
159
function libraries_scan_info_files() {
160
  $profile = drupal_get_path('profile', drupal_get_profile());
161
  $config = conf_path();
162

    
163
  // Build a list of directories.
164
  $directories = module_invoke_all('libraries_info_file_paths');
165
  $directories[] = 'libraries';
166
  $directories[] = "$profile/libraries";
167
  $directories[] = 'sites/all/libraries';
168
  $directories[] = "$config/libraries";
169

    
170
  // Scan for info files.
171
  $files = array();
172
  foreach ($directories as $dir) {
173
    if (file_exists($dir)) {
174
      $files = array_merge($files, file_scan_directory($dir, '@^[A-Za-z0-9._-]+\.libraries\.info$@', array(
175
        'key' => 'name',
176
        'recurse' => FALSE,
177
      )));
178
    }
179
  }
180

    
181
  foreach ($files as $filename => $file) {
182
    $files[basename($filename, '.libraries')] = $file;
183
    unset($files[$filename]);
184
  }
185

    
186
  return $files;
187
}
188

    
189
/**
190
 * Invokes library callbacks.
191
 *
192
 * @param $group
193
 *   A string containing the group of callbacks that is to be applied. Should be
194
 *   either 'info', 'pre-detect', 'post-detect', or 'load'.
195
 * @param $library
196
 *   An array of library information, passed by reference.
197
 */
198
function libraries_invoke($group, &$library) {
199
  // When introducing new callback groups in newer versions, stale cached
200
  // library information somehow reaches this point during the database update
201
  // before clearing the library cache.
202
  if (empty($library['callbacks'][$group])) {
203
    return;
204
  }
205

    
206
  foreach ($library['callbacks'][$group] as $callback) {
207
    libraries_traverse_library($library, $callback);
208
  }
209
}
210

    
211
/**
212
 * Helper function to apply a callback to all parts of a library.
213
 *
214
 * Because library declarations can include variants and versions, and those
215
 * version declarations can in turn include variants, modifying e.g. the 'files'
216
 * property everywhere it is declared can be quite cumbersome, in which case
217
 * this helper function is useful.
218
 *
219
 * @param $library
220
 *   An array of library information, passed by reference.
221
 * @param $callback
222
 *   A string containing the callback to apply to all parts of a library.
223
 */
224
function libraries_traverse_library(&$library, $callback) {
225
  // Always apply the callback to the top-level library.
226
  $callback($library, NULL, NULL);
227

    
228
  // Apply the callback to versions.
229
  if (isset($library['versions'])) {
230
    foreach ($library['versions'] as $version_string => &$version) {
231
      $callback($version, $version_string, NULL);
232
      // Versions can include variants as well.
233
      if (isset($version['variants'])) {
234
        foreach ($version['variants'] as $version_variant_name => &$version_variant) {
235
          $callback($version_variant, $version_string, $version_variant_name);
236
        }
237
      }
238
    }
239
  }
240

    
241
  // Apply the callback to variants.
242
  if (isset($library['variants'])) {
243
    foreach ($library['variants'] as $variant_name => &$variant) {
244
      $callback($variant, NULL, $variant_name);
245
    }
246
  }
247
}
248

    
249
/**
250
 * Library info callback to make all 'files' properties consistent.
251
 *
252
 * This turns libraries' file information declared as e.g.
253
 * @code
254
 * $library['files']['js'] = array('example_1.js', 'example_2.js');
255
 * @endcode
256
 * into
257
 * @code
258
 * $library['files']['js'] = array(
259
 *   'example_1.js' => array(),
260
 *   'example_2.js' => array(),
261
 * );
262
 * @endcode
263
 * It does the same for the 'integration files' property.
264
 *
265
 * @param $library
266
 *   An associative array of library information or a part of it, passed by
267
 *   reference.
268
 * @param $version
269
 *   If the library information belongs to a specific version, the version
270
 *   string. NULL otherwise.
271
 * @param $variant
272
 *   If the library information belongs to a specific variant, the variant name.
273
 *   NULL otherwise.
274
 *
275
 * @see libraries_info()
276
 * @see libraries_invoke()
277
 */
278
function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) {
279
  // Both the 'files' property and the 'integration files' property contain file
280
  // declarations, and we want to make both consistent.
281
  $file_types = array();
282
  if (isset($library['files'])) {
283
    $file_types[] = &$library['files'];
284
  }
285
  if (isset($library['integration files'])) {
286
    // Integration files are additionally keyed by module.
287
    foreach ($library['integration files'] as &$integration_files) {
288
      $file_types[] = &$integration_files;
289
    }
290
  }
291
  foreach ($file_types as &$files) {
292
    // Go through all supported types of files.
293
    foreach (array('js', 'css', 'php') as $type) {
294
      if (isset($files[$type])) {
295
        foreach ($files[$type] as $key => $value) {
296
          // Unset numeric keys and turn the respective values into keys.
297
          if (is_numeric($key)) {
298
            $files[$type][$value] = array();
299
            unset($files[$type][$key]);
300
          }
301
        }
302
      }
303
    }
304
  }
305
}
306

    
307
/**
308
 * Library post-detect callback to process and detect dependencies.
309
 *
310
 * It checks whether each of the dependencies of a library are installed and
311
 * available in a compatible version.
312
 *
313
 * @param $library
314
 *   An associative array of library information or a part of it, passed by
315
 *   reference.
316
 * @param $version
317
 *   If the library information belongs to a specific version, the version
318
 *   string. NULL otherwise.
319
 * @param $variant
320
 *   If the library information belongs to a specific variant, the variant name.
321
 *   NULL otherwise.
322
 *
323
 * @see libraries_info()
324
 * @see libraries_invoke()
325
 */
326
function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) {
327
  if (isset($library['dependencies'])) {
328
    foreach ($library['dependencies'] as &$dependency_string) {
329
      $dependency_info = drupal_parse_dependency($dependency_string);
330
      $dependency = libraries_detect($dependency_info['name']);
331
      if (!$dependency['installed']) {
332
        $library['installed'] = FALSE;
333
        $library['error'] = 'missing dependency';
334
        $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
335
          '%dependency' => $dependency['name'],
336
          '%library' => $library['name'],
337
        ));
338
      }
339
      elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
340
        $library['installed'] = FALSE;
341
        $library['error'] = 'incompatible dependency';
342
        $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
343
          '%dependency_version' => $dependency['version'],
344
          '%dependency' => $dependency['name'],
345
          '%library' => $library['name'],
346
        ));
347
      }
348

    
349
      // Remove the version string from the dependency, so libraries_load() can
350
      // load the libraries directly.
351
      $dependency_string = $dependency_info['name'];
352
    }
353
  }
354
}
355

    
356
/**
357
 * Returns information about registered libraries.
358
 *
359
 * The returned information is unprocessed; i.e., as registered by modules.
360
 *
361
 * @param $name
362
 *   (optional) The machine name of a library to return registered information
363
 *   for. If omitted, information about all registered libraries is returned.
364
 *
365
 * @return array|false
366
 *   An associative array containing registered information for all libraries,
367
 *   the registered information for the library specified by $name, or FALSE if
368
 *   the library $name is not registered.
369
 *
370
 * @see hook_libraries_info()
371
 *
372
 * @todo Re-introduce support for include file plugin system - either by copying
373
 *   Wysiwyg's code, or directly switching to CTools.
374
 */
375
function &libraries_info($name = NULL) {
376
  // This static cache is re-used by libraries_detect() to save memory.
377
  $libraries = &drupal_static(__FUNCTION__);
378

    
379
  if (!isset($libraries)) {
380
    $libraries = array();
381

    
382
    // Gather information from hook_libraries_info() in enabled modules.
383
    foreach (module_implements('libraries_info') as $module) {
384
      foreach (module_invoke($module, 'libraries_info') as $machine_name => $properties) {
385
        $properties['info type'] = 'module';
386
        $properties['module'] = $module;
387
        $libraries[$machine_name] = $properties;
388
      }
389
    }
390

    
391
    // Gather information from hook_libraries_info() in enabled themes.
392
    $themes = array();
393
    foreach (list_themes() as $theme_name => $theme_info) {
394
      if ($theme_info->status && file_exists(drupal_get_path('theme', $theme_name) . '/template.php')) {
395
        // Collect a list of viable themes for re-use when calling the alter
396
        // hook.
397
        $themes[] = $theme_name;
398

    
399
        include_once drupal_get_path('theme', $theme_name) . '/template.php';
400

    
401
        $function = $theme_name . '_libraries_info';
402
        if (function_exists($function)) {
403
          foreach ($function() as $machine_name => $properties) {
404
            $properties['info type'] = 'theme';
405
            $properties['theme'] = $theme_name;
406
            $libraries[$machine_name] = $properties;
407
          }
408
        }
409
      }
410
    }
411

    
412
    // Gather information from .info files.
413
    // .info files override module definitions.
414
    foreach (libraries_scan_info_files() as $machine_name => $file) {
415
      $properties = drupal_parse_info_file($file->uri);
416
      $properties['info type'] = 'info file';
417
      $properties['info file'] = $file->uri;
418
      $libraries[$machine_name] = $properties;
419
    }
420

    
421
    // Provide defaults.
422
    foreach ($libraries as $machine_name => &$properties) {
423
      libraries_info_defaults($properties, $machine_name);
424
    }
425

    
426
    // Allow enabled modules and themes to alter the registered libraries.
427
    // drupal_alter() only takes the currently active theme into account, not
428
    // all enabled themes.
429
    foreach (module_implements('libraries_info_alter') as $module) {
430
      $function = $module . '_libraries_info_alter';
431
      $function($libraries);
432
    }
433
    foreach ($themes as $theme) {
434
      $function = $theme . '_libraries_info_alter';
435
      // The template.php file was included above.
436
      if (function_exists($function)) {
437
        $function($libraries);
438
      }
439
    }
440

    
441
    // Invoke callbacks in the 'info' group.
442
    foreach ($libraries as &$properties) {
443
      libraries_invoke('info', $properties);
444
    }
445
  }
446

    
447
  if (isset($name)) {
448
    if (!empty($libraries[$name])) {
449
      return $libraries[$name];
450
    }
451
    else {
452
      $false = FALSE;
453
      return $false;
454
    }
455
  }
456
  return $libraries;
457
}
458

    
459
/**
460
 * Applies default properties to a library definition.
461
 *
462
 * @param array $library
463
 *   An array of library information, passed by reference.
464
 * @param string $name
465
 *   The machine name of the passed-in library.
466
 *
467
 * @return array
468
 *   The library information array with defaults populated.
469
 */
470
function libraries_info_defaults(array &$library, $name) {
471
  $library += array(
472
    'machine name' => $name,
473
    'name' => $name,
474
    'vendor url' => '',
475
    'download url' => '',
476
    'download file url' => '',
477
    'path' => '',
478
    'library path' => NULL,
479
    'version callback' => 'libraries_get_version',
480
    'version arguments' => array(),
481
    'files' => array(),
482
    'dependencies' => array(),
483
    'variants' => array(),
484
    'versions' => array(),
485
    'integration files' => array(),
486
    'callbacks' => array(),
487
    // @todo Remove in 7.x-3.x
488
    'post-load integration files' => FALSE,
489
  );
490
  $library['callbacks'] += array(
491
    'info' => array(),
492
    'pre-detect' => array(),
493
    'post-detect' => array(),
494
    'pre-dependencies-load' => array(),
495
    'pre-load' => array(),
496
    'post-load' => array(),
497
  );
498

    
499
  // Add our own callbacks before any others.
500
  array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
501
  array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
502

    
503
  return $library;
504
}
505

    
506
/**
507
 * Tries to detect a library and its installed version.
508
 *
509
 * @param string $name
510
 *   (optional) The machine name of a library to detect and return registered
511
 *   information for. If omitted, information about all registered libraries is
512
 *   returned.
513
 *
514
 * @return array|false
515
 *   An associative array containing registered information for all libraries,
516
 *   the registered information for the library specified by $name, or FALSE if
517
 *   the library $name is not registered.
518
 *   In addition to the keys returned by libraries_info(), the following keys
519
 *   are contained:
520
 *   - installed: A boolean indicating whether the library is installed. Note
521
 *     that not only the top-level library, but also each variant contains this
522
 *     key.
523
 *   - version: If the version could be detected, the full version string.
524
 *   - error: If an error occurred during library detection, one of the
525
 *     following error statuses: "not found", "not detected", "not supported".
526
 *   - error message: If an error occurred during library detection, a detailed
527
 *     error message.
528
 *
529
 * @see libraries_info()
530
 */
531
function libraries_detect($name = NULL) {
532
  if (!isset($name)) {
533
    $libraries = &libraries_info();
534
    foreach ($libraries as $name => $library) {
535
      libraries_detect($name);
536
    }
537
    return $libraries;
538
  }
539

    
540
  // Re-use the statically cached value of libraries_info() to save memory.
541
  $library = &libraries_info($name);
542

    
543
  // Exit early if the library was not found.
544
  if ($library === FALSE) {
545
    return $library;
546
  }
547

    
548
  // If 'installed' is set, library detection ran already.
549
  if (isset($library['installed'])) {
550
    return $library;
551
  }
552

    
553
  $library['installed'] = FALSE;
554

    
555
  // Check whether the library exists.
556
  if (!isset($library['library path'])) {
557
    $library['library path'] = libraries_get_path($library['machine name']);
558
  }
559
  if ($library['library path'] === FALSE || !file_exists($library['library path'])) {
560
    $library['error'] = 'not found';
561
    $library['error message'] = t('The %library library could not be found.', array(
562
      '%library' => $library['name'],
563
    ));
564
    return $library;
565
  }
566

    
567
  // Invoke callbacks in the 'pre-detect' group.
568
  libraries_invoke('pre-detect', $library);
569

    
570
  // Detect library version, if not hardcoded.
571
  if (!isset($library['version'])) {
572
    // We support both a single parameter, which is an associative array, and an
573
    // indexed array of multiple parameters.
574
    if (isset($library['version arguments'][0])) {
575
      // Add the library as the first argument.
576
      $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
577
    }
578
    else {
579
      $library['version'] = call_user_func_array($library['version callback'], array(&$library, $library['version arguments']));
580
    }
581
    if (empty($library['version'])) {
582
      $library['error'] = 'not detected';
583
      $library['error message'] = t('The version of the %library library could not be detected.', array(
584
        '%library' => $library['name'],
585
      ));
586
      return $library;
587
    }
588
  }
589

    
590
  // Determine to which supported version the installed version maps.
591
  if (!empty($library['versions'])) {
592
    ksort($library['versions']);
593
    $version = 0;
594
    foreach ($library['versions'] as $supported_version => $version_properties) {
595
      if (version_compare($library['version'], $supported_version, '>=')) {
596
        $version = $supported_version;
597
      }
598
    }
599
    if (!$version) {
600
      $library['error'] = 'not supported';
601
      $library['error message'] = t('The installed version %version of the %library library is not supported.', array(
602
        '%version' => $library['version'],
603
        '%library' => $library['name'],
604
      ));
605
      return $library;
606
    }
607

    
608
    // Apply version specific definitions and overrides.
609
    $library = array_merge($library, $library['versions'][$version]);
610
    unset($library['versions']);
611
  }
612

    
613
  // Check each variant if it is installed.
614
  if (!empty($library['variants'])) {
615
    foreach ($library['variants'] as $variant_name => &$variant) {
616
      // If no variant callback has been set, assume the variant to be
617
      // installed.
618
      if (!isset($variant['variant callback'])) {
619
        $variant['installed'] = TRUE;
620
      }
621
      else {
622
        // We support both a single parameter, which is an associative array,
623
        // and an indexed array of multiple parameters.
624
        if (isset($variant['variant arguments'][0])) {
625
          // Add the library as the first argument, and the variant name as the second.
626
          $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments']));
627
        }
628
        else {
629
          $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
630
        }
631
        if (!$variant['installed']) {
632
          $variant['error'] = 'not found';
633
          $variant['error message'] = t('The %variant variant of the %library library could not be found.', array(
634
            '%variant' => $variant_name,
635
            '%library' => $library['name'],
636
          ));
637
        }
638
      }
639
    }
640
  }
641

    
642
  // If we end up here, the library should be usable.
643
  $library['installed'] = TRUE;
644

    
645
  // Invoke callbacks in the 'post-detect' group.
646
  libraries_invoke('post-detect', $library);
647

    
648
  return $library;
649
}
650

    
651
/**
652
 * Loads a library.
653
 *
654
 * @param $name
655
 *   The name of the library to load.
656
 * @param $variant
657
 *   The name of the variant to load. Note that only one variant of a library
658
 *   can be loaded within a single request. The variant that has been passed
659
 *   first is used; different variant names in subsequent calls are ignored.
660
 *
661
 * @return
662
 *   An associative array of the library information as returned from
663
 *   libraries_info(). The top-level properties contain the effective definition
664
 *   of the library (variant) that has been loaded. Additionally:
665
 *   - installed: Whether the library is installed, as determined by
666
 *     libraries_detect_library().
667
 *   - loaded: Either the amount of library files that have been loaded, or
668
 *     FALSE if the library could not be loaded.
669
 *   See hook_libraries_info() for more information.
670
 */
671
function libraries_load($name, $variant = NULL) {
672
  $loaded = &drupal_static(__FUNCTION__, array());
673

    
674
  if (!isset($loaded[$name])) {
675
    $library = cache_get($name, 'cache_libraries');
676
    if ($library) {
677
      $library = $library->data;
678
    }
679
    else {
680
      $library = libraries_detect($name);
681
      cache_set($name, $library, 'cache_libraries');
682
    }
683

    
684
    // Exit early if the library was not found.
685
    if ($library === FALSE) {
686
      $loaded[$name] = $library;
687
      return $loaded[$name];
688
    }
689

    
690
    // If a variant was specified, override the top-level properties with the
691
    // variant properties.
692
    if (isset($variant)) {
693
      // Ensure that the $variant key exists, and if it does not, set its
694
      // 'installed' property to FALSE by default. This will prevent the loading
695
      // of the library files below.
696
      $library['variants'] += array($variant => array('installed' => FALSE));
697
      $library = array_merge($library, $library['variants'][$variant]);
698
    }
699
    // Regardless of whether a specific variant was requested or not, there can
700
    // only be one variant of a library within a single request.
701
    unset($library['variants']);
702

    
703
    // Invoke callbacks in the 'pre-dependencies-load' group.
704
    libraries_invoke('pre-dependencies-load', $library);
705

    
706
    // If the library (variant) is installed, load it.
707
    $library['loaded'] = FALSE;
708
    if ($library['installed']) {
709
      // Load library dependencies.
710
      if (isset($library['dependencies'])) {
711
        foreach ($library['dependencies'] as $dependency) {
712
          libraries_load($dependency);
713
        }
714
      }
715

    
716
      // Invoke callbacks in the 'pre-load' group.
717
      libraries_invoke('pre-load', $library);
718

    
719
      // Load all the files associated with the library.
720
      $library['loaded'] = libraries_load_files($library);
721

    
722
      // Invoke callbacks in the 'post-load' group.
723
      libraries_invoke('post-load', $library);
724
    }
725
    $loaded[$name] = $library;
726
  }
727

    
728
  return $loaded[$name];
729
}
730

    
731
/**
732
 * Loads a library's files.
733
 *
734
 * @param $library
735
 *   An array of library information as returned by libraries_info().
736
 *
737
 * @return
738
 *   The number of loaded files.
739
 */
740
function libraries_load_files($library) {
741
  // As this key was added after 7.x-2.1 cached library structures might not
742
  // have it.
743
  $library += array('post-load integration files' => FALSE);
744

    
745
  // Load integration files.
746
  if (!$library['post-load integration files'] && !empty($library['integration files'])) {
747
    $enabled_themes = array();
748
    foreach (list_themes() as $theme_name => $theme) {
749
      if ($theme->status) {
750
        $enabled_themes[] = $theme_name;
751
      }
752
    }
753
    foreach ($library['integration files'] as $provider => $files) {
754
      if (module_exists($provider)) {
755
        libraries_load_files(array(
756
          'files' => $files,
757
          'path' => '',
758
          'library path' => drupal_get_path('module', $provider),
759
          'post-load integration files' => FALSE,
760
        ));
761
      }
762
      elseif (in_array($provider, $enabled_themes)) {
763
        libraries_load_files(array(
764
          'files' => $files,
765
          'path' => '',
766
          'library path' => drupal_get_path('theme', $provider),
767
          'post-load integration files' => FALSE,
768
        ));
769
      }
770
    }
771
  }
772

    
773
  // Construct the full path to the library for later use.
774
  $path = $library['library path'];
775
  $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
776

    
777
  // Count the number of loaded files for the return value.
778
  $count = 0;
779

    
780
  // Load both the JavaScript and the CSS files.
781
  // The parameters for drupal_add_js() and drupal_add_css() require special
782
  // handling.
783
  // @see drupal_process_attached()
784
  foreach (array('js', 'css') as $type) {
785
    if (!empty($library['files'][$type])) {
786
      foreach ($library['files'][$type] as $data => $options) {
787
        // If the value is not an array, it's a filename and passed as first
788
        // (and only) argument.
789
        if (!is_array($options)) {
790
          $data = $options;
791
          $options = array();
792
        }
793
        // In some cases, the first parameter ($data) is an array. Arrays can't
794
        // be passed as keys in PHP, so we have to get $data from the value
795
        // array.
796
        if (is_numeric($data)) {
797
          $data = $options['data'];
798
          unset($options['data']);
799
        }
800
        // Prepend the library path to the file name.
801
        $data = "$path/$data";
802
        // Apply the default group if the group isn't explicitly given.
803
        if (!isset($options['group'])) {
804
          $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT;
805
        }
806
        call_user_func('drupal_add_' . $type, $data, $options);
807
        $count++;
808
      }
809
    }
810
  }
811

    
812
  // Load PHP files.
813
  if (!empty($library['files']['php'])) {
814
    foreach ($library['files']['php'] as $file => $array) {
815
      $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
816
      if (file_exists($file_path)) {
817
        _libraries_require_once($file_path);
818
        $count++;
819
      }
820
    }
821
  }
822

    
823
  // Load integration files.
824
  if ($library['post-load integration files'] && !empty($library['integration files'])) {
825
    $enabled_themes = array();
826
    foreach (list_themes() as $theme_name => $theme) {
827
      if ($theme->status) {
828
        $enabled_themes[] = $theme_name;
829
      }
830
    }
831
    foreach ($library['integration files'] as $provider => $files) {
832
      if (module_exists($provider)) {
833
        libraries_load_files(array(
834
          'files' => $files,
835
          'path' => '',
836
          'library path' => drupal_get_path('module', $provider),
837
          'post-load integration files' => FALSE,
838
        ));
839
      }
840
      elseif (in_array($provider, $enabled_themes)) {
841
        libraries_load_files(array(
842
          'files' => $files,
843
          'path' => '',
844
          'library path' => drupal_get_path('theme', $provider),
845
          'post-load integration files' => FALSE,
846
        ));
847
      }
848
    }
849
  }
850

    
851
  return $count;
852
}
853

    
854
/**
855
 * Wrapper function for require_once.
856
 *
857
 * A library file could set a $path variable in file scope. Requiring such a
858
 * file directly in libraries_load_files() would lead to the local $path
859
 * variable being overridden after the require_once statement. This would
860
 * break loading further files. Therefore we use this trivial wrapper which has
861
 * no local state that can be tampered with.
862
 *
863
 * @param $file_path
864
 *   The file path of the file to require.
865
 */
866
function _libraries_require_once($file_path) {
867
  require_once $file_path;
868
}
869

    
870

    
871
/**
872
 * Gets the version information from an arbitrary library.
873
 *
874
 * @param $library
875
 *   An associative array containing all information about the library.
876
 * @param $options
877
 *   An associative array containing with the following keys:
878
 *   - file: The filename to parse for the version, relative to the library
879
 *     path. For example: 'docs/changelog.txt'.
880
 *   - pattern: A string containing a regular expression (PCRE) to match the
881
 *     library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that
882
 *     the returned version is not the match of the entire pattern (i.e.
883
 *     '@version 1.2.3' in the above example) but the match of the first
884
 *     sub-pattern (i.e. '1.2.3' in the above example).
885
 *   - lines: (optional) The maximum number of lines to search the pattern in.
886
 *     Defaults to 20.
887
 *   - cols: (optional) The maximum number of characters per line to take into
888
 *     account. Defaults to 200. In case of minified or compressed files, this
889
 *     prevents reading the entire file into memory.
890
 *
891
 * @return
892
 *   A string containing the version of the library.
893
 *
894
 * @see libraries_get_path()
895
 */
896
function libraries_get_version($library, $options) {
897
  // Provide defaults.
898
  $options += array(
899
    'file' => '',
900
    'pattern' => '',
901
    'lines' => 20,
902
    'cols' => 200,
903
  );
904

    
905
  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
906
  if (empty($options['file']) || !file_exists($file)) {
907
    return;
908
  }
909
  $file = fopen($file, 'r');
910
  while ($options['lines'] && $line = fgets($file, $options['cols'])) {
911
    if (preg_match($options['pattern'], $line, $version)) {
912
      fclose($file);
913
      return $version[1];
914
    }
915
    $options['lines']--;
916
  }
917
  fclose($file);
918
}
919

    
920
/**
921
 * Gets the version information from a library's package.json file.
922
 *
923
 * @param $library
924
 *   An associative array containing all information about the library.
925
 * @param $options
926
 *   This callback expects no option.
927
 * @return
928
 *   A string containing the version of the library.
929
 *
930
 * @see libraries_get_path()
931
 */
932
function libraries_get_package_json_version($library, $options) {
933
  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/package.json';
934
  if (!file_exists($file)) {
935
    return;
936
  }
937

    
938
  $content = file_get_contents($file);
939
  if (!$content) {
940
    return;
941
  }
942

    
943
  $data = drupal_json_decode($content);
944
  if (isset($data['version'])) {
945
    return $data['version'];
946
  }
947
}
948

    
949
/**
950
 * Implements hook_help().
951
 */
952
function libraries_help($path, $arg) {
953
  switch ($path) {
954
    case 'admin/reports/libraries':
955
      return t('Click on a library for a status report or detailed installation instructions.');
956
  }
957
}
958

    
959
/**
960
 * Implements hook_permission().
961
 */
962
function libraries_permission() {
963
  return array(
964
    'access library reports' => array(
965
      'title' => t('View library reports'),
966
    ),
967
  );
968
}
969

    
970
/**
971
 * Implements hook_menu().
972
 */
973
function libraries_menu() {
974
  $items = array();
975
  $items['admin/reports/libraries'] = array(
976
    'title' => 'Libraries',
977
    'description' => 'An overview of libraries installed on this site.',
978
    'page callback' => 'drupal_get_form',
979
    'page arguments' => array('libraries_admin_overview'),
980
    'access arguments' => array('access library reports'),
981
    'file' => 'libraries.admin.inc'
982
  );
983
  $items['admin/reports/libraries/%libraries_ui'] = array(
984
    'title' => 'Library status report',
985
    'description' => 'Status overview for a single library',
986
    'page callback' => 'drupal_get_form',
987
    'page arguments' => array('libraries_admin_library_status_form', 3),
988
    'access arguments' => array('access library reports'),
989
    'file' => 'libraries.admin.inc'
990
  );
991
  return $items;
992
}
993

    
994
/**
995
 * Loads library information for display in the user interface.
996
 *
997
 * This can be used as a menu loader function by specifying a '%libraries_ui'
998
 * parameter in a path.
999
 *
1000
 * We do not use libraries_load() (and, thus, a '%libraries' parameter) directly
1001
 * for displaying library information in the user interface as we do not want
1002
 * the library files to be loaded.
1003
 *
1004
 * @param string $name
1005
 *   The machine name of a library to return registered information for.
1006
 *
1007
 * @return array|false
1008
 *   An associative array containing registered information for the library
1009
 *   specified by $name, or FALSE if the library $name is not registered.
1010
 *
1011
 * @see libraries_detect()
1012
 * @see libraries_menu()
1013
 */
1014
function libraries_ui_load($name) {
1015
  return libraries_detect($name);
1016
}
1017

    
1018
/**
1019
 * Implements hook_theme().
1020
 */
1021
function libraries_theme($existing, $type, $theme, $path) {
1022
  // Because we extend the 'table' theme function, fetch the respective
1023
  // variables dynamically.
1024
  $common_theme = drupal_common_theme();
1025
  $variables = $common_theme['table']['variables'] + array('title' => '', 'description' => '');
1026
  return array(
1027
    'libraries_table_with_title' => array(
1028
      'variables' => $variables,
1029
      'file' => 'libraries.theme.inc',
1030
    ),
1031
  );
1032
}