345b779092658c991f73df8165623469e2155f46
[yaffs-website] / web / core / modules / menu_ui / tests / src / Functional / MenuUiTest.php
1 <?php
2
3 namespace Drupal\Tests\menu_ui\Functional;
4
5 use Drupal\block\Entity\Block;
6 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
7 use Drupal\Core\Menu\MenuLinkInterface;
8 use Drupal\Core\Url;
9 use Drupal\menu_link_content\Entity\MenuLinkContent;
10 use Drupal\system\Entity\Menu;
11 use Drupal\node\Entity\Node;
12 use Drupal\node\NodeInterface;
13 use Drupal\Tests\BrowserTestBase;
14 use Drupal\Tests\menu_ui\Traits\MenuUiTrait;
15
16 /**
17  * Add a custom menu, add menu links to the custom menu and Tools menu, check
18  * their data, and delete them using the UI.
19  *
20  * @group menu_ui
21  */
22 class MenuUiTest extends BrowserTestBase {
23
24   use MenuUiTrait;
25
26   /**
27    * Modules to enable.
28    *
29    * @var array
30    */
31   protected static $modules = [
32     'block',
33     'contextual',
34     'help',
35     'menu_link_content',
36     'menu_ui',
37     'node',
38     'path',
39     'test_page_test',
40   ];
41
42   /**
43    * A user with administration rights.
44    *
45    * @var \Drupal\user\UserInterface
46    */
47   protected $adminUser;
48
49   /**
50    * An authenticated user.
51    *
52    * @var \Drupal\user\UserInterface
53    */
54   protected $authenticatedUser;
55
56   /**
57    * Array of placed menu blocks keyed by block ID.
58    *
59    * @var array
60    */
61   protected $blockPlacements;
62
63   /**
64    * A test menu.
65    *
66    * @var \Drupal\system\Entity\Menu
67    */
68   protected $menu;
69
70   /**
71    * An array of test menu links.
72    *
73    * @var \Drupal\menu_link_content\MenuLinkContentInterface[]
74    */
75   protected $items;
76
77   protected function setUp() {
78     parent::setUp();
79
80     $this->drupalPlaceBlock('page_title_block');
81
82     $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
83
84     // Create users.
85     $this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer blocks', 'administer menu', 'create article content']);
86     $this->authenticatedUser = $this->drupalCreateUser([]);
87   }
88
89   /**
90    * Tests menu functionality using the admin and user interfaces.
91    */
92   public function testMenu() {
93     // Log in the user.
94     $this->drupalLogin($this->adminUser);
95     $this->items = [];
96
97     $this->menu = $this->addCustomMenu();
98     $this->doMenuTests();
99     $this->doTestMenuBlock();
100     $this->addInvalidMenuLink();
101     $this->addCustomMenuCRUD();
102
103     // Verify that the menu links rebuild is idempotent and leaves the same
104     // number of links in the table.
105     /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
106     $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
107     $before_count = $menu_link_manager->countMenuLinks(NULL);
108     $menu_link_manager->rebuild();
109     $after_count = $menu_link_manager->countMenuLinks(NULL);
110     $this->assertIdentical($before_count, $after_count, 'MenuLinkManager::rebuild() does not add more links');
111     // Do standard user tests.
112     // Log in the user.
113     $this->drupalLogin($this->authenticatedUser);
114     $this->verifyAccess(403);
115
116     foreach ($this->items as $item) {
117       // Menu link URIs are stored as 'internal:/node/$nid'.
118       $node = Node::load(str_replace('internal:/node/', '', $item->link->uri));
119       $this->verifyMenuLink($item, $node);
120     }
121
122     // Log in the administrator.
123     $this->drupalLogin($this->adminUser);
124
125     // Verify delete link exists and reset link does not exist.
126     $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id());
127     $this->assertLinkByHref(Url::fromRoute('entity.menu_link_content.delete_form', ['menu_link_content' => $this->items[0]->id()])->toString());
128     $this->assertNoLinkByHref(Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $this->items[0]->getPluginId()])->toString());
129     // Check delete and reset access.
130     $this->drupalGet('admin/structure/menu/item/' . $this->items[0]->id() . '/delete');
131     $this->assertResponse(200);
132     $this->drupalGet('admin/structure/menu/link/' . $this->items[0]->getPluginId() . '/reset');
133     $this->assertResponse(403);
134
135     // Delete menu links.
136     foreach ($this->items as $item) {
137       $this->deleteMenuLink($item);
138     }
139
140     // Delete custom menu.
141     $this->deleteCustomMenu();
142
143     // Modify and reset a standard menu link.
144     $instance = $this->getStandardMenuLink();
145     $old_weight = $instance->getWeight();
146     // Edit the static menu link.
147     $edit = [];
148     $edit['weight'] = 10;
149     $id = $instance->getPluginId();
150     $this->drupalPostForm("admin/structure/menu/link/$id/edit", $edit, t('Save'));
151     $this->assertResponse(200);
152     $this->assertText('The menu link has been saved.');
153     $menu_link_manager->resetDefinitions();
154
155     $instance = $menu_link_manager->createInstance($instance->getPluginId());
156     $this->assertEqual($edit['weight'], $instance->getWeight(), 'Saving an existing link updates the weight.');
157     $this->resetMenuLink($instance, $old_weight);
158   }
159
160   /**
161    * Adds a custom menu using CRUD functions.
162    */
163   public function addCustomMenuCRUD() {
164     // Add a new custom menu.
165     $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
166     $label = $this->randomMachineName(16);
167
168     $menu = Menu::create([
169       'id' => $menu_name,
170       'label' => $label,
171       'description' => 'Description text',
172     ]);
173     $menu->save();
174
175     // Assert the new menu.
176     $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
177     $this->assertRaw($label, 'Custom menu was added.');
178
179     // Edit the menu.
180     $new_label = $this->randomMachineName(16);
181     $menu->set('label', $new_label);
182     $menu->save();
183     $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
184     $this->assertRaw($new_label, 'Custom menu was edited.');
185   }
186
187   /**
188    * Creates a custom menu.
189    *
190    * @return \Drupal\system\Entity\Menu
191    *   The custom menu that has been created.
192    */
193   public function addCustomMenu() {
194     // Try adding a menu using a menu_name that is too long.
195     $this->drupalGet('admin/structure/menu/add');
196     $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1);
197     $label = $this->randomMachineName(16);
198     $edit = [
199       'id' => $menu_name,
200       'description' => '',
201       'label' => $label,
202     ];
203     $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
204
205     // Verify that using a menu_name that is too long results in a validation
206     // message.
207     $this->assertRaw(t('@name cannot be longer than %max characters but is currently %length characters long.', [
208       '@name' => t('Menu name'),
209       '%max' => MENU_MAX_MENU_NAME_LENGTH_UI,
210       '%length' => mb_strlen($menu_name),
211     ]));
212
213     // Change the menu_name so it no longer exceeds the maximum length.
214     $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
215     $edit['id'] = $menu_name;
216     $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
217
218     // Verify that no validation error is given for menu_name length.
219     $this->assertNoRaw(t('@name cannot be longer than %max characters but is currently %length characters long.', [
220       '@name' => t('Menu name'),
221       '%max' => MENU_MAX_MENU_NAME_LENGTH_UI,
222       '%length' => mb_strlen($menu_name),
223     ]));
224     // Verify that the confirmation message is displayed.
225     $this->assertRaw(t('Menu %label has been added.', ['%label' => $label]));
226     $this->drupalGet('admin/structure/menu');
227     $this->assertText($label, 'Menu created');
228
229     // Confirm that the custom menu block is available.
230     $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
231     $this->clickLink('Place block');
232     $this->assertText($label);
233
234     // Enable the block.
235     $block = $this->drupalPlaceBlock('system_menu_block:' . $menu_name);
236     $this->blockPlacements[$menu_name] = $block->id();
237     return Menu::load($menu_name);
238   }
239
240   /**
241    * Deletes the locally stored custom menu.
242    *
243    * This deletes the custom menu that is stored in $this->menu and performs
244    * tests on the menu delete user interface.
245    */
246   public function deleteCustomMenu() {
247     $menu_name = $this->menu->id();
248     $label = $this->menu->label();
249
250     // Delete custom menu.
251     $this->drupalPostForm("admin/structure/menu/manage/$menu_name/delete", [], t('Delete'));
252     $this->assertResponse(200);
253     $this->assertRaw(t('The menu %title has been deleted.', ['%title' => $label]), 'Custom menu was deleted');
254     $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted');
255     // Test if all menu links associated with the menu were removed from
256     // database.
257     $result = entity_load_multiple_by_properties('menu_link_content', ['menu_name' => $menu_name]);
258     $this->assertFalse($result, 'All menu links associated with the custom menu were deleted.');
259
260     // Make sure there's no delete button on system menus.
261     $this->drupalGet('admin/structure/menu/manage/main');
262     $this->assertNoRaw('edit-delete', 'The delete button was not found');
263
264     // Try to delete the main menu.
265     $this->drupalGet('admin/structure/menu/manage/main/delete');
266     $this->assertText(t('You are not authorized to access this page.'));
267   }
268
269   /**
270    * Tests menu functionality.
271    */
272   public function doMenuTests() {
273     $menu_name = $this->menu->id();
274
275     // Test the 'Add link' local action.
276     $this->drupalGet(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
277
278     $this->clickLink(t('Add link'));
279     $link_title = $this->randomString();
280     $this->drupalPostForm(NULL, ['link[0][uri]' => '/', 'title[0][value]' => $link_title], t('Save'));
281     $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
282     // Test the 'Edit' operation.
283     $this->clickLink(t('Edit'));
284     $this->assertFieldByName('title[0][value]', $link_title);
285     $link_title = $this->randomString();
286     $this->drupalPostForm(NULL, ['title[0][value]' => $link_title], t('Save'));
287     $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
288     // Test the 'Delete' operation.
289     $this->clickLink(t('Delete'));
290     $this->assertRaw(t('Are you sure you want to delete the custom menu link %item?', ['%item' => $link_title]));
291     $this->drupalPostForm(NULL, [], t('Delete'));
292     $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
293
294     // Add nodes to use as links for menu links.
295     $node1 = $this->drupalCreateNode(['type' => 'article']);
296     $node2 = $this->drupalCreateNode(['type' => 'article']);
297     $node3 = $this->drupalCreateNode(['type' => 'article']);
298     $node4 = $this->drupalCreateNode(['type' => 'article']);
299     // Create a node with an alias.
300     $node5 = $this->drupalCreateNode([
301       'type' => 'article',
302       'path' => [
303         'alias' => '/node5',
304       ],
305     ]);
306
307     // Verify add link button.
308     $this->drupalGet('admin/structure/menu');
309     $this->assertLinkByHref('admin/structure/menu/manage/' . $menu_name . '/add', 0, "The add menu link button URL is correct");
310
311     // Verify form defaults.
312     $this->doMenuLinkFormDefaultsTest();
313
314     // Add menu links.
315     $item1 = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE);
316     $item2 = $this->addMenuLink($item1->getPluginId(), '/node/' . $node2->id(), $menu_name, FALSE);
317     $item3 = $this->addMenuLink($item2->getPluginId(), '/node/' . $node3->id(), $menu_name);
318
319     // Hierarchy
320     // <$menu_name>
321     // - item1
322     // -- item2
323     // --- item3
324
325     $this->assertMenuLink([
326       'children' => [$item2->getPluginId(), $item3->getPluginId()],
327       'parents' => [$item1->getPluginId()],
328       // We assert the language code here to make sure that the language
329       // selection element degrades gracefully without the Language module.
330       'langcode' => 'en',
331     ], $item1->getPluginId());
332     $this->assertMenuLink([
333       'children' => [$item3->getPluginId()],
334       'parents' => [$item2->getPluginId(), $item1->getPluginId()],
335       // See above.
336       'langcode' => 'en',
337     ], $item2->getPluginId());
338     $this->assertMenuLink([
339       'children' => [],
340       'parents' => [$item3->getPluginId(), $item2->getPluginId(), $item1->getPluginId()],
341       // See above.
342       'langcode' => 'en',
343     ], $item3->getPluginId());
344
345     // Verify menu links.
346     $this->verifyMenuLink($item1, $node1);
347     $this->verifyMenuLink($item2, $node2, $item1, $node1);
348     $this->verifyMenuLink($item3, $node3, $item2, $node2);
349
350     // Add more menu links.
351     $item4 = $this->addMenuLink('', '/node/' . $node4->id(), $menu_name);
352     $item5 = $this->addMenuLink($item4->getPluginId(), '/node/' . $node5->id(), $menu_name);
353     // Create a menu link pointing to an alias.
354     $item6 = $this->addMenuLink($item4->getPluginId(), '/node5', $menu_name, TRUE, '0');
355
356     // Hierarchy
357     // <$menu_name>
358     // - item1
359     // -- item2
360     // --- item3
361     // - item4
362     // -- item5
363     // -- item6
364
365     $this->assertMenuLink([
366       'children' => [$item5->getPluginId(), $item6->getPluginId()],
367       'parents' => [$item4->getPluginId()],
368       // See above.
369       'langcode' => 'en',
370     ], $item4->getPluginId());
371     $this->assertMenuLink([
372       'children' => [],
373       'parents' => [$item5->getPluginId(), $item4->getPluginId()],
374       'langcode' => 'en',
375     ], $item5->getPluginId());
376     $this->assertMenuLink([
377       'children' => [],
378       'parents' => [$item6->getPluginId(), $item4->getPluginId()],
379       'route_name' => 'entity.node.canonical',
380       'route_parameters' => ['node' => $node5->id()],
381       'url' => '',
382       // See above.
383       'langcode' => 'en',
384     ], $item6->getPluginId());
385
386     // Modify menu links.
387     $this->modifyMenuLink($item1);
388     $this->modifyMenuLink($item2);
389
390     // Toggle menu links.
391     $this->toggleMenuLink($item1);
392     $this->toggleMenuLink($item2);
393
394     // Move link and verify that descendants are updated.
395     $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name);
396     // Hierarchy
397     // <$menu_name>
398     // - item1
399     // - item4
400     // -- item5
401     // --- item2
402     // ---- item3
403     // -- item6
404
405     $this->assertMenuLink([
406       'children' => [],
407       'parents' => [$item1->getPluginId()],
408       // See above.
409       'langcode' => 'en',
410     ], $item1->getPluginId());
411     $this->assertMenuLink([
412       'children' => [$item5->getPluginId(), $item6->getPluginId(), $item2->getPluginId(), $item3->getPluginId()],
413       'parents' => [$item4->getPluginId()],
414       // See above.
415       'langcode' => 'en',
416     ], $item4->getPluginId());
417
418     $this->assertMenuLink([
419       'children' => [$item2->getPluginId(), $item3->getPluginId()],
420       'parents' => [$item5->getPluginId(), $item4->getPluginId()],
421       // See above.
422       'langcode' => 'en',
423     ], $item5->getPluginId());
424     $this->assertMenuLink([
425       'children' => [$item3->getPluginId()],
426       'parents' => [$item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()],
427       // See above.
428       'langcode' => 'en',
429     ], $item2->getPluginId());
430     $this->assertMenuLink([
431       'children' => [],
432       'parents' => [$item3->getPluginId(), $item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()],
433       // See above.
434       'langcode' => 'en',
435     ], $item3->getPluginId());
436
437     // Add 102 menu links with increasing weights, then make sure the last-added
438     // item's weight doesn't get changed because of the old hardcoded delta=50.
439     $items = [];
440     for ($i = -50; $i <= 51; $i++) {
441       $items[$i] = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE, strval($i));
442     }
443     $this->assertMenuLink(['weight' => '51'], $items[51]->getPluginId());
444
445     // Disable a link and then re-enable the link via the overview form.
446     $this->disableMenuLink($item1);
447     $edit = [];
448     $edit['links[menu_plugin_id:' . $item1->getPluginId() . '][enabled]'] = TRUE;
449     $this->drupalPostForm('admin/structure/menu/manage/' . $item1->getMenuName(), $edit, t('Save'));
450
451     // Mark item2, item4 and item5 as expanded.
452     // This is done in order to show them on the frontpage.
453     $item2->expanded->value = 1;
454     $item2->save();
455     $item4->expanded->value = 1;
456     $item4->save();
457     $item5->expanded->value = 1;
458     $item5->save();
459
460     // Verify in the database.
461     $this->assertMenuLink(['enabled' => 1], $item1->getPluginId());
462
463     // Add an external link.
464     $item7 = $this->addMenuLink('', 'https://www.drupal.org', $menu_name);
465     $this->assertMenuLink(['url' => 'https://www.drupal.org'], $item7->getPluginId());
466
467     // Add <front> menu item.
468     $item8 = $this->addMenuLink('', '/', $menu_name);
469     $this->assertMenuLink(['route_name' => '<front>'], $item8->getPluginId());
470     $this->drupalGet('');
471     $this->assertResponse(200);
472     // Make sure we get routed correctly.
473     $this->clickLink($item8->getTitle());
474     $this->assertResponse(200);
475
476     // Check invalid menu link parents.
477     $this->checkInvalidParentMenuLinks();
478
479     // Save menu links for later tests.
480     $this->items[] = $item1;
481     $this->items[] = $item2;
482   }
483
484   /**
485    * Ensures that the proper default values are set when adding a menu link
486    */
487   protected function doMenuLinkFormDefaultsTest() {
488     $this->drupalGet("admin/structure/menu/manage/tools/add");
489     $this->assertResponse(200);
490
491     $this->assertFieldByName('title[0][value]', '');
492     $this->assertFieldByName('link[0][uri]', '');
493
494     $this->assertNoFieldChecked('edit-expanded-value');
495     $this->assertFieldChecked('edit-enabled-value');
496
497     $this->assertFieldByName('description[0][value]', '');
498     $this->assertFieldByName('weight[0][value]', 0);
499   }
500
501   /**
502    * Adds and removes a menu link with a query string and fragment.
503    */
504   public function testMenuQueryAndFragment() {
505     $this->drupalLogin($this->adminUser);
506
507     // Make a path with query and fragment on.
508     $path = '/test-page?arg1=value1&arg2=value2';
509     $item = $this->addMenuLink('', $path);
510
511     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
512     $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.');
513
514     // Now change the path to something without query and fragment.
515     $path = '/test-page';
516     $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', ['link[0][uri]' => $path], t('Save'));
517     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
518     $this->assertFieldByName('link[0][uri]', $path, 'Path no longer has query or fragment.');
519
520     // Use <front>#fragment and ensure that saving it does not lose its content.
521     $path = '<front>?arg1=value#fragment';
522     $item = $this->addMenuLink('', $path);
523
524     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
525     $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.');
526
527     $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', [], t('Save'));
528
529     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
530     $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.');
531   }
532
533   /**
534    * Tests renaming the built-in menu.
535    */
536   public function testSystemMenuRename() {
537     $this->drupalLogin($this->adminUser);
538     $edit = [
539       'label' => $this->randomMachineName(16),
540     ];
541     $this->drupalPostForm('admin/structure/menu/manage/main', $edit, t('Save'));
542
543     // Make sure menu shows up with new name in block addition.
544     $default_theme = $this->config('system.theme')->get('default');
545     $this->drupalget('admin/structure/block/list/' . $default_theme);
546     $this->clickLink('Place block');
547     $this->assertText($edit['label']);
548   }
549
550   /**
551    * Tests that menu items pointing to unpublished nodes are editable.
552    */
553   public function testUnpublishedNodeMenuItem() {
554     $this->drupalLogin($this->drupalCreateUser(['access administration pages', 'administer blocks', 'administer menu', 'create article content', 'bypass node access']));
555     // Create an unpublished node.
556     $node = $this->drupalCreateNode([
557       'type' => 'article',
558       'status' => NodeInterface::NOT_PUBLISHED,
559     ]);
560
561     $item = $this->addMenuLink('', '/node/' . $node->id());
562     $this->modifyMenuLink($item);
563
564     // Test that a user with 'administer menu' but without 'bypass node access'
565     // cannot see the menu item.
566     $this->drupalLogout();
567     $this->drupalLogin($this->adminUser);
568     $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName());
569     $this->assertNoText($item->getTitle(), "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission");
570     // The cache contexts associated with the (in)accessible menu links are
571     // bubbled. See DefaultMenuLinkTreeManipulators::menuLinkCheckAccess().
572     $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
573   }
574
575   /**
576    * Adds a menu link using the UI.
577    *
578    * @param string $parent
579    *   Optional parent menu link id.
580    * @param string $path
581    *   The path to enter on the form. Defaults to the front page.
582    * @param string $menu_name
583    *   Menu name. Defaults to 'tools'.
584    * @param bool $expanded
585    *   Whether or not this menu link is expanded. Setting this to TRUE should
586    *   test whether it works when we do the authenticatedUser tests. Defaults
587    *   to FALSE.
588    * @param string $weight
589    *   Menu weight. Defaults to 0.
590    *
591    * @return \Drupal\menu_link_content\Entity\MenuLinkContent
592    *   A menu link entity.
593    */
594   public function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded = FALSE, $weight = '0') {
595     // View add menu link page.
596     $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
597     $this->assertResponse(200);
598
599     $title = '!link_' . $this->randomMachineName(16);
600     $edit = [
601       'link[0][uri]' => $path,
602       'title[0][value]' => $title,
603       'description[0][value]' => '',
604       'enabled[value]' => 1,
605       'expanded[value]' => $expanded,
606       'menu_parent' => $menu_name . ':' . $parent,
607       'weight[0][value]' => $weight,
608     ];
609
610     // Add menu link.
611     $this->drupalPostForm(NULL, $edit, t('Save'));
612     $this->assertResponse(200);
613     $this->assertText('The menu link has been saved.');
614
615     $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $title]);
616
617     $menu_link = reset($menu_links);
618     $this->assertTrue($menu_link, 'Menu link was found in database.');
619     $this->assertMenuLink(['menu_name' => $menu_name, 'children' => [], 'parent' => $parent], $menu_link->getPluginId());
620
621     return $menu_link;
622   }
623
624   /**
625    * Attempts to add menu link with invalid path or no access permission.
626    */
627   public function addInvalidMenuLink() {
628     foreach (['access' => '/admin/people/permissions'] as $type => $link_path) {
629       $edit = [
630         'link[0][uri]' => $link_path,
631         'title[0][value]' => 'title',
632       ];
633       $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save'));
634       $this->assertRaw(t("The path '@link_path' is inaccessible.", ['@link_path' => $link_path]), 'Menu link was not created');
635     }
636   }
637
638   /**
639    * Tests that parent options are limited by depth when adding menu links.
640    */
641   public function checkInvalidParentMenuLinks() {
642     $last_link = NULL;
643     $created_links = [];
644
645     // Get the max depth of the tree.
646     $menu_link_tree = \Drupal::service('menu.link_tree');
647     $max_depth = $menu_link_tree->maxDepth();
648
649     // Create a maximum number of menu links, each a child of the previous.
650     for ($i = 0; $i <= $max_depth - 1; $i++) {
651       $parent = $last_link ? 'tools:' . $last_link->getPluginId() : 'tools:';
652       $title = 'title' . $i;
653       $edit = [
654         'link[0][uri]' => '/',
655         'title[0][value]' => $title,
656         'menu_parent' => $parent,
657         'description[0][value]' => '',
658         'enabled[value]' => 1,
659         'expanded[value]' => FALSE,
660         'weight[0][value]' => '0',
661       ];
662       $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save'));
663       $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $title]);
664       $last_link = reset($menu_links);
665       $created_links[] = 'tools:' . $last_link->getPluginId();
666     }
667
668     // The last link cannot be a parent in the new menu link form.
669     $this->drupalGet('admin/structure/menu/manage/admin/add');
670     $value = 'tools:' . $last_link->getPluginId();
671     $this->assertNoOption('edit-menu-parent', $value, 'The invalid option is not there.');
672
673     // All but the last link can be parents in the new menu link form.
674     array_pop($created_links);
675     foreach ($created_links as $key => $link) {
676       $this->assertOption('edit-menu-parent', $link, 'The valid option number ' . ($key + 1) . ' is there.');
677     }
678   }
679
680   /**
681    * Verifies a menu link using the UI.
682    *
683    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
684    *   Menu link.
685    * @param object $item_node
686    *   Menu link content node.
687    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent
688    *   Parent menu link.
689    * @param object $parent_node
690    *   Parent menu link content node.
691    */
692   public function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) {
693     // View home page.
694     $this->drupalGet('');
695     $this->assertResponse(200);
696
697     // Verify parent menu link.
698     if (isset($parent)) {
699       // Verify menu link.
700       $title = $parent->getTitle();
701       $this->assertLink($title, 0, 'Parent menu link was displayed');
702
703       // Verify menu link link.
704       $this->clickLink($title);
705       $title = $parent_node->label();
706       $this->assertTitle(t("@title | Drupal", ['@title' => $title]), 'Parent menu link link target was correct');
707     }
708
709     // Verify menu link.
710     $title = $item->getTitle();
711     $this->assertLink($title, 0, 'Menu link was displayed');
712
713     // Verify menu link link.
714     $this->clickLink($title);
715     $title = $item_node->label();
716     $this->assertTitle(t("@title | Drupal", ['@title' => $title]), 'Menu link link target was correct');
717   }
718
719   /**
720    * Changes the parent of a menu link using the UI.
721    *
722    * @param \Drupal\menu_link_content\MenuLinkContentInterface $item
723    *   The menu link item to move.
724    * @param int $parent
725    *   The id of the new parent.
726    * @param string $menu_name
727    *   The menu the menu link will be moved to.
728    */
729   public function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) {
730     $mlid = $item->id();
731
732     $edit = [
733       'menu_parent' => $menu_name . ':' . $parent,
734     ];
735     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
736     $this->assertResponse(200);
737   }
738
739   /**
740    * Modifies a menu link using the UI.
741    *
742    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
743    *   Menu link entity.
744    */
745   public function modifyMenuLink(MenuLinkContent $item) {
746     $item->title->value = $this->randomMachineName(16);
747
748     $mlid = $item->id();
749     $title = $item->getTitle();
750
751     // Edit menu link.
752     $edit = [];
753     $edit['title[0][value]'] = $title;
754     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
755     $this->assertResponse(200);
756     $this->assertText('The menu link has been saved.');
757     // Verify menu link.
758     $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName());
759     $this->assertText($title, 'Menu link was edited');
760   }
761
762   /**
763    * Resets a standard menu link using the UI.
764    *
765    * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
766    *   The Menu link.
767    * @param int $old_weight
768    *   Original title for menu link.
769    */
770   public function resetMenuLink(MenuLinkInterface $menu_link, $old_weight) {
771     // Reset menu link.
772     $this->drupalPostForm("admin/structure/menu/link/{$menu_link->getPluginId()}/reset", [], t('Reset'));
773     $this->assertResponse(200);
774     $this->assertRaw(t('The menu link was reset to its default settings.'), 'Menu link was reset');
775
776     // Verify menu link.
777     $instance = \Drupal::service('plugin.manager.menu.link')->createInstance($menu_link->getPluginId());
778     $this->assertEqual($old_weight, $instance->getWeight(), 'Resets to the old weight.');
779   }
780
781   /**
782    * Deletes a menu link using the UI.
783    *
784    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
785    *   Menu link.
786    */
787   public function deleteMenuLink(MenuLinkContent $item) {
788     $mlid = $item->id();
789     $title = $item->getTitle();
790
791     // Delete menu link.
792     $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", [], t('Delete'));
793     $this->assertResponse(200);
794     $this->assertRaw(t('The menu link %title has been deleted.', ['%title' => $title]), 'Menu link was deleted');
795
796     // Verify deletion.
797     $this->drupalGet('');
798     $this->assertNoText($title, 'Menu link was deleted');
799   }
800
801   /**
802    * Alternately disables and enables a menu link.
803    *
804    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
805    *   Menu link.
806    */
807   public function toggleMenuLink(MenuLinkContent $item) {
808     $this->disableMenuLink($item);
809
810     // Verify menu link is absent.
811     $this->drupalGet('');
812     $this->assertNoText($item->getTitle(), 'Menu link was not displayed');
813     $this->enableMenuLink($item);
814
815     // Verify menu link is displayed.
816     $this->drupalGet('');
817     $this->assertText($item->getTitle(), 'Menu link was displayed');
818   }
819
820   /**
821    * Disables a menu link.
822    *
823    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
824    *   Menu link.
825    */
826   public function disableMenuLink(MenuLinkContent $item) {
827     $mlid = $item->id();
828     $edit['enabled[value]'] = FALSE;
829     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
830
831     // Unlike most other modules, there is no confirmation message displayed.
832     // Verify in the database.
833     $this->assertMenuLink(['enabled' => 0], $item->getPluginId());
834   }
835
836   /**
837    * Enables a menu link.
838    *
839    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
840    *   Menu link.
841    */
842   public function enableMenuLink(MenuLinkContent $item) {
843     $mlid = $item->id();
844     $edit['enabled[value]'] = TRUE;
845     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
846
847     // Verify in the database.
848     $this->assertMenuLink(['enabled' => 1], $item->getPluginId());
849   }
850
851   /**
852    * Tests if admin users, other than UID1, can access parents AJAX callback.
853    */
854   public function testMenuParentsJsAccess() {
855     $this->drupalLogin($this->drupalCreateUser(['administer menu']));
856     // Just check access to the callback overall, the POST data is irrelevant.
857     $this->drupalGet('admin/structure/menu/parents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With: XMLHttpRequest']);
858     $this->assertSession()->statusCodeEquals(200);
859
860     // Log in as authenticated user.
861     $this->drupalLogin($this->drupalCreateUser());
862     // Check that a simple user is not able to access the menu.
863     $this->drupalGet('admin/structure/menu/parents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With: XMLHttpRequest']);
864     $this->assertSession()->statusCodeEquals(403);
865   }
866
867   /**
868    * Returns standard menu link.
869    *
870    * @return \Drupal\Core\Menu\MenuLinkInterface
871    *   A menu link plugin.
872    */
873   private function getStandardMenuLink() {
874     // Retrieve menu link id of the Log out menu link, which will always be on
875     // the front page.
876     /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
877     $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
878     $instance = $menu_link_manager->getInstance(['id' => 'user.logout']);
879
880     $this->assertTrue((bool) $instance, 'Standard menu link was loaded');
881     return $instance;
882   }
883
884   /**
885    * Verifies the logged in user has the desired access to various menu pages.
886    *
887    * @param int $response
888    *   (optional) The expected HTTP response code. Defaults to 200.
889    */
890   private function verifyAccess($response = 200) {
891     // View menu help page.
892     $this->drupalGet('admin/help/menu');
893     $this->assertResponse($response);
894     if ($response == 200) {
895       $this->assertText(t('Menu'), 'Menu help was displayed');
896     }
897
898     // View menu build overview page.
899     $this->drupalGet('admin/structure/menu');
900     $this->assertResponse($response);
901     if ($response == 200) {
902       $this->assertText(t('Menus'), 'Menu build overview page was displayed');
903     }
904
905     // View tools menu customization page.
906     $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id());
907     $this->assertResponse($response);
908     if ($response == 200) {
909       $this->assertText(t('Tools'), 'Tools menu page was displayed');
910     }
911
912     // View menu edit page for a static link.
913     $item = $this->getStandardMenuLink();
914     $this->drupalGet('admin/structure/menu/link/' . $item->getPluginId() . '/edit');
915     $this->assertResponse($response);
916     if ($response == 200) {
917       $this->assertText(t('Edit menu item'), 'Menu edit page was displayed');
918     }
919
920     // View add menu page.
921     $this->drupalGet('admin/structure/menu/add');
922     $this->assertResponse($response);
923     if ($response == 200) {
924       $this->assertText(t('Menus'), 'Add menu page was displayed');
925     }
926   }
927
928   /**
929    * Tests menu block settings.
930    */
931   protected function doTestMenuBlock() {
932     $menu_id = $this->menu->id();
933     $block_id = $this->blockPlacements[$menu_id];
934     $this->drupalGet('admin/structure/block/manage/' . $block_id);
935     $this->drupalPostForm(NULL, [
936       'settings[depth]' => 3,
937       'settings[level]' => 2,
938     ], t('Save block'));
939     $block = Block::load($block_id);
940     $settings = $block->getPlugin()->getConfiguration();
941     $this->assertEqual($settings['depth'], 3);
942     $this->assertEqual($settings['level'], 2);
943     // Reset settings.
944     $block->getPlugin()->setConfigurationValue('depth', 0);
945     $block->getPlugin()->setConfigurationValue('level', 1);
946     $block->save();
947   }
948
949 }