access($item['view']['display']) && $view->set_display($item['view']['display'])) { $view->set_arguments(explode('/', $item['view']['arguments'])); // Provide title options for the view. $title = \Drupal\Component\Utility\Xss::filterAdmin($item['view']['settings']['title_override']); if (empty($title)) { $title = \Drupal\Component\Utility\Xss::filterAdmin($view->get_title()); } } $view->destroy(); } if (empty($title)) { $title = t($item['view']['name']) . '-' . t($item['view']['display']); } $element['title'] = array( '#prefix' => '
', '#suffix' => '
', '#markup' => 'View: ' . $title . ($menu_item['hidden'] ? ' (' . t('disabled') . ')' : ''), ); } } } } /** * Implements hook_form_FORM_ID_alter(). * * Adds menu views options to the menu edit item form. * * @see _menu_views_form_alter() */ function menu_views_form_menu_link_edit_alter(&$form, &$form_state, $form_id) { // @FIXME // drupal_set_title() has been removed. There are now a few ways to set the title // dynamically, depending on the situation. // // // @see https://www.drupal.org/node/2067859 // drupal_set_title(t('Edit menu item')); // Moved into form_alter - jeff _menu_views_form_alter($form, $form, $form_state, $form_id); } /** * Implements hook_form_FORM_ID_alter(). * * Adds menu views options to the node's edit menu item form. * * @see _menu_views_form_alter() */ function menu_views_form_node_form_alter(&$form, &$form_state, $form_id) { if (isset($form['menu']['link'])) { _menu_views_form_alter($form['menu']['link'], $form, $form_state, $form_id); } } /** * Helper function to determine where form values for menu views are located. * * @return * Array of parents to the menu views form elements. */ function _menu_views_form_parents($form) { $parents = array(); if (isset($form['#node'])) { $parents[] = 'menu'; $parents[] = 'options'; } elseif (_menu_views_options_tree($form)) { $parents[] = 'options'; } $parents[] = 'menu_views'; $parents[] = 'view'; return $parents; } /** * Helper function that inserts a fieldset containing available replacement tokens. * * @see _menu_views_form() */ function _menu_views_form_tokens_ui(array &$element) { static $enabled; if (!isset($enabled)) { $enabled = \Drupal::moduleHandler()->moduleExists('token'); } if ($enabled) { $i = 0; while (isset($element['tokens_' . $i])) { $i++; } $element['tokens_' . $i] = array( '#type' => 'container', ); $element['tokens_' . $i]['tokens'] = array( '#theme' => 'token_tree', '#token_types' => array('menu-link'), '#dialog' => TRUE, ); } } /** * Alters existing forms in preparation for adding Menu Views to it. * * @param $element * The top level menu item edit form element passed by reference. * @param $form * The complete form array passed by reference. * @param $form_state * The complete form state array passed by reference. */ function _menu_views_form_alter(array &$element, array &$form, array &$form_state, $form_id) { // Still need to render the form, just hide it for those who don't have access. $access = \Drupal::currentUser()->hasPermission('administer menu views'); // Only need to attach or alter form if user has access. if ($access) { $form['#title'] = t('Edit menu item'); // Unset the previous values so that the new values can get saved. if (isset($element['options']['#value']['menu_views'])) { unset($element['options']['#value']['menu_views']); } $module_path = drupal_get_path('module', 'menu_views'); // Attach CSS. $element['#attached']['css'][] = array( 'data' => $module_path . '/menu_views.admin.css', 'type' => 'file', 'group' => CSS_THEME, 'weight' => 100 ); // Attach JavaScript. // @FIXME // // @FIXME // // This looks like another module's variable. You'll need to rewrite this call // // to ensure that it uses the correct configuration object. // $element['#attached']['js'][] = array( // 'data' => array('menu_views' => array( // 'admin_theme' => variable_get('admin_theme', 'bartik'), // 'node_form' => isset($form['#node']), // )), // 'type' => 'setting', // ); $element['#attached']['js'][] = array( 'data' => $module_path . '/menu_views.admin.js', 'type' => 'file', 'group' => JS_THEME, 'weight' => 100, ); // Create AJAX wrapper around the menu edit form element. $element['#prefix'] = ''; } // Get values for the new menu views item, existing menu views item or submitted form values. $item = _menu_views_get_item($form, $form_state); // Use the loaded node if it exists. $node = isset($form['#node']) ? $form['#node'] : FALSE; // Get the original menu link, if it exists. $menu_link = array(); if (isset($form['original_item']['#value'])) { $menu_link = $form['original_item']['#value']; } elseif ($node && isset($node->menu)) { $menu_link = $node->menu; } // Determine which menu item type is visible. $visible = !('view' === $item['type']); // Determine the correct link path to show or set values for. if (isset($element['link_path'])) { if ('link' === $item['type'] && !empty($element['link_path']['#default_value']) && '' === $element['link_path']['#default_value']) { $element['link_path']['#default_value'] = $item['original_path']; } elseif ('view' === $item['type']) { if (!empty($element['link_path']['#default_value']) && '' !== $element['link_path']['#default_value']) { $item['original_path'] = $element['link_path']['#default_value']; } $element['link_path']['#default_value'] = ''; } } elseif ($node) { if ('view' === $item['type']) { $element['link_path'] = array( '#type' => 'value', '#value' => '', ); } else { $element['link_path'] = array( '#type' => 'value', '#value' => $node && isset($node->nid) ? 'node/' . $node->nid : '', ); } } // If the original path is empty and this is a node, use the node path. if (empty($item['original_path']) && $node && isset($node->nid) && $node->nid) { $item['original_path'] = 'node/' . $node->nid; } // If this menu item is a view and user does not have permissions to administer menu views, restrict access to the form. // Cannot use drupal_access_denied() because this form can also be inside a node edit form. if ($item['type'] == 'view' && !$access) { $element['disabled'] = array( '#prefix' => '
', '#suffix' => '
', '#markup' => t('You do not have access to edit this type of menu item.'), '#weight' => -9999, ); $visible = FALSE; // Hide the action buttons on menu item edit forms. if (isset($element['actions'])) { $element['actions']['#access'] = $access; } // Hide the checkbox toggle on node edit forms. if (isset($form['menu']['enabled'])) { $form['menu']['enabled']['#access'] = $access; } } // Replace Parent Menu Options $options = _menu_views_parent_options(menu_get_menus(), $menu_link); if ($node && isset($node->type)) { $options = _menu_views_parent_options(menu_get_menus(), $menu_link['mlid'] ? $menu_link : $node->type, $node->type); } $element['parent']['#title'] = t('Parent Item'); $element['parent']['#options'] = $options; // Move general menu item settings into a container. if ($access) { $element['menu_item_settings'] = array( '#access' => $access, '#type' => 'fieldset', '#title' => t('Menu item settings'), '#parents' => isset($form['#node']) ? array('menu') : array(), '#weight' => $item['type'] == 'view' ? 50 : 1, '#attributes' => array('id' => 'menu_item_settings', 'class' => array('menu-item-settings')), ); if (isset($element['enabled'])) { $element['menu_item_settings']['enabled'] = $element['enabled']; unset($element['enabled']); } $element['menu_item_settings']['expanded'] = $element['expanded']; unset($element['expanded']); $element['menu_item_settings']['parent'] = $element['parent']; unset($element['parent']); $element['menu_item_settings']['weight'] = $element['weight']; unset($element['weight']); } // Add support for the xmlsitemap_menu module. if (\Drupal::moduleHandler()->moduleExists('xmlsitemap_menu')) { $element['xmlsitemap']['#weight'] = $item['type'] === 'view' ? 51 : 30; } // Create a radio toggle for menu item types: link or view. $element['menu_item_type'] = array( '#access' => $access, '#type' => 'radios', '#title' => t('Menu item type'), '#options' => array('link' => t('Link'), 'view' => t('View')), '#default_value' => $item['type'], // Add container-inline style for admin themes like Rubik. '#prefix' => '', '#ajax' => array( 'callback' => '_menu_views_form_ajax', 'wrapper' => 'menu-edit-item-wrapper', ), // Ensure this is rendered at the top of the form. '#weight' => -1000, ); // Determine if menu_attributes is enabled. $menu_attributes = \Drupal::moduleHandler()->moduleExists('menu_attributes'); // Show/Hide the core link form elements based on menu item type of: link or view. foreach (\Drupal\Core\Render\Element::children($element) as $child) { // Skip options (handled below) and actions. $type = isset($element[$child]['#type']) ? $element[$child]['#type'] : ''; // Ensure necessary children are always present (regardless of their type). $ignore_children = array( 'menu_item_type', 'disabled', 'menu_item_settings', 'form_build_id', 'form_token', 'form_id', 'xmlsitemap', ); // Allow the menu_attributes module to control the description field. if ($menu_attributes) { $ignore_children[] = 'description'; } $ignore_types = array('actions', 'hidden', 'value', 'token'); if (in_array($child, $ignore_children) || !$type || in_array($type, $ignore_types)) { continue; } $element[$child]['#access'] = $visible; } // Show/Hide the link options form elements based on menu item type of: link or view. foreach (\Drupal\Core\Render\Element::children($element['options']) as $child) { // Skip menu_view options. $type = isset($element['options'][$child]['#type']) ? $element['options'][$child]['#type'] : ''; // Ensure necessary children are always present (regardless of their type). $ignore_children = array('menu_views'); $ignore_types = array('hidden', 'value', 'token'); if (in_array($child, $ignore_children) || !$type || in_array($type, $ignore_types)) { continue; } $element['options'][$child]['#access'] = $visible; } // Place menu views in the options array. $element['options']['menu_views'] = array( '#type' => 'container', '#tree' => TRUE, '#weight' => 10, '#access' => $access, ); // Create the actual Menu Views form. _menu_views_form($item, $element['options']['menu_views'], $form, $form_state, $form_id); // Add menu views form handlers. array_unshift($form['#validate'], '_menu_views_form_validate'); array_unshift($form['#submit'], '_menu_views_form_submit'); } /** * Adds Menu Views settings to an existing form. * * @param $item * The menu views item array passed by reference. * @see _menu_views_get_item() * @param $element * The top level menu item edit form element passed by reference. * @param $form * The complete form array passed by reference. * @param $form_state * The complete form state array passed by reference. */ function _menu_views_form(&$item, &$element, &$form, &$form_state, $form_id) { $element['mlid'] = array( '#type' => 'value', '#value' => $item['mlid'], ); $element['type'] = array( '#type' => 'value', '#value' => $item['type'], ); $element['original_path'] = array( '#type' => 'value', '#value' => $item['original_path'], ); // Container for the actual view settings. $element['view'] = array( '#type' => 'container', '#access' => $item['type'] == 'view' ? TRUE : FALSE, ); $view_options = array(); $display_options = array(); foreach (views_get_enabled_views() as $view_name => $view) { $view_options[$view_name] = $view->human_name . ' (' . $view_name . ')'; foreach ($view->display as $display_name => $display) { if ('default' !== $display_name && 'page' !== $display_name && 'rss' !== $display_name) { $display_options[$view_name][$display_name] = $display->display_title . ' (' . $display_name . ')'; } } // If view has no compatiable displays, remove the view as an option. if (empty($display_options[$view_name])) { unset($view_options[$view_name]); } else { // Sort the displays for the view. ksort($display_options[$view_name]); } } ksort($view_options); $element['view']['container'] = array( '#type' => 'fieldset', '#title' => t('View'), '#parents' => _menu_views_form_parents($form), ); $element['view']['container']['name'] = array( '#type' => 'select', '#title' => t('Name'), '#empty_option' => t('- Select View -'), '#description' => t('Select the name of a view to use. The following view displays cannot be used: Master (default), Page and RSS. If the view or display you are looking for does not exist, create a block display in that view.'), '#required' => TRUE, '#default_value' => $item['view']['name'], '#options' => $view_options, '#ajax' => array( 'callback' => '_menu_views_form_ajax', 'wrapper' => 'menu-edit-item-wrapper', ), ); if ($item['view']['name'] && !empty($display_options[$item['view']['name']])) { $element['view']['container']['display'] = array( '#type' => 'select', '#title' => t('Display'), '#description' => t('Select the name of a view display to use. The following view displays cannot be used: Master (default), Page and RSS. If the view or display you are looking for does not exist, create a block display in that view.'), '#required' => TRUE, '#empty_option' => t('- Select Display -'), '#default_value' => $item['view']['display'], '#options' => $display_options[$item['view']['name']], '#ajax' => array( 'callback' => '_menu_views_form_ajax', 'wrapper' => 'menu-edit-item-wrapper', ), ); if ($item['view']['display']) { $element['view']['container']['arguments'] = array( '#type' => 'textfield', '#title' => t('Arguments'), '#description' => t('You can reference the node associated with this menu link by using [menu-link:node:nid].
You can reference the node associated with the parent of this menu link by using [menu-link:parent:node:nid].'), '#default_value' => $item['view']['arguments'], ); _menu_views_form_tokens_ui($element['view']['container']); // Advanced Settings. $element['view']['settings'] = array( '#type' => 'container', ); $parents = _menu_views_form_parents($form); $parents[] = 'settings'; $parents_name = $parents; $parents_name_first = array_shift($parents_name); $parents_name = $parents_name_first . '[' . implode('][', $parents_name); $settings = &$element['view']['settings']; // Wrapper Class $settings['wrapper_classes'] = array( '#type' => 'fieldset', '#title' => t('Wrapper Classes'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#parents' => $parents, ); $settings['wrapper_classes']['wrapper_classes'] = array( '#type' => 'textfield', '#description' => t('Provide any additional classes, separated by spaces.'), '#default_value' => $item['view']['settings']['wrapper_classes'], ); // View Title $settings['title'] = array( '#type' => 'fieldset', '#title' => t('View Title'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#parents' => $parents, ); $settings['title']['title'] = array( '#type' => 'checkbox', '#title' => t('Show view title'), '#default_value' => $item['view']['settings']['title'], ); $settings['title']['title_settings'] = array( '#type' => 'container', '#parents' => $parents, '#states' => array( 'visible' => array( ':input[name="' . $parents_name . '][title]"]' => array('checked' => TRUE), ), ), ); $title = &$settings['title']['title_settings']; // @FIXME // // @FIXME // // This looks like another module's variable. You'll need to rewrite this call // // to ensure that it uses the correct configuration object. // $title['title_wrapper'] = array( // '#type' => 'select', // '#title' => t('Element Wrapper'), // '#description' => t('Choose which element wrapper to use around the title of the view. Default: H3'), // '#default_value' => $item['view']['settings']['title_wrapper'], // '#options' => variable_get('views_field_rewrite_elements', array( // '' => t('- Use default -'), // '0' => t('- None -'), // 'div' => 'DIV', // 'span' => 'SPAN', // 'h1' => 'H1', // 'h2' => 'H2', // 'h3' => 'H3', // 'h4' => 'H4', // 'h5' => 'H5', // 'h6' => 'H6', // 'p' => 'P', // 'strong' => 'STRONG', // 'em' => 'EM', // )), // ); $title['title_classes'] = array( '#type' => 'textfield', '#title' => t('Classes'), '#description' => t('Provide any additional classes, separated by spaces. Title must use the element wrapper above for this take effect.'), '#default_value' => $item['view']['settings']['title_classes'], ); $title['title_override'] = array( '#type' => 'textfield', '#title' => t('Override Title'), '#description' => t('Enter custom text to override title output. Default: Title will be provided from the view.'), '#default_value' => $item['view']['settings']['title_override'], ); _menu_views_form_tokens_ui($title); // Breadcrumb $settings['breadcrumb'] = array( '#type' => 'fieldset', '#title' => t('Breadcrumb'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#parents' => $parents, ); $settings['breadcrumb']['breadcrumb'] = array( '#type' => 'checkbox', '#title' => t('Show breadcrumb link'), '#default_value' => $item['view']['settings']['breadcrumb'], ); $settings['breadcrumb']['breadcrumb_settings'] = array( '#type' => 'container', '#parents' => $parents, '#states' => array( 'visible' => array( ':input[name="' . $parents_name . '][breadcrumb]"]' => array('checked' => TRUE), ), ), ); $breadcrumb = &$settings['breadcrumb']['breadcrumb_settings']; $breadcrumb['breadcrumb_title'] = array( '#type' => 'textfield', '#title' => t('Link Title'), '#description' => t('Title to use for the breadcrumb link. Note: If there is not a title provided by the view then the breadcrumb will not be displayed. Default: View Title'), '#default_value' => $item['view']['settings']['breadcrumb_title'], ); _menu_views_form_tokens_ui($breadcrumb); $breadcrumb['breadcrumb_path'] = array( '#type' => 'textfield', '#title' => t('Link Path'), '#description' => t('Path to use for the breadcrumb link. Default: %front.', array('%front' => '')), '#default_value' => $item['view']['settings']['breadcrumb_path'], ); _menu_views_form_tokens_ui($breadcrumb); } } } /** * Ajax callback for re-rendering the attach views portion of the form. */ function _menu_views_form_ajax(&$form, &$form_state, $form_id) { if (isset($form['#node'])) { return $form['menu']['link']; } return $form; } /** * Validate handler for menu_edit_item form. */ function _menu_views_form_validate($form, &$form_state) { // Only run this validation when the form is fully submitted. if ($form_state['submitted']) { $item = _menu_views_get_item($form, $form_state); if ($item['type'] == 'view') { if (!$item['view']['name']) { form_set_error(implode('][', _menu_views_form_parents($form)) . '][name', t('The menu item type for this @type is a view. A view name is required before this @type can be saved.', array('@type' => isset($form['#node']) ? 'node' : 'menu item'))); } elseif (!$item['view']['display']) { form_set_error(implode('][', _menu_views_form_parents($form)) . '][display', t('The menu item type for this @type is a view. A view display is required before this @type can be saved.', array('@type' => isset($form['#node']) ? 'node' : 'menu item'))); } } } } /** * Submit handler for menu_edit_item form. */ function _menu_views_form_submit($form, &$form_state) { $values = &$form_state['values']; if (isset($form['#node'])) { $values = &$form_state['values']['menu']; } // Get view settings from menu item. $item = _menu_views_get_item($form, $form_state); // Remove this unecessary property from the values. if (isset($values['menu_views'])) { unset($values['menu_views']); } // We really only want to intercept the menu edit item submit handle if this is actually a view. if ($item['type'] == 'view') { // If this is a new menu item, save a quick version of the menu item and return the mlid. // The menu item will get updated with the rest of the values again shortly. if (!$values['mlid']) { $values['hidden'] = (int) !$values['enabled']; $values['options']['attributes']['title'] = $values['description']; list($values['menu_name'], $values['plid']) = explode(':', $values['parent']); if (!$values['mlid'] = menu_link_save($values)) { drupal_set_message(t('There was an error creating the menu item.'), 'error'); } } $default = _menu_views_default_values(); $item = array( // The mlid should remain constant, always use the information provided by the menu module and not this one. 'mlid' => $values['mlid'], 'type' => $item['type'], 'original_path' => $item['original_path'], 'view' => $item['view'], ); // Replace the menu views values in the menu item's options. $values['options']['menu_views'] = _menu_views_array_merge_recursive($default, $item); } } /** * Return a list of menu items that are valid possible parents for the given menu item. * * @param $menus * An array of menu names and titles, such as from menu_get_menus(). * @param $item * The menu item or the node type for which to generate a list of parents. * If $item['mlid'] == 0 then the complete tree is returned. * @param $type * The node type for which to generate a list of parents. * If $item itself is a node type then $type is ignored. * @return * An array of menu link titles keyed on the a string containing the menu name * and mlid. The list excludes the given item and its children. * * @todo This has to be turned into a #process form element callback. The * 'menu_override_parent_selector' variable is entirely superfluous. */ function _menu_views_parent_options($menus, $item, $type = '') { // The menu_links table can be practically any size and we need a way to // allow contrib modules to provide more scalable pattern choosers. // hook_form_alter is too late in itself because all the possible parents are // retrieved here, unless menu_override_parent_selector is set to TRUE. // @FIXME // // @FIXME // // This looks like another module's variable. You'll need to rewrite this call // // to ensure that it uses the correct configuration object. // if (variable_get('menu_override_parent_selector', FALSE)) { // return array(); // } $available_menus = array(); if (!is_array($item)) { // If $item is not an array then it is a node type. // Use it as $type and prepare a dummy menu item for _menu_get_options(). $type = $item; $item = array('mlid' => 0); } if (isset($item['mlid']) && $item['mlid']) { $item = menu_link_load($item['mlid']); } if (empty($type)) { // If no node type is set, use all menus given to this function. $available_menus = $menus; } else { // If a node type is set, use all available menus for this type. // @FIXME // // @FIXME // // The correct configuration object could not be determined. You'll need to // // rewrite this call manually. // $type_menus = variable_get('menu_options_' . $type, array('main-menu' => 'main-menu')); foreach ($type_menus as $menu) { $available_menus[$menu] = $menu; } } return _menu_views_get_options($menus, $available_menus, $item); } /** * Helper function to get the items of the given menu. */ function _menu_views_get_options($menus, $available_menus, $item) { // If the item has children, there is an added limit to the depth of valid parents. if (isset($item['parent_depth_limit'])) { $limit = $item['parent_depth_limit']; } else { $limit = _menu_parent_depth_limit($item); } $options = array(); foreach ($menus as $menu_name => $title) { if (isset($available_menus[$menu_name])) { // @FIXME // menu_tree_all_data() is gone in Drupal 8. To generate or work with menu trees, you'll need to // use the menu.link_tree service. // // // @see https://www.drupal.org/node/2226481 // @see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21MenuLinkTree.php/class/MenuLinkTree/8 // $tree = menu_tree_all_data($menu_name, NULL); $menu_tree = \Drupal::service('toolbar.menu_tree'); $tree = MenuLinkTreeInterface::load(); $options[$menu_name . ':0'] = '<' . $title . '>'; _menu_views_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit); } } return $options; } /** * Recursive helper function for menu_parent_options(). */ function _menu_views_parents_recurse($tree, $menu_name, $indent, &$options, $exclude, $depth_limit) { foreach ($tree as $data) { if ($data['link']['depth'] > $depth_limit) { // Don't iterate through any links on this level. break; } if ($data['link']['mlid'] != $exclude && $data['link']['hidden'] >= 0) { $item = _menu_views_get_item($data['link']); _menu_views_tokenize($item); if ($item['type'] == 'view' && $item['view']['name'] && $item['view']['display']) { $title = t($item['view']['name']) . '-' . t($item['view']['display']); $view_title = \Drupal\Component\Utility\Xss::filterAdmin($item['view']['settings']['title_override']); if (empty($view_title) && ($view = views_get_view($item['view']['name']))) { if ($view->access($item['view']['display']) && $view->set_display($item['view']['display'])) { $view->set_arguments(explode('/', $item['view']['arguments'])); $view_title = \Drupal\Component\Utility\Xss::filterAdmin($view->get_title()); } $view->destroy(); } if (!empty($view_title)) { $title = $view_title . ' (' . $title . ')'; } $title = $indent . ' [view] ' . \Drupal\Component\Utility\Unicode::truncate($title, 30, TRUE, FALSE); } else { $title = $indent . ' ' . \Drupal\Component\Utility\Unicode::truncate($data['link']['title'], 30, TRUE, FALSE); } if ($data['link']['hidden']) { $title .= ' (' . t('disabled') . ')'; } $options[$menu_name . ':' . $data['link']['mlid']] = $title; if ($data['below']) { _menu_views_parents_recurse($data['below'], $menu_name, $indent . '--', $options, $exclude, $depth_limit); } } } }