libraries.admin.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <?php
  2. /**
  3. * @file
  4. * Provides administrative page and form callbacks for Libraries module.
  5. */
  6. /**
  7. * Form generation callback for the libraries overview table.
  8. *
  9. * This is a form instead of a page to allow easier extending in contributed
  10. * modules.
  11. *
  12. * @param array $form
  13. * An associative array containing the structure of the form.
  14. * @param array $form_state
  15. * A keyed array containing the current state of the form.
  16. *
  17. * @return array
  18. * The form array for the overview form.
  19. */
  20. function libraries_admin_overview(array $form, array &$form_state) {
  21. $header = array(t('Name'), t('Status'), t('Installed version'), t('Provider'), t('Links'));
  22. $rows = array();
  23. $libraries = libraries_detect();
  24. uasort($libraries, 'libraries_admin_sort_title');
  25. foreach ($libraries as $machine_name => $library) {
  26. $actions = array();
  27. if ($library['vendor url']) {
  28. $actions[] = l('Homepage', $library['vendor url']);
  29. }
  30. if ($library['download url']) {
  31. $actions[] = l('Download', $library['download url']);
  32. }
  33. $rows[] = array(
  34. 'data' => array(
  35. l($library['name'], 'admin/reports/libraries/' . $machine_name),
  36. ($library['installed'] ? t('OK') : drupal_ucfirst($library['error'])),
  37. (isset($library['version']) ? $library['version'] : ''),
  38. libraries_admin_get_provider_with_type($library),
  39. implode(' | ', $actions),
  40. ),
  41. 'class' => ($library['installed'] ? array('ok') : array('error')),
  42. );
  43. }
  44. $form['libraries']['list'] = array(
  45. '#theme' => 'table',
  46. '#header' => $header,
  47. '#rows' => $rows,
  48. '#empty' => t('There are currently no libraries installed'),
  49. );
  50. return $form;
  51. }
  52. /**
  53. * Form generation callback for the status overview for a single library.
  54. *
  55. * This is a form instead of a page to allow easier extending in contributed
  56. * modules.
  57. *
  58. * @param array $form
  59. * An associative array containing the structure of the form.
  60. * @param array $form_state
  61. * A keyed array containing the current state of the form.
  62. * @param array $library
  63. * A library information array.
  64. *
  65. * @return array|null
  66. * The form array for the status form or NULL if the library was not found.
  67. *
  68. * @todo Add some var_export($library)-style output
  69. */
  70. function libraries_admin_library_status_form(array $form, array &$form_state, $library) {
  71. drupal_set_title(t('Status report for library %library', array('%library' => $library['name'])), PASS_THROUGH);
  72. if ($library['installed']) {
  73. drupal_set_message(t('The %name library is installed correctly.', array('%name' => $library['name'])));
  74. $form['status'] = libraries_admin_status_table($library);
  75. }
  76. else {
  77. drupal_set_message($library['error message'], 'error');
  78. switch ($library['error']) {
  79. case 'not found':
  80. $form['instructions'] = libraries_admin_instructions_missing($library);
  81. break;
  82. case 'not detected':
  83. $form['instructions'] = libraries_admin_instructions_undetected($library);;
  84. break;
  85. case 'not supported':
  86. $form['instructions'] = libraries_admin_instructions_unsupported($library);
  87. break;
  88. case 'missing dependency':
  89. $form['instructions']['instruction']['#markup'] = t('There a missing dependency in your configuration that prevent this library to work properly.') . '<br>';
  90. break;
  91. case 'incompatible dependency':
  92. $form['instructions']['instruction']['#markup'] = t('There an incompatible dependency in your configuration that prevent this library to work properly.') . '<br>';
  93. break;
  94. }
  95. }
  96. return $form;
  97. }
  98. /**
  99. * Displays a table of status information about a library.
  100. *
  101. * @param array $library
  102. * A library information array.
  103. *
  104. * @return array
  105. * A renderable array containing a table with status information.
  106. */
  107. function libraries_admin_status_table(array $library) {
  108. $header = array(array(
  109. // @todo The title implies that other type of information is displayed, as
  110. // well, but this is currently not the case.
  111. // @todo Use CSS instead of a <strong> element.
  112. 'data' => '<strong>' . t('General information') . '</strong>',
  113. 'colspan' => 2,
  114. 'class' => 'table-heading',
  115. 'no_striping' => TRUE,
  116. ));
  117. $rows = array();
  118. // @todo Use CSS instead of <strong> elements.
  119. $rows['name'] = array('<strong>' . t('Name') . '</strong>', check_plain($library['name']));
  120. $rows['machine_name'] = array('<strong>' . t('Machine name') . '</strong>', check_plain($library['machine name']));
  121. if ($library['vendor url']) {
  122. $rows['vendor_url'] = array('<strong>' . t('Vendor URL') . '</strong>', l($library['vendor url'], $library['vendor url']));
  123. }
  124. if ($library['download url']) {
  125. $rows['download_url'] = array('<strong>' . t('Download URL') . '</strong>', l($library['download url'], $library['download url']));
  126. }
  127. $rows['provider'] = array('<strong>' . t('Provider') . '</strong>', libraries_admin_get_provider_with_type($library));
  128. $rows['library_path'] = array('<strong>' . t('Library path') . '</strong>', $library['library path']);
  129. $rows['version'] = array('<strong>' . t('Version') . '</strong>', $library['version']);
  130. if (!empty($library['variants'])) {
  131. $rows['variants'] = array('<strong>' . t('Variants') . '</strong>', implode(', ', array_keys($library['variants'])));
  132. }
  133. return array(
  134. '#theme' => 'table',
  135. '#header' => $header,
  136. '#rows' => $rows,
  137. );
  138. }
  139. /**
  140. * Returns instructions for dealing with a missing library.
  141. *
  142. * @param array $library
  143. * A library information array.
  144. *
  145. * @return array
  146. * A renderable array containing the instructions.
  147. */
  148. function libraries_admin_instructions_missing(array $library) {
  149. $build = array();
  150. $build['instruction']['#markup'] = t('Follow these steps to install the library:');
  151. $items = array();
  152. // 1. Download the library.
  153. // If no supported versions are specified, the latest version is
  154. // recommended.
  155. if (empty($library['versions'])) {
  156. $items[] = t('Download the latest version of the library <a href="@download-url">here</a>.', array(
  157. '@download-url' => $library['download url'],
  158. ));
  159. }
  160. // Otherwise, the latest supported version is recommended.
  161. else {
  162. $versions = array_keys($library['versions']);
  163. usort($versions, 'version_compare');
  164. $versions = array_reverse($versions);
  165. $version = $versions[0];
  166. $items[] = t('Download version %version of the library <a href="@download-url">here</a>.', array(
  167. '%version' => $version,
  168. '@download-url' => $library['download url'],
  169. ));
  170. }
  171. // 2. Unpack it.
  172. $items[] = t('If the library is an archive, i.e. if the file ending is for example <em>.tar.gz</em> or <em>.zip</em>, unpack it.');
  173. // 3. Create the libraries folder.
  174. $items[] = t('In the %library-directory directory of your Drupal installation create a %library directory.', array(
  175. '%library-directory' => 'sites/all/libraries',
  176. '%library' => $library['machine name'],
  177. ));
  178. // 4. Upload it.
  179. // If the library has variant-independent files, give the user the
  180. // location of an example file to check his filesystem against.
  181. if ($directory_layout = libraries_admin_directory_layout($library)) {
  182. $items[] = t('Upload the whole library (which can consist of multiple directories) into the newly created %library-path directory. The following files and directories should be contained in that directory: !directory-layout', array(
  183. '%library-path' => 'sites/all/libraries/' . $library['machine name'],
  184. '!directory-layout' => drupal_render($directory_layout),
  185. ));
  186. }
  187. else {
  188. $items[] = t('Upload the whole library (which can consist of multiple directories) into the newly created %library-path directory.', array(
  189. '%library-path' => 'sites/all/libraries/' . $library['machine name'],
  190. ));
  191. }
  192. // 5. Reload.
  193. $items[] = t('<a href="">Reload</a> the page. If successful, you should see status information about this library.');
  194. $build['steps'] = array(
  195. '#theme' => 'item_list',
  196. '#items' => $items,
  197. '#type' => 'ol'
  198. );
  199. return $build;
  200. }
  201. /**
  202. * Returns instructions for dealing with an undetected library.
  203. *
  204. * @param array $library
  205. * A library information array.
  206. *
  207. * @return array
  208. * A renderable array containing the instructions.
  209. */
  210. function libraries_admin_instructions_undetected($library) {
  211. $build = array();
  212. // Re-check location.
  213. // @todo Avoid usage of <br> elements.
  214. $build['instruction']['#markup'] = t('Check that the whole library is located at %library-path.', array(
  215. '%library-path' => $library['library path'],
  216. )) . '<br>';
  217. // If the library has variant-independent files, give the user the
  218. // exact location of the files to check against.
  219. // @todo It should be possible to display even variant-specific files
  220. // in case the variant is installed, but libraries_detect() does not
  221. // detect variants if the library version cannot be detected.
  222. if ($directory_layout = libraries_admin_directory_layout($library)) {
  223. $build['directory_layout'] = $directory_layout;
  224. $build['directory_layout']['#prefix'] = t('The following files and directories should be contained in that directory:');
  225. }
  226. // If the library is placed correctly the library information is
  227. // incorrect.
  228. // This switch could be avoided by using $library['info type'], but that would
  229. // hinder properly translating these strings.
  230. $build['reload']['#markup'] = t('If you have moved any files, <a href="">reload</a> the page. If successful, you should see status information about this library.') . '<br>';
  231. $build['notice']['#markup'] = t('If the files are placed correctly and the version can still not be detected, the library information is incorrect.') . '<br>';
  232. $provider = libraries_admin_get_provider($library);
  233. switch ($library['info type']) {
  234. case 'module':
  235. $build['contact']['#markup'] = t('Contact the maintainer of the %module module to correct this.', array(
  236. '%module' => $provider,
  237. )) . '<br>';
  238. break;
  239. case 'theme':
  240. $build['contact']['#markup'] = t('Contact the maintainer of the %theme theme to correct this.', array(
  241. '%theme' => $provider,
  242. )) . '<br>';
  243. break;
  244. case 'info file':
  245. $build['contact']['#markup'] = t('Contact the maintainer of the %info-file info file to correct this.', array(
  246. '%info-file' => $provider,
  247. )) . '<br>';
  248. break;
  249. }
  250. return $build;
  251. }
  252. /**
  253. * Returns instructions for dealing with an unsupported library.
  254. *
  255. * @param array $library
  256. * A library information array.
  257. *
  258. * @return array
  259. * A renderable array containing the instructions.
  260. */
  261. function libraries_admin_instructions_unsupported($library) {
  262. $build = array();
  263. $items = array();
  264. // Either download a different version of the library...
  265. $versions = array_keys($library['versions']);
  266. usort($versions, 'version_compare');
  267. $versions = array_reverse($versions);
  268. $version = $versions[0];
  269. $build['instruction']['#markup'] = t('Please install version %version of the library by following the following steps:',
  270. array(
  271. '%version' => $version,
  272. ));
  273. // 1. Delete the old library.
  274. $items[] = t('Delete the entire contents of the %library-path directory.',
  275. array(
  276. '%library-path' => $library['library path'],
  277. ));
  278. // 2. Download the new library.
  279. $items[] = t('Download version %version of the library <a href="@download-url">here</a>.',
  280. array(
  281. '%version' => $version,
  282. '@download-url' => $library['download url'],
  283. ));
  284. // 3. Unpack it.
  285. $items[] = t('If the library is an archive, i.e. if the file ending is for example <em>.tar.gz</em> or <em>.zip</em>, unpack it.');
  286. // 4. Upload the new library.
  287. // If the library has variant-independent files, give the user the
  288. // location of an example file to check his filesystem against.
  289. if ($directory_layout = libraries_admin_directory_layout($library)) {
  290. $items[] = t('Upload the new files into the %library-path directory. The following files and directories should be contained in that directory: !directory-layout',
  291. array(
  292. '%library-path' => $library['library path'],
  293. '!directory-layout' => drupal_render($directory_layout),
  294. ));
  295. }
  296. else {
  297. $items[] = t('Upload the new files into the %library-path directory.',
  298. array(
  299. '%library-path' => $library['library path'],
  300. ));
  301. }
  302. // 5. Reload.
  303. $items[] = t('<a href="">Reload</a> the page. If successful, you should see status information about this library.');
  304. $build['steps'] = array(
  305. '#theme' => 'item_list',
  306. '#items' => $items,
  307. '#type' => 'ol',
  308. );
  309. // ...or contact the maintainer of the library information.
  310. $provider = libraries_admin_get_provider($library);
  311. switch ($library['info type']) {
  312. case 'module':
  313. $build['contact']['#markup'] = t('If you are bound to version @version of the library, ask the maintainer of the %module module to provide support for it.', array(
  314. '@version' => $library['version'],
  315. '%module' => $provider,
  316. )) . '<br>';
  317. break;
  318. case 'theme':
  319. $build['contact']['#markup'] = t('If you are bound to version @version of the library, ask the maintainer of the %theme theme to provide support for it.', array(
  320. '@version' => $library['version'],
  321. '%theme' => $provider,
  322. )) . '<br>';
  323. break;
  324. case 'info file':
  325. $build['contact']['#markup'] = t('If you are bound to version @version of the library, ask the maintainer of the %info-file info file to provide support for it.', array(
  326. '@version' => $library['version'],
  327. '%info-file' => $provider,
  328. )) . '<br>';
  329. break;
  330. }
  331. return $build;
  332. }
  333. /**
  334. * Returns the directory layout of the library, if possible.
  335. *
  336. * The result of this function can help users to verify that they have uploaded
  337. * the library to the correct location.
  338. *
  339. * @param array $library
  340. * A library information array.
  341. *
  342. * @return array|false
  343. * A renderable array containing the directory layout of the library or FALSE
  344. * if a directory layout could not be generated.
  345. */
  346. function libraries_admin_directory_layout(array $library) {
  347. $build = array(
  348. '#theme' => 'item_list',
  349. '#type' => 'ul',
  350. '#items' => array(),
  351. );
  352. $items = &$build['#items'];
  353. if ($library['path']) {
  354. $items = &libraries_admin_path_to_tree($items, $library['path']);
  355. }
  356. foreach (array('js', 'css', 'php') as $type) {
  357. if (!empty($library['files'][$type])) {
  358. $files = array_keys($library['files'][$type]);
  359. foreach ($files as $file) {
  360. // Skip JavaScript settings.
  361. if (is_int($file)) {
  362. continue;
  363. }
  364. $children = &$items;
  365. libraries_admin_path_to_tree($children, $file);
  366. }
  367. }
  368. }
  369. return $build['#items'] ? $build : FALSE;
  370. }
  371. /**
  372. * Converts a file path into a tree structure for use in an item list.
  373. *
  374. * For example, the path 'foo/bar/baz' will be converted into the tree structure
  375. * represented by the following list:
  376. * - foo
  377. * - bar
  378. * - baz
  379. *
  380. * The $items array that is modified by reference or returned (see below) can
  381. * be used as the 'items' variable for theme_item_list().
  382. *
  383. * This function modifies passed-in $items array, so that multiple paths can
  384. * be placed into the same tree structure easily.
  385. *
  386. * @code
  387. * $items = array();
  388. * foreach ($paths as $path) {
  389. * libraries_admin_path_to_tree($items, $path);
  390. * }
  391. * @endcode
  392. *
  393. * It also returns the last item by reference, so that it can also be used to
  394. * traverse into a sub-structure and add further children there.
  395. *
  396. * @code
  397. * $items = array();
  398. * $children = &libraries_admin_path_to_tree($items, $path);
  399. * foreach ($sub_paths as $sub_path) {
  400. * libraries_admin_path_to_tree($children, $sub_path);
  401. * }
  402. * @endcode
  403. *
  404. * @param array $items
  405. * @param string $path
  406. *
  407. * @return array
  408. */
  409. function &libraries_admin_path_to_tree(array &$items, $path) {
  410. $part = strtok($path, '/');
  411. while ($part) {
  412. if (!isset($items[$part])) {
  413. $items[$part] = array(
  414. 'data' => $part,
  415. 'children' => array(),
  416. );
  417. }
  418. $items = &$items[$part]['children'];
  419. $part = strtok('/');
  420. }
  421. return $items;
  422. }
  423. /**
  424. * Sorts libraries by name.
  425. *
  426. * This function can be used as a callback for usort() or uasort().
  427. *
  428. * @param array $a
  429. * The first library information array.
  430. * @param array $b
  431. * The second library information array.
  432. *
  433. * @return int
  434. * Returns -1 if $a is considered smaller than $b, 1 if $a considered greater
  435. * than $b and 0 if $a and $b are considered equal.
  436. *
  437. * @see strnatcasecmp()
  438. * @see usort()
  439. * @see uasort()
  440. */
  441. function libraries_admin_sort_title(array $a, array $b) {
  442. return strnatcasecmp($a['name'], $b['name']);
  443. }
  444. /**
  445. * Returns the library's provider.
  446. *
  447. * The provider can be a module, a theme, or an info file.
  448. *
  449. * @param array $library
  450. * A library information array.
  451. *
  452. * @return string
  453. * The provider.
  454. */
  455. function libraries_admin_get_provider($library) {
  456. $provider = '';
  457. switch ($library['info type']) {
  458. case 'module':
  459. case 'theme':
  460. $info = system_get_info($library['info type'], $library[$library['info type']]);
  461. $provider = $info['name'];
  462. break;
  463. case 'info file':
  464. $provider = basename($library['info file']);
  465. break;
  466. }
  467. return $provider;
  468. }
  469. /**
  470. * Returns the library's provider and provider type.
  471. *
  472. * The provider type is either 'module', 'theme', or 'info file'.
  473. *
  474. * @param array $library
  475. * A library information array.
  476. *
  477. * @return string
  478. * The provider and provider type.
  479. */
  480. function libraries_admin_get_provider_with_type($library) {
  481. $provider = libraries_admin_get_provider($library);
  482. $provider_with_type = '';
  483. // This switch could be avoided by using $library['info type'], but that would
  484. // hinder properly translating these strings.
  485. switch ($library['info type']) {
  486. case 'module':
  487. $provider_with_type = t('%module module', array('%module' => $provider));
  488. break;
  489. case 'theme':
  490. $provider_with_type = t('%theme theme', array('%theme' => $provider));
  491. break;
  492. case 'info file':
  493. $provider_with_type = t('%info-file info file', array('%info-file' => $provider));
  494. break;
  495. }
  496. return $provider_with_type;
  497. }