MenuParentFormSelector.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. namespace Drupal\Core\Menu;
  3. use Drupal\Core\Cache\CacheableMetadata;
  4. use Drupal\Core\Entity\EntityManagerInterface;
  5. use Drupal\Component\Utility\Unicode;
  6. use Drupal\Core\StringTranslation\StringTranslationTrait;
  7. use Drupal\Core\StringTranslation\TranslationInterface;
  8. /**
  9. * Default implementation of the menu parent form selector service.
  10. *
  11. * The form selector is a list of all appropriate menu links.
  12. */
  13. class MenuParentFormSelector implements MenuParentFormSelectorInterface {
  14. use StringTranslationTrait;
  15. /**
  16. * The menu link tree service.
  17. *
  18. * @var \Drupal\Core\Menu\MenuLinkTreeInterface
  19. */
  20. protected $menuLinkTree;
  21. /**
  22. * The entity manager.
  23. *
  24. * @var \Drupal\Core\Entity\EntityManagerInterface
  25. */
  26. protected $entityManager;
  27. /**
  28. * Constructs a \Drupal\Core\Menu\MenuParentFormSelector
  29. *
  30. * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_link_tree
  31. * The menu link tree service.
  32. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  33. * The entity manager.
  34. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
  35. * The string translation service.
  36. */
  37. public function __construct(MenuLinkTreeInterface $menu_link_tree, EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
  38. $this->menuLinkTree = $menu_link_tree;
  39. $this->entityManager = $entity_manager;
  40. $this->stringTranslation = $string_translation;
  41. }
  42. /**
  43. * {@inheritdoc}
  44. */
  45. public function getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &$cacheability = NULL) {
  46. if (!isset($menus)) {
  47. $menus = $this->getMenuOptions();
  48. }
  49. $options = [];
  50. $depth_limit = $this->getParentDepthLimit($id);
  51. foreach ($menus as $menu_name => $menu_title) {
  52. $options[$menu_name . ':'] = '<' . $menu_title . '>';
  53. $parameters = new MenuTreeParameters();
  54. $parameters->setMaxDepth($depth_limit);
  55. $tree = $this->menuLinkTree->load($menu_name, $parameters);
  56. $manipulators = [
  57. ['callable' => 'menu.default_tree_manipulators:checkNodeAccess'],
  58. ['callable' => 'menu.default_tree_manipulators:checkAccess'],
  59. ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
  60. ];
  61. $tree = $this->menuLinkTree->transform($tree, $manipulators);
  62. $this->parentSelectOptionsTreeWalk($tree, $menu_name, '--', $options, $id, $depth_limit, $cacheability);
  63. }
  64. return $options;
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function parentSelectElement($menu_parent, $id = '', array $menus = NULL) {
  70. $options_cacheability = new CacheableMetadata();
  71. $options = $this->getParentSelectOptions($id, $menus, $options_cacheability);
  72. // If no options were found, there is nothing to select.
  73. if ($options) {
  74. $element = [
  75. '#type' => 'select',
  76. '#options' => $options,
  77. ];
  78. if (!isset($options[$menu_parent])) {
  79. // The requested menu parent cannot be found in the menu anymore. Try
  80. // setting it to the top level in the current menu.
  81. list($menu_name, $parent) = explode(':', $menu_parent, 2);
  82. $menu_parent = $menu_name . ':';
  83. }
  84. if (isset($options[$menu_parent])) {
  85. // Only provide the default value if it is valid among the options.
  86. $element += ['#default_value' => $menu_parent];
  87. }
  88. $options_cacheability->applyTo($element);
  89. return $element;
  90. }
  91. return [];
  92. }
  93. /**
  94. * Returns the maximum depth of the possible parents of the menu link.
  95. *
  96. * @param string $id
  97. * The menu link plugin ID or an empty value for a new link.
  98. *
  99. * @return int
  100. * The depth related to the depth of the given menu link.
  101. */
  102. protected function getParentDepthLimit($id) {
  103. if ($id) {
  104. $limit = $this->menuLinkTree->maxDepth() - $this->menuLinkTree->getSubtreeHeight($id);
  105. }
  106. else {
  107. $limit = $this->menuLinkTree->maxDepth() - 1;
  108. }
  109. return $limit;
  110. }
  111. /**
  112. * Iterates over all items in the tree to prepare the parents select options.
  113. *
  114. * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
  115. * The menu tree.
  116. * @param string $menu_name
  117. * The menu name.
  118. * @param string $indent
  119. * The indentation string used for the label.
  120. * @param array $options
  121. * The select options.
  122. * @param string $exclude
  123. * An excluded menu link.
  124. * @param int $depth_limit
  125. * The maximum depth of menu links considered for the select options.
  126. * @param \Drupal\Core\Cache\CacheableMetadata|null &$cacheability
  127. * The object to add cacheability metadata to, if not NULL.
  128. */
  129. protected function parentSelectOptionsTreeWalk(array $tree, $menu_name, $indent, array &$options, $exclude, $depth_limit, CacheableMetadata &$cacheability = NULL) {
  130. foreach ($tree as $element) {
  131. if ($element->depth > $depth_limit) {
  132. // Don't iterate through any links on this level.
  133. break;
  134. }
  135. // Collect the cacheability metadata of the access result, as well as the
  136. // link.
  137. if ($cacheability) {
  138. $cacheability = $cacheability
  139. ->merge(CacheableMetadata::createFromObject($element->access))
  140. ->merge(CacheableMetadata::createFromObject($element->link));
  141. }
  142. // Only show accessible links.
  143. if (!$element->access->isAllowed()) {
  144. continue;
  145. }
  146. $link = $element->link;
  147. if ($link->getPluginId() != $exclude) {
  148. $title = $indent . ' ' . Unicode::truncate($link->getTitle(), 30, TRUE, FALSE);
  149. if (!$link->isEnabled()) {
  150. $title .= ' (' . $this->t('disabled') . ')';
  151. }
  152. $options[$menu_name . ':' . $link->getPluginId()] = $title;
  153. if (!empty($element->subtree)) {
  154. $this->parentSelectOptionsTreeWalk($element->subtree, $menu_name, $indent . '--', $options, $exclude, $depth_limit, $cacheability);
  155. }
  156. }
  157. }
  158. }
  159. /**
  160. * Gets a list of menu names for use as options.
  161. *
  162. * @param array $menu_names
  163. * (optional) Array of menu names to limit the options, or NULL to load all.
  164. *
  165. * @return array
  166. * Keys are menu names (ids) values are the menu labels.
  167. */
  168. protected function getMenuOptions(array $menu_names = NULL) {
  169. $menus = $this->entityManager->getStorage('menu')->loadMultiple($menu_names);
  170. $options = [];
  171. /** @var \Drupal\system\MenuInterface[] $menus */
  172. foreach ($menus as $menu) {
  173. $options[$menu->id()] = $menu->label();
  174. }
  175. return $options;
  176. }
  177. }