libraries.module 34 KB

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