libraries.module 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  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
  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
  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
  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. * @library
  394. * An array of library information, passed by reference.
  395. * @name
  396. * The machine name of the passed-in library.
  397. */
  398. function libraries_info_defaults(&$library, $name) {
  399. $library += array(
  400. 'machine name' => $name,
  401. 'name' => $name,
  402. 'vendor url' => '',
  403. 'download url' => '',
  404. 'path' => '',
  405. 'library path' => NULL,
  406. 'version callback' => 'libraries_get_version',
  407. 'version arguments' => array(),
  408. 'files' => array(),
  409. 'dependencies' => array(),
  410. 'variants' => array(),
  411. 'versions' => array(),
  412. 'integration files' => array(),
  413. 'callbacks' => array(),
  414. // @todo Remove in 7.x-3.x
  415. 'post-load integration files' => FALSE,
  416. );
  417. $library['callbacks'] += array(
  418. 'info' => array(),
  419. 'pre-detect' => array(),
  420. 'post-detect' => array(),
  421. 'pre-dependencies-load' => array(),
  422. 'pre-load' => array(),
  423. 'post-load' => array(),
  424. );
  425. // Add our own callbacks before any others.
  426. array_unshift($library['callbacks']['info'], 'libraries_prepare_files');
  427. array_unshift($library['callbacks']['post-detect'], 'libraries_detect_dependencies');
  428. return $library;
  429. }
  430. /**
  431. * Tries to detect a library and its installed version.
  432. *
  433. * @param $name
  434. * The machine name of a library to return registered information for.
  435. *
  436. * @return array|false
  437. * An associative array containing registered information for the library
  438. * specified by $name, or FALSE if the library $name is not registered.
  439. * In addition to the keys returned by libraries_info(), the following keys
  440. * are contained:
  441. * - installed: A boolean indicating whether the library is installed. Note
  442. * that not only the top-level library, but also each variant contains this
  443. * key.
  444. * - version: If the version could be detected, the full version string.
  445. * - error: If an error occurred during library detection, one of the
  446. * following error statuses: "not found", "not detected", "not supported".
  447. * - error message: If an error occurred during library detection, a detailed
  448. * error message.
  449. *
  450. * @see libraries_info()
  451. */
  452. function libraries_detect($name) {
  453. // Re-use the statically cached value of libraries_info() to save memory.
  454. $library = &libraries_info($name);
  455. // Exit early if the library was not found.
  456. if ($library === FALSE) {
  457. return $library;
  458. }
  459. // If 'installed' is set, library detection ran already.
  460. if (isset($library['installed'])) {
  461. return $library;
  462. }
  463. $library['installed'] = FALSE;
  464. // Check whether the library exists.
  465. if (!isset($library['library path'])) {
  466. $library['library path'] = libraries_get_path($library['machine name']);
  467. }
  468. if ($library['library path'] === FALSE || !file_exists($library['library path'])) {
  469. $library['error'] = 'not found';
  470. $library['error message'] = t('The %library library could not be found.', array(
  471. '%library' => $library['name'],
  472. ));
  473. return $library;
  474. }
  475. // Invoke callbacks in the 'pre-detect' group.
  476. libraries_invoke('pre-detect', $library);
  477. // Detect library version, if not hardcoded.
  478. if (!isset($library['version'])) {
  479. // We support both a single parameter, which is an associative array, and an
  480. // indexed array of multiple parameters.
  481. if (isset($library['version arguments'][0])) {
  482. // Add the library as the first argument.
  483. $library['version'] = call_user_func_array($library['version callback'], array_merge(array($library), $library['version arguments']));
  484. }
  485. else {
  486. $library['version'] = $library['version callback']($library, $library['version arguments']);
  487. }
  488. if (empty($library['version'])) {
  489. $library['error'] = 'not detected';
  490. $library['error message'] = t('The version of the %library library could not be detected.', array(
  491. '%library' => $library['name'],
  492. ));
  493. return $library;
  494. }
  495. }
  496. // Determine to which supported version the installed version maps.
  497. if (!empty($library['versions'])) {
  498. ksort($library['versions']);
  499. $version = 0;
  500. foreach ($library['versions'] as $supported_version => $version_properties) {
  501. if (version_compare($library['version'], $supported_version, '>=')) {
  502. $version = $supported_version;
  503. }
  504. }
  505. if (!$version) {
  506. $library['error'] = 'not supported';
  507. $library['error message'] = t('The installed version %version of the %library library is not supported.', array(
  508. '%version' => $library['version'],
  509. '%library' => $library['name'],
  510. ));
  511. return $library;
  512. }
  513. // Apply version specific definitions and overrides.
  514. $library = array_merge($library, $library['versions'][$version]);
  515. unset($library['versions']);
  516. }
  517. // Check each variant if it is installed.
  518. if (!empty($library['variants'])) {
  519. foreach ($library['variants'] as $variant_name => &$variant) {
  520. // If no variant callback has been set, assume the variant to be
  521. // installed.
  522. if (!isset($variant['variant callback'])) {
  523. $variant['installed'] = TRUE;
  524. }
  525. else {
  526. // We support both a single parameter, which is an associative array,
  527. // and an indexed array of multiple parameters.
  528. if (isset($variant['variant arguments'][0])) {
  529. // Add the library as the first argument, and the variant name as the second.
  530. $variant['installed'] = call_user_func_array($variant['variant callback'], array_merge(array($library, $variant_name), $variant['variant arguments']));
  531. }
  532. else {
  533. $variant['installed'] = $variant['variant callback']($library, $variant_name, $variant['variant arguments']);
  534. }
  535. if (!$variant['installed']) {
  536. $variant['error'] = 'not found';
  537. $variant['error message'] = t('The %variant variant of the %library library could not be found.', array(
  538. '%variant' => $variant_name,
  539. '%library' => $library['name'],
  540. ));
  541. }
  542. }
  543. }
  544. }
  545. // If we end up here, the library should be usable.
  546. $library['installed'] = TRUE;
  547. // Invoke callbacks in the 'post-detect' group.
  548. libraries_invoke('post-detect', $library);
  549. return $library;
  550. }
  551. /**
  552. * Loads a library.
  553. *
  554. * @param $name
  555. * The name of the library to load.
  556. * @param $variant
  557. * The name of the variant to load. Note that only one variant of a library
  558. * can be loaded within a single request. The variant that has been passed
  559. * first is used; different variant names in subsequent calls are ignored.
  560. *
  561. * @return
  562. * An associative array of the library information as returned from
  563. * libraries_info(). The top-level properties contain the effective definition
  564. * of the library (variant) that has been loaded. Additionally:
  565. * - installed: Whether the library is installed, as determined by
  566. * libraries_detect_library().
  567. * - loaded: Either the amount of library files that have been loaded, or
  568. * FALSE if the library could not be loaded.
  569. * See hook_libraries_info() for more information.
  570. */
  571. function libraries_load($name, $variant = NULL) {
  572. $loaded = &drupal_static(__FUNCTION__, array());
  573. if (!isset($loaded[$name])) {
  574. $library = cache_get($name, 'cache_libraries');
  575. if ($library) {
  576. $library = $library->data;
  577. }
  578. else {
  579. $library = libraries_detect($name);
  580. cache_set($name, $library, 'cache_libraries');
  581. }
  582. // Exit early if the library was not found.
  583. if ($library === FALSE) {
  584. $loaded[$name] = $library;
  585. return $loaded[$name];
  586. }
  587. // If a variant was specified, override the top-level properties with the
  588. // variant properties.
  589. if (isset($variant)) {
  590. // Ensure that the $variant key exists, and if it does not, set its
  591. // 'installed' property to FALSE by default. This will prevent the loading
  592. // of the library files below.
  593. $library['variants'] += array($variant => array('installed' => FALSE));
  594. $library = array_merge($library, $library['variants'][$variant]);
  595. }
  596. // Regardless of whether a specific variant was requested or not, there can
  597. // only be one variant of a library within a single request.
  598. unset($library['variants']);
  599. // Invoke callbacks in the 'pre-dependencies-load' group.
  600. libraries_invoke('pre-dependencies-load', $library);
  601. // If the library (variant) is installed, load it.
  602. $library['loaded'] = FALSE;
  603. if ($library['installed']) {
  604. // Load library dependencies.
  605. if (isset($library['dependencies'])) {
  606. foreach ($library['dependencies'] as $dependency) {
  607. libraries_load($dependency);
  608. }
  609. }
  610. // Invoke callbacks in the 'pre-load' group.
  611. libraries_invoke('pre-load', $library);
  612. // Load all the files associated with the library.
  613. $library['loaded'] = libraries_load_files($library);
  614. // Invoke callbacks in the 'post-load' group.
  615. libraries_invoke('post-load', $library);
  616. }
  617. $loaded[$name] = $library;
  618. }
  619. return $loaded[$name];
  620. }
  621. /**
  622. * Loads a library's files.
  623. *
  624. * @param $library
  625. * An array of library information as returned by libraries_info().
  626. *
  627. * @return
  628. * The number of loaded files.
  629. */
  630. function libraries_load_files($library) {
  631. // Load integration files.
  632. if (!$library['post-load integration files'] && !empty($library['integration files'])) {
  633. $enabled_themes = array();
  634. foreach (list_themes() as $theme_name => $theme) {
  635. if ($theme->status) {
  636. $enabled_themes[] = $theme_name;
  637. }
  638. }
  639. foreach ($library['integration files'] as $provider => $files) {
  640. if (module_exists($provider)) {
  641. libraries_load_files(array(
  642. 'files' => $files,
  643. 'path' => '',
  644. 'library path' => drupal_get_path('module', $provider),
  645. 'post-load integration files' => FALSE,
  646. ));
  647. }
  648. elseif (in_array($provider, $enabled_themes)) {
  649. libraries_load_files(array(
  650. 'files' => $files,
  651. 'path' => '',
  652. 'library path' => drupal_get_path('theme', $provider),
  653. 'post-load integration files' => FALSE,
  654. ));
  655. }
  656. }
  657. }
  658. // Construct the full path to the library for later use.
  659. $path = $library['library path'];
  660. $path = ($library['path'] !== '' ? $path . '/' . $library['path'] : $path);
  661. // Count the number of loaded files for the return value.
  662. $count = 0;
  663. // Load both the JavaScript and the CSS files.
  664. // The parameters for drupal_add_js() and drupal_add_css() require special
  665. // handling.
  666. // @see drupal_process_attached()
  667. foreach (array('js', 'css') as $type) {
  668. if (!empty($library['files'][$type])) {
  669. foreach ($library['files'][$type] as $data => $options) {
  670. // If the value is not an array, it's a filename and passed as first
  671. // (and only) argument.
  672. if (!is_array($options)) {
  673. $data = $options;
  674. $options = array();
  675. }
  676. // In some cases, the first parameter ($data) is an array. Arrays can't
  677. // be passed as keys in PHP, so we have to get $data from the value
  678. // array.
  679. if (is_numeric($data)) {
  680. $data = $options['data'];
  681. unset($options['data']);
  682. }
  683. // Prepend the library path to the file name.
  684. $data = "$path/$data";
  685. // Apply the default group if the group isn't explicitly given.
  686. if (!isset($options['group'])) {
  687. $options['group'] = ($type == 'js') ? JS_DEFAULT : CSS_DEFAULT;
  688. }
  689. call_user_func('drupal_add_' . $type, $data, $options);
  690. $count++;
  691. }
  692. }
  693. }
  694. // Load PHP files.
  695. if (!empty($library['files']['php'])) {
  696. foreach ($library['files']['php'] as $file => $array) {
  697. $file_path = DRUPAL_ROOT . '/' . $path . '/' . $file;
  698. if (file_exists($file_path)) {
  699. _libraries_require_once($file_path);
  700. $count++;
  701. }
  702. }
  703. }
  704. // Load integration files.
  705. if ($library['post-load integration files'] && !empty($library['integration files'])) {
  706. $enabled_themes = array();
  707. foreach (list_themes() as $theme_name => $theme) {
  708. if ($theme->status) {
  709. $enabled_themes[] = $theme_name;
  710. }
  711. }
  712. foreach ($library['integration files'] as $provider => $files) {
  713. if (module_exists($provider)) {
  714. libraries_load_files(array(
  715. 'files' => $files,
  716. 'path' => '',
  717. 'library path' => drupal_get_path('module', $provider),
  718. 'post-load integration files' => FALSE,
  719. ));
  720. }
  721. elseif (in_array($provider, $enabled_themes)) {
  722. libraries_load_files(array(
  723. 'files' => $files,
  724. 'path' => '',
  725. 'library path' => drupal_get_path('theme', $provider),
  726. 'post-load integration files' => FALSE,
  727. ));
  728. }
  729. }
  730. }
  731. return $count;
  732. }
  733. /**
  734. * Wrapper function for require_once.
  735. *
  736. * A library file could set a $path variable in file scope. Requiring such a
  737. * file directly in libraries_load_files() would lead to the local $path
  738. * variable being overridden after the require_once statement. This would
  739. * break loading further files. Therefore we use this trivial wrapper which has
  740. * no local state that can be tampered with.
  741. *
  742. * @param $file_path
  743. * The file path of the file to require.
  744. */
  745. function _libraries_require_once($file_path) {
  746. require_once $file_path;
  747. }
  748. /**
  749. * Gets the version information from an arbitrary library.
  750. *
  751. * @param $library
  752. * An associative array containing all information about the library.
  753. * @param $options
  754. * An associative array containing with the following keys:
  755. * - file: The filename to parse for the version, relative to the library
  756. * path. For example: 'docs/changelog.txt'.
  757. * - pattern: A string containing a regular expression (PCRE) to match the
  758. * library version. For example: '@version\s+([0-9a-zA-Z\.-]+)@'. Note that
  759. * the returned version is not the match of the entire pattern (i.e.
  760. * '@version 1.2.3' in the above example) but the match of the first
  761. * sub-pattern (i.e. '1.2.3' in the above example).
  762. * - lines: (optional) The maximum number of lines to search the pattern in.
  763. * Defaults to 20.
  764. * - cols: (optional) The maximum number of characters per line to take into
  765. * account. Defaults to 200. In case of minified or compressed files, this
  766. * prevents reading the entire file into memory.
  767. *
  768. * @return
  769. * A string containing the version of the library.
  770. *
  771. * @see libraries_get_path()
  772. */
  773. function libraries_get_version($library, $options) {
  774. // Provide defaults.
  775. $options += array(
  776. 'file' => '',
  777. 'pattern' => '',
  778. 'lines' => 20,
  779. 'cols' => 200,
  780. );
  781. $file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
  782. if (empty($options['file']) || !file_exists($file)) {
  783. return;
  784. }
  785. $file = fopen($file, 'r');
  786. while ($options['lines'] && $line = fgets($file, $options['cols'])) {
  787. if (preg_match($options['pattern'], $line, $version)) {
  788. fclose($file);
  789. return $version[1];
  790. }
  791. $options['lines']--;
  792. }
  793. fclose($file);
  794. }