Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Menu / MenuParentFormSelector.php
1 <?php
2
3 namespace Drupal\Core\Menu;
4
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;
10
11 /**
12  * Default implementation of the menu parent form selector service.
13  *
14  * The form selector is a list of all appropriate menu links.
15  */
16 class MenuParentFormSelector implements MenuParentFormSelectorInterface {
17   use StringTranslationTrait;
18
19   /**
20    * The menu link tree service.
21    *
22    * @var \Drupal\Core\Menu\MenuLinkTreeInterface
23    */
24   protected $menuLinkTree;
25
26   /**
27    * The entity manager.
28    *
29    * @var \Drupal\Core\Entity\EntityManagerInterface
30    */
31   protected $entityManager;
32
33   /**
34    * Constructs a \Drupal\Core\Menu\MenuParentFormSelector
35    *
36    * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
37    *   The menu link tree service.
38    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
39    *   The entity manager.
40    * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
41    *   The string translation service.
42    */
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;
47   }
48
49   /**
50    * {@inheritdoc}
51    */
52   public function getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &$cacheability = NULL) {
53     if (!isset($menus)) {
54       $menus = $this->getMenuOptions();
55     }
56
57     $options = [];
58     $depth_limit = $this->getParentDepthLimit($id);
59     foreach ($menus as $menu_name => $menu_title) {
60       $options[$menu_name . ':'] = '<' . $menu_title . '>';
61
62       $parameters = new MenuTreeParameters();
63       $parameters->setMaxDepth($depth_limit);
64       $tree = $this->menuLinkTree->load($menu_name, $parameters);
65       $manipulators = [
66         ['callable' => 'menu.default_tree_manipulators:checkNodeAccess'],
67         ['callable' => 'menu.default_tree_manipulators:checkAccess'],
68         ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
69       ];
70       $tree = $this->menuLinkTree->transform($tree, $manipulators);
71       $this->parentSelectOptionsTreeWalk($tree, $menu_name, '--', $options, $id, $depth_limit, $cacheability);
72     }
73     return $options;
74   }
75
76   /**
77    * {@inheritdoc}
78    */
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.
83     if ($options) {
84       $element = [
85         '#type' => 'select',
86         '#options' => $options,
87       ];
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 . ':';
93       }
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];
97       }
98       $options_cacheability->applyTo($element);
99       return $element;
100     }
101     return [];
102   }
103
104   /**
105    * Returns the maximum depth of the possible parents of the menu link.
106    *
107    * @param string $id
108    *   The menu link plugin ID or an empty value for a new link.
109    *
110    * @return int
111    *   The depth related to the depth of the given menu link.
112    */
113   protected function getParentDepthLimit($id) {
114     if ($id) {
115       $limit = $this->menuLinkTree->maxDepth() - $this->menuLinkTree->getSubtreeHeight($id);
116     }
117     else {
118       $limit = $this->menuLinkTree->maxDepth() - 1;
119     }
120     return $limit;
121   }
122
123   /**
124    * Iterates over all items in the tree to prepare the parents select options.
125    *
126    * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
127    *   The menu tree.
128    * @param string $menu_name
129    *   The 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.
140    */
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.
145         break;
146       }
147
148       // Collect the cacheability metadata of the access result, as well as the
149       // link.
150       if ($cacheability) {
151         $cacheability = $cacheability
152           ->merge(CacheableMetadata::createFromObject($element->access))
153           ->merge(CacheableMetadata::createFromObject($element->link));
154       }
155
156       // Only show accessible links.
157       if (!$element->access->isAllowed()) {
158         continue;
159       }
160
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') . ')';
166         }
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);
170         }
171       }
172     }
173   }
174
175   /**
176    * Gets a list of menu names for use as options.
177    *
178    * @param array $menu_names
179    *   (optional) Array of menu names to limit the options, or NULL to load all.
180    *
181    * @return array
182    *   Keys are menu names (ids) values are the menu labels.
183    */
184   protected function getMenuOptions(array $menu_names = NULL) {
185     $menus = $this->entityManager->getStorage('menu')->loadMultiple($menu_names);
186     $options = [];
187     /** @var \Drupal\system\MenuInterface[] $menus */
188     foreach ($menus as $menu) {
189       $options[$menu->id()] = $menu->label();
190     }
191     return $options;
192   }
193
194 }