advanced_help.module

Tracking 7.x-1.x branch
  1. drupal
    1. 6 contributions/advanced_help/advanced_help.module
    2. 7 contributions/advanced_help/advanced_help.module

Pluggable system to provide advanced help facilities for Drupal and modules.

Modules utilizing this help system should create a 'help' directory in their module. Inside that directory place MODULENAME.help.ini which will be formatted like this:

[buses]
title = "How buses are tied into the system"
file = buses

[TOPIC_ID]
title = "Title of topic"
file = filename of topic, without the .html extension
weight = the importance of the topic on the index page
parent = the optional topic parent to use in the breadcrumb.
Can be either topic or module%topic

All topics are addressed by the module that provides the topic, and the topic id. Links may be embedded as in the following example:

$output .= theme('advanced_help_topic', $module, $topic);

Link to other topics using <a href="topic:module/topic">. (Using this format ensures the popup status remains consistent for all links.)

Functions & methods

NameDescription
advanced_help_form_system_modules_alterImplements hook_form_system_modules_alter().
advanced_help_get_module_nameSmall helper function to get a module's proper name.
advanced_help_get_settingsReturns advanced help settings.
advanced_help_get_sidsGets search id for each topic.
advanced_help_get_topicGet the information for a single help topic.
advanced_help_get_topicsSearch the system for all available help topics.
advanced_help_get_topic_filenameLoad and render a help topic.
advanced_help_get_topic_file_infoLoad and render a help topic.
advanced_help_get_topic_hierarchyBuild a hierarchy for a single module's topics.
advanced_help_get_treeBuild a tree of advanced help topics.
advanced_help_index_pagePage callback to view the advanced help topic index.
advanced_help_lFormat a link but preserve popup identity.
advanced_help_menuImplements hook_menu().
advanced_help_menu_alterImplements hook_menu_alter().
advanced_help_permissionImplements hook_permission().
advanced_help_search_executeImplements hook_search_execute().
advanced_help_search_formForm builder callback to build the search form.
advanced_help_search_form_submitProcess a search form submission.
advanced_help_search_get_keys
advanced_help_search_infoImplements hook_search_info().
advanced_help_search_resetImplements hook_search_reset().
advanced_help_search_statusImplements hook_search_status().
advanced_help_search_viewPage callback for advanced help search.
advanced_help_themeImplements hook_theme().
advanced_help_topic_pagePage callback to view a help topic.
advanced_help_uasortHelper function to sort topics.
advanced_help_update_indexImplements hook_update_index().
advanced_help_urlFormat a URL but preserve popup identity.
advanced_help_view_topicLoad and render a help topic.
template_preprocess_advanced_help_popupFill in a bunch of page variables for our specialized popup page.
theme_advanced_help_topicDisplay a help icon with a link to view the topic in a popup.
_advanced_help_parse_iniFuntion to parse ini / txt files.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Pluggable system to provide advanced help facilities for Drupal and modules.
  5. *
  6. * Modules utilizing this help system should create a 'help' directory in their
  7. * module. Inside that directory place MODULENAME.help.ini which will be
  8. * formatted like this:
  9. *
  10. * @code
  11. * [buses]
  12. * title = "How buses are tied into the system"
  13. * file = buses
  14. *
  15. * [TOPIC_ID]
  16. * title = "Title of topic"
  17. * file = filename of topic, without the .html extension
  18. * weight = the importance of the topic on the index page
  19. * parent = the optional topic parent to use in the breadcrumb.
  20. * Can be either topic or module%topic
  21. * @endcode
  22. *
  23. * All topics are addressed by the module that provides the topic, and the topic
  24. * id. Links may be embedded as in the following example:
  25. *
  26. * @code
  27. * $output .= theme('advanced_help_topic', $module, $topic);
  28. * @endcode
  29. *
  30. * Link to other topics using <a href="topic:module/topic">. (Using
  31. * this format ensures the popup status remains consistent for all
  32. * links.)
  33. */
  34. /**
  35. * Implements hook_menu().
  36. */
  37. function advanced_help_menu() {
  38. // View help topic index.
  39. //
  40. // This is structured little oddly so that POTX can handle the translation.
  41. if (module_exists('help')) {
  42. $items['admin/advanced_help'] = array(
  43. 'title' => 'Advanced help',
  44. 'page callback' => 'advanced_help_index_page',
  45. 'access arguments' => array('view advanced help index'),
  46. 'weight' => 9,
  47. );
  48. }
  49. else {
  50. $items['admin/advanced_help'] = array(
  51. 'title' => 'Help',
  52. 'page callback' => 'advanced_help_index_page',
  53. 'access arguments' => array('view advanced help index'),
  54. 'weight' => 9,
  55. );
  56. }
  57. $items['advanced_help/search/%'] = array(
  58. 'title' => 'Search help',
  59. 'page callback' => 'advanced_help_search_view',
  60. 'page arguments' => array('advanced_help'),
  61. 'access arguments' => array('view advanced help index'),
  62. );
  63. // View help topic.
  64. $items['help/%/%'] = array(
  65. 'page callback' => 'advanced_help_topic_page',
  66. 'page arguments' => array(1, 2),
  67. 'access arguments' => array('view advanced help topic'),
  68. 'type' => MENU_CALLBACK,
  69. );
  70. return $items;
  71. }
  72. /**
  73. * Implements hook_menu_alter().
  74. */
  75. function advanced_help_menu_alter(&$callbacks) {
  76. // We need to fix the menu item provided by search module to restrict access.
  77. $callbacks['search/advanced_help/%menu_tail']['access callback'] = 'user_access';
  78. $callbacks['search/advanced_help/%menu_tail']['access arguments'] = array('view advanced help index');
  79. }
  80. /**
  81. * Implements hook_theme().
  82. */
  83. function advanced_help_theme() {
  84. $hooks['advanced_help_topic'] = array(
  85. 'variables' => array(
  86. 'module' => NULL,
  87. 'topic' => NULL,
  88. 'type' => 'icon',
  89. ),
  90. );
  91. $hooks['advanced_help_popup'] = array(
  92. 'render element' => 'content',
  93. 'template' => 'advanced-help-popup',
  94. );
  95. return $hooks;
  96. }
  97. /**
  98. * Helper function to sort topics.
  99. */
  100. function advanced_help_uasort($id_a, $id_b) {
  101. $topics = advanced_help_get_topics();
  102. list($module_a, $topic_a) = $id_a;
  103. $a = $topics[$module_a][$topic_a];
  104. list($module_b, $topic_b) = $id_b;
  105. $b = $topics[$module_b][$topic_b];
  106. $a_weight = isset($a['weight']) ? $a['weight'] : 0;
  107. $b_weight = isset($b['weight']) ? $b['weight'] : 0;
  108. if ($a_weight != $b_weight) {
  109. return ($a_weight < $b_weight) ? -1 : 1;
  110. }
  111. if ($a['title'] != $b['title']) {
  112. return ($a['title'] < $b['title']) ? -1 : 1;
  113. }
  114. return 0;
  115. }
  116. /* Helper function for grabbing search keys. Function is missing in D7.
  117. *
  118. * http://api.drupal.org/api/function/search_get_keys/6
  119. */
  120. function advanced_help_search_get_keys() {
  121. static $return;
  122. if (!isset($return)) {
  123. // Extract keys as remainder of path
  124. // Note: support old GET format of searches for existing links.
  125. $path = explode('/', $_GET['q'], 3);
  126. $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
  127. $return = count($path) == 3 ? $path[2] : $keys;
  128. }
  129. return $return;
  130. }
  131. /**
  132. * Page callback for advanced help search.
  133. */
  134. function advanced_help_search_view() {
  135. if (!module_exists('search')) {
  136. return drupal_not_found();
  137. }
  138. $breadcrumb[] = advanced_help_l(t('Help'), 'admin/advanced_help');
  139. if (!isset($_POST['form_id'])) {
  140. $keys = advanced_help_search_get_keys();
  141. // Only perform search if there is non-whitespace search term:
  142. $results = '';
  143. if (trim($keys)) {
  144. $search_results = search_data($keys, 'advanced_help');
  145. $search_results = drupal_render($search_results);
  146. // Collect the search results:
  147. $results = array(
  148. '#type' => 'markup',
  149. '#markup' => $search_results,
  150. );
  151. }
  152. // Construct the search form.
  153. $output['advanced_help_search_form'] = drupal_get_form('advanced_help_search_form', $keys);
  154. $output['results'] = $results;
  155. }
  156. else {
  157. $output = drupal_get_form('advanced_help_search_form', empty($keys) ? '' : $keys);
  158. }
  159. $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
  160. if ($popup) {
  161. // Prevent devel module from spewing.
  162. $GLOBALS['devel_shutdown'] = FALSE;
  163. // Suppress admin_menu.
  164. module_invoke('admin_menu', 'suppress');
  165. drupal_set_breadcrumb(array_reverse($breadcrumb));
  166. print theme('advanced_help_popup', array('content' => $output));
  167. return;
  168. }
  169. $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
  170. drupal_set_breadcrumb($breadcrumb);
  171. return $output;
  172. }
  173. /**
  174. * Page callback to view the advanced help topic index.
  175. *
  176. * @param string $module
  177. * Name of the module.
  178. *
  179. * @return array
  180. * Returns module index.
  181. */
  182. function advanced_help_index_page($module = '') {
  183. $topics = advanced_help_get_topics();
  184. $settings = advanced_help_get_settings();
  185. $output = array();
  186. // Print a search widget.
  187. $output['advanced_help_search'] = module_exists('search')
  188. ? drupal_get_form('advanced_help_search_form') : t('Enable the search module to search help.');
  189. $breadcrumb = array();
  190. if ($module) {
  191. if (empty($topics[$module])) {
  192. return drupal_not_found();
  193. }
  194. advanced_help_get_topic_hierarchy($topics);
  195. $items = advanced_help_get_tree($topics, $topics[$module]['']['children']);
  196. $breadcrumb[] = advanced_help_l(t('Help'), 'admin/advanced_help');
  197. drupal_set_title(t('@module help index', array('@module' => advanced_help_get_module_name($module))));
  198. $output['items-module'] = array(
  199. '#theme' => 'item_list',
  200. '#items' => $items,
  201. );
  202. }
  203. else {
  204. // Print a module index.
  205. $modules = array();
  206. $result = db_query('SELECT * FROM {system}');
  207. foreach ($result as $info) {
  208. $module_info = unserialize($info->info);
  209. $modules[$info->name] = $module_info['name'];
  210. }
  211. asort($modules);
  212. $items = array();
  213. foreach ($modules as $module => $module_name) {
  214. if (!empty($topics[$module]) && empty($settings[$module]['hide'])) {
  215. if (isset($settings[$module]['index name'])) {
  216. $name = $settings[$module]['index name'];
  217. }
  218. elseif (isset($settings[$module]['name'])) {
  219. $name = $settings[$module]['name'];
  220. }
  221. else {
  222. $name = t($module_name);
  223. }
  224. $items[] = advanced_help_l($name, "admin/advanced_help/$module");
  225. }
  226. }
  227. drupal_set_title(t('Module help index'));
  228. $output['items-nomodule'] = array(
  229. '#theme' => 'item_list',
  230. '#items' => $items,
  231. );
  232. }
  233. $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
  234. if ($popup) {
  235. // Prevent devel module from spewing.
  236. $GLOBALS['devel_shutdown'] = FALSE;
  237. // Suppress admin_menu.
  238. module_invoke('admin_menu', 'suppress');
  239. drupal_set_breadcrumb(array_reverse($breadcrumb));
  240. print theme('advanced_help_popup', array('content' => $output));
  241. return;
  242. }
  243. $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
  244. drupal_set_breadcrumb($breadcrumb);
  245. return $output;
  246. }
  247. /**
  248. * Build a tree of advanced help topics.
  249. *
  250. * @param array $topics
  251. * Topics.
  252. * @param array $topic_ids
  253. * Topic Ids.
  254. * @param int $max_depth
  255. * Maximum depth for subtopics.
  256. * @param int $depth
  257. * Default depth for subtopics.
  258. *
  259. * @return array
  260. * Returns list of topics/subtopics.
  261. */
  262. function advanced_help_get_tree($topics, $topic_ids, $max_depth = -1, $depth = 0) {
  263. uasort($topic_ids, 'advanced_help_uasort');
  264. $items = array();
  265. foreach ($topic_ids as $info) {
  266. list($module, $topic) = $info;
  267. $item = advanced_help_l($topics[$module][$topic]['title'], "help/$module/$topic");
  268. if (!empty($topics[$module][$topic]['children']) && ($max_depth == -1 || $depth < $max_depth)) {
  269. $item .= theme('item_list', array(
  270. 'items' => advanced_help_get_tree($topics, $topics[$module][$topic]['children'], $max_depth, $depth + 1),
  271. ));
  272. }
  273. $items[] = $item;
  274. }
  275. return $items;
  276. }
  277. /**
  278. * Build a hierarchy for a single module's topics.
  279. */
  280. function advanced_help_get_topic_hierarchy(&$topics) {
  281. foreach ($topics as $module => $module_topics) {
  282. foreach ($module_topics as $topic => $info) {
  283. $parent_module = $module;
  284. // We have a blank topic that we don't want parented to itself.
  285. if (!$topic) {
  286. continue;
  287. }
  288. if (empty($info['parent'])) {
  289. $parent = '';
  290. }
  291. elseif (strpos($info['parent'], '%')) {
  292. list($parent_module, $parent) = explode('%', $info['parent']);
  293. if (empty($topics[$parent_module][$parent])) {
  294. // If it doesn't exist, top level.
  295. $parent = '';
  296. }
  297. }
  298. else {
  299. $parent = $info['parent'];
  300. if (empty($module_topics[$parent])) {
  301. // If it doesn't exist, top level.
  302. $parent = '';
  303. }
  304. }
  305. if (!isset($topics[$parent_module][$parent]['children'])) {
  306. $topics[$parent_module][$parent]['children'] = array();
  307. }
  308. $topics[$parent_module][$parent]['children'][] = array($module, $topic);
  309. $topics[$module][$topic]['_parent'] = array($parent_module, $parent);
  310. }
  311. }
  312. }
  313. /**
  314. * Implements hook_form_system_modules_alter().
  315. *
  316. * Add advanced help links to the modules page.
  317. */
  318. function advanced_help_form_system_modules_alter(&$form, &$form_state) {
  319. if (!isset($form['modules'])) {
  320. return;
  321. }
  322. $advanced_help_modules = drupal_map_assoc(array_keys(advanced_help_get_topics()));
  323. foreach (element_children($form['modules']) as $group) {
  324. foreach (element_children($form['modules'][$group]) as $module) {
  325. if (isset($advanced_help_modules[$module])) {
  326. $form['modules'][$group][$module]['links']['help'] = array(
  327. '#type' => 'link',
  328. '#title' => t('Help'),
  329. '#href' => "admin/advanced_help/$module",
  330. '#options' => array(
  331. 'attributes' => array(
  332. 'class' => array('module-link', 'module-link-help'),
  333. 'title' => t('Help'),
  334. ),
  335. ),
  336. );
  337. }
  338. }
  339. }
  340. }
  341. /**
  342. * Form builder callback to build the search form.
  343. *
  344. * Load search/search.pages so that its template preprocess functions are
  345. * visible and can be invoked.
  346. */
  347. function advanced_help_search_form($form, &$form_state, $keys = '') {
  348. module_load_include('inc', 'search', 'search.pages');
  349. $form = search_form($form, $form_state, 'admin/advanced_help', $keys, 'advanced_help', t('Search help'));
  350. $form['basic']['inline']['submit']['#validate'] = array('search_form_validate');
  351. $form['basic']['inline']['submit']['#submit'] = array('advanced_help_search_form_submit');
  352. return $form;
  353. }
  354. /**
  355. * Process a search form submission.
  356. */
  357. function advanced_help_search_form_submit($form, &$form_state) {
  358. $keys = empty($form_state['values']['processed_keys']) ? $form_state['values']['keys'] : $form_state['values']['processed_keys'];
  359. if ($keys == '') {
  360. form_set_error('keys', t('Please enter some keywords.'));
  361. return;
  362. }
  363. $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
  364. if ($popup) {
  365. $form_state['redirect'] = array('advanced_help/search/' . $keys, array('query' => array('popup' => 'true')));
  366. }
  367. else {
  368. $form_state['redirect'] = 'advanced_help/search/' . $keys;
  369. }
  370. }
  371. /**
  372. * Small helper function to get a module's proper name.
  373. *
  374. * @param string $module
  375. * Name of the module.
  376. *
  377. * @return string
  378. * Returns module's descriptive name.
  379. */
  380. function advanced_help_get_module_name($module) {
  381. $settings = advanced_help_get_settings();
  382. if (isset($settings[$module]['name'])) {
  383. $name = $settings[$module]['name'];
  384. }
  385. else {
  386. $info = db_query("SELECT s.info FROM {system} s WHERE s.name = :name",
  387. array(':name' => $module))
  388. ->fetchField();
  389. $info = unserialize($info);
  390. $name = t($info['name']);
  391. }
  392. return $name;
  393. }
  394. /**
  395. * Page callback to view a help topic.
  396. */
  397. function advanced_help_topic_page($module, $topic) {
  398. $info = advanced_help_get_topic($module, $topic);
  399. if (!$info) {
  400. return drupal_not_found();
  401. }
  402. $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
  403. drupal_set_title($info['title']);
  404. // Set up breadcrumb.
  405. $breadcrumb = array();
  406. $parent = $info;
  407. $pmodule = $module;
  408. // Loop checker.
  409. $checked = array();
  410. while (!empty($parent['parent'])) {
  411. if (strpos($parent['parent'], '%')) {
  412. list($pmodule, $ptopic) = explode('%', $parent['parent']);
  413. }
  414. else {
  415. $ptopic = $parent['parent'];
  416. }
  417. if (!empty($checked[$pmodule][$ptopic])) {
  418. break;
  419. }
  420. $checked[$pmodule][$ptopic] = TRUE;
  421. $parent = advanced_help_get_topic($pmodule, $ptopic);
  422. if (!$parent) {
  423. break;
  424. }
  425. $breadcrumb[] = advanced_help_l($parent['title'], "help/$pmodule/$ptopic");
  426. }
  427. $breadcrumb[] = advanced_help_l(advanced_help_get_module_name($pmodule), "admin/advanced_help/$pmodule");
  428. $breadcrumb[] = advanced_help_l(t('Help'), "admin/advanced_help");
  429. $output = advanced_help_view_topic($module, $topic, $popup);
  430. if (empty($output)) {
  431. $output = t('Missing help topic.');
  432. }
  433. if ($popup) {
  434. // Prevent devel module from spewing.
  435. $GLOBALS['devel_shutdown'] = FALSE;
  436. // Suppress admin_menu.
  437. module_invoke('admin_menu', 'suppress');
  438. drupal_set_breadcrumb(array_reverse($breadcrumb));
  439. print theme('advanced_help_popup', array('content' => $output));
  440. return;
  441. }
  442. drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
  443. $breadcrumb[] = l(t('Home'), '');
  444. drupal_set_breadcrumb(array_reverse($breadcrumb));
  445. return $output;
  446. }
  447. /**
  448. * Implements hook_permission().
  449. */
  450. function advanced_help_permission() {
  451. return array(
  452. 'view advanced help topic' => array('title' => t('View help topics')),
  453. 'view advanced help popup' => array('title' => t('View help popups')),
  454. 'view advanced help index' => array('title' => t('View help index')),
  455. );
  456. }
  457. /**
  458. * Display a help icon with a link to view the topic in a popup.
  459. *
  460. * @param array $variables
  461. * An associative array containing:
  462. * - module: The module that owns this help topic.
  463. * - topic: The identifier for the topic
  464. * - type
  465. * - 'icon' to display the question mark icon
  466. * - 'title' to display the topic's title
  467. * - any other text to display the text. Be sure to t() it!
  468. */
  469. function theme_advanced_help_topic($variables) {
  470. $module = $variables['module'];
  471. $topic = $variables['topic'];
  472. $type = $variables['type'];
  473. $info = advanced_help_get_topic($module, $topic);
  474. if (!$info) {
  475. return;
  476. }
  477. switch ($type) {
  478. case 'icon':
  479. $text = '<span>' . t('Help') . '</span>';
  480. $class = 'advanced-help-link';
  481. break;
  482. case 'title':
  483. $text = $info['title'];
  484. $class = 'advanced-help-title';
  485. break;
  486. default:
  487. $class = 'advanced-help-title';
  488. $text = $type;
  489. break;
  490. }
  491. if (user_access('view advanced help popup')) {
  492. drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-icon.css');
  493. return l($text, "help/$module/$topic", array(
  494. 'attributes' => array(
  495. 'class' => $class,
  496. 'onclick' => "var w=window.open(this.href, 'advanced_help_window', 'width=" . $info['popup width'] . ",height=" . $info['popup height'] . ",scrollbars,resizable'); w.focus(); return false;",
  497. 'title' => $info['title'],
  498. ),
  499. 'query' => array('popup' => TRUE),
  500. 'html' => TRUE)
  501. );
  502. }
  503. else {
  504. return l($text, "help/$module/$topic", array(
  505. 'attributes' => array(
  506. 'class' => $class,
  507. 'title' => $info['title'],
  508. ),
  509. 'html' => TRUE)
  510. );
  511. }
  512. }
  513. /**
  514. * Load and render a help topic.
  515. */
  516. function advanced_help_get_topic_filename($module, $topic) {
  517. $info = advanced_help_get_topic_file_info($module, $topic);
  518. if ($info) {
  519. return "./$info[path]/$info[file]";
  520. }
  521. }
  522. /**
  523. * Load and render a help topic.
  524. */
  525. function advanced_help_get_topic_file_info($module, $topic) {
  526. global $language;
  527. $info = advanced_help_get_topic($module, $topic);
  528. if (empty($info)) {
  529. return;
  530. }
  531. // Search paths:
  532. $paths = array(
  533. // Allow theme override.
  534. path_to_theme() . '/help',
  535. // Translations.
  536. drupal_get_path('module', $module) . "/translations/help/$language->language",
  537. // In same directory as .inc file.
  538. $info['path'],
  539. );
  540. foreach ($paths as $path) {
  541. if (file_exists("./$path/$info[file]")) {
  542. return array('path' => $path, 'file' => $info['file']);
  543. }
  544. }
  545. }
  546. /**
  547. * Load and render a help topic.
  548. *
  549. * @param string $module
  550. * Name of the module.
  551. * @param string $topic
  552. * Name of the topic.
  553. * @param boolean $popup
  554. * Whether to show in popup or not.
  555. *
  556. * @return string
  557. * Returns formatted topic.
  558. */
  559. function advanced_help_view_topic($module, $topic, $popup = FALSE) {
  560. $file_info = advanced_help_get_topic_file_info($module, $topic);
  561. if ($file_info) {
  562. $info = advanced_help_get_topic($module, $topic);
  563. $file = "./$file_info[path]/$file_info[file]";
  564. $output = file_get_contents($file);
  565. if (isset($info['readme file']) && $info['readme file']) {
  566. // Readme files are treated as plain text: filter accordingly.
  567. $output = check_plain($output);
  568. }
  569. // Make some exchanges. The strtr is because url() translates $ into %24
  570. // but we need to change it back for the regex replacement.
  571. //
  572. // Change 'topic:' to the URL for another help topic.
  573. if ($popup) {
  574. $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
  575. $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
  576. $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')), $output);
  577. }
  578. else {
  579. $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
  580. $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
  581. $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1'), array('%24' => '$')), $output);
  582. }
  583. global $base_path;
  584. // Change 'path:' to the URL to the base help directory.
  585. $output = preg_replace('/href="path:([^"]+)"/', 'href="' . $base_path . $info['path'] . '/$1"', $output);
  586. $output = preg_replace('/src="path:([^"]+)"/', 'src="' . $base_path . $info['path'] . '/$1"', $output);
  587. $output = str_replace('&path&', $base_path . $info['path'] . '/', $output);
  588. // Change 'trans_path:' to the URL to the actual help directory.
  589. $output = preg_replace('/href="trans_path:([^"]+)"/', 'href="' . $base_path . $file_info['path'] . '/$1"', $output);
  590. $output = preg_replace('/src="trans_path:([^"]+)"/', 'src="' . $base_path . $file_info['path'] . '/$1"', $output);
  591. $output = str_replace('&trans_path&', $base_path . $file_info['path'] . '/', $output);
  592. // Change 'base_url:' to the URL to the site.
  593. $output = preg_replace('/href="base_url:([^"]+)"/', 'href="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
  594. $output = preg_replace('/src="base_url:([^"]+)"/', 'src="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
  595. $output = preg_replace('/&base_url&([^"]+)"/', strtr(url('$1'), array('%24' => '$')) . '"', $output);
  596. // Run the line break filter if requested.
  597. if (!empty($info['line break'])) {
  598. // Remove the header since it adds an extra <br /> to the filter.
  599. $output = preg_replace('/^<!--[^\n]*-->\n/', '', $output);
  600. $output = _filter_autop($output);
  601. }
  602. if (!empty($info['navigation'])) {
  603. $topics = advanced_help_get_topics();
  604. advanced_help_get_topic_hierarchy($topics);
  605. if (!empty($topics[$module][$topic]['children'])) {
  606. $items = advanced_help_get_tree($topics, $topics[$module][$topic]['children']);
  607. $output .= theme('item_list', array('items' => $items));
  608. }
  609. list($parent_module, $parent_topic) = $topics[$module][$topic]['_parent'];
  610. if ($parent_topic) {
  611. $parent = $topics[$module][$topic]['_parent'];
  612. $up = "help/$parent[0]/$parent[1]";
  613. }
  614. else {
  615. $up = "admin/advanced_help/$module";
  616. }
  617. $siblings = $topics[$parent_module][$parent_topic]['children'];
  618. uasort($siblings, 'advanced_help_uasort');
  619. $prev = $next = NULL;
  620. $found = FALSE;
  621. foreach ($siblings as $sibling) {
  622. list($sibling_module, $sibling_topic) = $sibling;
  623. if ($found) {
  624. $next = $sibling;
  625. break;
  626. }
  627. if ($sibling_module == $module && $sibling_topic == $topic) {
  628. $found = TRUE;
  629. continue;
  630. }
  631. $prev = $sibling;
  632. }
  633. if ($prev || $up || $next) {
  634. $navigation = '<div class="help-navigation clear-block">';
  635. if ($prev) {
  636. $navigation .= advanced_help_l('<< ' . $topics[$prev[0]][$prev[1]]['title'], "help/$prev[0]/$prev[1]", array('attributes' => array('class' => 'help-left')));
  637. }
  638. if ($up) {
  639. $navigation .= advanced_help_l(t('Up'), $up, array('attributes' => array('class' => $prev ? 'help-up' : 'help-up-noleft')));
  640. }
  641. if ($next) {
  642. $navigation .= advanced_help_l($topics[$next[0]][$next[1]]['title'] . ' >>', "help/$next[0]/$next[1]", array('attributes' => array('class' => 'help-right')));
  643. }
  644. $navigation .= '</div>';
  645. $output .= $navigation;
  646. }
  647. }
  648. if (!empty($info['css'])) {
  649. drupal_add_css($info['path'] . '/' . $info['css']);
  650. }
  651. $output = '<div class="advanced-help-topic">' . $output . '</div>';
  652. drupal_alter('advanced_help_topic', $output, $popup);
  653. return $output;
  654. }
  655. }
  656. /**
  657. * Get the information for a single help topic.
  658. */
  659. function advanced_help_get_topic($module, $topic) {
  660. $topics = advanced_help_get_topics();
  661. if (!empty($topics[$module][$topic])) {
  662. return $topics[$module][$topic];
  663. }
  664. }
  665. /**
  666. * Search the system for all available help topics.
  667. */
  668. function advanced_help_get_topics() {
  669. $cache = _advanced_help_parse_ini();
  670. return $cache['topics'];
  671. }
  672. /**
  673. * Returns advanced help settings.
  674. */
  675. function advanced_help_get_settings() {
  676. $cache = _advanced_help_parse_ini();
  677. return $cache['settings'];
  678. }
  679. /**
  680. * Funtion to parse ini / txt files.
  681. */
  682. function _advanced_help_parse_ini() {
  683. static $cache = NULL;
  684. if (!isset($cache)) {
  685. $cache = array('topics' => array(), 'settings' => array());
  686. $help_path = drupal_get_path('module', 'advanced_help') . '/modules';
  687. foreach (module_list() as $module) {
  688. $module_path = drupal_get_path('module', $module);
  689. $info = array();
  690. if (file_exists("$module_path/help/$module.help.ini")) {
  691. $path = "$module_path/help";
  692. $info = parse_ini_file("./$module_path/help/$module.help.ini", TRUE);
  693. }
  694. elseif (file_exists("$help_path/$module/$module.help.ini")) {
  695. $path = "$help_path/$module";
  696. $info = parse_ini_file("./$help_path/$module/$module.help.ini", TRUE);
  697. }
  698. elseif (!file_exists("$module_path/help")) {
  699. // Look for one or more README files.
  700. $files = file_scan_directory("./$module_path",
  701. '/^(README|readme).*\.(txt|TXT)$/', array('recurse' => FALSE));
  702. $path = "./$module_path";
  703. foreach ($files as $name => $fileinfo) {
  704. $info[$fileinfo->filename] = array(
  705. 'line break' => TRUE,
  706. 'readme file' => TRUE,
  707. 'file' => $fileinfo->filename,
  708. 'title' => $fileinfo->name,
  709. );
  710. }
  711. }
  712. if (!empty($info)) {
  713. // Get translated titles:
  714. global $language;
  715. $translation = array();
  716. if (file_exists("$module_path/translations/help/$language->language/$module.help.ini")) {
  717. $translation = parse_ini_file("$module_path/translations/help/$language->language/$module.help.ini", TRUE);
  718. }
  719. $cache['settings'][$module] = array();
  720. if (!empty($info['advanced help settings'])) {
  721. $cache['settings'][$module] = $info['advanced help settings'];
  722. unset($info['advanced help settings']);
  723. // Check translated strings for translatable global settings.
  724. if (isset($translation['advanced help settings']['name'])) {
  725. $cache['settings']['name'] = $translation['advanced help settings']['name'];
  726. }
  727. if (isset($translation['advanced help settings']['index name'])) {
  728. $cache['settings']['index name'] = $translation['advanced help settings']['index name'];
  729. }
  730. }
  731. foreach ($info as $name => $topic) {
  732. // Each topic should have a name, a title, a file and path.
  733. $file = !empty($topic['file']) ? $topic['file'] : $name;
  734. $cache['topics'][$module][$name] = array(
  735. 'name' => $name,
  736. 'title' => !empty($translation[$name]['title']) ? $translation[$name]['title'] : $topic['title'],
  737. 'weight' => isset($topic['weight']) ? $topic['weight'] : 0,
  738. 'parent' => isset($topic['parent']) ? $topic['parent'] : 0,
  739. 'popup width' => isset($topic['popup width']) ? $topic['popup width'] : 500,
  740. 'popup height' => isset($topic['popup height']) ? $topic['popup height'] : 500,
  741. // Require extension.
  742. 'file' => isset($topic['readme file']) ? $file : $file . '.html',
  743. // Not in .ini file.
  744. 'path' => $path,
  745. 'line break' => isset($topic['line break']) ? $topic['line break'] : (isset($cache['settings'][$module]['line break']) ? $cache['settings'][$module]['line break'] : FALSE),
  746. 'navigation' => isset($topic['navigation']) ? $topic['navigation'] : (isset($cache['settings'][$module]['navigation']) ? $cache['settings'][$module]['navigation'] : TRUE),
  747. 'css' => isset($topic['css']) ? $topic['css'] : (isset($cache['settings'][$module]['css']) ? $cache['settings'][$module]['css'] : NULL),
  748. 'readme file' => isset($topic['readme file']) ? $topic['readme file'] : FALSE,
  749. );
  750. }
  751. }
  752. }
  753. drupal_alter('advanced_help_topic_info', $cache);
  754. }
  755. return $cache;
  756. }
  757. /**
  758. * Implements hook_search_info().
  759. *
  760. * @return array
  761. * Returns title for the tab on search page & path component after 'search/'.
  762. */
  763. function advanced_help_search_info() {
  764. return array(
  765. 'title' => t('Help'),
  766. 'path' => 'advanced_help',
  767. );
  768. }
  769. /**
  770. * Implements hook_search_execute().
  771. */
  772. function advanced_help_search_execute($keys = NULL) {
  773. $topics = advanced_help_get_topics();
  774. $query = db_select('search_index', 'i', array('target' => 'slave'))
  775. ->extend('SearchQuery')
  776. ->extend('PagerDefault');
  777. $query->join('advanced_help_index', 'ahi', 'i.sid = ahi.sid');
  778. $query->searchExpression($keys, 'help');
  779. // Only continue if the first pass query matches.
  780. if (!$query->executeFirstPass()) {
  781. return array();
  782. }
  783. $results = array();
  784. $find = $query->execute();
  785. foreach ($find as $item) {
  786. $sids[] = $item->sid;
  787. }
  788. $query = db_select('advanced_help_index', 'ahi');
  789. $result = $query
  790. ->fields('ahi')
  791. ->condition('sid', $sids, 'IN')
  792. ->execute();
  793. foreach ($result as $sid) {
  794. // Guard against removed help topics that are still indexed.
  795. if (empty($topics[$sid->module][$sid->topic])) {
  796. continue;
  797. }
  798. $info = $topics[$sid->module][$sid->topic];
  799. $text = advanced_help_view_topic($sid->module, $sid->topic);
  800. $results[] = array(
  801. 'link' => advanced_help_url("help/$sid->module/$sid->topic"),
  802. 'title' => $info['title'],
  803. 'snippet' => search_excerpt($keys, $text),
  804. );
  805. }
  806. return $results;
  807. }
  808. /**
  809. * Implements hook_search_reset().
  810. */
  811. function advanced_help_search_reset() {
  812. variable_del('advanced_help_last_cron');
  813. }
  814. /**
  815. * Implements hook_search_status().
  816. */
  817. function advanced_help_search_status() {
  818. $topics = advanced_help_get_topics();
  819. $total = 0;
  820. foreach ($topics as $module => $module_topics) {
  821. foreach ($module_topics as $topic => $info) {
  822. $file = advanced_help_get_topic_filename($module, $topic);
  823. if ($file) {
  824. $total++;
  825. }
  826. }
  827. }
  828. $last_cron = variable_get('advanced_help_last_cron', array('time' => 0));
  829. $indexed = 0;
  830. if ($last_cron['time'] != 0) {
  831. $indexed = db_query("SELECT COUNT(*) FROM {search_dataset} sd WHERE sd.type = 'help' AND sd.sid IS NOT NULL AND sd.reindex = 0")->fetchField();
  832. }
  833. return array('remaining' => $total - $indexed, 'total' => $total);
  834. }
  835. /**
  836. * Gets search id for each topic.
  837. *
  838. * Get or create an sid (search id) that correlates to each topic for
  839. * the search system.
  840. */
  841. function advanced_help_get_sids(&$topics) {
  842. global $language;
  843. $result = db_query("SELECT * FROM {advanced_help_index} WHERE language = :language",
  844. array(':language' => $language->language));
  845. foreach ($result as $sid) {
  846. if (empty($topics[$sid->module][$sid->topic])) {
  847. db_query("DELETE FROM {advanced_help_index} WHERE sid = :sid",
  848. array(':sid' => $sid->sid));
  849. }
  850. else {
  851. $topics[$sid->module][$sid->topic]['sid'] = $sid->sid;
  852. }
  853. }
  854. }
  855. /**
  856. * Implements hook_update_index().
  857. */
  858. function advanced_help_update_index() {
  859. global $language;
  860. // If we got interrupted by limit, this will contain the last module
  861. // and topic we looked at.
  862. $last = variable_get('advanced_help_last_cron', array('time' => 0));
  863. $limit = intval(variable_get('search_cron_limit', 100));
  864. $topics = advanced_help_get_topics();
  865. advanced_help_get_sids($topics);
  866. $count = 0;
  867. foreach ($topics as $module => $module_topics) {
  868. // Fast forward if necessary.
  869. if (!empty($last['module']) && $last['module'] != $module) {
  870. continue;
  871. }
  872. foreach ($module_topics as $topic => $info) {
  873. // Fast forward if necessary.
  874. if (!empty($last['topic']) && $last['topic'] != $topic) {
  875. continue;
  876. }
  877. // If we've been looking to catch up, and we have, reset so we
  878. // stop fast forwarding.
  879. if (!empty($last['module'])) {
  880. unset($last['topic']);
  881. unset($last['module']);
  882. }
  883. $file = advanced_help_get_topic_filename($module, $topic);
  884. if ($file && (empty($info['sid']) || filemtime($file) > $last['time'])) {
  885. if (empty($info['sid'])) {
  886. $info['sid'] = db_insert('advanced_help_index')
  887. ->fields(array(
  888. 'module' => $module,
  889. 'topic' => $topic,
  890. 'language' => $language->language,
  891. ))
  892. ->execute();
  893. }
  894. search_index($info['sid'], 'help', '<h1>' . $info['title'] . '</h1>' . file_get_contents($file));
  895. $count++;
  896. if ($count >= $limit) {
  897. $last['topic'] = $topic;
  898. $last['module'] = $module;
  899. // Don't change time if we stop.
  900. variable_set('advanced_help_last_cron', $last);
  901. return;
  902. }
  903. }
  904. }
  905. }
  906. variable_set('advanced_help_last_cron', array('time' => time()));
  907. }
  908. /**
  909. * Fill in a bunch of page variables for our specialized popup page.
  910. */
  911. function template_preprocess_advanced_help_popup(&$variables) {
  912. // Add favicon.
  913. if (theme_get_setting('toggle_favicon')) {
  914. drupal_add_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
  915. }
  916. global $theme;
  917. // Construct page title.
  918. if (drupal_get_title()) {
  919. $head_title = array(
  920. strip_tags(drupal_get_title()),
  921. variable_get('site_name', 'Drupal'),
  922. );
  923. }
  924. else {
  925. $head_title = array(variable_get('site_name', 'Drupal'));
  926. if (variable_get('site_slogan', '')) {
  927. $head_title[] = variable_get('site_slogan', '');
  928. }
  929. }
  930. drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-popup.css');
  931. drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
  932. $variables['head_title'] = implode(' | ', $head_title);
  933. $variables['base_path'] = base_path();
  934. $variables['front_page'] = url();
  935. $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));
  936. $variables['feed_icons'] = drupal_get_feeds();
  937. $variables['head'] = drupal_get_html_head();
  938. $variables['language'] = $GLOBALS['language'];
  939. $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
  940. $variables['logo'] = theme_get_setting('logo');
  941. $variables['messages'] = theme('status_messages');
  942. $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
  943. $variables['css'] = drupal_add_css();
  944. $css = drupal_add_css();
  945. // Remove theme css.
  946. foreach ($css as $media => $types) {
  947. if (isset($css[$media]['theme'])) {
  948. $css[$media]['theme'] = array();
  949. }
  950. }
  951. $variables['styles'] = drupal_get_css($css);
  952. $variables['scripts'] = drupal_get_js();
  953. $variables['title'] = drupal_get_title();
  954. // This function can be called either with a render array or
  955. // an already rendered string.
  956. if (is_array($variables['content'])) {
  957. $variables['content'] = drupal_render($variables['content']);
  958. }
  959. // Closure should be filled last.
  960. $variables['closure'] = theme('closure');
  961. }
  962. /**
  963. * Format a link but preserve popup identity.
  964. */
  965. function advanced_help_l($text, $dest, $options = array()) {
  966. $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
  967. if ($popup) {
  968. if (empty($options['query'])) {
  969. $options['query'] = array();
  970. }
  971. if (is_array($options['query'])) {
  972. $options['query'] += array('popup' => TRUE);
  973. }
  974. else {
  975. $options['query'] += '&popup=TRUE';
  976. }
  977. }
  978. return l($text, $dest, $options);
  979. }
  980. /**
  981. * Format a URL but preserve popup identity.
  982. */
  983. function advanced_help_url($dest, $options = array()) {
  984. $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
  985. if ($popup) {
  986. if (empty($options['query'])) {
  987. $options['query'] = array();
  988. }
  989. $options['query'] += array('popup' => TRUE);
  990. }
  991. return url($dest, $options);
  992. }