Projet

Général

Profil

Paste
Télécharger (37 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / advanced_help / advanced_help.module @ ba3b3627

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', array('module' => $module, 'topic' => $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
/**
36
 * Implements hook_menu().
37
 *
38
 * Strings in hook_help() should not be run through t().
39
 */
40
function advanced_help_menu() {
41
  $help_exists = module_exists('help') ? TRUE : FALSE;
42
  if ($help_exists) {
43
    // Add tabs to core Help page to access Advanced Help.
44
    $items['admin/help/tab1'] = array(
45
      'title' => 'Help',
46
      'type' => MENU_DEFAULT_LOCAL_TASK,
47
      'weight' => 0,
48
    );
49

    
50
    $items['admin/help/ah'] = array(
51
      'title' => 'Advanced Help',
52
      'page callback' => 'advanced_help_index_page',
53
      'access arguments' => array('view advanced help index'),
54
      'type' => MENU_LOCAL_TASK,
55
      'weight' => 2,
56
    );
57
  }
58
  else {
59
    // Make Advanced Help the normal help.
60
    $items['admin/help/ah'] = array(
61
      'title' => 'Help',
62
      'page callback' => 'advanced_help_index_page',
63
      'access arguments' => array('view advanced help index'),
64
      'type' => MENU_NORMAL_ITEM,
65
      'weight' => 9,
66
    );
67
  }
68
  $items['help/ah/search/%'] = array(
69
    'title' => 'Search help',
70
    'page callback' => 'advanced_help_search_view',
71
    'page arguments' => array('advanced_help'),
72
    'access arguments' => array('view advanced help index'),
73
  );
74

    
75
  // View help topic.
76
  $items['help/%/%'] = array(
77
    'page callback' => 'advanced_help_topic_page',
78
    'page arguments' => array(1, 2),
79
    'access arguments' => array('view advanced help topic'),
80
    'type' => MENU_CALLBACK,
81
  );
82

    
83
  return $items;
84
}
85

    
86
/**
87
 * Implements hook_menu_alter().
88
 */
89
function advanced_help_menu_alter(&$callbacks) {
90
  // We need to fix the menu item provided by search module to restrict access.
91
  $callbacks['search/advanced_help/%menu_tail']['access callback'] = 'user_access';
92
  $callbacks['search/advanced_help/%menu_tail']['access arguments'] = array('view advanced help index');
93
}
94

    
95
/**
96
 * Implements hook_theme().
97
 */
98
function advanced_help_theme() {
99
  $hooks['advanced_help_topic'] = array(
100
    'variables' => array(
101
      'module' => NULL,
102
      'topic'  => NULL,
103
      'type'   => 'icon',
104
    ),
105
  );
106

    
107
  $hooks['advanced_help_popup'] = array(
108
    'render element' => 'content',
109
    'template' => 'advanced-help-popup',
110
  );
111

    
112
  return $hooks;
113
}
114

    
115
/**
116
 * Helper function to sort topics.
117
 */
118
function advanced_help_uasort($id_a, $id_b) {
119
  $topics = advanced_help_get_topics();
120
  list($module_a, $topic_a) = $id_a;
121
  $a = $topics[$module_a][$topic_a];
122
  list($module_b, $topic_b) = $id_b;
123
  $b = $topics[$module_b][$topic_b];
124

    
125
  $a_weight = isset($a['weight']) ? $a['weight'] : 0;
126
  $b_weight = isset($b['weight']) ? $b['weight'] : 0;
127
  if ($a_weight != $b_weight) {
128
    return ($a_weight < $b_weight) ? -1 : 1;
129
  }
130

    
131
  if ($a['title'] != $b['title']) {
132
    return ($a['title'] < $b['title']) ? -1 : 1;
133
  }
134
  return 0;
135
}
136

    
137
/**
138
 * Helper function for grabbing search keys. Function is missing in D7.
139
 *
140
 * http://api.drupal.org/api/function/search_get_keys/6
141
 */
142
function advanced_help_search_get_keys() {
143
  static $return;
144
  if (!isset($return)) {
145
    // Extract keys as remainder of path
146
    // Note: support old GET format of searches for existing links.
147
    $path = explode('/', $_GET['q'], 4);
148
    $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
149
    $return = count($path) == 4 ? $path[3] : $keys;
150
  }
151
  return $return;
152
}
153

    
154
/**
155
 * Page callback for advanced help search.
156
 */
157
function advanced_help_search_view() {
158
  if (!module_exists('search')) {
159
    return drupal_not_found();
160
  }
161

    
162
  $breadcrumb[] = advanced_help_l(t('Advanced help'), 'admin/help/ah');
163

    
164
  if (!isset($_POST['form_id'])) {
165
    $keys = advanced_help_search_get_keys();
166
    // Only perform search if there is non-whitespace search term:
167
    $results = '';
168
    if (trim($keys)) {
169
      $search_results = search_data($keys, 'advanced_help');
170
      $search_results = drupal_render($search_results);
171
      // Collect the search results:
172
      $results = array(
173
        '#type' => 'markup',
174
        '#markup' => $search_results,
175
      );
176
    }
177

    
178
    // Construct the search form.
179
    $output['advanced_help_search_form'] = drupal_get_form('advanced_help_search_form', $keys);
180
    $output['results'] = $results;
181

    
182
  }
183
  else {
184
    $output = drupal_get_form('advanced_help_search_form', empty($keys) ? '' : $keys);
185
  }
186

    
187
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
188
  if ($popup) {
189
    // Prevent devel module from spewing.
190
    $GLOBALS['devel_shutdown'] = FALSE;
191
    // Suppress admin_menu.
192
    module_invoke('admin_menu', 'suppress');
193
    drupal_set_breadcrumb(array_reverse($breadcrumb));
194
    print theme('advanced_help_popup', array('content' => $output));
195
    return;
196
  }
197

    
198
  $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
199
  drupal_set_breadcrumb($breadcrumb);
200
  return $output;
201
}
202

    
203
/**
204
 * Page callback to view the advanced help topic index.
205
 *
206
 * @param string $module
207
 *   Name of the module.
208
 *
209
 * @return array
210
 *   Returns module index.
211
 */
212
function advanced_help_index_page($module = '') {
213
  $topics = advanced_help_get_topics();
214
  $settings = advanced_help_get_settings();
215

    
216
  $output = array();
217
  // Print a search widget.
218
  $output['advanced_help_search'] = module_exists('search') ? drupal_get_form('advanced_help_search_form') : array('#markup' => t('Enable the search module to search help.'));
219

    
220
  $breadcrumb = array();
221
  if ($module) {
222
    if (empty($topics[$module])) {
223
      return drupal_not_found();
224
    }
225

    
226
    advanced_help_get_topic_hierarchy($topics);
227
    $items = advanced_help_get_tree($topics, $topics[$module]['']['children']);
228

    
229
    $breadcrumb[] = advanced_help_l(t('Advanced help'), 'admin/help/ah');
230

    
231
    drupal_set_title(t('@module help index', array('@module' => advanced_help_get_module_name($module))));
232
    $output['items-module'] = array(
233
      '#theme' => 'item_list',
234
      '#items' => $items,
235
    );
236
  }
237
  else {
238
    // Print a module index.
239
    $modules = array();
240
    $result = db_query('SELECT * FROM {system}');
241
    foreach ($result as $info) {
242
      $module_info = unserialize($info->info);
243
      $modules[$info->name] = isset($module_info['name']) ? $module_info['name'] : $info->name;
244
    }
245

    
246
    asort($modules);
247

    
248
    $items = array();
249
    foreach ($modules as $module => $module_name) {
250
      if (!empty($topics[$module]) && empty($settings[$module]['hide'])) {
251
        if (isset($settings[$module]['index name'])) {
252
          $name = $settings[$module]['index name'];
253
        }
254
        elseif (isset($settings[$module]['name'])) {
255
          $name = $settings[$module]['name'];
256
        }
257
        else {
258
          $name = t($module_name);
259
        }
260
        $items[] = advanced_help_l($name, "admin/help/ah/$module");
261
      }
262
    }
263

    
264
    drupal_set_title(t('Advanced help'));
265
    $output['items-nomodule'] = array(
266
      '#theme' => 'item_list',
267
      '#items' => $items,
268
    );
269
  }
270

    
271
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
272
  if ($popup) {
273
    // Prevent devel module from spewing.
274
    $GLOBALS['devel_shutdown'] = FALSE;
275
    // Suppress admin_menu.
276
    module_invoke('admin_menu', 'suppress');
277
    drupal_set_breadcrumb(array_reverse($breadcrumb));
278
    print theme('advanced_help_popup', array('content' => $output));
279
    return;
280
  }
281

    
282
  $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
283
  drupal_set_breadcrumb($breadcrumb);
284
  return $output;
285
}
286

    
287
/**
288
 * Build a tree of advanced help topics.
289
 *
290
 * @param array $topics
291
 *   Topics.
292
 * @param array $topic_ids
293
 *   Topic Ids.
294
 * @param int $max_depth
295
 *   Maximum depth for subtopics.
296
 * @param int $depth
297
 *   Default depth for subtopics.
298
 *
299
 * @return array
300
 *   Returns list of topics/subtopics.
301
 */
302
function advanced_help_get_tree($topics, $topic_ids, $max_depth = -1, $depth = 0) {
303
  uasort($topic_ids, 'advanced_help_uasort');
304
  $items = array();
305
  foreach ($topic_ids as $info) {
306
    list($module, $topic) = $info;
307
    $item = advanced_help_l($topics[$module][$topic]['title'], "help/$module/$topic");
308
    if (!empty($topics[$module][$topic]['children']) && ($max_depth == -1 || $depth < $max_depth)) {
309
      $item .= theme('item_list', array(
310
        'items' => advanced_help_get_tree($topics, $topics[$module][$topic]['children'], $max_depth, $depth + 1),
311
      ));
312
    }
313

    
314
    $items[] = $item;
315
  }
316

    
317
  return $items;
318
}
319

    
320
/**
321
 * Build a hierarchy for a single module's topics.
322
 */
323
function advanced_help_get_topic_hierarchy(&$topics) {
324
  foreach ($topics as $module => $module_topics) {
325
    foreach ($module_topics as $topic => $info) {
326
      $parent_module = $module;
327
      // We have a blank topic that we don't want parented to itself.
328
      if (!$topic) {
329
        continue;
330
      }
331

    
332
      if (empty($info['parent'])) {
333
        $parent = '';
334
      }
335
      elseif (strpos($info['parent'], '%')) {
336
        list($parent_module, $parent) = explode('%', $info['parent']);
337
        if (empty($topics[$parent_module][$parent])) {
338
          // If it doesn't exist, top level.
339
          $parent = '';
340
        }
341
      }
342
      else {
343
        $parent = $info['parent'];
344
        if (empty($module_topics[$parent])) {
345
          // If it doesn't exist, top level.
346
          $parent = '';
347
        }
348
      }
349

    
350
      if (!isset($topics[$parent_module][$parent]['children'])) {
351
        $topics[$parent_module][$parent]['children'] = array();
352
      }
353
      $topics[$parent_module][$parent]['children'][] = array($module, $topic);
354
      $topics[$module][$topic]['_parent'] = array($parent_module, $parent);
355
    }
356
  }
357
}
358

    
359
/**
360
 * Implements hook_form_system_modules_alter().
361
 *
362
 * Add advanced help links to the modules page.
363
 */
364
function advanced_help_form_system_modules_alter(&$form, &$form_state) {
365
  if (!isset($form['modules'])) {
366
    return;
367
  }
368
  $advanced_help_modules = drupal_map_assoc(array_keys(advanced_help_get_topics()));
369
  foreach (element_children($form['modules']) as $group) {
370
    foreach (element_children($form['modules'][$group]) as $module) {
371
      if (isset($advanced_help_modules[$module])) {
372
        $form['modules'][$group][$module]['links']['help'] = array(
373
          '#type' => 'link',
374
          '#title' => t('Help'),
375
          '#href' => "admin/help/ah/$module",
376
          '#options' => array(
377
            'attributes' => array(
378
              'class' => array('module-link', 'module-link-help'),
379
              'title' => t('Help'),
380
            ),
381
          ),
382
        );
383
      }
384
    }
385
  }
386
}
387

    
388
/**
389
 * Form builder callback to build the search form.
390
 *
391
 * Load search/search.pages so that its template preprocess functions are
392
 * visible and can be invoked.
393
 */
394
function advanced_help_search_form($form, &$form_state, $keys = '') {
395
  module_load_include('inc', 'search', 'search.pages');
396
  $form = search_form($form, $form_state, 'admin/help/ah', $keys, 'advanced_help', t('Search help'));
397

    
398
  $form['basic']['inline']['submit']['#validate'] = array('search_form_validate');
399
  $form['basic']['inline']['submit']['#submit'] = array('advanced_help_search_form_submit');
400

    
401
  return $form;
402
}
403

    
404
/**
405
 * Process a search form submission.
406
 */
407
function advanced_help_search_form_submit($form, &$form_state) {
408
  $keys = empty($form_state['values']['processed_keys']) ? $form_state['values']['keys'] : $form_state['values']['processed_keys'];
409
  if ($keys == '') {
410
    form_set_error('keys', t('Please enter some keywords.'));
411
    return;
412
  }
413

    
414
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
415

    
416
  if ($popup) {
417
    $form_state['redirect'] = array('help/ah/search/' . $keys, array('query' => array('popup' => 'true')));
418
  }
419
  else {
420
    $form_state['redirect'] = 'help/ah/search/' . $keys;
421
  }
422
}
423

    
424
/**
425
 * Small helper function to get a module's proper name.
426
 *
427
 * @param string $module
428
 *   Name of the module.
429
 *
430
 * @return string
431
 *   Returns module's descriptive name.
432
 */
433
function advanced_help_get_module_name($module) {
434
  $settings = advanced_help_get_settings();
435
  if (isset($settings[$module]['name'])) {
436
    $name = $settings[$module]['name'];
437
  }
438
  else {
439
    $info = db_query("SELECT s.info FROM {system} s WHERE s.name = :name",
440
      array(':name' => $module))
441
      ->fetchField();
442
    $info = unserialize($info);
443
    $name = t($info['name']);
444
  }
445
  return $name;
446
}
447

    
448
/**
449
 * Page callback to view a help topic.
450
 */
451
function advanced_help_topic_page($module, $topic) {
452
  $info = advanced_help_get_topic($module, $topic);
453
  if (!$info) {
454
    return drupal_not_found();
455
  }
456

    
457
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
458

    
459
  drupal_set_title($info['title']);
460

    
461
  // Set up breadcrumb.
462
  $breadcrumb = array();
463

    
464
  $parent = $info;
465
  $pmodule = $module;
466

    
467
  // Loop checker.
468
  $checked = array();
469
  while (!empty($parent['parent'])) {
470
    if (strpos($parent['parent'], '%')) {
471
      list($pmodule, $ptopic) = explode('%', $parent['parent']);
472
    }
473
    else {
474
      $ptopic = $parent['parent'];
475
    }
476

    
477
    if (!empty($checked[$pmodule][$ptopic])) {
478
      break;
479
    }
480
    $checked[$pmodule][$ptopic] = TRUE;
481

    
482
    $parent = advanced_help_get_topic($pmodule, $ptopic);
483
    if (!$parent) {
484
      break;
485
    }
486

    
487
    $breadcrumb[] = advanced_help_l($parent['title'], "help/$pmodule/$ptopic");
488
  }
489

    
490
  $breadcrumb[] = advanced_help_l(advanced_help_get_module_name($pmodule), "admin/help/ah/$pmodule");
491
  $breadcrumb[] = advanced_help_l(t('Help'), "admin/help/ah");
492

    
493
  $output = advanced_help_view_topic($module, $topic, $popup);
494
  if (empty($output)) {
495
    $output = t('Missing help topic.');
496
  }
497

    
498
  if ($popup) {
499
    // Prevent devel module from spewing.
500
    $GLOBALS['devel_shutdown'] = FALSE;
501
    // Suppress admin_menu.
502
    module_invoke('admin_menu', 'suppress');
503
    drupal_set_breadcrumb(array_reverse($breadcrumb));
504
    print theme('advanced_help_popup', array('content' => $output));
505
    return;
506
  }
507

    
508
  drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
509
  $breadcrumb[] = l(t('Home'), '');
510
  drupal_set_breadcrumb(array_reverse($breadcrumb));
511
  return $output;
512
}
513

    
514
/**
515
 * Implements hook_permission().
516
 */
517
function advanced_help_permission() {
518
  return array(
519
    'view advanced help topic' => array('title' => t('View help topics')),
520
    'view advanced help popup' => array('title' => t('View help popups')),
521
    'view advanced help index' => array('title' => t('View help index')),
522
  );
523
}
524

    
525
/**
526
 * Display a help icon with a link to view the topic in a popup.
527
 *
528
 * @param array $variables
529
 *   An associative array containing:
530
 *   - module: The module that owns this help topic.
531
 *   - topic: The identifier for the topic
532
 *   - type
533
 *     - 'icon' to display the question mark icon
534
 *     - 'title' to display the topic's title
535
 *     - any other text to display the text. Be sure to t() it!
536
 */
537
function theme_advanced_help_topic($variables) {
538
  $module = $variables['module'];
539
  $topic  = $variables['topic'];
540
  $type   = $variables['type'];
541

    
542
  $info = advanced_help_get_topic($module, $topic);
543
  if (!$info) {
544
    return;
545
  }
546

    
547
  switch ($type) {
548
    case 'icon':
549
      $text = '<span>' . t('Help') . '</span>';
550
      $class = 'advanced-help-link';
551
      break;
552

    
553
    case 'title':
554
      $text = $info['title'];
555
      $class = 'advanced-help-title';
556
      break;
557

    
558
    default:
559
      $class = 'advanced-help-title';
560
      $text = $type;
561
      break;
562
  }
563

    
564
  if (user_access('view advanced help popup')) {
565
    drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-icon.css');
566
    return l($text, "help/$module/$topic", array(
567
      'attributes' => array(
568
        'class' => $class,
569
        'onclick' => "var w=window.open(this.href, 'advanced_help_window', 'width=" . $info['popup width'] . ",height=" . $info['popup height'] . ",scrollbars,resizable'); w.focus(); return false;",
570
        'title' => $info['title'],
571
      ),
572
      'query' => array('popup' => TRUE),
573
      'html' => TRUE)
574
    );
575
  }
576
  elseif (user_access('view advanced help topic')) {
577
    return l($text, "help/$module/$topic", array(
578
      'attributes' => array(
579
        'class' => $class,
580
        'title' => $info['title'],
581
      ),
582
      'html' => TRUE)
583
    );
584
  }
585
}
586

    
587
/**
588
 * Load and render a help topic.
589
 */
590
function advanced_help_get_topic_filename($module, $topic) {
591
  $info = advanced_help_get_topic_file_info($module, $topic);
592
  if ($info) {
593
    return "./$info[path]/$info[file]";
594
  }
595
}
596

    
597
/**
598
 * Get the module type (theme or module).
599
 */
600
function _advanced_help_get_module_type($module) {
601
  $theme_list = array_keys(list_themes());
602
  $is_theme = in_array($module, $theme_list);
603
  return $is_theme ? 'theme' : 'module';
604
}
605

    
606
/**
607
 * Load and render a help topic.
608
 */
609
function advanced_help_get_topic_file_info($module, $topic) {
610
  global $language;
611

    
612
  $info = advanced_help_get_topic($module, $topic);
613
  if (empty($info)) {
614
    return;
615
  }
616

    
617
  // Search paths:
618
  $module_type = _advanced_help_get_module_type($module);
619
  $paths = array(
620
    // Allow theme override.
621
    path_to_theme() . '/help',
622
    // Translations.
623
    drupal_get_path($module_type, $module) . "/translations/help/$language->language",
624
    // In same directory as .inc file.
625
    $info['path'],
626
  );
627

    
628
  foreach ($paths as $path) {
629
    if (file_exists("./$path/$info[file]")) {
630
      return array('path' => $path, 'file' => $info['file']);
631
    }
632
  }
633
}
634

    
635
/**
636
 * Load and render a help topic.
637
 *
638
 * @param string $module
639
 *   Name of the module.
640
 * @param string $topic
641
 *   Name of the topic.
642
 * @param bool $popup
643
 *   Whether to show in popup or not.
644
 *
645
 * @return string
646
 *   Returns formatted topic.
647
 */
648
function advanced_help_view_topic($module, $topic, $popup = FALSE) {
649
  $file_info = advanced_help_get_topic_file_info($module, $topic);
650
  if ($file_info) {
651
    $info = advanced_help_get_topic($module, $topic);
652
    $file = "./$file_info[path]/$file_info[file]";
653

    
654
    // Fix invalid byte sequences (Issue #1330056).
655
    $output = file_get_contents($file);
656
    mb_substitute_character(0xfffd);
657
    $output = mb_convert_encoding($output, 'UTF-8', 'UTF-8');
658

    
659
    if (isset($info['readme file']) && $info['readme file']) {
660
      $ext = pathinfo($file, PATHINFO_EXTENSION);
661
      if ('md' == $ext && module_exists('markdown')) {
662
        $filters = module_invoke('markdown', 'filter_info');
663
        $md_info = $filters['filter_markdown'];
664
        if (function_exists($md_info['process callback'])) {
665
          $function = $md_info['process callback'];
666
          $output = '<div class="advanced-help-topic">' . filter_xss_admin($function($output, NULL)) . '</div>';
667
        }
668
        else {
669
          $output = '<div class="advanced-help-topic"><pre class="readme">' . check_plain($output) . '</pre></div>';
670
        }
671
      }
672
      else {
673
        $readme = '';
674
        if ('md' == $ext) {
675
          $readme .=
676
            '<p>' .
677
            t('If you install the !module module, the text below will be filtered by the module, producing rich text.',
678
              array(
679
                '!module' => l(t('Markdown filter'),
680
                  'https://www.drupal.org/project/markdown',
681
                  array('attributes' => array('title' => t('Link to project.'))))
682
              )) . '</p>';
683
        }
684
        $readme .=
685
          '<div class="advanced-help-topic"><pre class="readme">' . check_plain($output) . '</pre></div>';
686
        $output = $readme;
687
      }
688
      return $output;
689
    }
690

    
691
    // Make some exchanges. The strtr is because url() translates $ into %24
692
    // but we need to change it back for the regex replacement.
693
    //
694
    // Change 'topic:' to the URL for another help topic.
695
    if ($popup) {
696
      $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
697
      $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
698
      $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')), $output);
699
    }
700
    else {
701
      $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
702
      $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
703
      $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1'), array('%24' => '$')), $output);
704
    }
705

    
706
    global $base_path;
707

    
708
    // Change 'path:' to the URL to the base help directory.
709
    $output = preg_replace('/href="path:([^"]+)"/', 'href="' . $base_path . $info['path'] . '/$1"', $output);
710
    $output = preg_replace('/src="path:([^"]+)"/', 'src="' . $base_path . $info['path'] . '/$1"', $output);
711
    $output = str_replace('&path&', $base_path . $info['path'] . '/', $output);
712

    
713
    // Change 'trans_path:' to the URL to the actual help directory.
714
    $output = preg_replace('/href="trans_path:([^"]+)"/', 'href="' . $base_path . $file_info['path'] . '/$1"', $output);
715
    $output = preg_replace('/src="trans_path:([^"]+)"/', 'src="' . $base_path . $file_info['path'] . '/$1"', $output);
716
    $output = str_replace('&trans_path&', $base_path . $file_info['path'] . '/', $output);
717

    
718
    // Change 'base_url:' to the URL to the site.
719
    $output = preg_replace('/href="base_url:([^"]+)"/', 'href="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
720
    $output = preg_replace('/src="base_url:([^"]+)"/', 'src="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
721
    $output = preg_replace('/&base_url&([^"]+)"/', strtr(url('$1'), array('%24' => '$')) . '"', $output);
722

    
723
    // Run the line break filter if requested.
724
    if (!empty($info['line break'])) {
725
      // Remove the header since it adds an extra <br /> to the filter.
726
      $output = preg_replace('/^<!--[^\n]*-->\n/', '', $output);
727

    
728
      $output = _filter_autop($output);
729
    }
730

    
731
    if (!empty($info['navigation'])) {
732
      $topics = advanced_help_get_topics();
733
      advanced_help_get_topic_hierarchy($topics);
734
      if (!empty($topics[$module][$topic]['children'])) {
735
        $items = advanced_help_get_tree($topics, $topics[$module][$topic]['children']);
736
        $output .= theme('item_list', array('items' => $items));
737
      }
738

    
739
      list($parent_module, $parent_topic) = $topics[$module][$topic]['_parent'];
740
      if ($parent_topic) {
741
        $parent = $topics[$module][$topic]['_parent'];
742
        $up = "help/$parent[0]/$parent[1]";
743
      }
744
      else {
745
        $up = "admin/help/ah/$module";
746
      }
747

    
748
      $siblings = $topics[$parent_module][$parent_topic]['children'];
749
      uasort($siblings, 'advanced_help_uasort');
750
      $prev = $next = NULL;
751
      $found = FALSE;
752
      foreach ($siblings as $sibling) {
753
        list($sibling_module, $sibling_topic) = $sibling;
754
        if ($found) {
755
          $next = $sibling;
756
          break;
757
        }
758
        if ($sibling_module == $module && $sibling_topic == $topic) {
759
          $found = TRUE;
760
          continue;
761
        }
762
        $prev = $sibling;
763
      }
764

    
765
      if ($prev || $up || $next) {
766
        $navigation = '<div class="help-navigation clear-block">';
767

    
768
        if ($prev) {
769
          $navigation .= advanced_help_l('«« ' . $topics[$prev[0]][$prev[1]]['title'], "help/$prev[0]/$prev[1]", array('attributes' => array('class' => 'help-left')));
770
        }
771
        if ($up) {
772
          $navigation .= advanced_help_l(t('Up'), $up, array('attributes' => array('class' => $prev ? 'help-up' : 'help-up-noleft')));
773
        }
774
        if ($next) {
775
          $navigation .= advanced_help_l($topics[$next[0]][$next[1]]['title'] . ' »»', "help/$next[0]/$next[1]", array('attributes' => array('class' => 'help-right')));
776
        }
777

    
778
        $navigation .= '</div>';
779

    
780
        $output .= $navigation;
781
      }
782
    }
783

    
784
    if (!empty($info['css'])) {
785
      drupal_add_css($info['path'] . '/' . $info['css']);
786
    }
787

    
788
    $output = '<div class="advanced-help-topic">' . $output . '</div>';
789
    drupal_alter('advanced_help_topic', $output, $popup);
790
    
791
    return $output;
792
  }
793
}
794

    
795
/**
796
 * Get the information for a single help topic.
797
 */
798
function advanced_help_get_topic($module, $topic) {
799
  $topics = advanced_help_get_topics();
800
  if (!empty($topics[$module][$topic])) {
801
    return $topics[$module][$topic];
802
  }
803
}
804

    
805
/**
806
 * Search the system for all available help topics.
807
 */
808
function advanced_help_get_topics() {
809
  $ini = _advanced_help_parse_ini();
810
  return $ini['topics'];
811
}
812

    
813
/**
814
 * Returns advanced help settings.
815
 */
816
function advanced_help_get_settings() {
817
  $ini = _advanced_help_parse_ini();
818
  return $ini['settings'];
819
}
820

    
821
/**
822
 * Function to parse ini / txt files.
823
 */
824
function _advanced_help_parse_ini() {
825
  global $language;
826
  static $ini = NULL;
827

    
828
  if (!isset($ini)) {
829
    $cache = cache_get('advanced_help_ini:' . $language->language);
830
    if ($cache) {
831
      $ini = $cache->data;
832
    }
833
  }
834

    
835
  if (!isset($ini)) {
836
    $ini = array('topics' => array(), 'settings' => array());
837

    
838
    $help_path = drupal_get_path('module', 'advanced_help') . '/modules';
839
    foreach (array_merge(module_list(), list_themes()) as $plugin) {
840
      $module = is_string($plugin) ? $plugin : $plugin->name;
841
      $module_path = drupal_get_path(is_string($plugin) ? 'module' : 'theme', $module);
842
      $info = array();
843
      if (file_exists("$module_path/help/$module.help.ini")) {
844
        $path = "$module_path/help";
845
        $info = parse_ini_file("./$module_path/help/$module.help.ini", TRUE);
846
      }
847
      elseif (file_exists("$help_path/$module/$module.help.ini")) {
848
        $path = "$help_path/$module";
849
        $info = parse_ini_file("./$help_path/$module/$module.help.ini", TRUE);
850
      }
851
      elseif (!file_exists("$module_path/help")) {
852
        // Look for one or more README files.
853
        $files = file_scan_directory("./$module_path",
854
          '/^(readme).*\.(txt|md)$/i', array('recurse' => FALSE));
855
        $path = "./$module_path";
856
        foreach ($files as $name => $fileinfo) {
857
          $info[$fileinfo->filename] = array(
858
            'line break' => TRUE,
859
            'readme file' => TRUE,
860
            'file' => $fileinfo->filename,
861
            'title' => $fileinfo->name,
862
          );
863
        }
864
      }
865

    
866
      if (!empty($info)) {
867
        // Get translated titles:
868
        $translation = array();
869
        if (file_exists("$module_path/translations/help/$language->language/$module.help.ini")) {
870
          $translation = parse_ini_file("$module_path/translations/help/$language->language/$module.help.ini", TRUE);
871
        }
872

    
873
        $ini['settings'][$module] = array();
874
        if (!empty($info['advanced help settings'])) {
875
          $ini['settings'][$module] = $info['advanced help settings'];
876
          unset($info['advanced help settings']);
877

    
878
          // Check translated strings for translatable global settings.
879
          if (isset($translation['advanced help settings']['name'])) {
880
            $ini['settings']['name'] = $translation['advanced help settings']['name'];
881
          }
882
          if (isset($translation['advanced help settings']['index name'])) {
883
            $ini['settings']['index name'] = $translation['advanced help settings']['index name'];
884
          }
885

    
886
        }
887

    
888
        foreach ($info as $name => $topic) {
889
          // Each topic should have a name, a title, a file and path.
890
          $file = !empty($topic['file']) ? $topic['file'] : $name;
891
          $ini['topics'][$module][$name] = array(
892
            'name' => $name,
893
            'module' => $module,
894
            'ini' => $topic,
895
            'title' => !empty($translation[$name]['title']) ? $translation[$name]['title'] : $topic['title'],
896
            'weight' => isset($topic['weight']) ? $topic['weight'] : 0,
897
            'parent' => isset($topic['parent']) ? $topic['parent'] : 0,
898
            'popup width' => isset($topic['popup width']) ? $topic['popup width'] : 500,
899
            'popup height' => isset($topic['popup height']) ? $topic['popup height'] : 500,
900
            // Require extension.
901
            'file' => isset($topic['readme file']) ? $file : $file . '.html',
902
            // Not in .ini file.
903
            'path' => $path,
904
            'line break' => isset($topic['line break']) ? $topic['line break'] : (isset($ini['settings'][$module]['line break']) ? $ini['settings'][$module]['line break'] : FALSE),
905
            'navigation' => isset($topic['navigation']) ? $topic['navigation'] : (isset($ini['settings'][$module]['navigation']) ? $ini['settings'][$module]['navigation'] : TRUE),
906
            'css' => isset($topic['css']) ? $topic['css'] : (isset($ini['settings'][$module]['css']) ? $ini['settings'][$module]['css'] : NULL),
907
            'readme file' => isset($topic['readme file']) ? $topic['readme file'] : FALSE,
908
          );
909
        }
910
      }
911
    }
912
    drupal_alter('advanced_help_topic_info', $ini);
913

    
914
    cache_set('advanced_help_ini:' . $language->language, $ini);
915
  }
916
  return $ini;
917
}
918

    
919
/**
920
 * Implements hook_search_info().
921
 *
922
 * Returns title for the tab on search page & path component after 'search/'.
923
 */
924
function advanced_help_search_info() {
925
  return array(
926
    'title' => t('Help'),
927
    'path' => 'advanced_help',
928
  );
929
}
930

    
931
/**
932
 * Implements hook_search_execute().
933
 */
934
function advanced_help_search_execute($keys = NULL) {
935
  $topics = advanced_help_get_topics();
936

    
937
  $query = db_select('search_index', 'i', array('target' => 'slave'))
938
    ->extend('SearchQuery')
939
    ->extend('PagerDefault');
940
  $query->join('advanced_help_index', 'ahi', 'i.sid = ahi.sid');
941
  $query->searchExpression($keys, 'help');
942

    
943
  // Only continue if the first pass query matches.
944
  if (!$query->executeFirstPass()) {
945
    return array();
946
  }
947

    
948
  $results = array();
949

    
950
  $find = $query->execute();
951
  foreach ($find as $item) {
952
    $sids[] = $item->sid;
953
  }
954

    
955
  $query = db_select('advanced_help_index', 'ahi');
956
  $result = $query
957
    ->fields('ahi')
958
    ->condition('sid', $sids, 'IN')
959
    ->execute();
960

    
961
  foreach ($result as $sid) {
962
    // Guard against removed help topics that are still indexed.
963
    if (empty($topics[$sid->module][$sid->topic])) {
964
      continue;
965
    }
966
    $info = $topics[$sid->module][$sid->topic];
967
    $text = advanced_help_view_topic($sid->module, $sid->topic);
968
    $results[] = array(
969
      'link' => advanced_help_url("help/$sid->module/$sid->topic"),
970
      'title' => $info['title'],
971
      'snippet' => search_excerpt($keys, $text),
972
    );
973
  }
974
  return $results;
975
}
976

    
977
/**
978
 * Implements hook_search_reset().
979
 */
980
function advanced_help_search_reset() {
981
  variable_del('advanced_help_last_cron');
982
}
983

    
984
/**
985
 * Implements hook_search_status().
986
 */
987
function advanced_help_search_status() {
988
  $topics = advanced_help_get_topics();
989
  $total = 0;
990
  foreach ($topics as $module => $module_topics) {
991
    foreach ($module_topics as $topic => $info) {
992
      $file = advanced_help_get_topic_filename($module, $topic);
993
      if ($file) {
994
        $total++;
995
      }
996
    }
997
  }
998

    
999
  $last_cron = variable_get('advanced_help_last_cron', array('time' => 0));
1000
  $indexed = 0;
1001
  if ($last_cron['time'] != 0) {
1002
    $indexed = db_query("SELECT COUNT(*) FROM {search_dataset} sd WHERE sd.type = 'help' AND sd.sid IS NOT NULL AND sd.reindex = 0")->fetchField();
1003
  }
1004
  return array('remaining' => $total - $indexed, 'total' => $total);
1005
}
1006

    
1007
/**
1008
 * Gets search id for each topic.
1009
 *
1010
 * Get or create an sid (search id) that correlates to each topic for
1011
 * the search system.
1012
 */
1013
function advanced_help_get_sids(&$topics) {
1014
  global $language;
1015
  $result = db_query("SELECT * FROM {advanced_help_index} WHERE language = :language",
1016
    array(':language' => $language->language));
1017
  foreach ($result as $sid) {
1018
    if (empty($topics[$sid->module][$sid->topic])) {
1019
      db_query("DELETE FROM {advanced_help_index} WHERE sid = :sid",
1020
        array(':sid' => $sid->sid));
1021
    }
1022
    else {
1023
      $topics[$sid->module][$sid->topic]['sid'] = $sid->sid;
1024
    }
1025
  }
1026
}
1027

    
1028
/**
1029
 * Implements hook_update_index().
1030
 */
1031
function advanced_help_update_index() {
1032
  global $language;
1033

    
1034
  // If we got interrupted by limit, this will contain the last module
1035
  // and topic we looked at.
1036
  $last = variable_get('advanced_help_last_cron', array('time' => 0));
1037
  $limit = intval(variable_get('search_cron_limit', 100));
1038
  $topics = advanced_help_get_topics();
1039
  advanced_help_get_sids($topics);
1040

    
1041
  $count = 0;
1042

    
1043
  foreach ($topics as $module => $module_topics) {
1044
    // Fast forward if necessary.
1045
    if (!empty($last['module']) && $last['module'] != $module) {
1046
      continue;
1047
    }
1048

    
1049
    foreach ($module_topics as $topic => $info) {
1050
      // Fast forward if necessary.
1051
      if (!empty($last['topic']) && $last['topic'] != $topic) {
1052
        continue;
1053
      }
1054

    
1055
      // If we've been looking to catch up, and we have, reset so we
1056
      // stop fast forwarding.
1057
      if (!empty($last['module'])) {
1058
        unset($last['topic']);
1059
        unset($last['module']);
1060
      }
1061

    
1062
      $file = advanced_help_get_topic_filename($module, $topic);
1063
      if ($file && (empty($info['sid']) || filemtime($file) > $last['time'])) {
1064
        if (empty($info['sid'])) {
1065
          $info['sid'] = db_insert('advanced_help_index')
1066
            ->fields(array(
1067
              'module' => $module,
1068
              'topic'  => $topic,
1069
              'language' => $language->language,
1070
            ))
1071
            ->execute();
1072
        }
1073

    
1074
        search_index($info['sid'], 'help', '<h1>' . $info['title'] . '</h1>' . file_get_contents($file));
1075
        $count++;
1076
        if ($count >= $limit) {
1077
          $last['topic'] = $topic;
1078
          $last['module'] = $module;
1079
          // Don't change time if we stop.
1080
          variable_set('advanced_help_last_cron', $last);
1081
          return;
1082
        }
1083
      }
1084
    }
1085
  }
1086

    
1087
  variable_set('advanced_help_last_cron', array('time' => time()));
1088
}
1089

    
1090
/**
1091
 * Fill in a bunch of page variables for our specialized popup page.
1092
 */
1093
function template_preprocess_advanced_help_popup(&$variables) {
1094
  // Add favicon.
1095
  if (theme_get_setting('toggle_favicon')) {
1096
    drupal_add_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
1097
  }
1098

    
1099
  global $theme;
1100
  // Construct page title.
1101
  if (drupal_get_title()) {
1102
    $head_title = array(
1103
      strip_tags(drupal_get_title()),
1104
      variable_get('site_name', 'Drupal'),
1105
    );
1106
  }
1107
  else {
1108
    $head_title = array(variable_get('site_name', 'Drupal'));
1109
    if (variable_get('site_slogan', '')) {
1110
      $head_title[] = variable_get('site_slogan', '');
1111
    }
1112
  }
1113

    
1114
  drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-popup.css');
1115
  drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
1116

    
1117
  $variables['head_title']        = implode(' | ', $head_title);
1118
  $variables['base_path']         = base_path();
1119
  $variables['front_page']        = url();
1120
  $variables['breadcrumb']        = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));
1121
  $variables['feed_icons']        = drupal_get_feeds();
1122
  $variables['head']              = drupal_get_html_head();
1123
  $variables['language']          = $GLOBALS['language'];
1124
  $variables['language']->dir     = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
1125
  $variables['logo']              = theme_get_setting('logo');
1126
  $variables['messages']          = theme('status_messages');
1127
  $variables['site_name']         = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
1128
  $variables['css']               = drupal_add_css();
1129
  $css = drupal_add_css();
1130

    
1131
  // Remove theme css.
1132
  foreach ($css as $key => $value) {
1133
    if ($value['group'] == CSS_THEME) {
1134
      $exclude[$key] = FALSE;
1135
    }
1136
  }
1137
  $css = array_diff_key($css, $exclude);
1138

    
1139
  $variables['styles']            = drupal_get_css($css);
1140
  $variables['scripts']           = drupal_get_js();
1141
  $variables['title']             = drupal_get_title();
1142

    
1143
  // This function can be called either with a render array or
1144
  // an already rendered string.
1145
  if (is_array($variables['content'])) {
1146
    $variables['content'] = drupal_render($variables['content']);
1147
  }
1148
  // Closure should be filled last.
1149
  // There has never been a theme hook for closure (going back to
1150
  // first release 2008-04-17).  Unable to figure out its purpose.
1151
  // $variables['closure']           = theme('closure');
1152
}
1153

    
1154
/**
1155
 * Format a link but preserve popup identity.
1156
 */
1157
function advanced_help_l($text, $dest, $options = array()) {
1158
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
1159
  if ($popup) {
1160
    if (empty($options['query'])) {
1161
      $options['query'] = array();
1162
    }
1163

    
1164
    if (is_array($options['query'])) {
1165
      $options['query'] += array('popup' => TRUE);
1166
    }
1167
    else {
1168
      $options['query'] += '&popup=TRUE';
1169
    }
1170
  }
1171

    
1172
  return l($text, $dest, $options);
1173
}
1174

    
1175
/**
1176
 * Format a URL but preserve popup identity.
1177
 */
1178
function advanced_help_url($dest, $options = array()) {
1179
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
1180
  if ($popup) {
1181
    if (empty($options['query'])) {
1182
      $options['query'] = array();
1183
    }
1184

    
1185
    $options['query'] += array('popup' => TRUE);
1186
  }
1187

    
1188
  return url($dest, $options);
1189
}