3 namespace Drupal\Core\Menu;
5 use Drupal\Core\Cache\CacheableMetadata;
6 use Drupal\Core\Entity\EntityManagerInterface;
7 use Drupal\Component\Utility\Unicode;
8 use Drupal\Core\StringTranslation\StringTranslationTrait;
9 use Drupal\Core\StringTranslation\TranslationInterface;
12 * Default implementation of the menu parent form selector service.
14 * The form selector is a list of all appropriate menu links.
16 class MenuParentFormSelector implements MenuParentFormSelectorInterface {
17 use StringTranslationTrait;
20 * The menu link tree service.
22 * @var \Drupal\Core\Menu\MenuLinkTreeInterface
24 protected $menuLinkTree;
29 * @var \Drupal\Core\Entity\EntityManagerInterface
31 protected $entityManager;
34 * Constructs a \Drupal\Core\Menu\MenuParentFormSelector
36 * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
37 * The menu link tree service.
38 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
40 * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
41 * The string translation service.
43 public function __construct(MenuLinkTreeInterface $menu_link_tree, EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
44 $this->menuLinkTree = $menu_link_tree;
45 $this->entityManager = $entity_manager;
46 $this->stringTranslation = $string_translation;
52 public function getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &$cacheability = NULL) {
54 $menus = $this->getMenuOptions();
58 $depth_limit = $this->getParentDepthLimit($id);
59 foreach ($menus as $menu_name => $menu_title) {
60 $options[$menu_name . ':'] = '<' . $menu_title . '>';
62 $parameters = new MenuTreeParameters();
63 $parameters->setMaxDepth($depth_limit);
64 $tree = $this->menuLinkTree->load($menu_name, $parameters);
66 ['callable' => 'menu.default_tree_manipulators:checkNodeAccess'],
67 ['callable' => 'menu.default_tree_manipulators:checkAccess'],
68 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
70 $tree = $this->menuLinkTree->transform($tree, $manipulators);
71 $this->parentSelectOptionsTreeWalk($tree, $menu_name, '--', $options, $id, $depth_limit, $cacheability);
79 public function parentSelectElement($menu_parent, $id = '', array $menus = NULL) {
80 $options_cacheability = new CacheableMetadata();
81 $options = $this->getParentSelectOptions($id, $menus, $options_cacheability);
82 // If no options were found, there is nothing to select.
86 '#options' => $options,
88 if (!isset($options[$menu_parent])) {
89 // The requested menu parent cannot be found in the menu anymore. Try
90 // setting it to the top level in the current menu.
91 list($menu_name, $parent) = explode(':', $menu_parent, 2);
92 $menu_parent = $menu_name . ':';
94 if (isset($options[$menu_parent])) {
95 // Only provide the default value if it is valid among the options.
96 $element += ['#default_value' => $menu_parent];
98 $options_cacheability->applyTo($element);
105 * Returns the maximum depth of the possible parents of the menu link.
108 * The menu link plugin ID or an empty value for a new link.
111 * The depth related to the depth of the given menu link.
113 protected function getParentDepthLimit($id) {
115 $limit = $this->menuLinkTree->maxDepth() - $this->menuLinkTree->getSubtreeHeight($id);
118 $limit = $this->menuLinkTree->maxDepth() - 1;
124 * Iterates over all items in the tree to prepare the parents select options.
126 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
128 * @param string $menu_name
130 * @param string $indent
131 * The indentation string used for the label.
132 * @param array $options
133 * The select options.
134 * @param string $exclude
135 * An excluded menu link.
136 * @param int $depth_limit
137 * The maximum depth of menu links considered for the select options.
138 * @param \Drupal\Core\Cache\CacheableMetadata|null &$cacheability
139 * The object to add cacheability metadata to, if not NULL.
141 protected function parentSelectOptionsTreeWalk(array $tree, $menu_name, $indent, array &$options, $exclude, $depth_limit, CacheableMetadata &$cacheability = NULL) {
142 foreach ($tree as $element) {
143 if ($element->depth > $depth_limit) {
144 // Don't iterate through any links on this level.
148 // Collect the cacheability metadata of the access result, as well as the
151 $cacheability = $cacheability
152 ->merge(CacheableMetadata::createFromObject($element->access))
153 ->merge(CacheableMetadata::createFromObject($element->link));
156 // Only show accessible links.
157 if (!$element->access->isAllowed()) {
161 $link = $element->link;
162 if ($link->getPluginId() != $exclude) {
163 $title = $indent . ' ' . Unicode::truncate($link->getTitle(), 30, TRUE, FALSE);
164 if (!$link->isEnabled()) {
165 $title .= ' (' . $this->t('disabled') . ')';
167 $options[$menu_name . ':' . $link->getPluginId()] = $title;
168 if (!empty($element->subtree)) {
169 $this->parentSelectOptionsTreeWalk($element->subtree, $menu_name, $indent . '--', $options, $exclude, $depth_limit, $cacheability);
176 * Gets a list of menu names for use as options.
178 * @param array $menu_names
179 * (optional) Array of menu names to limit the options, or NULL to load all.
182 * Keys are menu names (ids) values are the menu labels.
184 protected function getMenuOptions(array $menu_names = NULL) {
185 $menus = $this->entityManager->getStorage('menu')->loadMultiple($menu_names);
187 /** @var \Drupal\system\MenuInterface[] $menus */
188 foreach ($menus as $menu) {
189 $options[$menu->id()] = $menu->label();