Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/menu_ui/src/MenuForm.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\menu_ui; | |
4 | |
5 use Drupal\Component\Utility\NestedArray; | |
6 use Drupal\Core\Cache\CacheableMetadata; | |
7 use Drupal\Core\Entity\EntityForm; | |
8 use Drupal\Core\Form\FormStateInterface; | |
9 use Drupal\Core\Language\LanguageInterface; | |
10 use Drupal\Core\Link; | |
11 use Drupal\Core\Menu\MenuLinkManagerInterface; | |
12 use Drupal\Core\Menu\MenuLinkTreeElement; | |
13 use Drupal\Core\Menu\MenuLinkTreeInterface; | |
14 use Drupal\Core\Menu\MenuTreeParameters; | |
15 use Drupal\Core\Render\Element; | |
16 use Drupal\Core\Url; | |
17 use Drupal\Core\Utility\LinkGeneratorInterface; | |
18 use Symfony\Component\DependencyInjection\ContainerInterface; | |
19 | |
20 /** | |
21 * Base form for menu edit forms. | |
22 * | |
23 * @internal | |
24 */ | |
25 class MenuForm extends EntityForm { | |
26 | |
27 /** | |
28 * The menu link manager. | |
29 * | |
30 * @var \Drupal\Core\Menu\MenuLinkManagerInterface | |
31 */ | |
32 protected $menuLinkManager; | |
33 | |
34 /** | |
35 * The menu tree service. | |
36 * | |
37 * @var \Drupal\Core\Menu\MenuLinkTreeInterface | |
38 */ | |
39 protected $menuTree; | |
40 | |
41 /** | |
42 * The link generator. | |
43 * | |
44 * @var \Drupal\Core\Utility\LinkGeneratorInterface | |
45 */ | |
46 protected $linkGenerator; | |
47 | |
48 /** | |
49 * The overview tree form. | |
50 * | |
51 * @var array | |
52 */ | |
53 protected $overviewTreeForm = ['#tree' => TRUE]; | |
54 | |
55 /** | |
56 * Constructs a MenuForm object. | |
57 * | |
58 * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager | |
59 * The menu link manager. | |
60 * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree | |
61 * The menu tree service. | |
62 * @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator | |
63 * The link generator. | |
64 */ | |
65 public function __construct(MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree, LinkGeneratorInterface $link_generator) { | |
66 $this->menuLinkManager = $menu_link_manager; | |
67 $this->menuTree = $menu_tree; | |
68 $this->linkGenerator = $link_generator; | |
69 } | |
70 | |
71 /** | |
72 * {@inheritdoc} | |
73 */ | |
74 public static function create(ContainerInterface $container) { | |
75 return new static( | |
76 $container->get('plugin.manager.menu.link'), | |
77 $container->get('menu.link_tree'), | |
78 $container->get('link_generator') | |
79 ); | |
80 } | |
81 | |
82 /** | |
83 * {@inheritdoc} | |
84 */ | |
85 public function form(array $form, FormStateInterface $form_state) { | |
86 $menu = $this->entity; | |
87 | |
88 if ($this->operation == 'edit') { | |
89 $form['#title'] = $this->t('Edit menu %label', ['%label' => $menu->label()]); | |
90 } | |
91 | |
92 $form['label'] = [ | |
93 '#type' => 'textfield', | |
94 '#title' => $this->t('Title'), | |
95 '#default_value' => $menu->label(), | |
96 '#required' => TRUE, | |
97 ]; | |
98 $form['id'] = [ | |
99 '#type' => 'machine_name', | |
100 '#title' => $this->t('Menu name'), | |
101 '#default_value' => $menu->id(), | |
102 '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI, | |
103 '#description' => $this->t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'), | |
104 '#machine_name' => [ | |
105 'exists' => [$this, 'menuNameExists'], | |
106 'source' => ['label'], | |
107 'replace_pattern' => '[^a-z0-9-]+', | |
108 'replace' => '-', | |
109 ], | |
110 // A menu's machine name cannot be changed. | |
111 '#disabled' => !$menu->isNew() || $menu->isLocked(), | |
112 ]; | |
113 $form['description'] = [ | |
114 '#type' => 'textfield', | |
115 '#title' => t('Administrative summary'), | |
116 '#maxlength' => 512, | |
117 '#default_value' => $menu->getDescription(), | |
118 ]; | |
119 | |
120 $form['langcode'] = [ | |
121 '#type' => 'language_select', | |
122 '#title' => t('Menu language'), | |
123 '#languages' => LanguageInterface::STATE_ALL, | |
124 '#default_value' => $menu->language()->getId(), | |
125 ]; | |
126 | |
127 // Add menu links administration form for existing menus. | |
128 if (!$menu->isNew() || $menu->isLocked()) { | |
129 // Form API supports constructing and validating self-contained sections | |
130 // within forms, but does not allow handling the form section's submission | |
131 // equally separated yet. Therefore, we use a $form_state key to point to | |
132 // the parents of the form section. | |
133 // @see self::submitOverviewForm() | |
134 $form_state->set('menu_overview_form_parents', ['links']); | |
135 $form['links'] = []; | |
136 $form['links'] = $this->buildOverviewForm($form['links'], $form_state); | |
137 } | |
138 | |
139 return parent::form($form, $form_state); | |
140 } | |
141 | |
142 /** | |
143 * Returns whether a menu name already exists. | |
144 * | |
145 * @param string $value | |
146 * The name of the menu. | |
147 * | |
148 * @return bool | |
149 * Returns TRUE if the menu already exists, FALSE otherwise. | |
150 */ | |
151 public function menuNameExists($value) { | |
152 // Check first to see if a menu with this ID exists. | |
153 if ($this->entityTypeManager->getStorage('menu')->getQuery()->condition('id', $value)->range(0, 1)->count()->execute()) { | |
154 return TRUE; | |
155 } | |
156 | |
157 // Check for a link assigned to this menu. | |
158 return $this->menuLinkManager->menuNameInUse($value); | |
159 } | |
160 | |
161 /** | |
162 * {@inheritdoc} | |
163 */ | |
164 public function save(array $form, FormStateInterface $form_state) { | |
165 $menu = $this->entity; | |
166 $status = $menu->save(); | |
167 $edit_link = $this->entity->link($this->t('Edit')); | |
168 if ($status == SAVED_UPDATED) { | |
169 drupal_set_message($this->t('Menu %label has been updated.', ['%label' => $menu->label()])); | |
170 $this->logger('menu')->notice('Menu %label has been updated.', ['%label' => $menu->label(), 'link' => $edit_link]); | |
171 } | |
172 else { | |
173 drupal_set_message($this->t('Menu %label has been added.', ['%label' => $menu->label()])); | |
174 $this->logger('menu')->notice('Menu %label has been added.', ['%label' => $menu->label(), 'link' => $edit_link]); | |
175 } | |
176 | |
177 $form_state->setRedirectUrl($this->entity->urlInfo('edit-form')); | |
178 } | |
179 | |
180 /** | |
181 * {@inheritdoc} | |
182 */ | |
183 public function submitForm(array &$form, FormStateInterface $form_state) { | |
184 parent::submitForm($form, $form_state); | |
185 | |
186 if (!$this->entity->isNew() || $this->entity->isLocked()) { | |
187 $this->submitOverviewForm($form, $form_state); | |
188 } | |
189 } | |
190 | |
191 /** | |
192 * Form constructor to edit an entire menu tree at once. | |
193 * | |
194 * Shows for one menu the menu links accessible to the current user and | |
195 * relevant operations. | |
196 * | |
197 * This form constructor can be integrated as a section into another form. It | |
198 * relies on the following keys in $form_state: | |
199 * - menu: A menu entity. | |
200 * - menu_overview_form_parents: An array containing the parent keys to this | |
201 * form. | |
202 * Forms integrating this section should call menu_overview_form_submit() from | |
203 * their form submit handler. | |
204 */ | |
205 protected function buildOverviewForm(array &$form, FormStateInterface $form_state) { | |
206 // Ensure that menu_overview_form_submit() knows the parents of this form | |
207 // section. | |
208 if (!$form_state->has('menu_overview_form_parents')) { | |
209 $form_state->set('menu_overview_form_parents', []); | |
210 } | |
211 | |
212 $form['#attached']['library'][] = 'menu_ui/drupal.menu_ui.adminforms'; | |
213 | |
214 $tree = $this->menuTree->load($this->entity->id(), new MenuTreeParameters()); | |
215 | |
216 // We indicate that a menu administrator is running the menu access check. | |
217 $this->getRequest()->attributes->set('_menu_admin', TRUE); | |
218 $manipulators = [ | |
219 ['callable' => 'menu.default_tree_manipulators:checkAccess'], | |
220 ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], | |
221 ]; | |
222 $tree = $this->menuTree->transform($tree, $manipulators); | |
223 $this->getRequest()->attributes->set('_menu_admin', FALSE); | |
224 | |
225 // Determine the delta; the number of weights to be made available. | |
226 $count = function (array $tree) { | |
227 $sum = function ($carry, MenuLinkTreeElement $item) { | |
228 return $carry + $item->count(); | |
229 }; | |
230 return array_reduce($tree, $sum); | |
231 }; | |
232 $delta = max($count($tree), 50); | |
233 | |
234 $form['links'] = [ | |
235 '#type' => 'table', | |
236 '#theme' => 'table__menu_overview', | |
237 '#header' => [ | |
238 $this->t('Menu link'), | |
239 [ | |
240 'data' => $this->t('Enabled'), | |
241 'class' => ['checkbox'], | |
242 ], | |
243 $this->t('Weight'), | |
244 [ | |
245 'data' => $this->t('Operations'), | |
246 'colspan' => 3, | |
247 ], | |
248 ], | |
249 '#attributes' => [ | |
250 'id' => 'menu-overview', | |
251 ], | |
252 '#tabledrag' => [ | |
253 [ | |
254 'action' => 'match', | |
255 'relationship' => 'parent', | |
256 'group' => 'menu-parent', | |
257 'subgroup' => 'menu-parent', | |
258 'source' => 'menu-id', | |
259 'hidden' => TRUE, | |
260 'limit' => \Drupal::menuTree()->maxDepth() - 1, | |
261 ], | |
262 [ | |
263 'action' => 'order', | |
264 'relationship' => 'sibling', | |
265 'group' => 'menu-weight', | |
266 ], | |
267 ], | |
268 ]; | |
269 | |
270 $form['links']['#empty'] = $this->t('There are no menu links yet. <a href=":url">Add link</a>.', [ | |
271 ':url' => $this->url('entity.menu.add_link_form', ['menu' => $this->entity->id()], [ | |
272 'query' => ['destination' => $this->entity->url('edit-form')], | |
273 ]), | |
274 ]); | |
275 $links = $this->buildOverviewTreeForm($tree, $delta); | |
276 foreach (Element::children($links) as $id) { | |
277 if (isset($links[$id]['#item'])) { | |
278 $element = $links[$id]; | |
279 | |
280 $form['links'][$id]['#item'] = $element['#item']; | |
281 | |
282 // TableDrag: Mark the table row as draggable. | |
283 $form['links'][$id]['#attributes'] = $element['#attributes']; | |
284 $form['links'][$id]['#attributes']['class'][] = 'draggable'; | |
285 | |
286 // TableDrag: Sort the table row according to its existing/configured weight. | |
287 $form['links'][$id]['#weight'] = $element['#item']->link->getWeight(); | |
288 | |
289 // Add special classes to be used for tabledrag.js. | |
290 $element['parent']['#attributes']['class'] = ['menu-parent']; | |
291 $element['weight']['#attributes']['class'] = ['menu-weight']; | |
292 $element['id']['#attributes']['class'] = ['menu-id']; | |
293 | |
294 $form['links'][$id]['title'] = [ | |
295 [ | |
296 '#theme' => 'indentation', | |
297 '#size' => $element['#item']->depth - 1, | |
298 ], | |
299 $element['title'], | |
300 ]; | |
301 $form['links'][$id]['enabled'] = $element['enabled']; | |
302 $form['links'][$id]['enabled']['#wrapper_attributes']['class'] = ['checkbox', 'menu-enabled']; | |
303 | |
304 $form['links'][$id]['weight'] = $element['weight']; | |
305 | |
306 // Operations (dropbutton) column. | |
307 $form['links'][$id]['operations'] = $element['operations']; | |
308 | |
309 $form['links'][$id]['id'] = $element['id']; | |
310 $form['links'][$id]['parent'] = $element['parent']; | |
311 } | |
312 } | |
313 | |
314 return $form; | |
315 } | |
316 | |
317 /** | |
318 * Recursive helper function for buildOverviewForm(). | |
319 * | |
320 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree | |
321 * The tree retrieved by \Drupal\Core\Menu\MenuLinkTreeInterface::load(). | |
322 * @param int $delta | |
323 * The default number of menu items used in the menu weight selector is 50. | |
324 * | |
325 * @return array | |
326 * The overview tree form. | |
327 */ | |
328 protected function buildOverviewTreeForm($tree, $delta) { | |
329 $form = &$this->overviewTreeForm; | |
330 $tree_access_cacheability = new CacheableMetadata(); | |
331 foreach ($tree as $element) { | |
332 $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($element->access)); | |
333 | |
334 // Only render accessible links. | |
335 if (!$element->access->isAllowed()) { | |
336 continue; | |
337 } | |
338 | |
339 /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ | |
340 $link = $element->link; | |
341 if ($link) { | |
342 $id = 'menu_plugin_id:' . $link->getPluginId(); | |
343 $form[$id]['#item'] = $element; | |
344 $form[$id]['#attributes'] = $link->isEnabled() ? ['class' => ['menu-enabled']] : ['class' => ['menu-disabled']]; | |
345 $form[$id]['title'] = Link::fromTextAndUrl($link->getTitle(), $link->getUrlObject())->toRenderable(); | |
346 if (!$link->isEnabled()) { | |
347 $form[$id]['title']['#suffix'] = ' (' . $this->t('disabled') . ')'; | |
348 } | |
349 // @todo Remove this in https://www.drupal.org/node/2568785. | |
350 elseif ($id === 'menu_plugin_id:user.logout') { | |
351 $form[$id]['title']['#suffix'] = ' (' . $this->t('<q>Log in</q> for anonymous users') . ')'; | |
352 } | |
353 // @todo Remove this in https://www.drupal.org/node/2568785. | |
354 elseif (($url = $link->getUrlObject()) && $url->isRouted() && $url->getRouteName() == 'user.page') { | |
355 $form[$id]['title']['#suffix'] = ' (' . $this->t('logged in users only') . ')'; | |
356 } | |
357 | |
358 $form[$id]['enabled'] = [ | |
359 '#type' => 'checkbox', | |
360 '#title' => $this->t('Enable @title menu link', ['@title' => $link->getTitle()]), | |
361 '#title_display' => 'invisible', | |
362 '#default_value' => $link->isEnabled(), | |
363 ]; | |
364 $form[$id]['weight'] = [ | |
365 '#type' => 'weight', | |
366 '#delta' => $delta, | |
367 '#default_value' => $link->getWeight(), | |
368 '#title' => $this->t('Weight for @title', ['@title' => $link->getTitle()]), | |
369 '#title_display' => 'invisible', | |
370 ]; | |
371 $form[$id]['id'] = [ | |
372 '#type' => 'hidden', | |
373 '#value' => $link->getPluginId(), | |
374 ]; | |
375 $form[$id]['parent'] = [ | |
376 '#type' => 'hidden', | |
377 '#default_value' => $link->getParent(), | |
378 ]; | |
379 // Build a list of operations. | |
380 $operations = []; | |
381 $operations['edit'] = [ | |
382 'title' => $this->t('Edit'), | |
383 ]; | |
384 // Allow for a custom edit link per plugin. | |
385 $edit_route = $link->getEditRoute(); | |
386 if ($edit_route) { | |
387 $operations['edit']['url'] = $edit_route; | |
388 // Bring the user back to the menu overview. | |
389 $operations['edit']['query'] = $this->getDestinationArray(); | |
390 } | |
391 else { | |
392 // Fall back to the standard edit link. | |
393 $operations['edit'] += [ | |
394 'url' => Url::fromRoute('menu_ui.link_edit', ['menu_link_plugin' => $link->getPluginId()]), | |
395 ]; | |
396 } | |
397 // Links can either be reset or deleted, not both. | |
398 if ($link->isResettable()) { | |
399 $operations['reset'] = [ | |
400 'title' => $this->t('Reset'), | |
401 'url' => Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $link->getPluginId()]), | |
402 ]; | |
403 } | |
404 elseif ($delete_link = $link->getDeleteRoute()) { | |
405 $operations['delete']['url'] = $delete_link; | |
406 $operations['delete']['query'] = $this->getDestinationArray(); | |
407 $operations['delete']['title'] = $this->t('Delete'); | |
408 } | |
409 if ($link->isTranslatable()) { | |
410 $operations['translate'] = [ | |
411 'title' => $this->t('Translate'), | |
412 'url' => $link->getTranslateRoute(), | |
413 ]; | |
414 } | |
415 $form[$id]['operations'] = [ | |
416 '#type' => 'operations', | |
417 '#links' => $operations, | |
418 ]; | |
419 } | |
420 | |
421 if ($element->subtree) { | |
422 $this->buildOverviewTreeForm($element->subtree, $delta); | |
423 } | |
424 } | |
425 | |
426 $tree_access_cacheability | |
427 ->merge(CacheableMetadata::createFromRenderArray($form)) | |
428 ->applyTo($form); | |
429 | |
430 return $form; | |
431 } | |
432 | |
433 /** | |
434 * Submit handler for the menu overview form. | |
435 * | |
436 * This function takes great care in saving parent items first, then items | |
437 * underneath them. Saving items in the incorrect order can break the tree. | |
438 */ | |
439 protected function submitOverviewForm(array $complete_form, FormStateInterface $form_state) { | |
440 // Form API supports constructing and validating self-contained sections | |
441 // within forms, but does not allow to handle the form section's submission | |
442 // equally separated yet. Therefore, we use a $form_state key to point to | |
443 // the parents of the form section. | |
444 $parents = $form_state->get('menu_overview_form_parents'); | |
445 $input = NestedArray::getValue($form_state->getUserInput(), $parents); | |
446 $form = &NestedArray::getValue($complete_form, $parents); | |
447 | |
448 // When dealing with saving menu items, the order in which these items are | |
449 // saved is critical. If a changed child item is saved before its parent, | |
450 // the child item could be saved with an invalid path past its immediate | |
451 // parent. To prevent this, save items in the form in the same order they | |
452 // are sent, ensuring parents are saved first, then their children. | |
453 // See https://www.drupal.org/node/181126#comment-632270. | |
454 $order = is_array($input) ? array_flip(array_keys($input)) : []; | |
455 // Update our original form with the new order. | |
456 $form = array_intersect_key(array_merge($order, $form), $form); | |
457 | |
458 $fields = ['weight', 'parent', 'enabled']; | |
459 $form_links = $form['links']; | |
460 foreach (Element::children($form_links) as $id) { | |
461 if (isset($form_links[$id]['#item'])) { | |
462 $element = $form_links[$id]; | |
463 $updated_values = []; | |
464 // Update any fields that have changed in this menu item. | |
465 foreach ($fields as $field) { | |
466 if ($element[$field]['#value'] != $element[$field]['#default_value']) { | |
467 $updated_values[$field] = $element[$field]['#value']; | |
468 } | |
469 } | |
470 if ($updated_values) { | |
471 // Use the ID from the actual plugin instance since the hidden value | |
472 // in the form could be tampered with. | |
473 $this->menuLinkManager->updateDefinition($element['#item']->link->getPLuginId(), $updated_values); | |
474 } | |
475 } | |
476 } | |
477 } | |
478 | |
479 } |