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