libraries.module 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. <?php
  2. /**
  3. * @file
  4. * External library handling for Drupal modules.
  5. */
  6. /**
  7. * Implements hook_flush_caches().
  8. */
  9. function libraries_flush_caches() {
  10. // Clear static caches.
  11. // We don't clear the 'libraries_load' static cache, because that could result
  12. // in libraries that had been loaded before the cache flushing to be loaded
  13. // again afterwards.
  14. foreach (array('libraries_get_path', 'libraries_info') as $name) {
  15. drupal_static_reset($name);
  16. }
  17. // @todo When upgrading from 1.x, update.php attempts to flush caches before
  18. // the cache table has been created.
  19. // @see http://drupal.org/node/1477932
  20. if (db_table_exists('cache_libraries')) {
  21. return array('cache_libraries');
  22. }
  23. }
  24. /**
  25. * Gets the path of a library.
  26. *
  27. * @param $name
  28. * The machine name of a library to return the path for.
  29. * @param $base_path
  30. * Whether to prefix the resulting path with base_path().
  31. *
  32. * @return string
  33. * The path to the specified library or FALSE if the library wasn't found.
  34. *
  35. * @ingroup libraries
  36. */
  37. function libraries_get_path($name, $base_path = FALSE) {
  38. $libraries = &drupal_static(__FUNCTION__);
  39. if (!isset($libraries)) {
  40. $libraries = libraries_get_libraries();
  41. }
  42. $path = ($base_path ? base_path() : '');
  43. if (!isset($libraries[$name])) {
  44. return FALSE;
  45. }
  46. else {
  47. $path .= $libraries[$name];
  48. }
  49. return $path;
  50. }
  51. /**
  52. * Returns an array of library directories.
  53. *
  54. * Returns an array of library directories from the all-sites directory
  55. * (i.e. sites/all/libraries/), the profiles directory, and site-specific
  56. * directory (i.e. sites/somesite/libraries/). The returned array will be keyed
  57. * by the library name. Site-specific libraries are prioritized over libraries
  58. * in the default directories. That is, if a library with the same name appears
  59. * in both the site-wide directory and site-specific directory, only the
  60. * site-specific version will be listed.
  61. *
  62. * @return array
  63. * A list of library directories.
  64. *
  65. * @ingroup libraries
  66. */
  67. function libraries_get_libraries() {
  68. $searchdir = array();
  69. $profile = drupal_get_path('profile', drupal_get_profile());
  70. $config = conf_path();
  71. // Similar to 'modules' and 'themes' directories in the root directory,
  72. // certain distributions may want to place libraries into a 'libraries'
  73. // directory in Drupal's root directory.
  74. $searchdir[] = 'libraries';
  75. // Similar to 'modules' and 'themes' directories inside an installation
  76. // profile, installation profiles may want to place libraries into a
  77. // 'libraries' directory.
  78. $searchdir[] = "$profile/libraries";
  79. // Always search sites/all/libraries.
  80. $searchdir[] = 'sites/all/libraries';
  81. // Also search sites/<domain>/*.
  82. $searchdir[] = "$config/libraries";
  83. // Retrieve list of directories.
  84. $directories = array();
  85. $nomask = array('CVS');
  86. foreach ($searchdir as $dir) {
  87. if (is_dir($dir) && $handle = opendir($dir)) {
  88. while (FALSE !== ($file = readdir($handle))) {
  89. if (!in_array($file, $nomask) && $file[0] != '.') {
  90. if (is_dir("$dir/$file")) {
  91. $directories[$file] = "$dir/$file";
  92. }
  93. }
  94. }
  95. closedir($handle);
  96. }
  97. }
  98. return $directories;
  99. }
  100. /**
  101. * Looks for library info files.
  102. *
  103. * This function scans the following directories for info files:
  104. * - libraries
  105. * - profiles/$profilename/libraries
  106. * - sites/all/libraries
  107. * - sites/$sitename/libraries
  108. * - any directories specified via hook_libraries_info_file_paths()
  109. *
  110. * @return array
  111. * An array of info files, keyed by library name. The values are the paths of
  112. * the files.
  113. */
  114. function libraries_scan_info_files() {
  115. $profile = drupal_get_path('profile', drupal_get_profile());
  116. $config = conf_path();
  117. // Build a list of directories.
  118. $directories = module_invoke_all('libraries_info_file_paths');
  119. $directories[] = 'libraries';
  120. $directories[] = "$profile/libraries";
  121. $directories[] = 'sites/all/libraries';
  122. $directories[] = "$config/libraries";
  123. // Scan for info files.
  124. $files = array();
  125. foreach ($directories as $dir) {
  126. if (file_exists($dir)) {
  127. $files = array_merge($files, file_scan_directory($dir, '@^[A-Za-z0-9._-]+\.libraries\.info$@', array(
  128. 'key' => 'name',
  129. 'recurse' => FALSE,
  130. )));
  131. }
  132. }
  133. foreach ($files as $filename => $file) {
  134. $files[basename($filename, '.libraries')] = $file;
  135. unset($files[$filename]);
  136. }
  137. return $files;
  138. }
  139. /**
  140. * Invokes library callbacks.
  141. *
  142. * @param $group
  143. * A string containing the group of callbacks that is to be applied. Should be
  144. * either 'info', 'pre-detect', 'post-detect', or 'load'.
  145. * @param $library
  146. * An array of library information, passed by reference.
  147. */
  148. function libraries_invoke($group, &$library) {
  149. // When introducing new callback groups in newer versions, stale cached
  150. // library information somehow reaches this point during the database update
  151. // before clearing the library cache.
  152. if (empty($library['callbacks'][$group])) {
  153. return;
  154. }
  155. foreach ($library['callbacks'][$group] as $callback) {
  156. libraries_traverse_library($library, $callback);
  157. }
  158. }
  159. /**
  160. * Helper function to apply a callback to all parts of a library.
  161. *
  162. * Because library declarations can include variants and versions, and those
  163. * version declarations can in turn include variants, modifying e.g. the 'files'
  164. * property everywhere it is declared can be quite cumbersome, in which case
  165. * this helper function is useful.
  166. *
  167. * @param $library
  168. * An array of library information, passed by reference.
  169. * @param $callback
  170. * A string containing the callback to apply to all parts of a library.
  171. */
  172. function libraries_traverse_library(&$library, $callback) {
  173. // Always apply the callback to the top-level library.
  174. $callback($library, NULL, NULL);
  175. // Apply the callback to versions.
  176. if (isset($library['versions'])) {
  177. foreach ($library['versions'] as $version_string => &$version) {
  178. $callback($version, $version_string, NULL);
  179. // Versions can include variants as well.
  180. if (isset($version['variants'])) {
  181. foreach ($version['variants'] as $version_variant_name => &$version_variant) {
  182. $callback($version_variant, $version_string, $version_variant_name);
  183. }
  184. }
  185. }
  186. }
  187. // Apply the callback to variants.
  188. if (isset($library['variants'])) {
  189. foreach ($library['variants'] as $variant_name => &$variant) {
  190. $callback($variant, NULL, $variant_name);
  191. }
  192. }
  193. }
  194. /**
  195. * Library info callback to make all 'files' properties consistent.
  196. *
  197. * This turns libraries' file information declared as e.g.
  198. * @code
  199. * $library['files']['js'] = array('example_1.js', 'example_2.js');
  200. * @endcode
  201. * into
  202. * @code
  203. * $library['files']['js'] = array(
  204. * 'example_1.js' => array(),
  205. * 'example_2.js' => array(),
  206. * );
  207. * @endcode
  208. * It does the same for the 'integration files' property.
  209. *
  210. * @param $library
  211. * An associative array of library information or a part of it, passed by
  212. * reference.
  213. * @param $version
  214. * If the library information belongs to a specific version, the version
  215. * string. NULL otherwise.
  216. * @param $variant
  217. * If the library information belongs to a specific variant, the variant name.
  218. * NULL otherwise.
  219. *
  220. * @see libraries_info()
  221. * @see libraries_invoke()
  222. */
  223. function libraries_prepare_files(&$library, $version = NULL, $variant = NULL) {
  224. // Both the 'files' property and the 'integration files' property contain file
  225. // declarations, and we want to make both consistent.
  226. $file_types = array();
  227. if (isset($library['files'])) {
  228. $file_types[] = &$library['files'];
  229. }
  230. if (isset($library['integration files'])) {
  231. // Integration files are additionally keyed by module.
  232. foreach ($library['integration files'] as &$integration_files) {
  233. $file_types[] = &$integration_files;
  234. }
  235. }
  236. foreach ($file_types as &$files) {
  237. // Go through all supported types of files.
  238. foreach (array('js', 'css', 'php') as $type) {
  239. if (isset($files[$type])) {
  240. foreach ($files[$type] as $key => $value) {
  241. // Unset numeric keys and turn the respective values into keys.
  242. if (is_numeric($key)) {
  243. $files[$type][$value] = array();
  244. unset($files[$type][$key]);
  245. }
  246. }
  247. }
  248. }
  249. }
  250. }
  251. /**
  252. * Library post-detect callback to process and detect dependencies.
  253. *
  254. * It checks whether each of the dependencies of a library are installed and
  255. * available in a compatible version.
  256. *
  257. * @param $library
  258. * An associative array of library information or a part of it, passed by
  259. * reference.
  260. * @param $version
  261. * If the library information belongs to a specific version, the version
  262. * string. NULL otherwise.
  263. * @param $variant
  264. * If the library information belongs to a specific variant, the variant name.
  265. * NULL otherwise.
  266. *
  267. * @see libraries_info()
  268. * @see libraries_invoke()
  269. */
  270. function libraries_detect_dependencies(&$library, $version = NULL, $variant = NULL) {
  271. if (isset($library['dependencies'])) {
  272. foreach ($library['dependencies'] as &$dependency_string) {
  273. $dependency_info = drupal_parse_dependency($dependency_string);
  274. $dependency = libraries_detect($dependency_info['name']);
  275. if (!$dependency['installed']) {
  276. $library['installed'] = FALSE;
  277. $library['error'] = 'missing dependency';
  278. $library['error message'] = t('The %dependency library, which the %library library depends on, is not installed.', array(
  279. '%dependency' => $dependency['name'],
  280. '%library' => $library['name'],
  281. ));
  282. }
  283. elseif (drupal_check_incompatibility($dependency_info, $dependency['version'])) {
  284. $library['installed'] = FALSE;
  285. $library['error'] = 'incompatible dependency';
  286. $library['error message'] = t('The version %dependency_version of the %dependency library is not compatible with the %library library.', array(
  287. '%dependency_version' => $dependency['version'],
  288. '%dependency' => $dependency['name'],
  289. '%library' => $library['name'],
  290. ));
  291. }
  292. // Remove the version string from the dependency, so libraries_load() can
  293. // load the libraries directly.
  294. $dependency_string = $dependency_info['name'];
  295. }
  296. }
  297. }
  298. /**
  299. * Returns information about registered libraries.
  300. *
  301. * The returned information is unprocessed; i.e., as registered by modules.
  302. *
  303. * @param $name
  304. * (optional) The machine name of a library to return registered information
  305. * for. If omitted, information about all registered libraries is returned.
  306. *
  307. * @return array|false
  308. * An associative array containing registered information for all libraries,
  309. * the registered information for the library specified by $name, or FALSE if
  310. * the library $name is not registered.
  311. *
  312. * @see hook_libraries_info()
  313. *
  314. * @todo Re-introduce support for include file plugin system - either by copying
  315. * Wysiwyg's code, or directly switching to CTools.
  316. */
  317. function &libraries_info($name = NULL) {
  318. // This static cache is re-used by libraries_detect() to save memory.
  319. $libraries = &drupal_static(__FUNCTION__);
  320. if (!isset($libraries)) {
  321. $libraries = array();
  322. // Gather information from hook_libraries_info() in enabled modules.
  323. foreach (module_implements('libraries_info') as $module) {
  324. foreach (module_invoke($module, 'libraries_info') as $machine_name => $properties) {
  325. $properties['info type'] = 'module';
  326. $properties['module'] = $module;
  327. $libraries[$machine_name] = $properties;
  328. }
  329. }
  330. // Gather information from hook_libraries_info() in enabled themes.
  331. $themes = array();
  332. foreach (list_themes() as $theme_name => $theme_info) {
  333. if ($theme_info->status && file_exists(drupal_get_path('theme', $theme_name) . '/template.php')) {
  334. // Collect a list of viable themes for re-use when calling the alter
  335. // hook.
  336. $themes[] = $theme_name;
  337. include_once drupal_get_path('theme', $theme_name) . '/template.php';
  338. $function = $theme_name . '_libraries_info';
  339. if (function_exists($function)) {
  340. foreach ($function() as $machine_name => $properties) {
  341. $properties['info type'] = 'theme';
  342. $properties['theme'] = $theme_name;
  343. $libraries[$machine_name] = $properties;
  344. }
  345. }
  346. }
  347. }
  348. // Gather information from .info files.
  349. // .info files override module definitions.
  350. foreach (libraries_scan_info_files() as $machine_name => $file) {
  351. $properties = drupal_parse_info_file($file->uri);
  352. $properties['info type'] = 'info file';
  353. $properties['info file'] = $file->uri;
  354. $libraries[$machine_name] = $properties;
  355. }
  356. // Provide defaults.
  357. foreach ($libraries as $machine_name => &$properties) {
  358. libraries_info_defaults($properties, $machine_name);
  359. }
  360. // Allow enabled modules and themes to alter the registered libraries.
  361. // drupal_alter() only takes the currently active theme into account, not
  362. // all enabled themes.
  363. foreach (module_implements('libraries_info_alter') as $module) {
  364. $function = $module . '_libraries_info_alter';
  365. $function($libraries);
  366. }
  367. foreach ($themes as $theme) {
  368. $function = $theme . '_libraries_info_alter';
  369. // The template.php file was included above.
  370. if (function_exists($function)) {
  371. $function($libraries);
  372. }
  373. }
  374. // Invoke callbacks in the 'info' group.
  375. foreach ($libraries as &$properties) {
  376. libraries_invoke('info', $properties);
  377. }
  378. }
  379. if (isset($name)) {
  380. if (!empty($libraries[$name])) {
  381. return $libraries[$name];
  382. }
  383. else {
  384. $false = FALSE;
  385. return $false;
  386. }
  387. }
  388. return $libraries;
  389. }
  390. /**
  391. * Applies default properties to a library definition.
  392. *
  393. * @param array $library
  394. * An array of library information, passed by reference.
  395. * @param string $name
  396. * The machine name of the passed-in library.
  397. *
  398. * @return array
  399. * The library information array with defaults populated.
  400. */
  401. function libraries_info_defaults(array &$library, $name) {
  402. $library += array(
  403. 'machine name' => $name,
  404. 'name' => $name,
  405. 'vendor url' => '',
  406. 'download url' => '',
  407. 'download file url' => '',
  408. 'path' => '',
  409. 'library path' => NULL,
  410. 'version callback' => 'libraries_get_version',
  411. 'version arguments' => array(),
  412. 'files' => array(),
  413. 'dependencies' => array(),
  414. 'variants' => array(),
  415. 'versions' => array(),
  416. 'integration files' => array(),
  417. 'callbacks' => array(),
  418. // @todo Remove in 7.x-3.x
  419. 'post-load integration files' => FALSE,
  420. );
  421. $library['callbacks'] += array(
  422. 'info' => array(),
  423. 'pre-detect' => array(),
  424. 'post-detect' => array(),
  425. 'pre-dependencies-load' => array(),
  426. 'pre-load' => array(),
  427. 'post-load' => array(),
  428. );
  429. // Add our own callbacks before any others.
  430. array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
  431. array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
  432. return $library;
  433. }
  434. /**
  435. * Tries to detect a library and its installed version.
  436. *
  437. * @param string $name
  438. * (optional) The machine name of a library to detect and return registered
  439. * information for. If omitted, information about all registered libraries is
  440. * returned.
  441. *
  442. * @return array|false
  443. * An associative array containing registered information for all libraries,
  444. * the registered information for the library specified by $name, or FALSE if
  445. * the library $name is not registered.
  446. * In addition to the keys returned by libraries_info(), the following keys
  447. * are contained:
  448. * - installed: A boolean indicating whether the library is installed. Note
  449. * that not only the top-level library, but also each variant contains this
  450. * key.
  451. * - version: If the version could be detected, the full version string.
  452. * - error: If an error occurred during library detection, one of the
  453. * following error statuses: "not found", "not detected", "not supported".
  454. * - error message: If an error occurred during library detection, a detailed
  455. * error message.
  456. *
  457. * @see libraries_info()
  458. */
  459. function libraries_detect($name = NULL) {
  460. if (!isset($name)) {
  461. $libraries = &libraries_info();
  462. foreach ($libraries as $name => $library) {
  463. libraries_detect($name);
  464. }
  465. return $libraries;
  466. }
  467. // Re-use the statically cached value of libraries_info() to save memory.
  468. $library = &libraries_info($name);
  469. // Exit early if the library was not found.
  470. if ($library === FALSE) {
  471. return $library;
  472. }
  473. // If 'installed' is set, library detection ran already.
  474. if (isset($library['installed'])) {
  475. return $library;
  476. }
  477. $library['installed'] = FALSE;
  478. // Check whether the library exists.
  479. if (!isset($library['library path'])) {
  480. $library['library path'] = libraries_get_path($library['machine name']);
  481. }
  482. if ($library['library path'] === FALSE || !file_exists($library['library path'])) {
  483. $library['error'] = 'not found';
  484. $library['error message'] = t('The %library library could not be found.', array(
  485. '%library' => $library['name'],
  486. ));
  487. return $library;
  488. }
  489. // Invoke callbacks in the 'pre-detect' group.
  490. libraries_invoke('pre-detect', $library);
  491. // Detect library version, if not hardcoded.
  492. if (!isset($library['version'])) {
  493. // We support both a single parameter, which is an associative array, and an
  494. // indexed array of multiple parameters.
  495. if (isset($library['version arguments'][0])) {
  496. // Add the library as the first argument.
  497. $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
  498. }
  499. else {
  500. $library['version'] = call_user_func($library['version callback'], $library, $library['version arguments']);
  501. }
  502. if (empty($library['version'])) {
  503. $library['error'] = 'not detected';
  504. $library['error message'] = t('The version of the %library library could not be detected.', array(
  505. '%library' => $library['name'],
  506. ));
  507. return $library;
  508. }
  509. }
  510. // Determine to which supported version the installed version maps.
  511. if (!empty($library['versions'])) {
  512. ksort($library['versions']);
  513. $version = 0;
  514. foreach ($library['versions'] as $supported_version => $version_properties) {
  515. if (version_compare($library['version'], $supported_version, '>=')) {
  516. $version = $supported_version;
  517. }
  518. }
  519. if (!$version) {
  520. $library['error'] = 'not supported';
  521. $library['error message'] = t('The installed version %version of the %library library is not supported.', array(
  522. '%version' => $library['version'],
  523. '%library' => $library['name'],
  524. ));
  525. return $library;
  526. }
  527. // Apply version specific definitions and overrides.
  528. $library = array_merge($library, $library['versions'][$version]);
  529. unset($library['versions']);
  530. }
  531. // Check each variant if it is installed.
  532. if (!empty($library['variants'])) {
  533. foreach ($library['variants'] as $variant_name => &$variant) {
  534. // If no variant callback has been set, assume the variant to be
  535. // installed.
  536. if (!isset($variant['variant callback'])) {
  537. $variant['installed'] = TRUE;
  538. }
  539. else {
  540. // We support both a single parameter, which is an associative array,
  541. // and an indexed array of multiple parameters.
  542. if (isset($variant['variant arguments'][0])) {
  543. // Add the library as the first argument, and the variant name as the second.
  544. $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments']));
  545. }
  546. else {
  547. $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
  548. }
  549. if (!$variant['installed']) {
  550. $variant['error'] = 'not found';
  551. $variant['error message'] = t('The %variant variant of the %library library could not be found.', array(
  552. '%variant' => $variant_name,
  553. '%library' => $library['name'],
  554. ));
  555. }
  556. }
  557. }
  558. }
  559. // If we end up here, the library should be usable.
  560. $library['installed'] = TRUE;
  561. // Invoke callbacks in the 'post-detect' group.
  562. libraries_invoke('post-detect', $library);
  563. return $library;
  564. }
  565. /**
  566. * Loads a library.
  567. *
  568. * @param $name
  569. * The name of the library to load.
  570. * @param $variant
  571. * The name of the variant to load. Note that only one variant of a library
  572. * can be loaded within a single request. The variant that has been passed
  573. * first is used; different variant names in subsequent calls are ignored.
  574. *
  575. * @return
  576. * An associative array of the library information as returned from
  577. * libraries_info(). The top-level properties contain the effective definition
  578. * of the library (variant) that has been loaded. Additionally:
  579. * - installed: Whether the library is installed, as determined by
  580. * libraries_detect_library().
  581. * - loaded: Either the amount of library files that have been loaded, or
  582. * FALSE if the library could not be loaded.
  583. * See hook_libraries_info() for more information.
  584. */
  585. function libraries_load($name, $variant = NULL) {
  586. $loaded = &drupal_static(__FUNCTION__, array());
  587. if (!isset($loaded[$name])) {
  588. $library = cache_get($name, 'cache_libraries');
  589. if ($library) {
  590. $library = $library->data;
  591. }
  592. else {
  593. $library = libraries_detect($name);
  594. cache_set($name, $library, 'cache_libraries');
  595. }
  596. // Exit early if the library was not found.
  597. if ($library === FALSE) {
  598. $loaded[$name] = $library;
  599. return $loaded[$name];
  600. }
  601. // If a variant was specified, override the top-level properties with the
  602. // variant properties.
  603. if (isset($variant)) {
  604. // Ensure that the $variant key exists, and if it does not, set its
  605. // 'installed' property to FALSE by default. This will prevent the loading
  606. // of the library files below.
  607. $library['variants'] += array($variant => array('installed' => FALSE));
  608. $library = array_merge($library, $library['variants'][$variant]);
  609. }
  610. // Regardless of whether a specific variant was requested or not, there can
  611. // only be one variant of a library within a single request.
  612. unset($library['variants']);
  613. // Invoke callbacks in the 'pre-dependencies-load' group.
  614. libraries_invoke('pre-dependencies-load', $library);
  615. // If the library (variant) is installed, load it.
  616. $library['loaded'] = FALSE;
  617. if ($library['installed']) {
  618. // Load library dependencies.
  619. if (isset($library['dependencies'])) {
  620. foreach ($library['dependencies'] as $dependency) {
  621. libraries_load($dependency);
  622. }
  623. }
  624. // Invoke callbacks in the 'pre-load' group.
  625. libraries_invoke('pre-load', $library);
  626. // Load all the files associated with the library.
  627. $library['loaded'] = libraries_load_files($library);
  628. // Invoke callbacks in the 'post-load' group.
  629. libraries_invoke('post-load', $library);
  630. }
  631. $loaded[$name] = $library;
  632. }
  633. return $loaded[$name];
  634. }
  635. /**
  636. * Loads a library's files.
  637. *
  638. * @param $library
  639. * An array of library information as returned by libraries_info().
  640. *
  641. * @return
  642. * The number of loaded files.
  643. */
  644. function libraries_load_files($library) {
  645. // As this key was added after 7.x-2.1 cached library structures might not
  646. // have it.
  647. $library += array('post-load integration files' => FALSE);
  648. // Load integration files.
  649. if (!$library['post-load integration files'] && !empty($library['integration files'])) {
  650. $enabled_themes = array();
  651. foreach (list_themes() as $theme_name => $theme) {
  652. if ($theme->status) {
  653. $enabled_themes[] = $theme_name;
  654. }
  655. }
  656. foreach ($library['integration files'] as $provider => $files) {
  657. if (module_exists($provider)) {
  658. libraries_load_files(array(
  659. 'files' => $files,
  660. 'path' => '',
  661. 'library path' => drupal_get_path('module', $provider),
  662. 'post-load integration files' => FALSE,
  663. ));
  664. }
  665. elseif (in_array($provider, $enabled_themes)) {
  666. libraries_load_files(array(
  667. 'files' => $files,
  668. 'path' => '',
  669. 'library path' => drupal_get_path('theme', $provider),
  670. 'post-load integration files' => FALSE,
  671. ));
  672. }
  673. }
  674. }
  675. // Construct the full path to the library for later use.
  676. $path = $library['library path'];
  677. $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
  678. // Count the number of loaded files for the return value.
  679. $count = 0;
  680. // Load both the JavaScript and the CSS files.
  681. // The parameters for drupal_add_js() and drupal_add_css() require special
  682. // handling.
  683. // @see drupal_process_attached()
  684. foreach (array('js', 'css') as $type) {
  685. if (!empty($library['files'][$type])) {
  686. foreach ($library['files'][$type] as $data => $options) {
  687. // If the value is not an array, it's a filename and passed as first
  688. // (and only) argument.
  689. if (!is_array($options)) {
  690. $data = $options;
  691. $options = array();
  692. }
  693. // In some cases, the first parameter ($data) is an array. Arrays can't
  694. // be passed as keys in PHP, so we have to get $data from the value
  695. // array.
  696. if (is_numeric($data)) {
  697. $data = $options['data'];
  698. unset($options['data']);
  699. }
  700. // Prepend the library path to the file name.
  701. $data = "$path/$data";
  702. // Apply the default group if the group isn't explicitly given.
  703. if (!isset($options['group'])) {
  704. $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT;
  705. }
  706. call_user_func('drupal_add_' . $type, $data, $options);
  707. $count++;
  708. }
  709. }
  710. }
  711. // Load PHP files.
  712. if (!empty($library['files']['php'])) {
  713. foreach ($library['files']['php'] as $file => $array) {
  714. $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
  715. if (file_exists($file_path)) {
  716. _libraries_require_once($file_path);
  717. $count++;
  718. }
  719. }
  720. }
  721. // Load integration files.
  722. if ($library['post-load integration files'] && !empty($library['integration files'])) {
  723. $enabled_themes = array();
  724. foreach (list_themes() as $theme_name => $theme) {
  725. if ($theme->status) {
  726. $enabled_themes[] = $theme_name;
  727. }
  728. }
  729. foreach ($library['integration files'] as $provider => $files) {
  730. if (module_exists($provider)) {
  731. libraries_load_files(array(
  732. 'files' => $files,
  733. 'path' => '',
  734. 'library path' => drupal_get_path('module', $provider),
  735. 'post-load integration files' => FALSE,
  736. ));
  737. }
  738. elseif (in_array($provider, $enabled_themes)) {
  739. libraries_load_files(array(
  740. 'files' => $files,
  741. 'path' => '',
  742. 'library path' => drupal_get_path('theme', $provider),
  743. 'post-load integration files' => FALSE,
  744. ));
  745. }
  746. }
  747. }
  748. return $count;
  749. }
  750. /**
  751. * Wrapper function for require_once.
  752. *
  753. * A library file could set a $path variable in file scope. Requiring such a
  754. * file directly in libraries_load_files() would lead to the local $path
  755. * variable being overridden after the require_once statement. This would
  756. * break loading further files. Therefore we use this trivial wrapper which has
  757. * no local state that can be tampered with.
  758. *
  759. * @param $file_path
  760. * The file path of the file to require.
  761. */
  762. function _libraries_require_once($file_path) {
  763. require_once $file_path;
  764. }
  765. /**
  766. * Gets the version information from an arbitrary library.
  767. *
  768. * @param $library
  769. * An associative array containing all information about the library.
  770. * @param $options
  771. * An associative array containing with the following keys:
  772. * - file: The filename to parse for the version, relative to the library
  773. * path. For example: 'docs/changelog.txt'.
  774. * - pattern: A string containing a regular expression (PCRE) to match the
  775. * library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that
  776. * the returned version is not the match of the entire pattern (i.e.
  777. * '@version 1.2.3' in the above example) but the match of the first
  778. * sub-pattern (i.e. '1.2.3' in the above example).
  779. * - lines: (optional) The maximum number of lines to search the pattern in.
  780. * Defaults to 20.
  781. * - cols: (optional) The maximum number of characters per line to take into
  782. * account. Defaults to 200. In case of minified or compressed files, this
  783. * prevents reading the entire file into memory.
  784. *
  785. * @return
  786. * A string containing the version of the library.
  787. *
  788. * @see libraries_get_path()
  789. */
  790. function libraries_get_version($library, $options) {
  791. // Provide defaults.
  792. $options += array(
  793. 'file' => '',
  794. 'pattern' => '',
  795. 'lines' => 20,
  796. 'cols' => 200,
  797. );
  798. $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
  799. if (empty($options['file']) || !file_exists($file)) {
  800. return;
  801. }
  802. $file = fopen($file, 'r');
  803. while ($options['lines'] && $line = fgets($file, $options['cols'])) {
  804. if (preg_match($options['pattern'], $line, $version)) {
  805. fclose($file);
  806. return $version[1];
  807. }
  808. $options['lines']--;
  809. }
  810. fclose($file);
  811. }
  812. /**
  813. * Implements hook_help().
  814. */
  815. function libraries_help($path, $arg) {
  816. switch ($path) {
  817. case 'admin/reports/libraries':
  818. return t('Click on a library for a status report or detailed installation instructions in case the library is not installed correctly.');
  819. }
  820. }
  821. /**
  822. * Implements hook_menu().
  823. */
  824. function libraries_menu() {
  825. $items = array();
  826. $items['admin/reports/libraries'] = array(
  827. 'title' => 'Libraries',
  828. 'description' => 'An overview of libraries installed on this site.',
  829. 'page callback' => 'drupal_get_form',
  830. 'page arguments' => array('libraries_admin_overview'),
  831. 'access arguments' => array('access site reports'),
  832. 'file' => 'libraries.admin.inc'
  833. );
  834. $items['admin/reports/libraries/%libraries_ui'] = array(
  835. 'title' => 'Library status report',
  836. 'description' => 'Status overview for a single library',
  837. 'page callback' => 'drupal_get_form',
  838. 'page arguments' => array('libraries_admin_library_status_form', 3),
  839. 'access arguments' => array('access site reports'),
  840. 'file' => 'libraries.admin.inc'
  841. );
  842. return $items;
  843. }
  844. /**
  845. * Loads library information for display in the user interface.
  846. *
  847. * This can be used as a menu loader function by specifying a '%libraries_ui'
  848. * parameter in a path.
  849. *
  850. * We do not use libraries_load() (and, thus, a '%libraries' parameter) directly
  851. * for displaying library information in the user interface as we do not want
  852. * the library files to be loaded.
  853. *
  854. * @param string $name
  855. * The machine name of a library to return registered information for.
  856. *
  857. * @return array|false
  858. * An associative array containing registered information for the library
  859. * specified by $name, or FALSE if the library $name is not registered.
  860. *
  861. * @see libraries_detect()
  862. * @see libraries_menu()
  863. */
  864. function libraries_ui_load($name) {
  865. return libraries_detect($name);
  866. }