Projet

Général

Profil

Paste
Télécharger (39,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / advanced_help / advanced_help.module @ 86fa8ee5

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_help().
37
 */
38
function advanced_help_help($path, $arg) {
39
  if ($path == 'admin/help#advanced_help') {
40
    $output = '<p>' . t('This module provides extended help and documentation.') . '</p>';
41
    if (function_exists('advanced_help_hint_docs')) {
42
      $output .= '<p>' . advanced_help_hint_docs('advanced_help', 'https://www.drupal.org/docs/7/modules/advanced-help', TRUE) . '</p>';
43
    }
44
    else {
45
      $output .= t('If you install and enable the module <strong>!url</strong>, you will get more help for <strong>Advanced help</strong>.',
46
        array('!url' => l('Advanced help hint', 'https://www.drupal.org/project/advanced_help_hint')));
47
    }
48
    return $output;
49
  }
50
}
51

    
52
/**
53
 * Implements hook_admin_paths_alter().
54
 *
55
 * Force help pages for this module to be rendered in admin theme.
56
 */
57
function advanced_help_admin_paths_alter(&$paths) {
58
  $paths['help/advanced_help/*'] = TRUE;
59
}
60

    
61
/**
62
 * Implements hook_menu().
63
 *
64
 * Strings in hook_menu() should not be run through t().
65
 */
66
function advanced_help_menu() {
67
  $help_exists = module_exists('help') ? TRUE : FALSE;
68
  if ($help_exists) {
69
    // Add tabs to core Help page to access Advanced Help.
70
    $items['admin/help/tab1'] = array(
71
      'title' => 'Help',
72
      'type' => MENU_DEFAULT_LOCAL_TASK,
73
      'weight' => 0,
74
    );
75

    
76
    $items['admin/help/ah'] = array(
77
      'title' => 'Advanced Help',
78
      'page callback' => 'advanced_help_index_page',
79
      'access arguments' => array('view advanced help index'),
80
      'type' => MENU_LOCAL_TASK,
81
      'weight' => 2,
82
    );
83
  }
84
  else {
85
    // Make Advanced Help the normal help.
86
    $items['admin/help/ah'] = array(
87
      'title' => 'Help',
88
      'page callback' => 'advanced_help_index_page',
89
      'access arguments' => array('view advanced help index'),
90
      'type' => MENU_NORMAL_ITEM,
91
      'weight' => 9,
92
    );
93
  }
94
  $items['help/ah/search/%'] = array(
95
    'title' => 'Search help',
96
    'page callback' => 'advanced_help_search_view',
97
    'page arguments' => array('advanced_help'),
98
    'access arguments' => array('view advanced help index'),
99
  );
100

    
101
  // View help topic.
102
  $items['help/%/%'] = array(
103
    'page callback' => 'advanced_help_topic_page',
104
    'page arguments' => array(1, 2),
105
    'access arguments' => array('view advanced help topic'),
106
    'type' => MENU_CALLBACK,
107
  );
108

    
109
  return $items;
110
}
111

    
112
/**
113
 * Implements hook_menu_alter().
114
 */
115
function advanced_help_menu_alter(&$callbacks) {
116
  // Check if it advanced_help is registered.
117
  $sam = variable_get('search_active_modules', NULL);
118
  if (!empty($sam['advanced_help']) && 'advanced_help' == $sam['advanced_help']) {
119
    // We need to fix the menu item provided by search module to restrict access.
120
    $callbacks['search/advanced_help/%menu_tail']['access callback'] = 'user_access';
121
    $callbacks['search/advanced_help/%menu_tail']['access arguments'] = array('view advanced help index');
122
  }
123
}
124

    
125
/**
126
 * Implements hook_theme().
127
 */
128
function advanced_help_theme() {
129
  $hooks['advanced_help_topic'] = array(
130
    'variables' => array(
131
      'module' => NULL,
132
      'topic'  => NULL,
133
      'type'   => 'icon',
134
    ),
135
  );
136

    
137
  $hooks['advanced_help_popup'] = array(
138
    'render element' => 'content',
139
    'template' => 'advanced-help-popup',
140
  );
141

    
142
  return $hooks;
143
}
144

    
145
/**
146
 * Helper function to sort topics.
147
 */
148
function advanced_help_uasort($id_a, $id_b) {
149
  $topics = advanced_help_get_topics();
150
  list($module_a, $topic_a) = $id_a;
151
  $a = $topics[$module_a][$topic_a];
152
  list($module_b, $topic_b) = $id_b;
153
  $b = $topics[$module_b][$topic_b];
154

    
155
  $a_weight = isset($a['weight']) ? $a['weight'] : 0;
156
  $b_weight = isset($b['weight']) ? $b['weight'] : 0;
157
  if ($a_weight != $b_weight) {
158
    return ($a_weight < $b_weight) ? -1 : 1;
159
  }
160

    
161
  if ($a['title'] != $b['title']) {
162
    return ($a['title'] < $b['title']) ? -1 : 1;
163
  }
164
  return 0;
165
}
166

    
167
/**
168
 * Helper function for grabbing search keys. Function is missing in D7.
169
 *
170
 * http://api.drupal.org/api/function/search_get_keys/6
171
 */
172
function advanced_help_search_get_keys() {
173
  static $return;
174
  if (!isset($return)) {
175
    // Extract keys as remainder of path
176
    // Note: support old GET format of searches for existing links.
177
    $path = explode('/', $_GET['q'], 4);
178
    $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
179
    $return = count($path) == 4 ? $path[3] : $keys;
180
  }
181
  return $return;
182
}
183

    
184
/**
185
 * Page callback for advanced help search.
186
 */
187
function advanced_help_search_view() {
188
  if (!module_exists('search')) {
189
    return drupal_not_found();
190
  }
191

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

    
194
  if (!isset($_POST['form_id'])) {
195
    $keys = advanced_help_search_get_keys();
196
    // Only perform search if there is non-whitespace search term:
197
    $results = '';
198
    if (trim($keys)) {
199
      $search_results = search_data($keys, 'advanced_help');
200
      $search_results = drupal_render($search_results);
201
      // Collect the search results:
202
      $results = array(
203
        '#type' => 'markup',
204
        '#markup' => $search_results,
205
      );
206
    }
207

    
208
    // Construct the search form.
209
    $output['advanced_help_search_form'] = drupal_get_form('advanced_help_search_form', $keys);
210
    $output['results'] = $results;
211

    
212
  }
213
  else {
214
    $output = drupal_get_form('advanced_help_search_form', empty($keys) ? '' : $keys);
215
  }
216

    
217
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
218
  if ($popup) {
219
    // Prevent devel module from spewing.
220
    $GLOBALS['devel_shutdown'] = FALSE;
221
    // Suppress admin_menu.
222
    module_invoke('admin_menu', 'suppress');
223
    drupal_set_breadcrumb(array_reverse($breadcrumb));
224
    print theme('advanced_help_popup', array('content' => $output));
225
    return;
226
  }
227

    
228
  $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
229
  drupal_set_breadcrumb($breadcrumb);
230
  return $output;
231
}
232

    
233
/**
234
 * Page callback to view the advanced help topic index.
235
 *
236
 * @param string $module
237
 *   Name of the module.
238
 *
239
 * @return array
240
 *   Returns module index.
241
 */
242
function advanced_help_index_page($module = '') {
243
  $topics = advanced_help_get_topics();
244
  $settings = advanced_help_get_settings();
245

    
246
  $output = array();
247
  // Print a search widget.
248
  $output['advanced_help_search'] = module_exists('search') ? drupal_get_form('advanced_help_search_form') : array('#markup' => t('Enable the search module to search help.'));
249

    
250
  $breadcrumb = array();
251
  if ($module) {
252
    if (empty($topics[$module])) {
253
      return drupal_not_found();
254
    }
255

    
256
    advanced_help_get_topic_hierarchy($topics);
257
    $items = advanced_help_get_tree($topics, $topics[$module]['']['children']);
258

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

    
261
    drupal_set_title(t('@module help index', array('@module' => advanced_help_get_module_name($module))));
262
    $output['items-module'] = array(
263
      '#theme' => 'item_list',
264
      '#items' => $items,
265
    );
266
  }
267
  else {
268
    // Print a module index.
269
    $modules = array();
270
    $result = db_query('SELECT * FROM {system}');
271
    foreach ($result as $info) {
272
      $module_info = unserialize($info->info);
273
      $modules[$info->name] = isset($module_info['name']) ? $module_info['name'] : $info->name;
274
    }
275

    
276
    asort($modules);
277

    
278
    $items = array();
279
    foreach ($modules as $module => $module_name) {
280
      if (!empty($topics[$module]) && empty($settings[$module]['hide'])) {
281
        if (isset($settings[$module]['index name'])) {
282
          $name = $settings[$module]['index name'];
283
        }
284
        elseif (isset($settings[$module]['name'])) {
285
          $name = $settings[$module]['name'];
286
        }
287
        else {
288
          $name = t($module_name);
289
        }
290
        $items[] = advanced_help_l($name, "admin/help/ah/$module");
291
      }
292
    }
293

    
294
    drupal_set_title(t('Advanced help'));
295
    $output['items-nomodule'] = array(
296
      '#theme' => 'item_list',
297
      '#items' => $items,
298
    );
299
  }
300

    
301
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
302
  if ($popup) {
303
    // Prevent devel module from spewing.
304
    $GLOBALS['devel_shutdown'] = FALSE;
305
    // Suppress admin_menu.
306
    module_invoke('admin_menu', 'suppress');
307
    drupal_set_breadcrumb(array_reverse($breadcrumb));
308
    print theme('advanced_help_popup', array('content' => $output));
309
    return;
310
  }
311

    
312
  $breadcrumb = array_merge(drupal_get_breadcrumb(), array_reverse($breadcrumb));
313
  drupal_set_breadcrumb($breadcrumb);
314
  return $output;
315
}
316

    
317
/**
318
 * Build a tree of advanced help topics.
319
 *
320
 * @param array $topics
321
 *   Topics.
322
 * @param array $topic_ids
323
 *   Topic Ids.
324
 * @param int $max_depth
325
 *   Maximum depth for subtopics.
326
 * @param int $depth
327
 *   Default depth for subtopics.
328
 *
329
 * @return array
330
 *   Returns list of topics/subtopics.
331
 */
332
function advanced_help_get_tree($topics, $topic_ids, $max_depth = -1, $depth = 0) {
333
  uasort($topic_ids, 'advanced_help_uasort');
334
  $items = array();
335
  foreach ($topic_ids as $info) {
336
    list($module, $topic) = $info;
337
    $item = advanced_help_l($topics[$module][$topic]['title'], "help/$module/$topic");
338
    if (!empty($topics[$module][$topic]['children']) && ($max_depth == -1 || $depth < $max_depth)) {
339
      $item .= theme('item_list', array(
340
        'items' => advanced_help_get_tree($topics, $topics[$module][$topic]['children'], $max_depth, $depth + 1),
341
      ));
342
    }
343

    
344
    $items[] = $item;
345
  }
346

    
347
  return $items;
348
}
349

    
350
/**
351
 * Build a hierarchy for a single module's topics.
352
 */
353
function advanced_help_get_topic_hierarchy(&$topics) {
354
  foreach ($topics as $module => $module_topics) {
355
    foreach ($module_topics as $topic => $info) {
356
      $parent_module = $module;
357
      // We have a blank topic that we don't want parented to itself.
358
      if (!$topic) {
359
        continue;
360
      }
361

    
362
      if (empty($info['parent'])) {
363
        $parent = '';
364
      }
365
      elseif (strpos($info['parent'], '%')) {
366
        list($parent_module, $parent) = explode('%', $info['parent']);
367
        if (empty($topics[$parent_module][$parent])) {
368
          // If it doesn't exist, top level.
369
          $parent = '';
370
        }
371
      }
372
      else {
373
        $parent = $info['parent'];
374
        if (empty($module_topics[$parent])) {
375
          // If it doesn't exist, top level.
376
          $parent = '';
377
        }
378
      }
379

    
380
      if (!isset($topics[$parent_module][$parent]['children'])) {
381
        $topics[$parent_module][$parent]['children'] = array();
382
      }
383
      $topics[$parent_module][$parent]['children'][] = array($module, $topic);
384
      $topics[$module][$topic]['_parent'] = array($parent_module, $parent);
385
    }
386
  }
387
}
388

    
389
/**
390
 * Implements hook_form_system_modules_alter().
391
 *
392
 * Add advanced help links to the modules page.
393
 */
394
function advanced_help_form_system_modules_alter(&$form, &$form_state) {
395
  if (!isset($form['modules'])) {
396
    return;
397
  }
398
  $advanced_help_modules = drupal_map_assoc(array_keys(advanced_help_get_topics()));
399
  foreach (element_children($form['modules']) as $group) {
400
    foreach (element_children($form['modules'][$group]) as $module) {
401
      if (isset($advanced_help_modules[$module])) {
402
        $form['modules'][$group][$module]['links']['help'] = array(
403
          '#type' => 'link',
404
          '#title' => t('Help'),
405
          '#href' => "admin/help/ah/$module",
406
          '#options' => array(
407
            'attributes' => array(
408
              'class' => array('module-link', 'module-link-help'),
409
              'title' => t('Help'),
410
            ),
411
          ),
412
        );
413
      }
414
    }
415
  }
416
}
417

    
418
/**
419
 * Form builder callback to build the search form.
420
 *
421
 * Load search/search.pages so that its template preprocess functions are
422
 * visible and can be invoked.
423
 */
424
function advanced_help_search_form($form, &$form_state, $keys = '') {
425
  module_load_include('inc', 'search', 'search.pages');
426
  $form = search_form($form, $form_state, 'admin/help/ah', $keys, 'advanced_help', t('Search help'));
427

    
428
  $form['basic']['inline']['submit']['#validate'] = array('search_form_validate');
429
  $form['basic']['inline']['submit']['#submit'] = array('advanced_help_search_form_submit');
430

    
431
  return $form;
432
}
433

    
434
/**
435
 * Process a search form submission.
436
 */
437
function advanced_help_search_form_submit($form, &$form_state) {
438
  $keys = empty($form_state['values']['processed_keys']) ? $form_state['values']['keys'] : $form_state['values']['processed_keys'];
439
  if ($keys == '') {
440
    form_set_error('keys', t('Please enter some keywords.'));
441
    return;
442
  }
443

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

    
446
  if ($popup) {
447
    $form_state['redirect'] = array('help/ah/search/' . $keys, array('query' => array('popup' => 'true')));
448
  }
449
  else {
450
    $form_state['redirect'] = 'help/ah/search/' . $keys;
451
  }
452
}
453

    
454
/**
455
 * Small helper function to get a module's proper name.
456
 *
457
 * @param string $module
458
 *   Name of the module.
459
 *
460
 * @return string
461
 *   Returns module's descriptive name.
462
 */
463
function advanced_help_get_module_name($module) {
464
  $settings = advanced_help_get_settings();
465
  if (isset($settings[$module]['name'])) {
466
    $name = $settings[$module]['name'];
467
  }
468
  else {
469
    $info = db_query("SELECT s.info FROM {system} s WHERE s.name = :name",
470
      array(':name' => $module))
471
      ->fetchField();
472
    $info = unserialize($info);
473
    $name = t($info['name']);
474
  }
475
  return $name;
476
}
477

    
478
/**
479
 * Page callback to view a help topic.
480
 */
481
function advanced_help_topic_page($module, $topic) {
482
  if ('toc' == $topic) {
483
    return advanced_help_index_page($module);
484
  }
485
  $info = advanced_help_get_topic($module, $topic);
486
  if (!$info) {
487
    return drupal_not_found();
488
  }
489
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
490

    
491
  drupal_set_title($info['title']);
492

    
493
  // Set up breadcrumb.
494
  $breadcrumb = array();
495

    
496
  $parent = $info;
497
  $pmodule = $module;
498

    
499
  // Loop checker.
500
  $checked = array();
501
  while (!empty($parent['parent'])) {
502
    if (strpos($parent['parent'], '%')) {
503
      list($pmodule, $ptopic) = explode('%', $parent['parent']);
504
    }
505
    else {
506
      $ptopic = $parent['parent'];
507
    }
508

    
509
    if (!empty($checked[$pmodule][$ptopic])) {
510
      break;
511
    }
512
    $checked[$pmodule][$ptopic] = TRUE;
513

    
514
    $parent = advanced_help_get_topic($pmodule, $ptopic);
515
    if (!$parent) {
516
      break;
517
    }
518

    
519
    $breadcrumb[] = advanced_help_l($parent['title'], "help/$pmodule/$ptopic");
520
  }
521

    
522
  $breadcrumb[] = advanced_help_l(advanced_help_get_module_name($pmodule), "admin/help/ah/$pmodule");
523
  $breadcrumb[] = advanced_help_l(t('Help'), "admin/help/ah");
524

    
525
  $output = advanced_help_view_topic($module, $topic, $popup);
526
  if (empty($output)) {
527
    $output = t('Missing help topic.');
528
  }
529

    
530
  if ($popup) {
531
    // Prevent devel module from spewing.
532
    $GLOBALS['devel_shutdown'] = FALSE;
533
    // Suppress admin_menu.
534
    module_invoke('admin_menu', 'suppress');
535
    drupal_set_breadcrumb(array_reverse($breadcrumb));
536
    print theme('advanced_help_popup', array('content' => $output));
537
    return;
538
  }
539

    
540
  drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
541
  $breadcrumb[] = l(t('Home'), '');
542
  drupal_set_breadcrumb(array_reverse($breadcrumb));
543
  return $output;
544
}
545

    
546
/**
547
 * Implements hook_permission().
548
 */
549
function advanced_help_permission() {
550
  return array(
551
    'view advanced help topic' => array('title' => t('View help topics')),
552
    'view advanced help popup' => array('title' => t('View help popups')),
553
    'view advanced help index' => array('title' => t('View help index')),
554
  );
555
}
556

    
557
/**
558
 * Display a help icon with a link to view the topic in a popup.
559
 *
560
 * @param array $variables
561
 *   An associative array containing:
562
 *   - module: The module that owns this help topic.
563
 *   - topic: The identifier for the topic
564
 *   - type
565
 *     - 'icon' to display the question mark icon
566
 *     - 'title' to display the topic's title
567
 *     - any other text to display the text. Be sure to t() it!
568
 */
569
function theme_advanced_help_topic($variables) {
570
  $module = $variables['module'];
571
  $topic  = $variables['topic'];
572
  $type   = $variables['type'];
573

    
574
  if ('toc'  == $topic) {
575
    $info = array(
576
      'title' => 'Index page',
577
      'popup width' => 500,
578
      'popup height' => 500,
579
    );
580
  }
581
  else {
582
    $info = advanced_help_get_topic($module, $topic);
583
    if (!$info) {
584
      return;
585
    }
586
  }
587

    
588
  switch ($type) {
589
    case 'icon':
590
      $text = '<span>' . t('Help') . '</span>';
591
      $class = 'advanced-help-link';
592
      break;
593

    
594
    case 'title':
595
      $text = $info['title'];
596
      $class = 'advanced-help-title';
597
      break;
598

    
599
    default:
600
      $class = 'advanced-help-title';
601
      $text = $type;
602
      break;
603
  }
604

    
605
  if (user_access('view advanced help popup')) {
606
    drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-icon.css');
607
    return l($text, "help/$module/$topic", array(
608
      'attributes' => array(
609
        'class' => $class,
610
        'onclick' => "var w=window.open(this.href, 'advanced_help_window', 'width=" . $info['popup width'] . ",height=" . $info['popup height'] . ",scrollbars,resizable'); w.focus(); return false;",
611
        'title' => $info['title'],
612
      ),
613
      'query' => array('popup' => TRUE),
614
      'html' => TRUE)
615
    );
616
  }
617
  elseif (user_access('view advanced help topic')) {
618
    return l($text, "help/$module/$topic", array(
619
      'attributes' => array(
620
        'class' => $class,
621
        'title' => $info['title'],
622
      ),
623
      'html' => TRUE)
624
    );
625
  }
626
}
627

    
628
/**
629
 * Load and render a help topic.
630
 */
631
function advanced_help_get_topic_filename($module, $topic) {
632
  $info = advanced_help_get_topic_file_info($module, $topic);
633
  if ($info) {
634
    return "$info[path]/$info[file]";
635
  }
636
}
637

    
638
/**
639
 * Get the module type (theme or module).
640
 */
641
function _advanced_help_get_module_type($module) {
642
  $theme_list = array_keys(list_themes());
643
  $is_theme = in_array($module, $theme_list);
644
  return $is_theme ? 'theme' : 'module';
645
}
646

    
647
/**
648
 * Load and render a help topic.
649
 *
650
 * The strategy is first to see if a translated file for the current
651
 * active language exists.  If not found, look for the default.
652
 */
653
function advanced_help_get_topic_file_info($module, $topic) {
654
  global $language;
655

    
656
  $info = advanced_help_get_topic($module, $topic);
657
  if (empty($info)) {
658
    return;
659
  }
660

    
661
  // Search paths:
662
  $module_type = _advanced_help_get_module_type($module);
663
  $paths = array(
664
    // First see if a translation exists.
665
    drupal_get_path($module_type, $module) . "/translations/help/$language->language",
666
    // In same directory as .inc file.
667
    $info['path'],
668
  );
669

    
670
  foreach ($paths as $path) {
671
    if (file_exists("$path/$info[file]")) {
672
      return array('path' => $path, 'file' => $info['file']);
673
    }
674
  }
675
}
676

    
677
/**
678
 * Load and render a help topic.
679
 *
680
 * @param string $module
681
 *   Name of the module.
682
 * @param string $topic
683
 *   Name of the topic.
684
 * @param bool $popup
685
 *   Whether to show in popup or not.
686
 *
687
 * @return string
688
 *   Returns formatted topic.
689
 */
690
function advanced_help_view_topic($module, $topic, $popup = FALSE) {
691
  $file_info = advanced_help_get_topic_file_info($module, $topic);
692
  if ($file_info) {
693
    $info = advanced_help_get_topic($module, $topic);
694
    $file = "./$file_info[path]/$file_info[file]";
695

    
696
    // Fix invalid byte sequences (https://www.drupal.org/node/2659746).
697
    $output = file_get_contents($file);
698
    mb_substitute_character(0xfffd);
699
    $output = mb_convert_encoding($output, 'UTF-8', 'UTF-8');
700

    
701
    if (isset($info['readme file']) && $info['readme file']) {
702
      $ext = pathinfo($file, PATHINFO_EXTENSION);
703
      if ('md' == $ext && module_exists('markdown')) {
704
        $filters = module_invoke('markdown', 'filter_info');
705
        $md_info = $filters['filter_markdown'];
706
        if (function_exists($md_info['process callback'])) {
707
          $function = $md_info['process callback'];
708
          $output = '<div class="advanced-help-topic">' . filter_xss_admin($function($output, NULL)) . '</div>';
709
        }
710
        else {
711
          $output = '<div class="advanced-help-topic"><pre class="readme">' . check_plain($output) . '</pre></div>';
712
        }
713
      }
714
      else {
715
        $readme = '';
716
        if ('md' == $ext) {
717
          $readme .=
718
            '<p>' .
719
            t('If you install the !module module, the text below will be filtered by the module, producing rich text.',
720
              array(
721
                '!module' => l(t('Markdown'),
722
                  'https://www.drupal.org/project/markdown',
723
                  array('attributes' => array('title' => t('Link to project.'))))
724
              )) . '</p>';
725
        }
726
        $readme .=
727
          '<div class="advanced-help-topic"><pre class="readme">' . check_plain($output) . '</pre></div>';
728
        $output = $readme;
729
      }
730
      return $output;
731
    }
732

    
733
    // Make some exchanges. The strtr is because url() translates $ into %24
734
    // but we need to change it back for the regex replacement.
735
    //
736
    // Change 'topic:' to the URL for another help topic.
737
    if ($popup) {
738
      $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
739
      $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')) . '"', $output);
740
      $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1', array('query' => array('popup' => 'true'))), array('%24' => '$')), $output);
741
    }
742
    else {
743
      $output = preg_replace('/href="topic:([^"]+)"/', 'href="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
744
      $output = preg_replace('/src="topic:([^"]+)"/', 'src="' . strtr(url('help/$1'), array('%24' => '$')) . '"', $output);
745
      $output = preg_replace('/&topic:([^"]+)&/', strtr(url('help/$1'), array('%24' => '$')), $output);
746
    }
747

    
748
    global $base_path;
749

    
750
    // Change 'path:' to the URL to the base help directory.
751
    $output = preg_replace('/href="path:([^"]+)"/', 'href="' . $base_path . $info['path'] . '/$1"', $output);
752
    $output = preg_replace('/src="path:([^"]+)"/', 'src="' . $base_path . $info['path'] . '/$1"', $output);
753
    $output = str_replace('&path&', $base_path . $info['path'] . '/', $output);
754

    
755
    // Change 'trans_path:' to the URL to the actual help directory.
756
    $output = preg_replace('/href="trans_path:([^"]+)"/', 'href="' . $base_path . $file_info['path'] . '/$1"', $output);
757
    $output = preg_replace('/src="trans_path:([^"]+)"/', 'src="' . $base_path . $file_info['path'] . '/$1"', $output);
758
    $output = str_replace('&trans_path&', $base_path . $file_info['path'] . '/', $output);
759

    
760
    // Change 'base_url:' to the URL to the site.
761
    $output = preg_replace('/href="base_url:([^"]+)"/', 'href="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
762
    $output = preg_replace('/src="base_url:([^"]+)"/', 'src="' . strtr(url('$1'), array('%24' => '$')) . '"', $output);
763
    $output = preg_replace('/&base_url&([^"]+)"/', strtr(url('$1'), array('%24' => '$')) . '"', $output);
764

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

    
770
      $output = _filter_autop($output);
771
    }
772
    if (!empty($info['ini']['format'])) {
773
      $filter = $info['ini']['format'];
774
      if (filter_format_exists($filter)) {
775
        $output = check_markup($output, $filter);
776
      }
777
      else {
778
        drupal_set_message(t('Ini-file request non-existing format: “!format”.', array('!format' => $filter)), 'error');
779
      }
780
    }
781

    
782
    if (!empty($info['navigation'])) {
783
      $topics = advanced_help_get_topics();
784
      advanced_help_get_topic_hierarchy($topics);
785
      if (!empty($topics[$module][$topic]['children'])) {
786
        $items = advanced_help_get_tree($topics, $topics[$module][$topic]['children']);
787
        $output .= theme('item_list', array('items' => $items));
788
      }
789

    
790
      list($parent_module, $parent_topic) = $topics[$module][$topic]['_parent'];
791
      if ($parent_topic) {
792
        $parent = $topics[$module][$topic]['_parent'];
793
        $up = "help/$parent[0]/$parent[1]";
794
      }
795
      else {
796
        $up = "admin/help/ah/$module";
797
      }
798

    
799
      $siblings = $topics[$parent_module][$parent_topic]['children'];
800
      uasort($siblings, 'advanced_help_uasort');
801
      $prev = $next = NULL;
802
      $found = FALSE;
803
      foreach ($siblings as $sibling) {
804
        list($sibling_module, $sibling_topic) = $sibling;
805
        if ($found) {
806
          $next = $sibling;
807
          break;
808
        }
809
        if ($sibling_module == $module && $sibling_topic == $topic) {
810
          $found = TRUE;
811
          continue;
812
        }
813
        $prev = $sibling;
814
      }
815

    
816
      if ($prev || $up || $next) {
817
        $navigation = '<div class="help-navigation clear-block">';
818

    
819
        if ($prev) {
820
          $navigation .= advanced_help_l('«« ' . $topics[$prev[0]][$prev[1]]['title'], "help/$prev[0]/$prev[1]", array('attributes' => array('class' => 'help-left')));
821
        }
822
        if ($up) {
823
          $navigation .= advanced_help_l(t('Up'), $up, array('attributes' => array('class' => $prev ? 'help-up' : 'help-up-noleft')));
824
        }
825
        if ($next) {
826
          $navigation .= advanced_help_l($topics[$next[0]][$next[1]]['title'] . ' »»', "help/$next[0]/$next[1]", array('attributes' => array('class' => 'help-right')));
827
        }
828

    
829
        $navigation .= '</div>';
830

    
831
        $output .= $navigation;
832
      }
833
    }
834

    
835
    if (!empty($info['css'])) {
836
      drupal_add_css($info['path'] . '/' . $info['css']);
837
    }
838

    
839
    $output = '<div class="advanced-help-topic">' . $output . '</div>';
840
    drupal_alter('advanced_help_topic', $output, $popup);
841
    
842
    return $output;
843
  }
844
}
845

    
846
/**
847
 * Get the information for a single help topic.
848
 */
849
function advanced_help_get_topic($module, $topic) {
850
  $topics = advanced_help_get_topics();
851
  if (!empty($topics[$module][$topic])) {
852
    return $topics[$module][$topic];
853
  }
854
}
855

    
856
/**
857
 * Search the system for all available help topics.
858
 */
859
function advanced_help_get_topics() {
860
  $ini = _advanced_help_parse_ini();
861
  return $ini['topics'];
862
}
863

    
864
/**
865
 * Returns advanced help settings.
866
 */
867
function advanced_help_get_settings() {
868
  $ini = _advanced_help_parse_ini();
869
  return $ini['settings'];
870
}
871

    
872
/**
873
 * Function to parse ini / txt files.
874
 */
875
function _advanced_help_parse_ini() {
876
  global $language;
877
  static $ini = NULL;
878
  if (!isset($ini)) {
879
    $cache = cache_get('advanced_help_ini:' . $language->language);
880
    if ($cache) {
881
      $ini = $cache->data;
882
    }
883
  }
884
  if (!isset($ini)) {
885
    // No cached list of topics - create it.
886
    $ini = array('topics' => array(), 'settings' => array());
887

    
888
    foreach (array_merge(module_list(), list_themes()) as $plugin) {
889
      $module = is_string($plugin) ? $plugin : $plugin->name;
890
      $module_path = drupal_get_path(is_string($plugin) ? 'module' : 'theme', $module);
891

    
892
      // Parse ini-file if it exists
893
      if (file_exists("$module_path/help/$module.help.ini")) {
894
        $path_html = "$module_path/help";
895
        $info = parse_ini_file("./$module_path/help/$module.help.ini", TRUE);
896
        if (!$info) {
897
          drupal_set_message(t('There is a syntax error in the default .ini-file.  Unable to display topics.'), 'error');
898
        }
899
      }
900
      else {
901
        $info = array();
902
      }
903
      if (empty($info) || !empty($info['advanced help settings']['show readme'])) {
904
        // Look for one or more README files.
905
        $files = file_scan_directory("./$module_path",
906
          '/^(readme).*\.(txt|md)$/i', array('recurse' => FALSE));
907
        $path_readme = "$module_path";
908
        foreach ($files as $name => $fileinfo) {
909
          $info[$fileinfo->filename] = array(
910
            'line break' => TRUE,
911
            'readme file' => TRUE,
912
            'file' => $fileinfo->filename,
913
            'title' => $fileinfo->name,
914
            'weight' => -99,
915
          );
916
        }
917
      }
918

    
919
      if (!empty($info)) {
920
        // Get translated titles:
921
        $translation = array();
922
        if (file_exists("$module_path/translations/help/$language->language/$module.help.ini")) {
923
          $translation = parse_ini_file("$module_path/translations/help/$language->language/$module.help.ini", TRUE);
924
          if (!$translation) {
925
            drupal_set_message(t('There is a syntax error in the translated .ini-file.'), 'error');
926
          }
927
        }
928

    
929
        $ini['settings'][$module] = array();
930
        if (!empty($info['advanced help settings'])) {
931
          $ini['settings'][$module] = $info['advanced help settings'];
932
          unset($info['advanced help settings']);
933

    
934
          // Check translated strings for translatable global settings.
935
          if (isset($translation['advanced help settings']['name'])) {
936
            $ini['settings']['name'] = $translation['advanced help settings']['name'];
937
          }
938
          if (isset($translation['advanced help settings']['index name'])) {
939
            $ini['settings']['index name'] = $translation['advanced help settings']['index name'];
940
          }
941

    
942
        }
943

    
944
        foreach ($info as $name => $topic) {
945
          // Each topic should have a name, a title, a file and path.
946
          $file = !empty($topic['file']) ? $topic['file'] : $name;
947
          $ini['topics'][$module][$name] = array(
948
            'name' => $name,
949
            'module' => $module,
950
            'ini' => $topic,
951
            'title' => !empty($translation[$name]['title']) ? $translation[$name]['title'] : $topic['title'],
952
            'weight' => isset($topic['weight']) ? $topic['weight'] : 0,
953
            'parent' => isset($topic['parent']) ? $topic['parent'] : 0,
954
            'popup width' => isset($topic['popup width']) ? $topic['popup width'] : 500,
955
            'popup height' => isset($topic['popup height']) ? $topic['popup height'] : 500,
956
            // Require extension.
957
            'file' => isset($topic['readme file']) ? $file : $file . '.html',
958
            // Not in .ini file.
959
            'path' => isset($topic['readme file']) ? $path_readme : $path_html,
960
            'line break' => isset($topic['line break']) ? $topic['line break'] : (isset($ini['settings'][$module]['line break']) ? $ini['settings'][$module]['line break'] : FALSE),
961
            'navigation' => isset($topic['navigation']) ? $topic['navigation'] : (isset($ini['settings'][$module]['navigation']) ? $ini['settings'][$module]['navigation'] : TRUE),
962
            'show readme' => isset($topic['show readme']) ? $topic['show readme'] : (isset($ini['settings'][$module]['show readme']) ? $ini['settings'][$module]['show readme'] : FALSE),
963
            'css' => isset($topic['css']) ? $topic['css'] : (isset($ini['settings'][$module]['css']) ? $ini['settings'][$module]['css'] : NULL),
964
            'readme file' => isset($topic['readme file']) ? $topic['readme file'] : FALSE,
965
          );
966
        }
967
      }
968
    }
969
    drupal_alter('advanced_help_topic_info', $ini);
970

    
971
    cache_set('advanced_help_ini:' . $language->language, $ini);
972
  }
973
  return $ini;
974
}
975

    
976
/**
977
 * Implements hook_search_info().
978
 *
979
 * Returns title for the tab on search page & path component after 'search/'.
980
 */
981
function advanced_help_search_info() {
982
  return array(
983
    'title' => t('Help'),
984
    'path' => 'advanced_help',
985
  );
986
}
987

    
988
/**
989
 * Implements hook_search_execute().
990
 */
991
function advanced_help_search_execute($keys = NULL) {
992
  $topics = advanced_help_get_topics();
993

    
994
  $query = db_select('search_index', 'i', array('target' => 'slave'))
995
    ->extend('SearchQuery')
996
    ->extend('PagerDefault');
997
  $query->join('advanced_help_index', 'ahi', 'i.sid = ahi.sid');
998
  $query->searchExpression($keys, 'help');
999

    
1000
  // Only continue if the first pass query matches.
1001
  if (!$query->executeFirstPass()) {
1002
    return array();
1003
  }
1004

    
1005
  $results = array();
1006

    
1007
  $find = $query->execute();
1008
  foreach ($find as $item) {
1009
    $sids[] = $item->sid;
1010
  }
1011

    
1012
  $query = db_select('advanced_help_index', 'ahi');
1013
  $result = $query
1014
    ->fields('ahi')
1015
    ->condition('sid', $sids, 'IN')
1016
    ->execute();
1017

    
1018
  foreach ($result as $sid) {
1019
    // Guard against removed help topics that are still indexed.
1020
    if (empty($topics[$sid->module][$sid->topic])) {
1021
      continue;
1022
    }
1023
    $info = $topics[$sid->module][$sid->topic];
1024
    $text = advanced_help_view_topic($sid->module, $sid->topic);
1025
    $results[] = array(
1026
      'link' => advanced_help_url("help/$sid->module/$sid->topic"),
1027
      'title' => $info['title'],
1028
      'snippet' => search_excerpt($keys, $text),
1029
    );
1030
  }
1031
  return $results;
1032
}
1033

    
1034
/**
1035
 * Implements hook_search_reset().
1036
 */
1037
function advanced_help_search_reset() {
1038
  variable_del('advanced_help_last_cron');
1039
}
1040

    
1041
/**
1042
 * Implements hook_search_status().
1043
 */
1044
function advanced_help_search_status() {
1045
  $topics = advanced_help_get_topics();
1046
  $total = 0;
1047
  foreach ($topics as $module => $module_topics) {
1048
    foreach ($module_topics as $topic => $info) {
1049
      $file = advanced_help_get_topic_filename($module, $topic);
1050
      if ($file) {
1051
        $total++;
1052
      }
1053
    }
1054
  }
1055

    
1056
  $last_cron = variable_get('advanced_help_last_cron', array('time' => 0));
1057
  $indexed = 0;
1058
  if ($last_cron['time'] != 0) {
1059
    $indexed = db_query("SELECT COUNT(*) FROM {search_dataset} sd WHERE sd.type = 'help' AND sd.sid IS NOT NULL AND sd.reindex = 0")->fetchField();
1060
  }
1061
  return array('remaining' => $total - $indexed, 'total' => $total);
1062
}
1063

    
1064
/**
1065
 * Gets search id for each topic.
1066
 *
1067
 * Get or create an sid (search id) that correlates to each topic for
1068
 * the search system.
1069
 */
1070
function advanced_help_get_sids(&$topics) {
1071
  global $language;
1072
  $result = db_query("SELECT * FROM {advanced_help_index} WHERE language = :language",
1073
    array(':language' => $language->language));
1074
  foreach ($result as $sid) {
1075
    if (empty($topics[$sid->module][$sid->topic])) {
1076
      db_query("DELETE FROM {advanced_help_index} WHERE sid = :sid",
1077
        array(':sid' => $sid->sid));
1078
    }
1079
    else {
1080
      $topics[$sid->module][$sid->topic]['sid'] = $sid->sid;
1081
    }
1082
  }
1083
}
1084

    
1085
/**
1086
 * Implements hook_update_index().
1087
 */
1088
function advanced_help_update_index() {
1089
  global $language;
1090

    
1091
  // If we got interrupted by limit, this will contain the last module
1092
  // and topic we looked at.
1093
  $last = variable_get('advanced_help_last_cron', array('time' => 0));
1094
  $limit = intval(variable_get('search_cron_limit', 100));
1095
  $topics = advanced_help_get_topics();
1096
  advanced_help_get_sids($topics);
1097

    
1098
  $count = 0;
1099

    
1100
  foreach ($topics as $module => $module_topics) {
1101
    // Fast forward if necessary.
1102
    if (!empty($last['module']) && $last['module'] != $module) {
1103
      continue;
1104
    }
1105

    
1106
    foreach ($module_topics as $topic => $info) {
1107
      // Fast forward if necessary.
1108
      if (!empty($last['topic']) && $last['topic'] != $topic) {
1109
        continue;
1110
      }
1111

    
1112
      // If we've been looking to catch up, and we have, reset so we
1113
      // stop fast forwarding.
1114
      if (!empty($last['module'])) {
1115
        unset($last['topic']);
1116
        unset($last['module']);
1117
      }
1118

    
1119
      $file = advanced_help_get_topic_filename($module, $topic);
1120
      if ($file && (empty($info['sid']) || filemtime($file) > $last['time'])) {
1121
        if (empty($info['sid'])) {
1122
          $info['sid'] = db_insert('advanced_help_index')
1123
            ->fields(array(
1124
              'module' => $module,
1125
              'topic'  => $topic,
1126
              'language' => $language->language,
1127
            ))
1128
            ->execute();
1129
        }
1130

    
1131
        search_index($info['sid'], 'help', '<h1>' . $info['title'] . '</h1>' . file_get_contents($file));
1132
        $count++;
1133
        if ($count >= $limit) {
1134
          $last['topic'] = $topic;
1135
          $last['module'] = $module;
1136
          // Don't change time if we stop.
1137
          variable_set('advanced_help_last_cron', $last);
1138
          return;
1139
        }
1140
      }
1141
    }
1142
  }
1143

    
1144
  variable_set('advanced_help_last_cron', array('time' => time()));
1145
}
1146

    
1147
/**
1148
 * Fill in a bunch of page variables for our specialized popup page.
1149
 */
1150
function template_preprocess_advanced_help_popup(&$variables) {
1151
  // Add favicon.
1152
  if (theme_get_setting('toggle_favicon')) {
1153
    drupal_add_html_head('<link rel="shortcut icon" href="' . check_url(theme_get_setting('favicon')) . '" type="image/x-icon" />');
1154
  }
1155

    
1156
  global $theme;
1157
  // Construct page title.
1158
  if (drupal_get_title()) {
1159
    $head_title = array(
1160
      strip_tags(drupal_get_title()),
1161
      variable_get('site_name', 'Drupal'),
1162
    );
1163
  }
1164
  else {
1165
    $head_title = array(variable_get('site_name', 'Drupal'));
1166
    if (variable_get('site_slogan', '')) {
1167
      $head_title[] = variable_get('site_slogan', '');
1168
    }
1169
  }
1170

    
1171
  drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help-popup.css');
1172
  drupal_add_css(drupal_get_path('module', 'advanced_help') . '/help.css');
1173

    
1174
  $variables['head_title']        = implode(' | ', $head_title);
1175
  $variables['base_path']         = base_path();
1176
  $variables['front_page']        = url();
1177
  $variables['breadcrumb']        = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));
1178
  $variables['feed_icons']        = drupal_get_feeds();
1179
  $variables['head']              = drupal_get_html_head();
1180
  $variables['language']          = $GLOBALS['language'];
1181
  $variables['language']->dir     = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
1182
  $variables['logo']              = theme_get_setting('logo');
1183
  $variables['messages']          = theme('status_messages');
1184
  $variables['site_name']         = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
1185
  $variables['css']               = drupal_add_css();
1186
  $css = drupal_add_css();
1187

    
1188
  // Remove theme css.
1189
  foreach ($css as $key => $value) {
1190
    if ($value['group'] == CSS_THEME) {
1191
      $exclude[$key] = FALSE;
1192
    }
1193
  }
1194
  $css = array_diff_key($css, $exclude);
1195

    
1196
  $variables['styles']            = drupal_get_css($css);
1197
  $variables['scripts']           = drupal_get_js();
1198
  $variables['title']             = drupal_get_title();
1199

    
1200
  // This function can be called either with a render array or
1201
  // an already rendered string.
1202
  if (is_array($variables['content'])) {
1203
    $variables['content'] = drupal_render($variables['content']);
1204
  }
1205
  // Closure should be filled last.
1206
  // There has never been a theme hook for closure (going back to
1207
  // first release 2008-04-17), so it is always 0 bytes.
1208
  // Unable to figure out its purpose - commenting out.
1209
  // $variables['closure'] = theme('closure');
1210
}
1211

    
1212
/**
1213
 * Format a link but preserve popup identity.
1214
 */
1215
function advanced_help_l($text, $dest, $options = array()) {
1216
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
1217
  if ($popup) {
1218
    if (empty($options['query'])) {
1219
      $options['query'] = array();
1220
    }
1221

    
1222
    if (is_array($options['query'])) {
1223
      $options['query'] += array('popup' => TRUE);
1224
    }
1225
    else {
1226
      $options['query'] += '&popup=TRUE';
1227
    }
1228
  }
1229

    
1230
  return l($text, $dest, $options);
1231
}
1232

    
1233
/**
1234
 * Format a URL but preserve popup identity.
1235
 */
1236
function advanced_help_url($dest, $options = array()) {
1237
  $popup = !empty($_GET['popup']) && user_access('view advanced help popup');
1238
  if ($popup) {
1239
    if (empty($options['query'])) {
1240
      $options['query'] = array();
1241
    }
1242

    
1243
    $options['query'] += array('popup' => TRUE);
1244
  }
1245

    
1246
  return url($dest, $options);
1247
}