Chris@0: ' . t('About') . ''; Chris@0: $output .= '
' . t('The Menu UI module provides an interface for managing menus. A menu is a hierarchical collection of links, which can be within or external to the site, generally used for navigation. For more information, see the online documentation for the Menu UI module.', [':menu' => 'https://www.drupal.org/documentation/modules/menu/']) . '
'; Chris@0: $output .= '' . t('You can enable the newly-created block for this menu on the Block layout page.', [':blocks' => Url::fromRoute('block.admin_display')->toString()]) . '
'; Chris@0: } Chris@0: elseif ($route_name == 'entity.menu.collection' && \Drupal::moduleHandler()->moduleExists('block') && \Drupal::currentUser()->hasPermission('administer blocks')) { Chris@18: return '' . t('Each menu has a corresponding block that is managed on the Block layout page.', [':blocks' => Url::fromRoute('block.admin_display')->toString()]) . '
'; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_type_build(). Chris@0: */ Chris@0: function menu_ui_entity_type_build(array &$entity_types) { Chris@0: /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ Chris@0: $entity_types['menu'] Chris@0: ->setFormClass('add', 'Drupal\menu_ui\MenuForm') Chris@0: ->setFormClass('edit', 'Drupal\menu_ui\MenuForm') Chris@0: ->setFormClass('delete', 'Drupal\menu_ui\Form\MenuDeleteForm') Chris@0: ->setListBuilderClass('Drupal\menu_ui\MenuListBuilder') Chris@0: ->setLinkTemplate('add-form', '/admin/structure/menu/add') Chris@0: ->setLinkTemplate('delete-form', '/admin/structure/menu/manage/{menu}/delete') Chris@0: ->setLinkTemplate('edit-form', '/admin/structure/menu/manage/{menu}') Chris@0: ->setLinkTemplate('add-link-form', '/admin/structure/menu/manage/{menu}/add') Chris@0: ->setLinkTemplate('collection', '/admin/structure/menu'); Chris@0: Chris@0: if (isset($entity_types['node'])) { Chris@0: $entity_types['node']->addConstraint('MenuSettings', []); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_block_view_BASE_BLOCK_ID_alter() for 'system_menu_block'. Chris@0: */ Chris@0: function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginInterface $block) { Chris@0: if ($block->getBaseId() == 'system_menu_block') { Chris@0: $menu_name = $block->getDerivativeId(); Chris@0: $build['#contextual_links']['menu'] = [ Chris@0: 'route_parameters' => ['menu' => $menu_name], Chris@0: ]; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Helper function to create or update a menu link for a node. Chris@0: * Chris@0: * @param \Drupal\node\NodeInterface $node Chris@0: * Node entity. Chris@0: * @param array $values Chris@0: * Values for the menu link. Chris@0: */ Chris@0: function _menu_ui_node_save(NodeInterface $node, array $values) { Chris@0: /** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */ Chris@0: if (!empty($values['entity_id'])) { Chris@0: $entity = MenuLinkContent::load($values['entity_id']); Chris@0: if ($entity->isTranslatable()) { Chris@0: if (!$entity->hasTranslation($node->language()->getId())) { Chris@0: $entity = $entity->addTranslation($node->language()->getId(), $entity->toArray()); Chris@0: } Chris@0: else { Chris@0: $entity = $entity->getTranslation($node->language()->getId()); Chris@0: } Chris@0: } Chris@0: } Chris@0: else { Chris@0: // Create a new menu_link_content entity. Chris@0: $entity = MenuLinkContent::create([ Chris@0: 'link' => ['uri' => 'entity:node/' . $node->id()], Chris@0: 'langcode' => $node->language()->getId(), Chris@0: ]); Chris@0: $entity->enabled->value = 1; Chris@0: } Chris@0: $entity->title->value = trim($values['title']); Chris@0: $entity->description->value = trim($values['description']); Chris@0: $entity->menu_name->value = $values['menu_name']; Chris@0: $entity->parent->value = $values['parent']; Chris@0: $entity->weight->value = isset($values['weight']) ? $values['weight'] : 0; Chris@0: $entity->save(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the definition for a menu link for the given node. Chris@0: * Chris@0: * @param \Drupal\node\NodeInterface $node Chris@0: * The node entity. Chris@0: * Chris@0: * @return array Chris@0: * An array that contains default values for the menu link form. Chris@0: */ Chris@0: function menu_ui_get_menu_link_defaults(NodeInterface $node) { Chris@0: // Prepare the definition for the edit form. Chris@0: /** @var \Drupal\node\NodeTypeInterface $node_type */ Chris@0: $node_type = $node->type->entity; Chris@0: $menu_name = strtok($node_type->getThirdPartySetting('menu_ui', 'parent', 'main:'), ':'); Chris@0: $defaults = FALSE; Chris@0: if ($node->id()) { Chris@0: $id = FALSE; Chris@0: // Give priority to the default menu Chris@0: $type_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', ['main']); Chris@0: if (in_array($menu_name, $type_menus)) { Chris@0: $query = \Drupal::entityQuery('menu_link_content') Chris@0: ->condition('link.uri', 'node/' . $node->id()) Chris@0: ->condition('menu_name', $menu_name) Chris@0: ->sort('id', 'ASC') Chris@0: ->range(0, 1); Chris@0: $result = $query->execute(); Chris@0: Chris@0: $id = (!empty($result)) ? reset($result) : FALSE; Chris@0: } Chris@0: // Check all allowed menus if a link does not exist in the default menu. Chris@0: if (!$id && !empty($type_menus)) { Chris@0: $query = \Drupal::entityQuery('menu_link_content') Chris@0: ->condition('link.uri', 'entity:node/' . $node->id()) Chris@0: ->condition('menu_name', array_values($type_menus), 'IN') Chris@0: ->sort('id', 'ASC') Chris@0: ->range(0, 1); Chris@0: $result = $query->execute(); Chris@0: Chris@0: $id = (!empty($result)) ? reset($result) : FALSE; Chris@0: } Chris@0: if ($id) { Chris@0: $menu_link = MenuLinkContent::load($id); Chris@0: $menu_link = \Drupal::service('entity.repository')->getTranslationFromContext($menu_link); Chris@0: $defaults = [ Chris@0: 'entity_id' => $menu_link->id(), Chris@0: 'id' => $menu_link->getPluginId(), Chris@0: 'title' => $menu_link->getTitle(), Chris@0: 'title_max_length' => $menu_link->getFieldDefinitions()['title']->getSetting('max_length'), Chris@0: 'description' => $menu_link->getDescription(), Chris@17: 'description_max_length' => $menu_link->getFieldDefinitions()['description']->getSetting('max_length'), Chris@0: 'menu_name' => $menu_link->getMenuName(), Chris@0: 'parent' => $menu_link->getParentId(), Chris@0: 'weight' => $menu_link->getWeight(), Chris@0: ]; Chris@0: } Chris@0: } Chris@0: Chris@0: if (!$defaults) { Chris@0: // Get the default max_length of a menu link title from the base field Chris@0: // definition. Chris@0: $field_definitions = \Drupal::entityManager()->getBaseFieldDefinitions('menu_link_content'); Chris@0: $max_length = $field_definitions['title']->getSetting('max_length'); Chris@17: $description_max_length = $field_definitions['description']->getSetting('max_length'); Chris@0: $defaults = [ Chris@0: 'entity_id' => 0, Chris@0: 'id' => '', Chris@0: 'title' => '', Chris@0: 'title_max_length' => $max_length, Chris@0: 'description' => '', Chris@17: 'description_max_length' => $description_max_length, Chris@0: 'menu_name' => $menu_name, Chris@0: 'parent' => '', Chris@0: 'weight' => 0, Chris@0: ]; Chris@0: } Chris@0: return $defaults; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. Chris@0: * Chris@0: * Adds menu item fields to the node form. Chris@0: * Chris@0: * @see menu_ui_form_node_form_submit() Chris@0: */ Chris@0: function menu_ui_form_node_form_alter(&$form, FormStateInterface $form_state) { Chris@0: // Generate a list of possible parents (not including this link or descendants). Chris@0: // @todo This must be handled in a #process handler. Chris@0: $node = $form_state->getFormObject()->getEntity(); Chris@0: $defaults = menu_ui_get_menu_link_defaults($node); Chris@0: /** @var \Drupal\node\NodeTypeInterface $node_type */ Chris@0: $node_type = $node->type->entity; Chris@0: /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ Chris@0: $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); Chris@17: $type_menus_ids = $node_type->getThirdPartySetting('menu_ui', 'available_menus', ['main']); Chris@17: if (empty($type_menus_ids)) { Chris@17: return; Chris@17: } Chris@17: /** @var \Drupal\system\MenuInterface[] $type_menus */ Chris@17: $type_menus = Menu::loadMultiple($type_menus_ids); Chris@0: $available_menus = []; Chris@0: foreach ($type_menus as $menu) { Chris@17: $available_menus[$menu->id()] = $menu->label(); Chris@0: } Chris@0: if ($defaults['id']) { Chris@0: $default = $defaults['menu_name'] . ':' . $defaults['parent']; Chris@0: } Chris@0: else { Chris@0: $default = $node_type->getThirdPartySetting('menu_ui', 'parent', 'main:'); Chris@0: } Chris@0: $parent_element = $menu_parent_selector->parentSelectElement($default, $defaults['id'], $available_menus); Chris@0: // If no possible parent menu items were found, there is nothing to display. Chris@0: if (empty($parent_element)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $form['menu'] = [ Chris@0: '#type' => 'details', Chris@0: '#title' => t('Menu settings'), Chris@0: '#access' => \Drupal::currentUser()->hasPermission('administer menu'), Chris@0: '#open' => (bool) $defaults['id'], Chris@0: '#group' => 'advanced', Chris@0: '#attached' => [ Chris@0: 'library' => ['menu_ui/drupal.menu_ui'], Chris@0: ], Chris@0: '#tree' => TRUE, Chris@0: '#weight' => -2, Chris@0: '#attributes' => ['class' => ['menu-link-form']], Chris@0: ]; Chris@0: $form['menu']['enabled'] = [ Chris@0: '#type' => 'checkbox', Chris@0: '#title' => t('Provide a menu link'), Chris@0: '#default_value' => (int) (bool) $defaults['id'], Chris@0: ]; Chris@0: $form['menu']['link'] = [ Chris@0: '#type' => 'container', Chris@0: '#parents' => ['menu'], Chris@0: '#states' => [ Chris@0: 'invisible' => [ Chris@0: 'input[name="menu[enabled]"]' => ['checked' => FALSE], Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: Chris@0: // Populate the element with the link data. Chris@0: foreach (['id', 'entity_id'] as $key) { Chris@0: $form['menu']['link'][$key] = ['#type' => 'value', '#value' => $defaults[$key]]; Chris@0: } Chris@0: Chris@0: $form['menu']['link']['title'] = [ Chris@0: '#type' => 'textfield', Chris@0: '#title' => t('Menu link title'), Chris@0: '#default_value' => $defaults['title'], Chris@0: '#maxlength' => $defaults['title_max_length'], Chris@0: ]; Chris@0: Chris@0: $form['menu']['link']['description'] = [ Chris@17: '#type' => 'textfield', Chris@0: '#title' => t('Description'), Chris@0: '#default_value' => $defaults['description'], Chris@0: '#description' => t('Shown when hovering over the menu link.'), Chris@17: '#maxlength' => $defaults['description_max_length'], Chris@0: ]; Chris@0: Chris@0: $form['menu']['link']['menu_parent'] = $parent_element; Chris@0: $form['menu']['link']['menu_parent']['#title'] = t('Parent item'); Chris@0: $form['menu']['link']['menu_parent']['#attributes']['class'][] = 'menu-parent-select'; Chris@0: Chris@0: $form['menu']['link']['weight'] = [ Chris@0: '#type' => 'number', Chris@0: '#title' => t('Weight'), Chris@0: '#default_value' => $defaults['weight'], Chris@0: '#description' => t('Menu links with lower weights are displayed before links with higher weights.'), Chris@0: ]; Chris@0: Chris@0: foreach (array_keys($form['actions']) as $action) { Chris@0: if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') { Chris@0: $form['actions'][$action]['#submit'][] = 'menu_ui_form_node_form_submit'; Chris@0: } Chris@0: } Chris@0: Chris@0: $form['#entity_builders'][] = 'menu_ui_node_builder'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Entity form builder to add the menu information to the node. Chris@0: */ Chris@0: function menu_ui_node_builder($entity_type, NodeInterface $entity, &$form, FormStateInterface $form_state) { Chris@0: $entity->menu = $form_state->getValue('menu'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Form submission handler for menu item field on the node form. Chris@0: * Chris@0: * @see menu_ui_form_node_form_alter() Chris@0: */ Chris@0: function menu_ui_form_node_form_submit($form, FormStateInterface $form_state) { Chris@0: $node = $form_state->getFormObject()->getEntity(); Chris@0: if (!$form_state->isValueEmpty('menu')) { Chris@0: $values = $form_state->getValue('menu'); Chris@0: if (empty($values['enabled'])) { Chris@0: if ($values['entity_id']) { Chris@0: $entity = MenuLinkContent::load($values['entity_id']); Chris@0: $entity->delete(); Chris@0: } Chris@0: } Chris@0: elseif (trim($values['title'])) { Chris@0: // Decompose the selected menu parent option into 'menu_name' and 'parent', Chris@0: // if the form used the default parent selection widget. Chris@0: if (!empty($values['menu_parent'])) { Chris@0: list($menu_name, $parent) = explode(':', $values['menu_parent'], 2); Chris@0: $values['menu_name'] = $menu_name; Chris@0: $values['parent'] = $parent; Chris@0: } Chris@0: _menu_ui_node_save($node, $values); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_form_FORM_ID_alter() for \Drupal\node\NodeTypeForm. Chris@0: * Chris@0: * Adds menu options to the node type form. Chris@0: * Chris@0: * @see NodeTypeForm::form() Chris@0: * @see menu_ui_form_node_type_form_submit() Chris@0: */ Chris@0: function menu_ui_form_node_type_form_alter(&$form, FormStateInterface $form_state) { Chris@0: /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */ Chris@0: $menu_parent_selector = \Drupal::service('menu.parent_form_selector'); Chris@0: $menu_options = menu_ui_get_menus(); Chris@0: /** @var \Drupal\node\NodeTypeInterface $type */ Chris@0: $type = $form_state->getFormObject()->getEntity(); Chris@0: $form['menu'] = [ Chris@0: '#type' => 'details', Chris@0: '#title' => t('Menu settings'), Chris@0: '#attached' => [ Chris@0: 'library' => ['menu_ui/drupal.menu_ui.admin'], Chris@0: ], Chris@0: '#group' => 'additional_settings', Chris@0: ]; Chris@0: $form['menu']['menu_options'] = [ Chris@0: '#type' => 'checkboxes', Chris@0: '#title' => t('Available menus'), Chris@0: '#default_value' => $type->getThirdPartySetting('menu_ui', 'available_menus', ['main']), Chris@0: '#options' => $menu_options, Chris@0: '#description' => t('The menus available to place links in for this content type.'), Chris@0: ]; Chris@0: // @todo See if we can avoid pre-loading all options by changing the form or Chris@0: // using a #process callback. https://www.drupal.org/node/2310319 Chris@0: // To avoid an 'illegal option' error after saving the form we have to load Chris@0: // all available menu parents. Otherwise, it is not possible to dynamically Chris@0: // add options to the list using ajax. Chris@0: $options_cacheability = new CacheableMetadata(); Chris@0: $options = $menu_parent_selector->getParentSelectOptions('', NULL, $options_cacheability); Chris@0: $form['menu']['menu_parent'] = [ Chris@0: '#type' => 'select', Chris@0: '#title' => t('Default parent item'), Chris@0: '#default_value' => $type->getThirdPartySetting('menu_ui', 'parent', 'main:'), Chris@0: '#options' => $options, Chris@0: '#description' => t('Choose the menu item to be the default parent for a new link in the content authoring form.'), Chris@0: '#attributes' => ['class' => ['menu-title-select']], Chris@0: ]; Chris@0: $options_cacheability->applyTo($form['menu']['menu_parent']); Chris@0: Chris@0: $form['#validate'][] = 'menu_ui_form_node_type_form_validate'; Chris@0: $form['#entity_builders'][] = 'menu_ui_form_node_type_form_builder'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Validate handler for forms with menu options. Chris@0: * Chris@0: * @see menu_ui_form_node_type_form_alter() Chris@0: */ Chris@0: function menu_ui_form_node_type_form_validate(&$form, FormStateInterface $form_state) { Chris@0: $available_menus = array_filter($form_state->getValue('menu_options')); Chris@0: // If there is at least one menu allowed, the selected item should be in Chris@0: // one of them. Chris@0: if (count($available_menus)) { Chris@0: $menu_item_id_parts = explode(':', $form_state->getValue('menu_parent')); Chris@0: if (!in_array($menu_item_id_parts[0], $available_menus)) { Chris@0: $form_state->setErrorByName('menu_parent', t('The selected menu item is not under one of the selected menus.')); Chris@0: } Chris@0: } Chris@0: else { Chris@0: $form_state->setValue('menu_parent', ''); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Entity builder for the node type form with menu options. Chris@0: * Chris@0: * @see menu_ui_form_node_type_form_alter() Chris@0: */ Chris@0: function menu_ui_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) { Chris@0: $type->setThirdPartySetting('menu_ui', 'available_menus', array_values(array_filter($form_state->getValue('menu_options')))); Chris@0: $type->setThirdPartySetting('menu_ui', 'parent', $form_state->getValue('menu_parent')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return an associative array of the custom menus names. Chris@0: * Chris@0: * @param bool $all Chris@0: * (optional) If FALSE return only user-added menus, or if TRUE also include Chris@0: * the menus defined by the system. Defaults to TRUE. Chris@0: * Chris@0: * @return array Chris@0: * An array with the machine-readable names as the keys, and human-readable Chris@0: * titles as the values. Chris@0: */ Chris@0: function menu_ui_get_menus($all = TRUE) { Chris@0: if ($custom_menus = Menu::loadMultiple()) { Chris@0: if (!$all) { Chris@0: $custom_menus = array_diff_key($custom_menus, menu_list_system_menus()); Chris@0: } Chris@0: foreach ($custom_menus as $menu_name => $menu) { Chris@0: $custom_menus[$menu_name] = $menu->label(); Chris@0: } Chris@0: asort($custom_menus); Chris@0: } Chris@0: return $custom_menus; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_preprocess_HOOK() for block templates. Chris@0: */ Chris@0: function menu_ui_preprocess_block(&$variables) { Chris@0: if ($variables['configuration']['provider'] == 'menu_ui') { Chris@0: $variables['attributes']['role'] = 'navigation'; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_system_breadcrumb_alter(). Chris@0: */ Chris@0: function menu_ui_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) { Chris@0: // Custom breadcrumb behavior for editing menu links, we append a link to Chris@0: // the menu in which the link is found. Chris@0: if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link_plugin')) { Chris@0: if (($menu_link instanceof MenuLinkInterface)) { Chris@0: // Add a link to the menu admin screen. Chris@0: $menu = Menu::load($menu_link->getMenuName()); Chris@0: $breadcrumb->addLink(Link::createFromRoute($menu->label(), 'entity.menu.edit_form', ['menu' => $menu->id()])); Chris@0: } Chris@0: } Chris@0: }