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
|
}
|