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