annotate core/lib/Drupal/Core/Menu/MenuLinkManager.php @ 19:fa3358dc1485 tip

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