More tidying.
[yaffs-website] / web / modules / custom / menu_views / menu_views.module
1 <?php
2
3 /**
4  * @file
5  * Module to allow Views to be attached as menu items.
6  *
7  * This module is a utility module and allows an admin to select a view for a menu item instead of a title and link. When
8  * the link is rendered, the view is inserted instead of the link. In addition, if the
9  * parent item of the menu is a node page, the node id can be passed to the view as an argument using tokens.
10  *
11  * Original concept by Randall Knutson - LevelTen Interactive.
12  * Written and maintained by Mark Carver - LevelTen Interactive.
13  * http://www.leveltendesign.com
14  */
15
16 // Include admin form alter hooks.
17 module_load_include('inc', 'menu_views', 'menu_views.admin');
18 //include_once('menu_views.admin.inc');
19
20 /**
21  * Implements hook_menu().
22  */
23 function menu_views_menu() {
24   // Fake callback, needed for menu item add/edit validation.
25   $items['<view>'] = array(
26     'page callback' => 'drupal_not_found',
27     'access callback' => TRUE,
28     'type' => MENU_CALLBACK,
29   );
30   return $items;
31 }
32
33
34 /**
35  * Implements hook_theme_registry_alter().
36  * Intercepts theme_menu_link().
37  */
38 function menu_views_theme_registry_alter(&$registry) {
39   // Save previous value from registry in case another module/theme overwrites theme_menu_link() as well.
40   $registry['menu_views_menu_link_default'] = $registry['menu_link'];
41   $registry['menu_link']['function'] = 'menu_views_menu_link';
42   // Provide Superfish support.
43   if (isset($registry['superfish_menu_item_link'])) {
44     $registry['menu_views_superfish_menu_item_link_default'] = $registry['superfish_menu_item_link'];
45     $registry['superfish_menu_item_link']['function'] = 'menu_views_superfish_menu_item_link';
46   }
47   // Provide Responsive Dropdown Menus support.
48   if (isset($registry['responsive_dropdown_menus_item_link'])) {
49     $registry['menu_views_responsive_dropdown_menus_item_link_default'] = $registry['responsive_dropdown_menus_item_link'];
50     $registry['responsive_dropdown_menus_item_link']['function'] = 'menu_views_responsive_dropdown_menus_item_link';
51   }
52 }
53
54 /**
55  * Implements theme_menu_link().
56  * Overrides default theming function to intercept views.
57  */
58 function menu_views_menu_link(array $variables) {
59   // Only intercept if this menu link is a view.
60   $view = _menu_views_replace_menu_item($variables['element']);
61   if ($view !== FALSE) {
62     if (!empty($view)) {
63       $sub_menu = '';
64       $classes = isset($variables['element']['#attributes']['class']) ? $variables['element']['#attributes']['class'] : array();
65       $item = _menu_views_get_item($variables['element']);
66       foreach (explode(' ', $item['view']['settings']['wrapper_classes']) as $class) {
67         if (!in_array($class, $classes)) {
68           $classes[] = $class;
69         }
70       }
71       $variables['element']['#attributes']['class'] = $classes;
72       if ($variables['element']['#below']) {
73         $sub_menu = \Drupal::service("renderer")->render($variables['element']['#below']);
74       }
75       return '<li' . drupal_attributes($variables['element']['#attributes']) . '>' . $view . $sub_menu . "</li>\n";
76     }
77     return '';
78   }
79   // Otherwise, use the default theming function.
80   // @FIXME
81 // theme() has been renamed to _theme() and should NEVER be called directly.
82 // Calling _theme() directly can alter the expected output and potentially
83 // introduce security issues (see https://www.drupal.org/node/2195739). You
84 // should use renderable arrays instead.
85 // 
86 // 
87 // @see https://www.drupal.org/node/2195739
88 // return theme('menu_views_menu_link_default', $variables);
89
90 }
91
92 /**
93  * Implements theme_superfish_menu_item_link().
94  * Overrides default theming function to intercept views.
95  */
96 function menu_views_superfish_menu_item_link(array $variables) {
97   // Only intercept if this menu item link is a view.
98   if (isset($variables['menu_item']['link']) && $view = _menu_views_replace_menu_item($variables['menu_item']['link'])) {
99     $item = _menu_views_get_item($variables['menu_item']['link']);
100     return '<div' . drupal_attributes(array('class' => explode(' ', $item['view']['settings']['wrapper_classes']))) . '>' . $view . '</div>';
101   }
102   // Otherwise, use the default theming function.
103   // @FIXME
104 // theme() has been renamed to _theme() and should NEVER be called directly.
105 // Calling _theme() directly can alter the expected output and potentially
106 // introduce security issues (see https://www.drupal.org/node/2195739). You
107 // should use renderable arrays instead.
108 // 
109 // 
110 // @see https://www.drupal.org/node/2195739
111 // return theme('menu_views_superfish_menu_item_link_default', $variables);
112
113 }
114
115 /**
116  * Implements theme_responsive_dropdown_menus_item_link().
117  * Overrides default theming function to intercept views.
118  */
119 function menu_views_responsive_dropdown_menus_item_link(array $variables) {
120   // Only intercept if this menu item link is a view.
121   if (isset($variables['menu_item']['link']) && $view = _menu_views_replace_menu_item($variables['menu_item']['link'])) {
122     $item = _menu_views_get_item($variables['menu_item']['link']);
123     return '<div' . drupal_attributes(array('class' => explode(' ', $item['view']['settings']['wrapper_classes']))) . '>' . $view . '</div>';
124   }
125   // Otherwise, use the default theming function.
126   // @FIXME
127 // theme() has been renamed to _theme() and should NEVER be called directly.
128 // Calling _theme() directly can alter the expected output and potentially
129 // introduce security issues (see https://www.drupal.org/node/2195739). You
130 // should use renderable arrays instead.
131 // 
132 // 
133 // @see https://www.drupal.org/node/2195739
134 // return theme('menu_views_responsive_dropdown_menus_item_link_default', $variables);
135
136 }
137
138 /**
139  * Implements hook_menu_breadcrumb_alter().
140  */
141 function menu_views_menu_breadcrumb_alter(&$active_trail, $item) {
142   foreach ($active_trail as $key => $parent) {
143     if (isset($parent['link_path']) && $parent['link_path'] == '<view>') {
144       $menu_view = _menu_views_get_item($parent);
145       _menu_views_tokenize($menu_view);
146       // Remove this breadcrumb if the menu item view wants it hidden.
147       if (!(bool)$menu_view['view']['settings']['breadcrumb']) {
148         unset($active_trail[$key]);
149       }
150       else {
151         // Use overridden title if provided.
152         $title = \Drupal\Component\Utility\Xss::filterAdmin($menu_view['view']['settings']['breadcrumb_title']);
153         // Use title provided by view next.
154         if (empty($title)) {
155           $view = views_get_view($menu_view['view']['name']);
156           if ($view && $view->access($menu_view['view']['display']) && $view->set_display($menu_view['view']['display'])) {
157             $title = \Drupal\Component\Utility\Xss::filterAdmin($view->get_title());
158             $view->destroy();
159           }
160         }
161         // If title is still empty, just remove it from the breadcrumb.
162         if (empty($title)) {
163           unset($active_trail[$key]);
164         }
165         else {
166           $active_trail[$key]['title'] = $title;
167           $active_trail[$key]['href'] = $menu_view['view']['settings']['breadcrumb_path'];
168         }
169       }
170     }
171   }
172 }
173
174 function _menu_views_replace_menu_item($element) {
175   $build = array();
176   $item = _menu_views_get_item($element);
177   _menu_views_tokenize($item);
178   if ($item['type'] == 'view' && $item['view']['name'] && $item['view']['display']) {
179     $element['#attributes']['class'][] = 'menu-views';
180     if ($view = views_get_view($item['view']['name'])) {
181       if ($view->access($item['view']['display']) && $view->set_display($item['view']['display'])) {
182         $arguments = explode('/', $item['view']['arguments']);
183         // Need to replace empty arguments with NULL values for views.
184         foreach ($arguments as $key => $value) {
185           if (empty($value)) {
186             $arguments[$key] = NULL;
187           }
188         }
189         $view->set_arguments($arguments);
190         $build['view'] = array(
191           '#markup' => $view->preview(),
192           '#weight' => 10,
193         );
194
195         // Provide title options for the view.
196         if ((bool)$item['view']['settings']['title']) {
197           $title = \Drupal\Component\Utility\Xss::filterAdmin($item['view']['settings']['title_override']);
198           if (empty($title)) {
199             $title = \Drupal\Component\Utility\Xss::filterAdmin($view->get_title());
200           }
201           if (!empty($title)) {
202             $tag = $item['view']['settings']['title_wrapper'];
203             if ($tag === '0') {
204               $tag = FALSE;
205             }
206             elseif ($tag === '') {
207               $tag = 'h3';
208             }
209             if ($tag) {
210               $title_attributes = array();
211               if (!empty($item['view']['settings']['title_classes'])) {
212                 $title_attributes['class'] = array_filter(explode(' ', $item['view']['settings']['title_classes']));
213                 foreach ($title_attributes['class'] as $key => $class) {
214                   $title_attributes['class'][$key] = \Drupal\Component\Utility\Html::getClass($class);
215                 }
216               }
217               $build['title'] = array(
218                 '#theme' => 'html_tag__menu_views__title',
219                 '#tag' => $tag,
220                 '#attributes' => $title_attributes,
221                 '#value' => \Drupal\Component\Utility\Xss::filterAdmin($title),
222               );
223             }
224             else {
225               $build['title'] = array(
226                 '#markup' => $title,
227               );
228             }
229           }
230         }
231
232         // Add contextual links if allowed and if views_ui module is enabled.
233         if (\Drupal::moduleHandler()->moduleExists('contextual_links') && \Drupal::currentUser()->hasPermission('access contextual links') && \Drupal::moduleHandler()->moduleExists('views_ui')) {
234           views_add_contextual_links($build, 'special_block_-exp', $view, $item['view']['display']);
235           if (!empty($build['#contextual_links'])) {
236             $build['#prefix'] = '<div class="contextual-links-region">';
237             $build['#suffix'] = '</div>';
238             $build['contextual_links'] = array(
239               '#type' => 'contextual_links',
240               '#contextual_links' => $build['#contextual_links'],
241               '#element' => $build,
242               '#weight' => -1,
243             );
244           }
245         }
246
247         $view->destroy();
248       }
249     }
250     return \Drupal::service("renderer")->render($build);
251   }
252   return FALSE;
253 }
254
255 function _menu_views_default_values() {
256   return array(
257     'mlid' => 0,
258     'type' => 'link',
259     'original_path' => '',
260     'view' => array(
261       'name' => FALSE,
262       'display' => FALSE,
263       'arguments' => '',
264       'settings' => array(
265         'wrapper_classes' => 'menu-views',
266         'breadcrumb' => TRUE,
267         'breadcrumb_title' => '',
268         'breadcrumb_path' => '<front>',
269         'title' => FALSE,
270         'title_wrapper' => '',
271         'title_classes' => '',
272         'title_override' => '',
273       ),
274     ),
275   );
276 }
277
278 /**
279  * Helper function to determine whether the form menu item options are a tree.
280  */
281 function _menu_views_options_tree($form) {
282   return ((isset($form['#node']) && isset($form['menu']['link']['options']['#tree']) && $form['menu']['link']['options']['#tree']) || (isset($form['options']['#tree']) && $form['options']['#tree']));
283 }
284
285 /**
286  * Helper function to return the menu view array from a menu item array or form array.
287  */
288 function _menu_views_get_item(array &$element = array(), array &$form_state = array()) {
289   // Default values.
290   $item = $default = _menu_views_default_values();
291   // Remove the type of menu item, will get set afterwards.
292   unset($item['type']);
293   $provided = FALSE;
294   $ajax_type = FALSE;
295   // If $form_state is empty, this is a menu item.
296   if (empty($form_state)) {
297     if (isset($element['menu_views'])) {
298       $provided = &$element['menu_views'];
299     }
300     elseif (isset($element['localized_options']['menu_views'])) {
301       $provided = &$element['localized_options']['menu_views'];
302     }
303     elseif (isset($element['#localized_options']['menu_views'])) {
304       $provided = &$element['#localized_options']['menu_views'];
305     }
306     elseif (isset($element['options']['menu_views'])) {
307       $provided = &$element['options']['menu_views'];
308     }
309   }
310   // $form_state should be set when used in forms, otherwise this function will not work.
311   else {
312     $original_element = $element;
313     $values = &$form_state['values'];
314     // Determine if this is a node form.
315     if (isset($element['#node'])) {
316       $element = &$element['menu']['link'];
317       $values = &$values['menu'];
318     }
319     // Save the menu item type before proceeding.
320     if (isset($values['menu_item_type'])) {
321       $ajax_type = $values['menu_item_type'];
322     }
323     elseif (isset($values['menu']['menu_views']['menu_item_type'])) {
324       $ajax_type = $values['menu']['menu_views']['menu_item_type'];
325     }
326     // Save the menu item provided for initial form population.
327     if (isset($element['original_item']['#value']['options']['menu_views'])) {
328       $provided = &$element['original_item']['#value']['options']['menu_views'];
329       if (isset($provided['type'])) {
330         $item['type'] = $provided['type'];
331       }
332     }
333     elseif (isset($original_element['#node']->menu['options']['menu_views'])) {
334       $provided = &$original_element['#node']->menu['options']['menu_views'];
335       if (isset($provided['type'])) {
336         $item['type'] = $provided['type'];
337       }
338     }
339     // Determine if the options has a tree value.
340     if (_menu_views_options_tree($original_element)) {
341       $element = &$element['options'];
342       $values = &$values['options'];
343     }
344     if (!$provided && isset($element['menu_views'])) {
345       if (isset($element['menu_views']['#value'])) {
346         $provided = $element['menu_views']['#value'];
347       }
348       elseif (isset($element['menu_views'])) {
349         $provided = $element['menu_views'];
350       }
351     }
352     // Allow submitted values to override form values.
353     if (isset($values['menu_views'])) {
354       $provided = $values['menu_views'];
355     }
356     if (isset($provided['type']) && isset($item['type'])) {
357       $provided['type'] = $item['type'];
358     }
359   }
360   // If the menu view element were not set, attempt to determine if this is a form.
361   // By default, the menu view returns default values (no view). If settings were provided by an element or form item, then use those.
362   if ($provided) {
363     // Extract available element settings to use for this menu view.
364     foreach (array('mlid', 'original_path', 'view') as $property) {
365       if (isset($provided[$property])) {
366         if (isset($item[$property]) && is_array($item[$property])) {
367           $item[$property] = _menu_views_array_merge_recursive($item[$property], $provided[$property]);
368         }
369         else {
370           $item[$property] = $provided[$property];
371         }
372       }
373     }
374   }
375   // Set the type of menu item.
376   if ($ajax_type) {
377     $item['type'] = $ajax_type;
378   }
379   elseif (isset($provided['type'])) {
380     $item['type'] = $provided['type'];
381   }
382   else {
383     $item['type'] = $default['type'];
384   }
385   // Filter out any disabled views.
386   $views = array_keys(views_get_enabled_views());
387   if (!in_array($item['view']['name'], $views)) {
388     $item['view'] = $default['view'];
389   }
390   return $item;
391 }
392
393 function menu_views_menu_link_alter(&$link) {
394   $item = _menu_views_get_item($link);
395   if ($item['type'] == 'view') {
396     if (isset($link['link_path']) && $link['link_path'] != '<view>') {
397       $item['original_path'] = $link['link_path'];
398     }
399     $link['link_path'] = '<view>';
400   }
401 }
402
403 /**
404  * Helper function to return the menu link item based on it's original path.
405  *
406  * @param (string) $original_path
407  *   The [node] path for which to search for in menu views.
408  * @param (string)|(array) $menu_name
409  *   Limit the search of menu view items to the specified menu names.
410  * @return
411  *   (array) $mlids
412  *   A keyed array containing the identification integers matching the original path of menu items in the {menu_links} table,
413  *   or an empty array if no menu items were found.
414  *
415  * @see: menu_views_node_prepare() and menu_views_node_delete().
416  */
417 function _menu_views_items_from_original_path($original_path, $menu_name = NULL) {
418   $mlids = array();
419   // Build the query.
420   $query = db_select('menu_links', 'm')
421             ->fields('m', array('mlid', 'options'))
422             ->condition('module', 'menu')
423             ->condition('link_path', '<view>');
424   // Set the menu_name condition if present.
425   if (!empty($menu_name)) {
426     $query = $query->condition('menu_name', $menu_name);
427   }
428   // Execute the query.
429   $query = $query->execute();
430   // Iterate through all available menu items that are views to match against the original path.
431   while($link = $query->fetchObject()) {
432     if (PHP_VERSION_ID >= 70000) {
433       // FIXME $options = unserialize($link->options, array('allowed_classes' => ['Class1', 'Class2']));
434       $options = unserialize($link->options);
435     } else {
436       $options = unserialize($link->options);
437     }
438
439     if ($options) {
440       $item = _menu_views_get_item($options);
441       if ($item['original_path'] == $original_path) {
442         $mlids[] = $link->mlid;
443         break;
444       }
445     }
446   }
447   return $mlids;
448 }
449
450
451 /**
452  * Implements hook_node_prepare().
453  */
454 function menu_views_node_prepare($node) {
455   // Manually call menu's hook_node_prepare() if $node->menu doesn't exist.
456   // @see: drupal.org/node/1878968
457   if (!isset($node->menu) || empty($node->menu)) {
458     menu_node_prepare($node);
459   }
460   if (isset($node->nid) && !$node->menu['mlid']) {
461     // Prepare the node for the edit form so that $node->menu always exists.
462     // @FIXME
463 // // @FIXME
464 // // The correct configuration object could not be determined. You'll need to
465 // // rewrite this call manually.
466 // $menu_name = strtok(variable_get('menu_parent_' . $node->type, 'main-menu:0'), ':');
467
468     $item = array();
469     $mlids = array();
470     // Give priority to the default menu.
471     // @FIXME
472 // // @FIXME
473 // // The correct configuration object could not be determined. You'll need to
474 // // rewrite this call manually.
475 // $type_menus = variable_get('menu_options_' . $node->type, array('main-menu' => 'main-menu'));
476
477     if (in_array($menu_name, $type_menus)) {
478       $mlids = _menu_views_items_from_original_path('node/' . $node->nid, $menu_name);
479     }
480     // Check all allowed menus if a link does not exist in the default menu.
481     if (empty($mlid) && !empty($type_menus)) {
482       $mlids = _menu_views_items_from_original_path('node/' . $node->nid, array_values($type_menus));
483     }
484     // Load the menu link if one was found.
485     $item = empty($mlids) ? array() : menu_link_load(reset($mlids));
486     // Set default values.
487     $default = array(
488       'link_title' => '',
489       'mlid' => 0,
490       'plid' => 0,
491       'menu_name' => $menu_name,
492       'weight' => 0,
493       'options' => array(),
494       'module' => 'menu',
495       'expanded' => 0,
496       'hidden' => 0,
497       'has_children' => 0,
498       'customized' => 0,
499     );
500     // Set the menu item.
501     $node->menu = _menu_views_array_merge_recursive($default, $item);
502     // Find the depth limit for the parent select.
503     if (!isset($node->menu['parent_depth_limit'])) {
504       $node->menu['parent_depth_limit'] = _menu_parent_depth_limit($node->menu);
505     }
506   }
507 }
508
509 /**
510  * Implements hook_node_delete().
511  */
512 function menu_views_node_delete(\Drupal\node\NodeInterface $node) {
513   $mlids = _menu_views_items_from_original_path('node/' . $node->id());
514   foreach ($mlids as $mlid) {
515     menu_link_delete($mlid);
516   }
517 }
518
519 /**
520  * Implements hook_node_insert().
521  */
522 function menu_views_node_insert(\Drupal\node\NodeInterface $node) {
523   menu_views_node_save($node);
524 }
525
526 /**
527  * Implements hook_node_presave().
528  */
529 function menu_views_node_presave(\Drupal\node\NodeInterface $node) {
530   if (isset($node->menu)) {
531     $link = &$node->menu;
532     $item = _menu_views_get_item($link);
533     // Ensure the enabled property is set.
534     if (!isset($link['enabled'])) {
535       $link['enabled'] = !(bool) $link['hidden'];
536     }
537     // If this is a menu view item, override properties on the link so this module handles the save.
538     if ($link['enabled'] && $item['type'] == 'view') {
539       // Save the mlid in the menu_views array so the menu module doesn't delete the link when it detects the mlid.
540       if (!empty($link['mlid'])) {
541         $link['options']['menu_views']['mlid'] = $link['mlid'];
542         $link['mlid'] = 0;
543       }
544       // Ensure there is no title so the menu module doesn't try to save this menu item.
545       $link['link_title'] = '';
546     }
547   }
548 }
549
550
551 /**
552  * Implements hook_node_update().
553  */
554 function menu_views_node_update(\Drupal\node\NodeInterface $node) {
555   menu_views_node_save($node);
556 }
557
558
559 /**
560  * Helper for hook_node_insert() and hook_node_update().
561  */
562 function menu_views_node_save(\Drupal\node\NodeInterface $node) {
563   if (isset($node->menu)) {
564     $link = &$node->menu;
565     $item = _menu_views_get_item($link);
566     // Check to see if Menu Views should handle the menu item save.
567     if (!empty($link['enabled']) && $item['type'] == 'view') {
568       // If this an existing menu item, check to see if the mlid was saved in the menu view options array.
569       if (!empty($item['mlid']) && empty($link['mlid'])) {
570         $link['mlid'] = $item['mlid'];
571       }
572       // This is a new menu link, create one so we can get the mlid.
573       // Note: This will save the menu link twice on new nodes, which is unavoidable since
574       // we need the mlid to be saved in the menu views options array.
575       elseif (empty($link['mlid'])) {
576         if (!menu_link_save($link)) {
577           drupal_set_message(t('There was an error saving the menu link.'), 'error');
578           return;
579         }
580       }
581       // Ensure mlid is properly set.
582       $item['mlid'] = $link['mlid'];
583       // Ensure link_path is properly set.
584       $link['link_path'] = '<view>';
585       // Ensure original_path is properly set.
586       $item['original_path'] = 'node/' . $node->id();
587       // Replace the menu view options in the link and save it.
588       $link['options']['menu_views'] = $item;
589       if (!menu_link_save($link)) {
590         drupal_set_message(t('There was an error saving the menu link.'), 'error');
591       }
592     }
593   }
594 }
595
596
597 /**
598  * Implements hook_token_info().
599  */
600 function menu_views_token_info() {
601   $tokens['tokens']['menu-link']['node'] = array(
602     'name' => t('Node'),
603     'description' => t('The node of the menu link.'),
604     'type' => 'node',
605   );
606   $tokens['tokens']['menu-link']['parent']['node'] = array(
607     'name' => t('Node'),
608     'description' => t('The node of the menu link\'s parent.'),
609     'type' => 'node',
610   );
611   return $tokens;
612 }
613
614
615 /**
616  * Implements hook_tokens().
617  */
618 function menu_views_tokens($type, $tokens, array $data = array(), array $options = array()) {
619   $url_options = array('absolute' => TRUE);
620   if (isset($options['language'])) {
621     $url_options['language'] = $options['language'];
622     $language_code = $options['language']->language;
623   }
624   else {
625     $language_code = NULL;
626   }
627   $sanitize = !empty($options['sanitize']);
628   $replacements = array();
629   // Menu link tokens.
630   if ($type == 'menu-link' && !empty($data['menu-link'])) {
631     $link = (array) $data['menu-link'];
632     // menu-link:node tokens.
633     if ($node_tokens = \Drupal::token()->findWithPrefix($tokens, 'node')) {
634       $node = \Drupal::routeMatch()->getParameter('node', 1, $link['link_path']);
635       if (!$node && '<view>' === $link['link_path'] && !empty($link['options']['menu_views']['original_path'])) {
636         $node = \Drupal::routeMatch()->getParameter('node', 1, $link['options']['menu_views']['original_path']);
637       }
638       if ($node) {
639         $replacements += \Drupal::token()->generate('node', $node_tokens, array('node' => $node), $options);
640       }
641       else {
642         $replacements += \Drupal::token()->generate('node', $node_tokens, array('node' => NULL), $options);
643       }
644     }
645     // menu-link:parent tokens.
646     elseif (($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) && !empty($link['plid']) && ($parent = menu_link_load($link['plid']))) {
647       $node = \Drupal::routeMatch()->getParameter('node', 1, $parent['link_path']);
648       if (!$node && '<view>' === $parent['link_path'] && !empty($parent['options']['menu_views']['original_path'])) {
649         $node = \Drupal::routeMatch()->getParameter('node', 1, $parent['options']['menu_views']['original_path']);
650       }
651       if ($node) {
652         $replacements += \Drupal::token()->generate('node', $parent_tokens, array('node' => $node), $options);
653       }
654       else {
655         $replacements += \Drupal::token()->generate('node', $parent_tokens, array('node' => NULL), $options);
656       }
657     }
658   }
659   return $replacements;
660 }
661
662
663 /**
664  * Helper function to tokenize a menu item view arguments and settings.
665  * Alters the menu view item array and the original values are replaced.
666  */
667 function _menu_views_tokenize(&$item) {
668   if (isset($item['mlid']) && $item['mlid'] > 0) {
669     $context['menu-link'] = menu_link_load($item['mlid']);
670     $options = array(
671       'callback' => '_menu_views_tokenize_callback',
672     );
673     if (isset($item['view']['arguments']) && !empty($item['view']['arguments'])) {
674       $item['view']['arguments'] = \Drupal::token()->replace($item['view']['arguments'], $context, array_merge($options, array('urlencode' => TRUE, 'clear' => TRUE)));
675     }
676     $tokenizable_settings = array('breadcrumb_title', 'breadcrumb_path', 'title_override');
677     if (isset($item['view']['settings'])) {
678       foreach ($item['view']['settings'] as $key => $value) {
679         if (in_array($key, $tokenizable_settings)) {
680           $item['view']['settings'][$key] = \Drupal::token()->replace($value, $context, $options);
681         }
682       }
683     }
684   }
685 }
686
687
688 /**
689  * Callback for human-readable token value replacements.
690  */
691 function _menu_views_tokenize_callback(&$replacements, $data, $options) {
692   foreach ($replacements as $token => $value) {
693     if (isset($options['urlencode']) && $options['urlencode']) {
694       $replacements[$token] = urlencode($value);
695     }
696   }
697 }
698
699
700 /**
701  * array_merge_recursive does indeed merge arrays, but it converts values with duplicate
702  * keys to arrays rather than overwriting the value in the first array with the duplicate
703  * value in the second array, as array_merge does. I.e., with array_merge_recursive,
704  * this happens (documented behavior):
705  * 
706  * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
707  *     => array('key' => array('org value', 'new value'));
708  * 
709  * array_merge_recursive_distinct does not change the datatypes of the values in the arrays.
710  * Matching keys' values in the second array overwrite those in the first array, as is the
711  * case with array_merge, i.e.:
712  * 
713  * array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value'));
714  *     => array('key' => 'new value');
715  * 
716  * Parameters are passed by reference, though only for performance reasons. They're not
717  * altered by this function.
718  * 
719  * @param array $array1
720  * @param mixed $array2
721  * @author daniel@danielsmedegaardbuus.dk
722  * @return array
723  */
724 function &_menu_views_array_merge_recursive(array &$array1, &$array2 = NULL) {
725   $merged = $array1;
726   if (is_array($array2)) {
727     foreach ($array2 as $key => $val) {
728       if (is_array($array2[$key])) {
729         $merged[$key] = isset($merged[$key]) && is_array($merged[$key]) ? _menu_views_array_merge_recursive($merged[$key], $array2[$key]) : $array2[$key];
730       }        
731       else {
732         $merged[$key] = $val;
733       }
734     }
735   }
736   return $merged;
737 }