PageRenderTime 41ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Menu/MenuLinkManager.php

https://gitlab.com/geeta7/drupal
PHP | 422 lines | 196 code | 40 blank | 186 comment | 18 complexity | 20e721e34163c1a48b8711f4c5f699d5 MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\Core\Menu\MenuLinkManager.
  5. */
  6. namespace Drupal\Core\Menu;
  7. use Drupal\Component\Plugin\Exception\PluginException;
  8. use Drupal\Component\Plugin\Exception\PluginNotFoundException;
  9. use Drupal\Component\Utility\NestedArray;
  10. use Drupal\Core\Extension\ModuleHandlerInterface;
  11. use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
  12. use Drupal\Core\Plugin\Discovery\YamlDiscovery;
  13. use Drupal\Core\Plugin\Factory\ContainerFactory;
  14. /**
  15. * Manages discovery, instantiation, and tree building of menu link plugins.
  16. *
  17. * This manager finds plugins that are rendered as menu links.
  18. */
  19. class MenuLinkManager implements MenuLinkManagerInterface {
  20. /**
  21. * Provides some default values for the definition of all menu link plugins.
  22. *
  23. * @todo Decide how to keep these field definitions in sync.
  24. * https://www.drupal.org/node/2302085
  25. *
  26. * @var array
  27. */
  28. protected $defaults = array(
  29. // (required) The name of the menu for this link.
  30. 'menu_name' => 'tools',
  31. // (required) The name of the route this links to, unless it's external.
  32. 'route_name' => '',
  33. // Parameters for route variables when generating a link.
  34. 'route_parameters' => array(),
  35. // The external URL if this link has one (required if route_name is empty).
  36. 'url' => '',
  37. // The static title for the menu link. If this came from a YAML definition
  38. // or other safe source this may be a TranslatableMarkup object.
  39. 'title' => '',
  40. // The description. If this came from a YAML definition or other safe source
  41. // this may be be a TranslatableMarkup object.
  42. 'description' => '',
  43. // The plugin ID of the parent link (or NULL for a top-level link).
  44. 'parent' => '',
  45. // The weight of the link.
  46. 'weight' => 0,
  47. // The default link options.
  48. 'options' => array(),
  49. 'expanded' => 0,
  50. 'enabled' => 1,
  51. // The name of the module providing this link.
  52. 'provider' => '',
  53. 'metadata' => array(),
  54. // Default class for local task implementations.
  55. 'class' => 'Drupal\Core\Menu\MenuLinkDefault',
  56. 'form_class' => 'Drupal\Core\Menu\Form\MenuLinkDefaultForm',
  57. // The plugin ID. Set by the plugin system based on the top-level YAML key.
  58. 'id' => '',
  59. );
  60. /**
  61. * The object that discovers plugins managed by this manager.
  62. *
  63. * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
  64. */
  65. protected $discovery;
  66. /**
  67. * The object that instantiates plugins managed by this manager.
  68. *
  69. * @var \Drupal\Component\Plugin\Factory\FactoryInterface
  70. */
  71. protected $factory;
  72. /**
  73. * The menu link tree storage.
  74. *
  75. * @var \Drupal\Core\Menu\MenuTreeStorageInterface
  76. */
  77. protected $treeStorage;
  78. /**
  79. * Service providing overrides for static links.
  80. *
  81. * @var \Drupal\Core\Menu\StaticMenuLinkOverridesInterface
  82. */
  83. protected $overrides;
  84. /**
  85. * The module handler.
  86. *
  87. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  88. */
  89. protected $moduleHandler;
  90. /**
  91. * Constructs a \Drupal\Core\Menu\MenuLinkManager object.
  92. *
  93. * @param \Drupal\Core\Menu\MenuTreeStorageInterface $tree_storage
  94. * The menu link tree storage.
  95. * @param \Drupal\Core\Menu\StaticMenuLinkOverridesInterface $overrides
  96. * The service providing overrides for static links.
  97. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  98. * The module handler.
  99. */
  100. public function __construct(MenuTreeStorageInterface $tree_storage, StaticMenuLinkOverridesInterface $overrides, ModuleHandlerInterface $module_handler) {
  101. $this->treeStorage = $tree_storage;
  102. $this->overrides = $overrides;
  103. $this->moduleHandler = $module_handler;
  104. }
  105. /**
  106. * Performs extra processing on plugin definitions.
  107. *
  108. * By default we add defaults for the type to the definition. If a type has
  109. * additional processing logic, the logic can be added by replacing or
  110. * extending this method.
  111. *
  112. * @param array $definition
  113. * The definition to be processed and modified by reference.
  114. * @param $plugin_id
  115. * The ID of the plugin this definition is being used for.
  116. */
  117. protected function processDefinition(array &$definition, $plugin_id) {
  118. $definition = NestedArray::mergeDeep($this->defaults, $definition);
  119. // Typecast so NULL, no parent, will be an empty string since the parent ID
  120. // should be a string.
  121. $definition['parent'] = (string) $definition['parent'];
  122. $definition['id'] = $plugin_id;
  123. }
  124. /**
  125. * Gets the plugin discovery.
  126. *
  127. * @return \Drupal\Component\Plugin\Discovery\DiscoveryInterface
  128. */
  129. protected function getDiscovery() {
  130. if (!isset($this->discovery)) {
  131. $yaml_discovery = new YamlDiscovery('links.menu', $this->moduleHandler->getModuleDirectories());
  132. $yaml_discovery->addTranslatableProperty('title', 'title_context');
  133. $yaml_discovery->addTranslatableProperty('description', 'description_context');
  134. $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
  135. }
  136. return $this->discovery;
  137. }
  138. /**
  139. * Gets the plugin factory.
  140. *
  141. * @return \Drupal\Component\Plugin\Factory\FactoryInterface
  142. */
  143. protected function getFactory() {
  144. if (!isset($this->factory)) {
  145. $this->factory = new ContainerFactory($this);
  146. }
  147. return $this->factory;
  148. }
  149. /**
  150. * {@inheritdoc}
  151. */
  152. public function getDefinitions() {
  153. // Since this function is called rarely, instantiate the discovery here.
  154. $definitions = $this->getDiscovery()->getDefinitions();
  155. $this->moduleHandler->alter('menu_links_discovered', $definitions);
  156. foreach ($definitions as $plugin_id => &$definition) {
  157. $definition['id'] = $plugin_id;
  158. $this->processDefinition($definition, $plugin_id);
  159. }
  160. // If this plugin was provided by a module that does not exist, remove the
  161. // plugin definition.
  162. // @todo Address what to do with an invalid plugin.
  163. // https://www.drupal.org/node/2302623
  164. foreach ($definitions as $plugin_id => $plugin_definition) {
  165. if (!empty($plugin_definition['provider']) && !$this->moduleHandler->moduleExists($plugin_definition['provider'])) {
  166. unset($definitions[$plugin_id]);
  167. }
  168. }
  169. return $definitions;
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function rebuild() {
  175. $definitions = $this->getDefinitions();
  176. // Apply overrides from config.
  177. $overrides = $this->overrides->loadMultipleOverrides(array_keys($definitions));
  178. foreach ($overrides as $id => $changes) {
  179. if (!empty($definitions[$id])) {
  180. $definitions[$id] = $changes + $definitions[$id];
  181. }
  182. }
  183. $this->treeStorage->rebuild($definitions);
  184. }
  185. /**
  186. * {@inheritdoc}
  187. */
  188. public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
  189. $definition = $this->treeStorage->load($plugin_id);
  190. if (empty($definition) && $exception_on_invalid) {
  191. throw new PluginNotFoundException($plugin_id);
  192. }
  193. return $definition;
  194. }
  195. /**
  196. * {@inheritdoc}
  197. */
  198. public function hasDefinition($plugin_id) {
  199. return (bool) $this->getDefinition($plugin_id, FALSE);
  200. }
  201. /**
  202. * Returns a pre-configured menu link plugin instance.
  203. *
  204. * @param string $plugin_id
  205. * The ID of the plugin being instantiated.
  206. * @param array $configuration
  207. * An array of configuration relevant to the plugin instance.
  208. *
  209. * @return \Drupal\Core\Menu\MenuLinkInterface
  210. * A menu link instance.
  211. *
  212. * @throws \Drupal\Component\Plugin\Exception\PluginException
  213. * If the instance cannot be created, such as if the ID is invalid.
  214. */
  215. public function createInstance($plugin_id, array $configuration = array()) {
  216. return $this->getFactory()->createInstance($plugin_id, $configuration);
  217. }
  218. /**
  219. * {@inheritdoc}
  220. */
  221. public function getInstance(array $options) {
  222. if (isset($options['id'])) {
  223. return $this->createInstance($options['id']);
  224. }
  225. }
  226. /**
  227. * {@inheritdoc}
  228. */
  229. public function deleteLinksInMenu($menu_name) {
  230. foreach ($this->treeStorage->loadByProperties(array('menu_name' => $menu_name)) as $plugin_id => $definition) {
  231. $instance = $this->createInstance($plugin_id);
  232. if ($instance->isDeletable()) {
  233. $this->deleteInstance($instance, TRUE);
  234. }
  235. elseif ($instance->isResettable()) {
  236. $new_instance = $this->resetInstance($instance);
  237. $affected_menus[$new_instance->getMenuName()] = $new_instance->getMenuName();
  238. }
  239. }
  240. }
  241. /**
  242. * Deletes a specific instance.
  243. *
  244. * @param \Drupal\Core\Menu\MenuLinkInterface $instance
  245. * The plugin instance to be deleted.
  246. * @param bool $persist
  247. * If TRUE, calls MenuLinkInterface::deleteLink() on the instance.
  248. *
  249. * @throws \Drupal\Component\Plugin\Exception\PluginException
  250. * If the plugin instance does not support deletion.
  251. */
  252. protected function deleteInstance(MenuLinkInterface $instance, $persist) {
  253. $id = $instance->getPluginId();
  254. if ($instance->isDeletable()) {
  255. if ($persist) {
  256. $instance->deleteLink();
  257. }
  258. }
  259. else {
  260. throw new PluginException("Menu link plugin with ID '$id' does not support deletion");
  261. }
  262. $this->treeStorage->delete($id);
  263. }
  264. /**
  265. * {@inheritdoc}
  266. */
  267. public function removeDefinition($id, $persist = TRUE) {
  268. $definition = $this->treeStorage->load($id);
  269. // It's possible the definition has already been deleted, or doesn't exist.
  270. if ($definition) {
  271. $instance = $this->createInstance($id);
  272. $this->deleteInstance($instance, $persist);
  273. }
  274. }
  275. /**
  276. * {@inheritdoc}
  277. */
  278. public function menuNameInUse($menu_name) {
  279. $this->treeStorage->menuNameInUse($menu_name);
  280. }
  281. /**
  282. * {@inheritdoc}
  283. */
  284. public function countMenuLinks($menu_name = NULL) {
  285. return $this->treeStorage->countMenuLinks($menu_name);
  286. }
  287. /**
  288. * {@inheritdoc}
  289. */
  290. public function getParentIds($id) {
  291. if ($this->getDefinition($id, FALSE)) {
  292. return $this->treeStorage->getRootPathIds($id);
  293. }
  294. return NULL;
  295. }
  296. /**
  297. * {@inheritdoc}
  298. */
  299. public function getChildIds($id) {
  300. if ($this->getDefinition($id, FALSE)) {
  301. return $this->treeStorage->getAllChildIds($id);
  302. }
  303. return NULL;
  304. }
  305. /**
  306. * {@inheritdoc}
  307. */
  308. public function loadLinksByRoute($route_name, array $route_parameters = array(), $menu_name = NULL) {
  309. $instances = array();
  310. $loaded = $this->treeStorage->loadByRoute($route_name, $route_parameters, $menu_name);
  311. foreach ($loaded as $plugin_id => $definition) {
  312. $instances[$plugin_id] = $this->createInstance($plugin_id);
  313. }
  314. return $instances;
  315. }
  316. /**
  317. * {@inheritdoc}
  318. */
  319. public function addDefinition($id, array $definition) {
  320. if ($this->treeStorage->load($id)) {
  321. throw new PluginException("The menu link ID $id already exists as a plugin definition");
  322. }
  323. elseif ($id === '') {
  324. throw new PluginException("The menu link ID cannot be empty");
  325. }
  326. // Add defaults, so there is no requirement to specify everything.
  327. $this->processDefinition($definition, $id);
  328. // Store the new link in the tree.
  329. $this->treeStorage->save($definition);
  330. return $this->createInstance($id);
  331. }
  332. /**
  333. * {@inheritdoc}
  334. */
  335. public function updateDefinition($id, array $new_definition_values, $persist = TRUE) {
  336. $instance = $this->createInstance($id);
  337. if ($instance) {
  338. $new_definition_values['id'] = $id;
  339. $changed_definition = $instance->updateLink($new_definition_values, $persist);
  340. $this->treeStorage->save($changed_definition);
  341. }
  342. return $instance;
  343. }
  344. /**
  345. * {@inheritdoc}
  346. */
  347. public function resetLink($id) {
  348. $instance = $this->createInstance($id);
  349. $new_instance = $this->resetInstance($instance);
  350. return $new_instance;
  351. }
  352. /**
  353. * Resets the menu link to its default settings.
  354. *
  355. * @param \Drupal\Core\Menu\MenuLinkInterface $instance
  356. * The menu link which should be reset.
  357. *
  358. * @return \Drupal\Core\Menu\MenuLinkInterface
  359. * The reset menu link.
  360. *
  361. * @throws \Drupal\Component\Plugin\Exception\PluginException
  362. * Thrown when the menu link is not resettable.
  363. */
  364. protected function resetInstance(MenuLinkInterface $instance) {
  365. $id = $instance->getPluginId();
  366. if (!$instance->isResettable()) {
  367. throw new PluginException("Menu link $id is not resettable");
  368. }
  369. // Get the original data from disk, reset the override and re-save the menu
  370. // tree for this link.
  371. $definition = $this->getDefinitions()[$id];
  372. $this->overrides->deleteOverride($id);
  373. $this->treeStorage->save($definition);
  374. return $this->createInstance($id);
  375. }
  376. /**
  377. * {@inheritdoc}
  378. */
  379. public function resetDefinitions() {
  380. $this->treeStorage->resetDefinitions();
  381. }
  382. }