X-Git-Url: https://yaffs.net/gitweb/?a=blobdiff_plain;f=web%2Fmodules%2Fcustom%2Fmenu_views%2Fmenu_views.module;fp=web%2Fmodules%2Fcustom%2Fmenu_views%2Fmenu_views.module;h=df4664fa4dc15baf8c047e02c8c4ed44c1331831;hb=a2bd1bf0c2c1f1a17d188f4dc0726a45494cefae;hp=0000000000000000000000000000000000000000;hpb=57c063afa3f66b07c4bbddc2d6129a96d90f0aad;p=yaffs-website diff --git a/web/modules/custom/menu_views/menu_views.module b/web/modules/custom/menu_views/menu_views.module new file mode 100644 index 000000000..df4664fa4 --- /dev/null +++ b/web/modules/custom/menu_views/menu_views.module @@ -0,0 +1,737 @@ +'] = 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['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; +}