'] = array(
'page callback' => 'drupal_not_found',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_theme_registry_alter().
* Intercepts theme_menu_link().
*/
function menu_views_theme_registry_alter(&$registry) {
// Save previous value from registry in case another module/theme overwrites theme_menu_link() as well.
$registry['menu_views_menu_link_default'] = $registry['menu_link'];
$registry['menu_link']['function'] = 'menu_views_menu_link';
// Provide Superfish support.
if (isset($registry['superfish_menu_item_link'])) {
$registry['menu_views_superfish_menu_item_link_default'] = $registry['superfish_menu_item_link'];
$registry['superfish_menu_item_link']['function'] = 'menu_views_superfish_menu_item_link';
}
// Provide Responsive Dropdown Menus support.
if (isset($registry['responsive_dropdown_menus_item_link'])) {
$registry['menu_views_responsive_dropdown_menus_item_link_default'] = $registry['responsive_dropdown_menus_item_link'];
$registry['responsive_dropdown_menus_item_link']['function'] = 'menu_views_responsive_dropdown_menus_item_link';
}
}
/**
* Implements theme_menu_link().
* Overrides default theming function to intercept views.
*/
function menu_views_menu_link(array $variables) {
// Only intercept if this menu link is a view.
$view = _menu_views_replace_menu_item($variables['element']);
if ($view !== FALSE) {
if (!empty($view)) {
$sub_menu = '';
$classes = isset($variables['element']['#attributes']['class']) ? $variables['element']['#attributes']['class'] : array();
$item = _menu_views_get_item($variables['element']);
foreach (explode(' ', $item['view']['settings']['wrapper_classes']) as $class) {
if (!in_array($class, $classes)) {
$classes[] = $class;
}
}
$variables['element']['#attributes']['class'] = $classes;
if ($variables['element']['#below']) {
$sub_menu = \Drupal::service("renderer")->render($variables['element']['#below']);
}
return '
' . $view . $sub_menu . "\n";
}
return '';
}
// Otherwise, use the default theming function.
// @FIXME
// theme() has been renamed to _theme() and should NEVER be called directly.
// Calling _theme() directly can alter the expected output and potentially
// introduce security issues (see https://www.drupal.org/node/2195739). You
// should use renderable arrays instead.
//
//
// @see https://www.drupal.org/node/2195739
// return theme('menu_views_menu_link_default', $variables);
}
/**
* Implements theme_superfish_menu_item_link().
* Overrides default theming function to intercept views.
*/
function menu_views_superfish_menu_item_link(array $variables) {
// Only intercept if this menu item link is a view.
if (isset($variables['menu_item']['link']) && $view = _menu_views_replace_menu_item($variables['menu_item']['link'])) {
$item = _menu_views_get_item($variables['menu_item']['link']);
return ' explode(' ', $item['view']['settings']['wrapper_classes']))) . '>' . $view . '
';
}
// Otherwise, use the default theming function.
// @FIXME
// theme() has been renamed to _theme() and should NEVER be called directly.
// Calling _theme() directly can alter the expected output and potentially
// introduce security issues (see https://www.drupal.org/node/2195739). You
// should use renderable arrays instead.
//
//
// @see https://www.drupal.org/node/2195739
// return theme('menu_views_superfish_menu_item_link_default', $variables);
}
/**
* Implements theme_responsive_dropdown_menus_item_link().
* Overrides default theming function to intercept views.
*/
function menu_views_responsive_dropdown_menus_item_link(array $variables) {
// Only intercept if this menu item link is a view.
if (isset($variables['menu_item']['link']) && $view = _menu_views_replace_menu_item($variables['menu_item']['link'])) {
$item = _menu_views_get_item($variables['menu_item']['link']);
return ' explode(' ', $item['view']['settings']['wrapper_classes']))) . '>' . $view . '
';
}
// Otherwise, use the default theming function.
// @FIXME
// theme() has been renamed to _theme() and should NEVER be called directly.
// Calling _theme() directly can alter the expected output and potentially
// introduce security issues (see https://www.drupal.org/node/2195739). You
// should use renderable arrays instead.
//
//
// @see https://www.drupal.org/node/2195739
// return theme('menu_views_responsive_dropdown_menus_item_link_default', $variables);
}
/**
* Implements hook_menu_breadcrumb_alter().
*/
function menu_views_menu_breadcrumb_alter(&$active_trail, $item) {
foreach ($active_trail as $key => $parent) {
if (isset($parent['link_path']) && $parent['link_path'] == '') {
$menu_view = _menu_views_get_item($parent);
_menu_views_tokenize($menu_view);
// Remove this breadcrumb if the menu item view wants it hidden.
if (!(bool)$menu_view['view']['settings']['breadcrumb']) {
unset($active_trail[$key]);
}
else {
// Use overridden title if provided.
$title = \Drupal\Component\Utility\Xss::filterAdmin($menu_view['view']['settings']['breadcrumb_title']);
// Use title provided by view next.
if (empty($title)) {
$view = views_get_view($menu_view['view']['name']);
if ($view && $view->access($menu_view['view']['display']) && $view->set_display($menu_view['view']['display'])) {
$title = \Drupal\Component\Utility\Xss::filterAdmin($view->get_title());
$view->destroy();
}
}
// If title is still empty, just remove it from the breadcrumb.
if (empty($title)) {
unset($active_trail[$key]);
}
else {
$active_trail[$key]['title'] = $title;
$active_trail[$key]['href'] = $menu_view['view']['settings']['breadcrumb_path'];
}
}
}
}
}
function _menu_views_replace_menu_item($element) {
$build = array();
$item = _menu_views_get_item($element);
_menu_views_tokenize($item);
if ($item['type'] == 'view' && $item['view']['name'] && $item['view']['display']) {
$element['#attributes']['class'][] = 'menu-views';
if ($view = views_get_view($item['view']['name'])) {
if ($view->access($item['view']['display']) && $view->set_display($item['view']['display'])) {
$arguments = explode('/', $item['view']['arguments']);
// Need to replace empty arguments with NULL values for views.
foreach ($arguments as $key => $value) {
if (empty($value)) {
$arguments[$key] = NULL;
}
}
$view->set_arguments($arguments);
$build['view'] = array(
'#markup' => $view->preview(),
'#weight' => 10,
);
// Provide title options for the view.
if ((bool)$item['view']['settings']['title']) {
$title = \Drupal\Component\Utility\Xss::filterAdmin($item['view']['settings']['title_override']);
if (empty($title)) {
$title = \Drupal\Component\Utility\Xss::filterAdmin($view->get_title());
}
if (!empty($title)) {
$tag = $item['view']['settings']['title_wrapper'];
if ($tag === '0') {
$tag = FALSE;
}
elseif ($tag === '') {
$tag = 'h3';
}
if ($tag) {
$title_attributes = array();
if (!empty($item['view']['settings']['title_classes'])) {
$title_attributes['class'] = array_filter(explode(' ', $item['view']['settings']['title_classes']));
foreach ($title_attributes['class'] as $key => $class) {
$title_attributes['class'][$key] = \Drupal\Component\Utility\Html::getClass($class);
}
}
$build['title'] = array(
'#theme' => 'html_tag__menu_views__title',
'#tag' => $tag,
'#attributes' => $title_attributes,
'#value' => \Drupal\Component\Utility\Xss::filterAdmin($title),
);
}
else {
$build['title'] = array(
'#markup' => $title,
);
}
}
}
// Add contextual links if allowed and if views_ui module is enabled.
if (\Drupal::moduleHandler()->moduleExists('contextual_links') && \Drupal::currentUser()->hasPermission('access contextual links') && \Drupal::moduleHandler()->moduleExists('views_ui')) {
views_add_contextual_links($build, 'special_block_-exp', $view, $item['view']['display']);
if (!empty($build['#contextual_links'])) {
$build['#prefix'] = '';
$build['#suffix'] = '
';
$build['contextual_links'] = array(
'#type' => 'contextual_links',
'#contextual_links' => $build['#contextual_links'],
'#element' => $build,
'#weight' => -1,
);
}
}
$view->destroy();
}
}
return \Drupal::service("renderer")->render($build);
}
return FALSE;
}
function _menu_views_default_values() {
return array(
'mlid' => 0,
'type' => 'link',
'original_path' => '',
'view' => array(
'name' => FALSE,
'display' => FALSE,
'arguments' => '',
'settings' => array(
'wrapper_classes' => 'menu-views',
'breadcrumb' => TRUE,
'breadcrumb_title' => '',
'breadcrumb_path' => '',
'title' => FALSE,
'title_wrapper' => '',
'title_classes' => '',
'title_override' => '',
),
),
);
}
/**
* Helper function to determine whether the form menu item options are a tree.
*/
function _menu_views_options_tree($form) {
return ((isset($form['#node']) && isset($form['menu']['link']['options']['#tree']) && $form['menu']['link']['options']['#tree']) || (isset($form['options']['#tree']) && $form['options']['#tree']));
}
/**
* Helper function to return the menu view array from a menu item array or form array.
*/
function _menu_views_get_item(array &$element = array(), array &$form_state = array()) {
// Default values.
$item = $default = _menu_views_default_values();
// Remove the type of menu item, will get set afterwards.
unset($item['type']);
$provided = FALSE;
$ajax_type = FALSE;
// If $form_state is empty, this is a menu item.
if (empty($form_state)) {
if (isset($element['menu_views'])) {
$provided = &$element['menu_views'];
}
elseif (isset($element['localized_options']['menu_views'])) {
$provided = &$element['localized_options']['menu_views'];
}
elseif (isset($element['#localized_options']['menu_views'])) {
$provided = &$element['#localized_options']['menu_views'];
}
elseif (isset($element['options']['menu_views'])) {
$provided = &$element['options']['menu_views'];
}
}
// $form_state should be set when used in forms, otherwise this function will not work.
else {
$original_element = $element;
$values = &$form_state['values'];
// Determine if this is a node form.
if (isset($element['#node'])) {
$element = &$element['menu']['link'];
$values = &$values['menu'];
}
// Save the menu item type before proceeding.
if (isset($values['menu_item_type'])) {
$ajax_type = $values['menu_item_type'];
}
elseif (isset($values['menu']['menu_views']['menu_item_type'])) {
$ajax_type = $values['menu']['menu_views']['menu_item_type'];
}
// Save the menu item provided for initial form population.
if (isset($element['original_item']['#value']['options']['menu_views'])) {
$provided = &$element['original_item']['#value']['options']['menu_views'];
if (isset($provided['type'])) {
$item['type'] = $provided['type'];
}
}
elseif (isset($original_element['#node']->menu['options']['menu_views'])) {
$provided = &$original_element['#node']->menu['options']['menu_views'];
if (isset($provided['type'])) {
$item['type'] = $provided['type'];
}
}
// Determine if the options has a tree value.
if (_menu_views_options_tree($original_element)) {
$element = &$element['options'];
$values = &$values['options'];
}
if (!$provided && isset($element['menu_views'])) {
if (isset($element['menu_views']['#value'])) {
$provided = $element['menu_views']['#value'];
}
elseif (isset($element['menu_views'])) {
$provided = $element['menu_views'];
}
}
// Allow submitted values to override form values.
if (isset($values['menu_views'])) {
$provided = $values['menu_views'];
}
if (isset($provided['type']) && isset($item['type'])) {
$provided['type'] = $item['type'];
}
}
// If the menu view element were not set, attempt to determine if this is a form.
// By default, the menu view returns default values (no view). If settings were provided by an element or form item, then use those.
if ($provided) {
// Extract available element settings to use for this menu view.
foreach (array('mlid', 'original_path', 'view') as $property) {
if (isset($provided[$property])) {
if (isset($item[$property]) && is_array($item[$property])) {
$item[$property] = _menu_views_array_merge_recursive($item[$property], $provided[$property]);
}
else {
$item[$property] = $provided[$property];
}
}
}
}
// Set the type of menu item.
if ($ajax_type) {
$item['type'] = $ajax_type;
}
elseif (isset($provided['type'])) {
$item['type'] = $provided['type'];
}
else {
$item['type'] = $default['type'];
}
// Filter out any disabled views.
$views = array_keys(views_get_enabled_views());
if (!in_array($item['view']['name'], $views)) {
$item['view'] = $default['view'];
}
return $item;
}
function menu_views_menu_link_alter(&$link) {
$item = _menu_views_get_item($link);
if ($item['type'] == 'view') {
if (isset($link['link_path']) && $link['link_path'] != '') {
$item['original_path'] = $link['link_path'];
}
$link['link_path'] = '';
}
}
/**
* Helper function to return the menu link item based on it's original path.
*
* @param (string) $original_path
* The [node] path for which to search for in menu views.
* @param (string)|(array) $menu_name
* Limit the search of menu view items to the specified menu names.
* @return
* (array) $mlids
* A keyed array containing the identification integers matching the original path of menu items in the {menu_links} table,
* or an empty array if no menu items were found.
*
* @see: menu_views_node_prepare() and menu_views_node_delete().
*/
function _menu_views_items_from_original_path($original_path, $menu_name = NULL) {
$mlids = array();
// Build the query.
$query = db_select('menu_links', 'm')
->fields('m', array('mlid', 'options'))
->condition('module', 'menu')
->condition('link_path', '');
// Set the menu_name condition if present.
if (!empty($menu_name)) {
$query = $query->condition('menu_name', $menu_name);
}
// Execute the query.
$query = $query->execute();
// Iterate through all available menu items that are views to match against the original path.
while($link = $query->fetchObject()) {
if (PHP_VERSION_ID >= 70000) {
// FIXME $options = unserialize($link->options, array('allowed_classes' => ['Class1', 'Class2']));
$options = unserialize($link->options);
} else {
$options = unserialize($link->options);
}
if ($options) {
$item = _menu_views_get_item($options);
if ($item['original_path'] == $original_path) {
$mlids[] = $link->mlid;
break;
}
}
}
return $mlids;
}
/**
* Implements hook_node_prepare().
*/
function menu_views_node_prepare($node) {
// Manually call menu's hook_node_prepare() if $node->menu doesn't exist.
// @see: drupal.org/node/1878968
if (!isset($node->menu) || empty($node->menu)) {
menu_node_prepare($node);
}
if (isset($node->nid) && !$node->menu['mlid']) {
// Prepare the node for the edit form so that $node->menu always exists.
// @FIXME
// // @FIXME
// // The correct configuration object could not be determined. You'll need to
// // rewrite this call manually.
// $menu_name = strtok(variable_get('menu_parent_' . $node->type, 'main-menu:0'), ':');
$item = array();
$mlids = array();
// Give priority to the default menu.
// @FIXME
// // @FIXME
// // The correct configuration object could not be determined. You'll need to
// // rewrite this call manually.
// $type_menus = variable_get('menu_options_' . $node->type, array('main-menu' => 'main-menu'));
if (in_array($menu_name, $type_menus)) {
$mlids = _menu_views_items_from_original_path('node/' . $node->nid, $menu_name);
}
// Check all allowed menus if a link does not exist in the default menu.
if (empty($mlid) && !empty($type_menus)) {
$mlids = _menu_views_items_from_original_path('node/' . $node->nid, array_values($type_menus));
}
// Load the menu link if one was found.
$item = empty($mlids) ? array() : menu_link_load(reset($mlids));
// Set default values.
$default = array(
'link_title' => '',
'mlid' => 0,
'plid' => 0,
'menu_name' => $menu_name,
'weight' => 0,
'options' => array(),
'module' => 'menu',
'expanded' => 0,
'hidden' => 0,
'has_children' => 0,
'customized' => 0,
);
// Set the menu item.
$node->menu = _menu_views_array_merge_recursive($default, $item);
// Find the depth limit for the parent select.
if (!isset($node->menu['parent_depth_limit'])) {
$node->menu['parent_depth_limit'] = _menu_parent_depth_limit($node->menu);
}
}
}
/**
* Implements hook_node_delete().
*/
function menu_views_node_delete(\Drupal\node\NodeInterface $node) {
$mlids = _menu_views_items_from_original_path('node/' . $node->id());
foreach ($mlids as $mlid) {
menu_link_delete($mlid);
}
}
/**
* Implements hook_node_insert().
*/
function menu_views_node_insert(\Drupal\node\NodeInterface $node) {
menu_views_node_save($node);
}
/**
* Implements hook_node_presave().
*/
function menu_views_node_presave(\Drupal\node\NodeInterface $node) {
if (isset($node->menu)) {
$link = &$node->menu;
$item = _menu_views_get_item($link);
// Ensure the enabled property is set.
if (!isset($link['enabled'])) {
$link['enabled'] = !(bool) $link['hidden'];
}
// If this is a menu view item, override properties on the link so this module handles the save.
if ($link['enabled'] && $item['type'] == 'view') {
// Save the mlid in the menu_views array so the menu module doesn't delete the link when it detects the mlid.
if (!empty($link['mlid'])) {
$link['options']['menu_views']['mlid'] = $link['mlid'];
$link['mlid'] = 0;
}
// Ensure there is no title so the menu module doesn't try to save this menu item.
$link['link_title'] = '';
}
}
}
/**
* Implements hook_node_update().
*/
function menu_views_node_update(\Drupal\node\NodeInterface $node) {
menu_views_node_save($node);
}
/**
* Helper for hook_node_insert() and hook_node_update().
*/
function menu_views_node_save(\Drupal\node\NodeInterface $node) {
if (isset($node->menu)) {
$link = &$node->menu;
$item = _menu_views_get_item($link);
// Check to see if Menu Views should handle the menu item save.
if (!empty($link['enabled']) && $item['type'] == 'view') {
// If this an existing menu item, check to see if the mlid was saved in the menu view options array.
if (!empty($item['mlid']) && empty($link['mlid'])) {
$link['mlid'] = $item['mlid'];
}
// This is a new menu link, create one so we can get the mlid.
// Note: This will save the menu link twice on new nodes, which is unavoidable since
// we need the mlid to be saved in the menu views options array.
elseif (empty($link['mlid'])) {
if (!menu_link_save($link)) {
drupal_set_message(t('There was an error saving the menu link.'), 'error');
return;
}
}
// Ensure mlid is properly set.
$item['mlid'] = $link['mlid'];
// Ensure link_path is properly set.
$link['link_path'] = '';
// Ensure original_path is properly set.
$item['original_path'] = 'node/' . $node->id();
// Replace the menu view options in the link and save it.
$link['options']['menu_views'] = $item;
if (!menu_link_save($link)) {
drupal_set_message(t('There was an error saving the menu link.'), 'error');
}
}
}
}
/**
* Implements hook_token_info().
*/
function menu_views_token_info() {
$tokens['tokens']['menu-link']['node'] = array(
'name' => t('Node'),
'description' => t('The node of the menu link.'),
'type' => 'node',
);
$tokens['tokens']['menu-link']['parent']['node'] = array(
'name' => t('Node'),
'description' => t('The node of the menu link\'s parent.'),
'type' => 'node',
);
return $tokens;
}
/**
* Implements hook_tokens().
*/
function menu_views_tokens($type, $tokens, array $data = array(), array $options = array()) {
$url_options = array('absolute' => TRUE);
if (isset($options['language'])) {
$url_options['language'] = $options['language'];
$language_code = $options['language']->language;
}
else {
$language_code = NULL;
}
$sanitize = !empty($options['sanitize']);
$replacements = array();
// Menu link tokens.
if ($type == 'menu-link' && !empty($data['menu-link'])) {
$link = (array) $data['menu-link'];
// menu-link:node tokens.
if ($node_tokens = \Drupal::token()->findWithPrefix($tokens, 'node')) {
$node = \Drupal::routeMatch()->getParameter('node', 1, $link['link_path']);
if (!$node && '' === $link['link_path'] && !empty($link['options']['menu_views']['original_path'])) {
$node = \Drupal::routeMatch()->getParameter('node', 1, $link['options']['menu_views']['original_path']);
}
if ($node) {
$replacements += \Drupal::token()->generate('node', $node_tokens, array('node' => $node), $options);
}
else {
$replacements += \Drupal::token()->generate('node', $node_tokens, array('node' => NULL), $options);
}
}
// menu-link:parent tokens.
elseif (($parent_tokens = \Drupal::token()->findWithPrefix($tokens, 'parent')) && !empty($link['plid']) && ($parent = menu_link_load($link['plid']))) {
$node = \Drupal::routeMatch()->getParameter('node', 1, $parent['link_path']);
if (!$node && '' === $parent['link_path'] && !empty($parent['options']['menu_views']['original_path'])) {
$node = \Drupal::routeMatch()->getParameter('node', 1, $parent['options']['menu_views']['original_path']);
}
if ($node) {
$replacements += \Drupal::token()->generate('node', $parent_tokens, array('node' => $node), $options);
}
else {
$replacements += \Drupal::token()->generate('node', $parent_tokens, array('node' => NULL), $options);
}
}
}
return $replacements;
}
/**
* Helper function to tokenize a menu item view arguments and settings.
* Alters the menu view item array and the original values are replaced.
*/
function _menu_views_tokenize(&$item) {
if (isset($item['mlid']) && $item['mlid'] > 0) {
$context['menu-link'] = menu_link_load($item['mlid']);
$options = array(
'callback' => '_menu_views_tokenize_callback',
);
if (isset($item['view']['arguments']) && !empty($item['view']['arguments'])) {
$item['view']['arguments'] = \Drupal::token()->replace($item['view']['arguments'], $context, array_merge($options, array('urlencode' => TRUE, 'clear' => TRUE)));
}
$tokenizable_settings = array('breadcrumb_title', 'breadcrumb_path', 'title_override');
if (isset($item['view']['settings'])) {
foreach ($item['view']['settings'] as $key => $value) {
if (in_array($key, $tokenizable_settings)) {
$item['view']['settings'][$key] = \Drupal::token()->replace($value, $context, $options);
}
}
}
}
}
/**
* Callback for human-readable token value replacements.
*/
function _menu_views_tokenize_callback(&$replacements, $data, $options) {
foreach ($replacements as $token => $value) {
if (isset($options['urlencode']) && $options['urlencode']) {
$replacements[$token] = urlencode($value);
}
}
}
/**
* array_merge_recursive does indeed merge arrays, but it converts values with duplicate
* keys to arrays rather than overwriting the value in the first array with the duplicate
* value in the second array, as array_merge does. I.e., with array_merge_recursive,
* this happens (documented behavior):
*
* array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
* => array('key' => array('org value', 'new value'));
*
* array_merge_recursive_distinct does not change the datatypes of the values in the arrays.
* Matching keys' values in the second array overwrite those in the first array, as is the
* case with array_merge, i.e.:
*
* array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value'));
* => array('key' => 'new value');
*
* Parameters are passed by reference, though only for performance reasons. They're not
* altered by this function.
*
* @param array $array1
* @param mixed $array2
* @author daniel@danielsmedegaardbuus.dk
* @return array
*/
function &_menu_views_array_merge_recursive(array &$array1, &$array2 = NULL) {
$merged = $array1;
if (is_array($array2)) {
foreach ($array2 as $key => $val) {
if (is_array($array2[$key])) {
$merged[$key] = isset($merged[$key]) && is_array($merged[$key]) ? _menu_views_array_merge_recursive($merged[$key], $array2[$key]) : $array2[$key];
}
else {
$merged[$key] = $val;
}
}
}
return $merged;
}