Projet

Général

Profil

Paste
Télécharger (34,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / libraries / libraries.module @ 1aa883a3

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 all enabled themes.
84
 *
85
 * Themes are sorted so that base themes always precede their child themes.
86
 *
87
 * @return array
88
 *   An associative array of theme objects keyed by theme name.
89
 */
90
function libraries_get_enabled_themes() {
91
  $themes = array();
92
  foreach (list_themes() as $name => $theme) {
93
    if ($theme->status) {
94
      $themes[$name] = $theme;
95
    }
96
  }
97

    
98
  return libraries_sort_themes($themes);
99
}
100

    
101
/**
102
 * Sort a themes array.
103
 *
104
 * @param array $themes
105
 *   Array of themes as objects, keyed by theme name.
106
 * @param string $base
107
 *   A base theme (internal use only).
108
 *
109
 * @return array
110
 *   A similar array to $themes, but sorted in such a way that subthemes are
111
 *   always located after its base theme.
112
 */
113
function libraries_sort_themes($themes, $base = '') {
114
  $output = array();
115
  foreach ($themes as $name => $theme) {
116
    if (!isset($theme->base_theme) || $theme->base_theme == $base) {
117
      $output[$name] = $theme;
118
      unset($themes[$name]);
119
      $subthemes = libraries_sort_themes($themes, $name);
120
      foreach ($subthemes as $sub_name => $subtheme) {
121
        $output[$sub_name] = $subtheme;
122
      }
123
    }
124
  }
125
  return $output;
126
}
127

    
128
/**
129
 * Returns an array of library directories.
130
 *
131
 * Returns an array of library directories from the all-sites directory
132
 * (i.e. sites/all/libraries/), the profiles directory, and site-specific
133
 * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
134
 * by the library name. Site-specific libraries are prioritized over libraries
135
 * in the default directories. That is, if a library with the same name appears
136
 * in both the site-wide directory and site-specific directory, only the
137
 * site-specific version will be listed.
138
 *
139
 * @return array
140
 *   A list of library directories.
141
 *
142
 * @ingroup libraries
143
 */
144
function libraries_get_libraries() {
145
  $searchdir = array();
146
  $profile = drupal_get_path('profile', drupal_get_profile());
147
  $config = conf_path();
148

    
149
  // $config and $profile should never be empty in a proper Drupal setup.
150
  // However, we should never search into the root filesystem under any
151
  // circumstances, so just bail out in that case.
152
  if (!$profile && !$config) {
153
    return array();
154
  }
155

    
156
  // Similar to 'modules' and 'themes' directories in the root directory,
157
  // certain distributions may want to place libraries into a 'libraries'
158
  // directory in Drupal's root directory.
159
  $searchdir[] = 'libraries';
160

    
161
  // Similar to 'modules' and 'themes' directories inside an installation
162
  // profile, installation profiles may want to place libraries into a
163
  // 'libraries' directory.
164
  $searchdir[] = "$profile/libraries";
165

    
166
  // Always search sites/all/libraries.
167
  $searchdir[] = 'sites/all/libraries';
168

    
169
  // Also search sites/<domain>/*.
170
  $searchdir[] = "$config/libraries";
171

    
172
  // Retrieve list of directories.
173
  $directories = array();
174
  $nomask = array('CVS');
175
  foreach ($searchdir as $dir) {
176
    if (is_dir($dir) && $handle = opendir($dir)) {
177
      while (FALSE !== ($file = readdir($handle))) {
178
        if (!in_array($file, $nomask) && $file[0] != '.') {
179
          if (is_dir("$dir/$file")) {
180
            $directories[$file] = "$dir/$file";
181
          }
182
        }
183
      }
184
      closedir($handle);
185
    }
186
  }
187

    
188
  return $directories;
189
}
190

    
191
/**
192
 * Looks for library info files.
193
 *
194
 * This function scans the following directories for info files:
195
 * - libraries
196
 * - profiles/$profilename/libraries
197
 * - sites/all/libraries
198
 * - sites/$sitename/libraries
199
 * - any directories specified via hook_libraries_info_file_paths()
200
 *
201
 * @return array
202
 *   An array of info files, keyed by library name. The values are the paths of
203
 *   the files.
204
 */
205
function libraries_scan_info_files() {
206
  $profile = drupal_get_path('profile', drupal_get_profile());
207
  $config = conf_path();
208

    
209
  // Build a list of directories.
210
  $directories = module_invoke_all('libraries_info_file_paths');
211
  $directories[] = 'libraries';
212
  $directories[] = "$profile/libraries";
213
  $directories[] = 'sites/all/libraries';
214
  $directories[] = "$config/libraries";
215

    
216
  // Scan for info files.
217
  $files = array();
218
  foreach ($directories as $dir) {
219
    if (file_exists($dir)) {
220
      $files = array_merge($files, file_scan_directory($dir, '@^[A-Za-z0-9._-]+\.libraries\.info$@', array(
221
        'key' => 'name',
222
        'recurse' => FALSE,
223
      )));
224
    }
225
  }
226

    
227
  foreach ($files as $filename => $file) {
228
    $files[basename($filename, '.libraries')] = $file;
229
    unset($files[$filename]);
230
  }
231

    
232
  return $files;
233
}
234

    
235
/**
236
 * Invokes library callbacks.
237
 *
238
 * @param $group
239
 *   A string containing the group of callbacks that is to be applied. Should be
240
 *   either 'info', 'pre-detect', 'post-detect', or 'load'.
241
 * @param $library
242
 *   An array of library information, passed by reference.
243
 */
244
function libraries_invoke($group, &$library) {
245
  // When introducing new callback groups in newer versions, stale cached
246
  // library information somehow reaches this point during the database update
247
  // before clearing the library cache.
248
  if (empty($library['callbacks'][$group])) {
249
    return;
250
  }
251

    
252
  foreach ($library['callbacks'][$group] as $callback) {
253
    libraries_traverse_library($library, $callback);
254
  }
255
}
256

    
257
/**
258
 * Helper function to apply a callback to all parts of a library.
259
 *
260
 * Because library declarations can include variants and versions, and those
261
 * version declarations can in turn include variants, modifying e.g. the 'files'
262
 * property everywhere it is declared can be quite cumbersome, in which case
263
 * this helper function is useful.
264
 *
265
 * @param $library
266
 *   An array of library information, passed by reference.
267
 * @param $callback
268
 *   A string containing the callback to apply to all parts of a library.
269
 */
270
function libraries_traverse_library(&$library, $callback) {
271
  // Always apply the callback to the top-level library.
272
  $callback($library, NULL, NULL);
273

    
274
  // Apply the callback to versions.
275
  if (isset($library['versions'])) {
276
    foreach ($library['versions'] as $version_string => &$version) {
277
      $callback($version, $version_string, NULL);
278
      // Versions can include variants as well.
279
      if (isset($version['variants'])) {
280
        foreach ($version['variants'] as $version_variant_name => &$version_variant) {
281
          $callback($version_variant, $version_string, $version_variant_name);
282
        }
283
      }
284
    }
285
  }
286

    
287
  // Apply the callback to variants.
288
  if (isset($library['variants'])) {
289
    foreach ($library['variants'] as $variant_name => &$variant) {
290
      $callback($variant, NULL, $variant_name);
291
    }
292
  }
293
}
294

    
295
/**
296
 * Library info callback to make all 'files' properties consistent.
297
 *
298
 * This turns libraries' file information declared as e.g.
299
 * @code
300
 * $library['files']['js'] = array('example_1.js', 'example_2.js');
301
 * @endcode
302
 * into
303
 * @code
304
 * $library['files']['js'] = array(
305
 *   'example_1.js' => array(),
306
 *   'example_2.js' => array(),
307
 * );
308
 * @endcode
309
 * It does the same for the 'integration files' property.
310
 *
311
 * @param $library
312
 *   An associative array of library information or a part of it, passed by
313
 *   reference.
314
 * @param $version
315
 *   If the library information belongs to a specific version, the version
316
 *   string. NULL otherwise.
317
 * @param $variant
318
 *   If the library information belongs to a specific variant, the variant name.
319
 *   NULL otherwise.
320
 *
321
 * @see libraries_info()
322
 * @see libraries_invoke()
323
 */
324
function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) {
325
  // Both the 'files' property and the 'integration files' property contain file
326
  // declarations, and we want to make both consistent.
327
  $file_types = array();
328
  if (isset($library['files'])) {
329
    $file_types[] = &$library['files'];
330
  }
331
  if (isset($library['integration files'])) {
332
    // Integration files are additionally keyed by module.
333
    foreach ($library['integration files'] as &$integration_files) {
334
      $file_types[] = &$integration_files;
335
    }
336
  }
337
  foreach ($file_types as &$files) {
338
    // Go through all supported types of files.
339
    foreach (array('js', 'css', 'php') as $type) {
340
      if (isset($files[$type])) {
341
        foreach ($files[$type] as $key => $value) {
342
          // Unset numeric keys and turn the respective values into keys.
343
          if (is_numeric($key)) {
344
            $files[$type][$value] = array();
345
            unset($files[$type][$key]);
346
          }
347
        }
348
      }
349
    }
350
  }
351
}
352

    
353
/**
354
 * Library post-detect callback to process and detect dependencies.
355
 *
356
 * It checks whether each of the dependencies of a library are installed and
357
 * available in a compatible version.
358
 *
359
 * @param $library
360
 *   An associative array of library information or a part of it, passed by
361
 *   reference.
362
 * @param $version
363
 *   If the library information belongs to a specific version, the version
364
 *   string. NULL otherwise.
365
 * @param $variant
366
 *   If the library information belongs to a specific variant, the variant name.
367
 *   NULL otherwise.
368
 *
369
 * @see libraries_info()
370
 * @see libraries_invoke()
371
 */
372
function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) {
373
  if (isset($library['dependencies'])) {
374
    foreach ($library['dependencies'] as &$dependency_string) {
375
      $dependency_info = drupal_parse_dependency($dependency_string);
376
      $dependency = libraries_detect($dependency_info['name']);
377
      if (!$dependency['installed']) {
378
        $library['installed'] = FALSE;
379
        $library['error'] = 'missing dependency';
380
        $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
381
          '%dependency' => $dependency['name'],
382
          '%library' => $library['name'],
383
        ));
384
      }
385
      elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
386
        $library['installed'] = FALSE;
387
        $library['error'] = 'incompatible dependency';
388
        $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
389
          '%dependency_version' => $dependency['version'],
390
          '%dependency' => $dependency['name'],
391
          '%library' => $library['name'],
392
        ));
393
      }
394

    
395
      // Remove the version string from the dependency, so libraries_load() can
396
      // load the libraries directly.
397
      $dependency_string = $dependency_info['name'];
398
    }
399
  }
400
}
401

    
402
/**
403
 * Returns information about registered libraries.
404
 *
405
 * The returned information is unprocessed; i.e., as registered by modules.
406
 *
407
 * @param $name
408
 *   (optional) The machine name of a library to return registered information
409
 *   for. If omitted, information about all registered libraries is returned.
410
 *
411
 * @return array|false
412
 *   An associative array containing registered information for all libraries,
413
 *   the registered information for the library specified by $name, or FALSE if
414
 *   the library $name is not registered.
415
 *
416
 * @see hook_libraries_info()
417
 *
418
 * @todo Re-introduce support for include file plugin system - either by copying
419
 *   Wysiwyg's code, or directly switching to CTools.
420
 */
421
function &libraries_info($name = NULL) {
422
  // This static cache is re-used by libraries_detect() to save memory.
423
  $libraries = &drupal_static(__FUNCTION__);
424

    
425
  if (!isset($libraries)) {
426
    $libraries = array();
427

    
428
    // Gather information from hook_libraries_info() in enabled modules.
429
    foreach (module_implements('libraries_info') as $module) {
430
      foreach (module_invoke($module, 'libraries_info') as $machine_name => $properties) {
431
        $properties['info type'] = 'module';
432
        $properties['module'] = $module;
433
        $libraries[$machine_name] = $properties;
434
      }
435
    }
436

    
437
    // Gather information from hook_libraries_info() in enabled themes. Themes
438
    // are sorted to ensure that a base theme's template.php is included before
439
    // its children's ones.
440
    $themes = array();
441
    foreach (libraries_get_enabled_themes() as $theme_name => $theme_info) {
442
      if (file_exists(drupal_get_path('theme', $theme_name) . '/template.php')) {
443
        // Collect a list of viable themes for re-use when calling the alter
444
        // hook.
445
        $themes[] = $theme_name;
446

    
447
        include_once drupal_get_path('theme', $theme_name) . '/template.php';
448

    
449
        $function = $theme_name . '_libraries_info';
450
        if (function_exists($function)) {
451
          foreach ($function() as $machine_name => $properties) {
452
            $properties['info type'] = 'theme';
453
            $properties['theme'] = $theme_name;
454
            $libraries[$machine_name] = $properties;
455
          }
456
        }
457
      }
458
    }
459

    
460
    // Gather information from .info files.
461
    // .info files override module definitions.
462
    foreach (libraries_scan_info_files() as $machine_name => $file) {
463
      $properties = drupal_parse_info_file($file->uri);
464
      $properties['info type'] = 'info file';
465
      $properties['info file'] = $file->uri;
466
      $libraries[$machine_name] = $properties;
467
    }
468

    
469
    // Provide defaults.
470
    foreach ($libraries as $machine_name => &$properties) {
471
      libraries_info_defaults($properties, $machine_name);
472
    }
473

    
474
    // Allow enabled modules and themes to alter the registered libraries.
475
    // drupal_alter() only takes the currently active theme into account, not
476
    // all enabled themes.
477
    foreach (module_implements('libraries_info_alter') as $module) {
478
      $function = $module . '_libraries_info_alter';
479
      $function($libraries);
480
    }
481
    foreach ($themes as $theme) {
482
      $function = $theme . '_libraries_info_alter';
483
      // The template.php file was included above.
484
      if (function_exists($function)) {
485
        $function($libraries);
486
      }
487
    }
488

    
489
    // Invoke callbacks in the 'info' group.
490
    foreach ($libraries as &$properties) {
491
      libraries_invoke('info', $properties);
492
    }
493
  }
494

    
495
  if (isset($name)) {
496
    if (!empty($libraries[$name])) {
497
      return $libraries[$name];
498
    }
499
    else {
500
      $false = FALSE;
501
      return $false;
502
    }
503
  }
504
  return $libraries;
505
}
506

    
507
/**
508
 * Applies default properties to a library definition.
509
 *
510
 * @param array $library
511
 *   An array of library information, passed by reference.
512
 * @param string $name
513
 *   The machine name of the passed-in library.
514
 *
515
 * @return array
516
 *   The library information array with defaults populated.
517
 */
518
function libraries_info_defaults(array &$library, $name) {
519
  $library += array(
520
    'machine name' => $name,
521
    'name' => $name,
522
    'vendor url' => '',
523
    'download url' => '',
524
    'download file url' => '',
525
    'path' => '',
526
    'library path' => NULL,
527
    'version callback' => 'libraries_get_version',
528
    'version arguments' => array(),
529
    'files' => array(),
530
    'dependencies' => array(),
531
    'variants' => array(),
532
    'versions' => array(),
533
    'integration files' => array(),
534
    'callbacks' => array(),
535
    // @todo Remove in 7.x-3.x
536
    'post-load integration files' => FALSE,
537
  );
538
  $library['callbacks'] += array(
539
    'info' => array(),
540
    'pre-detect' => array(),
541
    'post-detect' => array(),
542
    'pre-dependencies-load' => array(),
543
    'pre-load' => array(),
544
    'post-load' => array(),
545
  );
546

    
547
  // Add our own callbacks before any others.
548
  array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
549
  array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
550

    
551
  return $library;
552
}
553

    
554
/**
555
 * Tries to detect a library and its installed version.
556
 *
557
 * @param string $name
558
 *   (optional) The machine name of a library to detect and return registered
559
 *   information for. If omitted, information about all registered libraries is
560
 *   returned.
561
 *
562
 * @return array|false
563
 *   An associative array containing registered information for all libraries,
564
 *   the registered information for the library specified by $name, or FALSE if
565
 *   the library $name is not registered.
566
 *   In addition to the keys returned by libraries_info(), the following keys
567
 *   are contained:
568
 *   - installed: A boolean indicating whether the library is installed. Note
569
 *     that not only the top-level library, but also each variant contains this
570
 *     key.
571
 *   - version: If the version could be detected, the full version string.
572
 *   - error: If an error occurred during library detection, one of the
573
 *     following error statuses: "not found", "not detected", "not supported".
574
 *   - error message: If an error occurred during library detection, a detailed
575
 *     error message.
576
 *
577
 * @see libraries_info()
578
 */
579
function libraries_detect($name = NULL) {
580
  if (!isset($name)) {
581
    $libraries = &libraries_info();
582
    foreach ($libraries as $name => $library) {
583
      libraries_detect($name);
584
    }
585
    return $libraries;
586
  }
587

    
588
  // Re-use the statically cached value of libraries_info() to save memory.
589
  $library = &libraries_info($name);
590

    
591
  // Exit early if the library was not found.
592
  if ($library === FALSE) {
593
    return $library;
594
  }
595

    
596
  // If 'installed' is set, library detection ran already.
597
  if (isset($library['installed'])) {
598
    return $library;
599
  }
600

    
601
  $library['installed'] = FALSE;
602

    
603
  // Check whether the library exists.
604
  if (!isset($library['library path'])) {
605
    $library['library path'] = libraries_get_path($library['machine name']);
606
  }
607
  if ($library['library path'] === FALSE || !file_exists($library['library path'])) {
608
    $library['error'] = 'not found';
609
    $library['error message'] = t('The %library library could not be found.', array(
610
      '%library' => $library['name'],
611
    ));
612
    return $library;
613
  }
614

    
615
  // Invoke callbacks in the 'pre-detect' group.
616
  libraries_invoke('pre-detect', $library);
617

    
618
  // Detect library version, if not hardcoded.
619
  if (!isset($library['version'])) {
620
    // We support both a single parameter, which is an associative array, and an
621
    // indexed array of multiple parameters.
622
    if (isset($library['version arguments'][0])) {
623
      // Add the library as the first argument.
624
      $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
625
    }
626
    else {
627
      $library['version'] = call_user_func_array($library['version callback'], array(&$library, $library['version arguments']));
628
    }
629
    if (empty($library['version'])) {
630
      $library['error'] = 'not detected';
631
      $library['error message'] = t('The version of the %library library could not be detected.', array(
632
        '%library' => $library['name'],
633
      ));
634
      return $library;
635
    }
636
  }
637

    
638
  // Determine to which supported version the installed version maps.
639
  if (!empty($library['versions'])) {
640
    ksort($library['versions']);
641
    $version = 0;
642
    foreach ($library['versions'] as $supported_version => $version_properties) {
643
      if (version_compare($library['version'], $supported_version, '>=')) {
644
        $version = $supported_version;
645
      }
646
    }
647
    if (!$version) {
648
      $library['error'] = 'not supported';
649
      $library['error message'] = t('The installed version %version of the %library library is not supported.', array(
650
        '%version' => $library['version'],
651
        '%library' => $library['name'],
652
      ));
653
      return $library;
654
    }
655

    
656
    // Apply version specific definitions and overrides.
657
    $library = array_merge($library, $library['versions'][$version]);
658
    unset($library['versions']);
659
  }
660

    
661
  // Check each variant if it is installed.
662
  if (!empty($library['variants'])) {
663
    foreach ($library['variants'] as $variant_name => &$variant) {
664
      // If no variant callback has been set, assume the variant to be
665
      // installed.
666
      if (!isset($variant['variant callback'])) {
667
        $variant['installed'] = TRUE;
668
      }
669
      else {
670
        // We support both a single parameter, which is an associative array,
671
        // and an indexed array of multiple parameters.
672
        if (isset($variant['variant arguments'][0])) {
673
          // Add the library as the first argument, and the variant name as the second.
674
          $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments']));
675
        }
676
        else {
677
          $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
678
        }
679
        if (!$variant['installed']) {
680
          $variant['error'] = 'not found';
681
          $variant['error message'] = t('The %variant variant of the %library library could not be found.', array(
682
            '%variant' => $variant_name,
683
            '%library' => $library['name'],
684
          ));
685
        }
686
      }
687
    }
688
  }
689

    
690
  // If we end up here, the library should be usable.
691
  $library['installed'] = TRUE;
692

    
693
  // Invoke callbacks in the 'post-detect' group.
694
  libraries_invoke('post-detect', $library);
695

    
696
  return $library;
697
}
698

    
699
/**
700
 * Loads a library.
701
 *
702
 * @param $name
703
 *   The name of the library to load.
704
 * @param $variant
705
 *   The name of the variant to load. Note that only one variant of a library
706
 *   can be loaded within a single request. The variant that has been passed
707
 *   first is used; different variant names in subsequent calls are ignored.
708
 *
709
 * @return
710
 *   An associative array of the library information as returned from
711
 *   libraries_info(). The top-level properties contain the effective definition
712
 *   of the library (variant) that has been loaded. Additionally:
713
 *   - installed: Whether the library is installed, as determined by
714
 *     libraries_detect_library().
715
 *   - loaded: Either the amount of library files that have been loaded, or
716
 *     FALSE if the library could not be loaded.
717
 *   See hook_libraries_info() for more information.
718
 */
719
function libraries_load($name, $variant = NULL) {
720
  $loaded = &drupal_static(__FUNCTION__, array());
721

    
722
  if (!isset($loaded[$name])) {
723
    $library = cache_get($name, 'cache_libraries');
724
    if ($library) {
725
      $library = $library->data;
726
    }
727
    else {
728
      $library = libraries_detect($name);
729
      cache_set($name, $library, 'cache_libraries');
730
    }
731

    
732
    // Exit early if the library was not found.
733
    if ($library === FALSE) {
734
      $loaded[$name] = $library;
735
      return $loaded[$name];
736
    }
737

    
738
    // If a variant was specified, override the top-level properties with the
739
    // variant properties.
740
    if (isset($variant)) {
741
      // Ensure that the $variant key exists, and if it does not, set its
742
      // 'installed' property to FALSE by default. This will prevent the loading
743
      // of the library files below.
744
      $library['variants'] += array($variant => array('installed' => FALSE));
745
      $library = array_merge($library, $library['variants'][$variant]);
746
    }
747
    // Regardless of whether a specific variant was requested or not, there can
748
    // only be one variant of a library within a single request.
749
    unset($library['variants']);
750

    
751
    // Invoke callbacks in the 'pre-dependencies-load' group.
752
    libraries_invoke('pre-dependencies-load', $library);
753

    
754
    // If the library (variant) is installed, load it.
755
    $library['loaded'] = FALSE;
756
    if ($library['installed']) {
757
      // Load library dependencies.
758
      if (isset($library['dependencies'])) {
759
        foreach ($library['dependencies'] as $dependency) {
760
          libraries_load($dependency);
761
        }
762
      }
763

    
764
      // Invoke callbacks in the 'pre-load' group.
765
      libraries_invoke('pre-load', $library);
766

    
767
      // Load all the files associated with the library.
768
      $library['loaded'] = libraries_load_files($library);
769

    
770
      // Invoke callbacks in the 'post-load' group.
771
      libraries_invoke('post-load', $library);
772
    }
773
    $loaded[$name] = $library;
774
  }
775

    
776
  return $loaded[$name];
777
}
778

    
779
/**
780
 * Loads a library's files.
781
 *
782
 * @param $library
783
 *   An array of library information as returned by libraries_info().
784
 *
785
 * @return
786
 *   The number of loaded files.
787
 */
788
function libraries_load_files($library) {
789
  // As this key was added after 7.x-2.1 cached library structures might not
790
  // have it.
791
  $library += array('post-load integration files' => FALSE);
792

    
793
  // Load integration files.
794
  if (!$library['post-load integration files'] && !empty($library['integration files'])) {
795
    $enabled_themes = array();
796
    foreach (list_themes() as $theme_name => $theme) {
797
      if ($theme->status) {
798
        $enabled_themes[] = $theme_name;
799
      }
800
    }
801
    foreach ($library['integration files'] as $provider => $files) {
802
      if (module_exists($provider)) {
803
        libraries_load_files(array(
804
          'files' => $files,
805
          'path' => '',
806
          'library path' => drupal_get_path('module', $provider),
807
          'post-load integration files' => FALSE,
808
        ));
809
      }
810
      elseif (in_array($provider, $enabled_themes)) {
811
        libraries_load_files(array(
812
          'files' => $files,
813
          'path' => '',
814
          'library path' => drupal_get_path('theme', $provider),
815
          'post-load integration files' => FALSE,
816
        ));
817
      }
818
    }
819
  }
820

    
821
  // Construct the full path to the library for later use.
822
  $path = $library['library path'];
823
  $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
824

    
825
  // Count the number of loaded files for the return value.
826
  $count = 0;
827

    
828
  // Load both the JavaScript and the CSS files.
829
  // The parameters for drupal_add_js() and drupal_add_css() require special
830
  // handling.
831
  // @see drupal_process_attached()
832
  foreach (array('js', 'css') as $type) {
833
    if (!empty($library['files'][$type])) {
834
      foreach ($library['files'][$type] as $data => $options) {
835
        // If the value is not an array, it's a filename and passed as first
836
        // (and only) argument.
837
        if (!is_array($options)) {
838
          $data = $options;
839
          $options = array();
840
        }
841
        // In some cases, the first parameter ($data) is an array. Arrays can't
842
        // be passed as keys in PHP, so we have to get $data from the value
843
        // array.
844
        if (is_numeric($data)) {
845
          $data = $options['data'];
846
          unset($options['data']);
847
        }
848
        // Prepend the library path to the file name.
849
        $data = "$path/$data";
850
        // Apply the default group if the group isn't explicitly given.
851
        if (!isset($options['group'])) {
852
          $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT;
853
        }
854
        call_user_func('drupal_add_' . $type, $data, $options);
855
        $count++;
856
      }
857
    }
858
  }
859

    
860
  // Load PHP files.
861
  if (!empty($library['files']['php'])) {
862
    foreach ($library['files']['php'] as $file => $array) {
863
      $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
864
      if (file_exists($file_path)) {
865
        _libraries_require_once($file_path);
866
        $count++;
867
      }
868
    }
869
  }
870

    
871
  // Load integration files.
872
  if ($library['post-load integration files'] && !empty($library['integration files'])) {
873
    $enabled_themes = array();
874
    foreach (list_themes() as $theme_name => $theme) {
875
      if ($theme->status) {
876
        $enabled_themes[] = $theme_name;
877
      }
878
    }
879
    foreach ($library['integration files'] as $provider => $files) {
880
      if (module_exists($provider)) {
881
        libraries_load_files(array(
882
          'files' => $files,
883
          'path' => '',
884
          'library path' => drupal_get_path('module', $provider),
885
          'post-load integration files' => FALSE,
886
        ));
887
      }
888
      elseif (in_array($provider, $enabled_themes)) {
889
        libraries_load_files(array(
890
          'files' => $files,
891
          'path' => '',
892
          'library path' => drupal_get_path('theme', $provider),
893
          'post-load integration files' => FALSE,
894
        ));
895
      }
896
    }
897
  }
898

    
899
  return $count;
900
}
901

    
902
/**
903
 * Wrapper function for require_once.
904
 *
905
 * A library file could set a $path variable in file scope. Requiring such a
906
 * file directly in libraries_load_files() would lead to the local $path
907
 * variable being overridden after the require_once statement. This would
908
 * break loading further files. Therefore we use this trivial wrapper which has
909
 * no local state that can be tampered with.
910
 *
911
 * @param $file_path
912
 *   The file path of the file to require.
913
 */
914
function _libraries_require_once($file_path) {
915
  require_once $file_path;
916
}
917

    
918

    
919
/**
920
 * Gets the version information from an arbitrary library.
921
 *
922
 * @param $library
923
 *   An associative array containing all information about the library.
924
 * @param $options
925
 *   An associative array containing with the following keys:
926
 *   - file: The filename to parse for the version, relative to the library
927
 *     path. For example: 'docs/changelog.txt'.
928
 *   - pattern: A string containing a regular expression (PCRE) to match the
929
 *     library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that
930
 *     the returned version is not the match of the entire pattern (i.e.
931
 *     '@version 1.2.3' in the above example) but the match of the first
932
 *     sub-pattern (i.e. '1.2.3' in the above example).
933
 *   - lines: (optional) The maximum number of lines to search the pattern in.
934
 *     Defaults to 20.
935
 *   - cols: (optional) The maximum number of characters per line to take into
936
 *     account. Defaults to 200. In case of minified or compressed files, this
937
 *     prevents reading the entire file into memory.
938
 *
939
 * @return
940
 *   A string containing the version of the library.
941
 *
942
 * @see libraries_get_path()
943
 */
944
function libraries_get_version($library, $options) {
945
  // Provide defaults.
946
  $options += array(
947
    'file' => '',
948
    'pattern' => '',
949
    'lines' => 20,
950
    'cols' => 200,
951
  );
952

    
953
  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
954
  if (empty($options['file']) || !file_exists($file)) {
955
    return;
956
  }
957
  $file = fopen($file, 'r');
958
  while ($options['lines'] && $line = fgets($file, $options['cols'])) {
959
    if (preg_match($options['pattern'], $line, $version)) {
960
      fclose($file);
961
      return $version[1];
962
    }
963
    $options['lines']--;
964
  }
965
  fclose($file);
966
}
967

    
968
/**
969
 * Gets the version information from a library's package.json file.
970
 *
971
 * @param $library
972
 *   An associative array containing all information about the library.
973
 * @param $options
974
 *   This callback expects no option.
975
 * @return
976
 *   A string containing the version of the library.
977
 *
978
 * @see libraries_get_path()
979
 */
980
function libraries_get_package_json_version($library, $options) {
981
  $file = DRUPAL_ROOT . '/' . $library['library path'] . '/package.json';
982
  if (!file_exists($file)) {
983
    return;
984
  }
985

    
986
  $content = file_get_contents($file);
987
  if (!$content) {
988
    return;
989
  }
990

    
991
  $data = drupal_json_decode($content);
992
  if (isset($data['version'])) {
993
    return $data['version'];
994
  }
995
}
996

    
997
/**
998
 * Implements hook_help().
999
 */
1000
function libraries_help($path, $arg) {
1001
  switch ($path) {
1002
    case 'admin/reports/libraries':
1003
      return t('Click on a library for a status report or detailed installation instructions.');
1004
  }
1005
}
1006

    
1007
/**
1008
 * Implements hook_permission().
1009
 */
1010
function libraries_permission() {
1011
  return array(
1012
    'access library reports' => array(
1013
      'title' => t('View library reports'),
1014
    ),
1015
  );
1016
}
1017

    
1018
/**
1019
 * Implements hook_menu().
1020
 */
1021
function libraries_menu() {
1022
  $items = array();
1023
  $items['admin/reports/libraries'] = array(
1024
    'title' => 'Libraries',
1025
    'description' => 'An overview of libraries installed on this site.',
1026
    'page callback' => 'drupal_get_form',
1027
    'page arguments' => array('libraries_admin_overview'),
1028
    'access arguments' => array('access library reports'),
1029
    'file' => 'libraries.admin.inc'
1030
  );
1031
  $items['admin/reports/libraries/%libraries_ui'] = array(
1032
    'title' => 'Library status report',
1033
    'description' => 'Status overview for a single library',
1034
    'page callback' => 'drupal_get_form',
1035
    'page arguments' => array('libraries_admin_library_status_form', 3),
1036
    'access arguments' => array('access library reports'),
1037
    'file' => 'libraries.admin.inc'
1038
  );
1039
  return $items;
1040
}
1041

    
1042
/**
1043
 * Loads library information for display in the user interface.
1044
 *
1045
 * This can be used as a menu loader function by specifying a '%libraries_ui'
1046
 * parameter in a path.
1047
 *
1048
 * We do not use libraries_load() (and, thus, a '%libraries' parameter) directly
1049
 * for displaying library information in the user interface as we do not want
1050
 * the library files to be loaded.
1051
 *
1052
 * @param string $name
1053
 *   The machine name of a library to return registered information for.
1054
 *
1055
 * @return array|false
1056
 *   An associative array containing registered information for the library
1057
 *   specified by $name, or FALSE if the library $name is not registered.
1058
 *
1059
 * @see libraries_detect()
1060
 * @see libraries_menu()
1061
 */
1062
function libraries_ui_load($name) {
1063
  return libraries_detect($name);
1064
}
1065

    
1066
/**
1067
 * Implements hook_theme().
1068
 */
1069
function libraries_theme($existing, $type, $theme, $path) {
1070
  // Because we extend the 'table' theme function, fetch the respective
1071
  // variables dynamically.
1072
  $common_theme = drupal_common_theme();
1073
  $variables = $common_theme['table']['variables'] + array('title' => '', 'description' => '');
1074
  return array(
1075
    'libraries_table_with_title' => array(
1076
      'variables' => $variables,
1077
      'file' => 'libraries.theme.inc',
1078
    ),
1079
  );
1080
}