Upgraded drupal core with security updates
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Menu / MenuTreeStorageTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Menu;
4
5 use Drupal\Component\Plugin\Exception\PluginException;
6 use Drupal\Core\Menu\MenuTreeParameters;
7 use Drupal\Core\Menu\MenuTreeStorage;
8 use Drupal\KernelTests\KernelTestBase;
9
10 /**
11  * Tests the menu tree storage.
12  *
13  * @group Menu
14  *
15  * @see \Drupal\Core\Menu\MenuTreeStorage
16  */
17 class MenuTreeStorageTest extends KernelTestBase {
18
19   /**
20    * The tested tree storage.
21    *
22    * @var \Drupal\Core\Menu\MenuTreeStorage
23    */
24   protected $treeStorage;
25
26   /**
27    * The database connection.
28    *
29    * @var \Drupal\Core\Database\Connection
30    */
31   protected $connection;
32
33   /**
34    * {@inheritdoc}
35    */
36   protected function setUp() {
37     parent::setUp();
38
39     $this->treeStorage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'menu_tree');
40     $this->connection = $this->container->get('database');
41   }
42
43   /**
44    * Tests the tree storage when no tree was built yet.
45    */
46   public function testBasicMethods() {
47     $this->doTestEmptyStorage();
48     $this->doTestTable();
49   }
50
51   /**
52    * Ensures that there are no menu links by default.
53    */
54   protected function doTestEmptyStorage() {
55     $this->assertEqual(0, $this->treeStorage->countMenuLinks());
56   }
57
58   /**
59    * Ensures that table gets created on the fly.
60    */
61   protected function doTestTable() {
62     // Test that we can create a tree storage with an arbitrary table name and
63     // that selecting from the storage creates the table.
64     $tree_storage = new MenuTreeStorage($this->container->get('database'), $this->container->get('cache.menu'), $this->container->get('cache_tags.invalidator'), 'test_menu_tree');
65     $this->assertFalse($this->connection->schema()->tableExists('test_menu_tree'), 'Test table is not yet created');
66     $tree_storage->countMenuLinks();
67     $this->assertTrue($this->connection->schema()->tableExists('test_menu_tree'), 'Test table was created');
68   }
69
70   /**
71    * Tests with a simple linear hierarchy.
72    */
73   public function testSimpleHierarchy() {
74     // Add some links with parent on the previous one and test some values.
75     // <tools>
76     // - test1
77     // -- test2
78     // --- test3
79     $this->addMenuLink('test1', '');
80     $this->assertMenuLink('test1', ['has_children' => 0, 'depth' => 1]);
81
82     $this->addMenuLink('test2', 'test1');
83     $this->assertMenuLink('test1', ['has_children' => 1, 'depth' => 1], [], ['test2']);
84     $this->assertMenuLink('test2', ['has_children' => 0, 'depth' => 2], ['test1']);
85
86     $this->addMenuLink('test3', 'test2');
87     $this->assertMenuLink('test1', ['has_children' => 1, 'depth' => 1], [], ['test2', 'test3']);
88     $this->assertMenuLink('test2', ['has_children' => 1, 'depth' => 2], ['test1'], ['test3']);
89     $this->assertMenuLink('test3', ['has_children' => 0, 'depth' => 3], ['test2', 'test1']);
90   }
91
92   /**
93    * Tests the tree with moving links inside the hierarchy.
94    */
95   public function testMenuLinkMoving() {
96     // Before the move.
97     // <tools>
98     // - test1
99     // -- test2
100     // --- test3
101     // - test4
102     // -- test5
103     // --- test6
104
105     $this->addMenuLink('test1', '');
106     $this->addMenuLink('test2', 'test1');
107     $this->addMenuLink('test3', 'test2');
108     $this->addMenuLink('test4', '');
109     $this->addMenuLink('test5', 'test4');
110     $this->addMenuLink('test6', 'test5');
111
112     $this->assertMenuLink('test1', ['has_children' => 1, 'depth' => 1], [], ['test2', 'test3']);
113     $this->assertMenuLink('test2', ['has_children' => 1, 'depth' => 2], ['test1'], ['test3']);
114     $this->assertMenuLink('test4', ['has_children' => 1, 'depth' => 1], [], ['test5', 'test6']);
115     $this->assertMenuLink('test5', ['has_children' => 1, 'depth' => 2], ['test4'], ['test6']);
116     $this->assertMenuLink('test6', ['has_children' => 0, 'depth' => 3], ['test5', 'test4']);
117
118     $this->moveMenuLink('test2', 'test5');
119     // After the 1st move.
120     // <tools>
121     // - test1
122     // - test4
123     // -- test5
124     // --- test2
125     // ---- test3
126     // --- test6
127
128     $this->assertMenuLink('test1', ['has_children' => 0, 'depth' => 1]);
129     $this->assertMenuLink('test2', ['has_children' => 1, 'depth' => 3], ['test5', 'test4'], ['test3']);
130     $this->assertMenuLink('test3', ['has_children' => 0, 'depth' => 4], ['test2', 'test5', 'test4']);
131     $this->assertMenuLink('test4', ['has_children' => 1, 'depth' => 1], [], ['test5', 'test2', 'test3', 'test6']);
132     $this->assertMenuLink('test5', ['has_children' => 1, 'depth' => 2], ['test4'], ['test2', 'test3', 'test6']);
133     $this->assertMenuLink('test6', ['has_children' => 0, 'depth' => 3], ['test5', 'test4']);
134
135     $this->moveMenuLink('test4', 'test1');
136     $this->moveMenuLink('test3', 'test1');
137     // After the next 2 moves.
138     // <tools>
139     // - test1
140     // -- test3
141     // -- test4
142     // --- test5
143     // ---- test2
144     // ---- test6
145
146     $this->assertMenuLink('test1', ['has_children' => 1, 'depth' => 1], [], ['test4', 'test5', 'test2', 'test3', 'test6']);
147     $this->assertMenuLink('test2', ['has_children' => 0, 'depth' => 4], ['test5', 'test4', 'test1']);
148     $this->assertMenuLink('test3', ['has_children' => 0, 'depth' => 2], ['test1']);
149     $this->assertMenuLink('test4', ['has_children' => 1, 'depth' => 2], ['test1'], ['test2', 'test5', 'test6']);
150     $this->assertMenuLink('test5', ['has_children' => 1, 'depth' => 3], ['test4', 'test1'], ['test2', 'test6']);
151     $this->assertMenuLink('test6', ['has_children' => 0, 'depth' => 4], ['test5', 'test4', 'test1']);
152
153     // Deleting a link in the middle should re-attach child links to the parent.
154     $this->treeStorage->delete('test4');
155     // After the delete.
156     // <tools>
157     // - test1
158     // -- test3
159     // -- test5
160     // --- test2
161     // --- test6
162     $this->assertMenuLink('test1', ['has_children' => 1, 'depth' => 1], [], ['test5', 'test2', 'test3', 'test6']);
163     $this->assertMenuLink('test2', ['has_children' => 0, 'depth' => 3], ['test5', 'test1']);
164     $this->assertMenuLink('test3', ['has_children' => 0, 'depth' => 2], ['test1']);
165     $this->assertFalse($this->treeStorage->load('test4'));
166     $this->assertMenuLink('test5', ['has_children' => 1, 'depth' => 2], ['test1'], ['test2', 'test6']);
167     $this->assertMenuLink('test6', ['has_children' => 0, 'depth' => 3], ['test5', 'test1']);
168   }
169
170   /**
171    * Tests with disabled child links.
172    */
173   public function testMenuDisabledChildLinks() {
174     // Add some links with parent on the previous one and test some values.
175     // <tools>
176     // - test1
177     // -- test2 (disabled)
178
179     $this->addMenuLink('test1', '');
180     $this->assertMenuLink('test1', ['has_children' => 0, 'depth' => 1]);
181
182     $this->addMenuLink('test2', 'test1', '<front>', [], 'tools', ['enabled' => 0]);
183     // The 1st link does not have any visible children, so has_children is 0.
184     $this->assertMenuLink('test1', ['has_children' => 0, 'depth' => 1]);
185     $this->assertMenuLink('test2', ['has_children' => 0, 'depth' => 2, 'enabled' => 0], ['test1']);
186
187     // Add more links with parent on the previous one.
188     // <footer>
189     // - footerA
190     // ===============
191     // <tools>
192     // - test1
193     // -- test2 (disabled)
194     // --- test3
195     // ---- test4
196     // ----- test5
197     // ------ test6
198     // ------- test7
199     // -------- test8
200     // --------- test9
201     $this->addMenuLink('footerA', '', '<front>', [], 'footer');
202     $visible_children = [];
203     for ($i = 3; $i <= $this->treeStorage->maxDepth(); $i++) {
204       $parent = $i - 1;
205       $this->addMenuLink("test$i", "test$parent");
206       $visible_children[] = "test$i";
207     }
208     // The 1st link does not have any visible children, so has_children is still
209     // 0. However, it has visible links below it that will be found.
210     $this->assertMenuLink('test1', ['has_children' => 0, 'depth' => 1], [], $visible_children);
211     // This should fail since test9 would end up at greater than max depth.
212     try {
213       $this->moveMenuLink('test1', 'footerA');
214       $this->fail('Exception was not thrown');
215     }
216     catch (PluginException $e) {
217       $this->pass($e->getMessage());
218     }
219     // The opposite move should work, and change the has_children flag.
220     $this->moveMenuLink('footerA', 'test1');
221     $visible_children[] = 'footerA';
222     $this->assertMenuLink('test1', ['has_children' => 1, 'depth' => 1], [], $visible_children);
223   }
224
225   /**
226    * Tests the loadTreeData method.
227    */
228   public function testLoadTree() {
229     $this->addMenuLink('test1', '');
230     $this->addMenuLink('test2', 'test1');
231     $this->addMenuLink('test3', 'test2');
232     $this->addMenuLink('test4');
233     $this->addMenuLink('test5', 'test4');
234
235     $data = $this->treeStorage->loadTreeData('tools', new MenuTreeParameters());
236     $tree = $data['tree'];
237     $this->assertEqual(count($tree['test1']['subtree']), 1);
238     $this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']), 1);
239     $this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']['test3']['subtree']), 0);
240     $this->assertEqual(count($tree['test4']['subtree']), 1);
241     $this->assertEqual(count($tree['test4']['subtree']['test5']['subtree']), 0);
242
243     $parameters = new MenuTreeParameters();
244     $parameters->setActiveTrail(['test4', 'test5']);
245     $data = $this->treeStorage->loadTreeData('tools', $parameters);
246     $tree = $data['tree'];
247     $this->assertEqual(count($tree['test1']['subtree']), 1);
248     $this->assertFalse($tree['test1']['in_active_trail']);
249     $this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']), 1);
250     $this->assertFalse($tree['test1']['subtree']['test2']['in_active_trail']);
251     $this->assertEqual(count($tree['test1']['subtree']['test2']['subtree']['test3']['subtree']), 0);
252     $this->assertFalse($tree['test1']['subtree']['test2']['subtree']['test3']['in_active_trail']);
253     $this->assertEqual(count($tree['test4']['subtree']), 1);
254     $this->assertTrue($tree['test4']['in_active_trail']);
255     $this->assertEqual(count($tree['test4']['subtree']['test5']['subtree']), 0);
256     $this->assertTrue($tree['test4']['subtree']['test5']['in_active_trail']);
257
258     // Add some conditions to ensure that conditions work as expected.
259     $parameters = new MenuTreeParameters();
260     $parameters->addCondition('parent', 'test1');
261     $data = $this->treeStorage->loadTreeData('tools', $parameters);
262     $this->assertEqual(count($data['tree']), 1);
263     $this->assertEqual($data['tree']['test2']['definition']['id'], 'test2');
264     $this->assertEqual($data['tree']['test2']['subtree'], []);
265
266     // Test for only enabled links.
267     $link = $this->treeStorage->load('test3');
268     $link['enabled'] = FALSE;
269     $this->treeStorage->save($link);
270     $link = $this->treeStorage->load('test4');
271     $link['enabled'] = FALSE;
272     $this->treeStorage->save($link);
273     $link = $this->treeStorage->load('test5');
274     $link['enabled'] = FALSE;
275     $this->treeStorage->save($link);
276
277     $parameters = new MenuTreeParameters();
278     $parameters->onlyEnabledLinks();
279     $data = $this->treeStorage->loadTreeData('tools', $parameters);
280     $this->assertEqual(count($data['tree']), 1);
281     $this->assertEqual($data['tree']['test1']['definition']['id'], 'test1');
282     $this->assertEqual(count($data['tree']['test1']['subtree']), 1);
283     $this->assertEqual($data['tree']['test1']['subtree']['test2']['definition']['id'], 'test2');
284     $this->assertEqual($data['tree']['test1']['subtree']['test2']['subtree'], []);
285
286   }
287
288   /**
289    * Tests finding the subtree height with content menu links.
290    */
291   public function testSubtreeHeight() {
292     // root
293     // - child1
294     // -- child2
295     // --- child3
296     // ---- child4
297     $this->addMenuLink('root');
298     $this->addMenuLink('child1', 'root');
299     $this->addMenuLink('child2', 'child1');
300     $this->addMenuLink('child3', 'child2');
301     $this->addMenuLink('child4', 'child3');
302
303     $this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
304     $this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
305     $this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
306     $this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
307     $this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
308   }
309
310   /**
311    * Ensure hierarchy persists after a menu rebuild.
312    */
313   public function testMenuRebuild() {
314     // root
315     // - child1
316     // -- child2
317     // --- child3
318     // ---- child4
319     $this->addMenuLink('root');
320     $this->addMenuLink('child1', 'root');
321     $this->addMenuLink('child2', 'child1');
322     $this->addMenuLink('child3', 'child2');
323     $this->addMenuLink('child4', 'child3');
324
325     $this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
326     $this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
327     $this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
328     $this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
329     $this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
330
331     // Intentionally leave child3 out to mimic static or external links.
332     $definitions = $this->treeStorage->loadMultiple(['root', 'child1', 'child2', 'child4']);
333     $this->treeStorage->rebuild($definitions);
334     $this->assertEqual($this->treeStorage->getSubtreeHeight('root'), 5);
335     $this->assertEqual($this->treeStorage->getSubtreeHeight('child1'), 4);
336     $this->assertEqual($this->treeStorage->getSubtreeHeight('child2'), 3);
337     $this->assertEqual($this->treeStorage->getSubtreeHeight('child3'), 2);
338     $this->assertEqual($this->treeStorage->getSubtreeHeight('child4'), 1);
339   }
340
341   /**
342    * Tests MenuTreeStorage::loadByProperties().
343    */
344   public function testLoadByProperties() {
345     $tests = [
346       ['foo' => 'bar'],
347       [0 => 'wrong'],
348     ];
349     $message = 'An invalid property name throws an exception.';
350     foreach ($tests as $properties) {
351       try {
352         $this->treeStorage->loadByProperties($properties);
353         $this->fail($message);
354       }
355       catch (\InvalidArgumentException $e) {
356         $this->assertTrue(preg_match('/^An invalid property name, .+ was specified. Allowed property names are:/', $e->getMessage()), 'Found expected exception message.');
357         $this->pass($message);
358       }
359     }
360     $this->addMenuLink('test_link.1', '', 'test', [], 'menu1');
361     $properties = ['menu_name' => 'menu1'];
362     $links = $this->treeStorage->loadByProperties($properties);
363     $this->assertEqual('menu1', $links['test_link.1']['menu_name']);
364     $this->assertEqual('test', $links['test_link.1']['route_name']);
365   }
366
367   /**
368    * Adds a link with the given ID and supply defaults.
369    */
370   protected function addMenuLink($id, $parent = '', $route_name = 'test', $route_parameters = [], $menu_name = 'tools', $extra = []) {
371     $link = [
372       'id' => $id,
373       'menu_name' => $menu_name,
374       'route_name' => $route_name,
375       'route_parameters' => $route_parameters,
376       'title' => 'test',
377       'parent' => $parent,
378       'options' => [],
379       'metadata' => [],
380     ] + $extra;
381     $this->treeStorage->save($link);
382   }
383
384   /**
385    * Moves the link with the given ID so it's under a new parent.
386    *
387    * @param string $id
388    *   The ID of the menu link to move.
389    * @param string $new_parent
390    *   The ID of the new parent link.
391    */
392   protected function moveMenuLink($id, $new_parent) {
393     $menu_link = $this->treeStorage->load($id);
394     $menu_link['parent'] = $new_parent;
395     $this->treeStorage->save($menu_link);
396   }
397
398   /**
399    * Tests that a link's stored representation matches the expected values.
400    *
401    * @param string $id
402    *   The ID of the menu link to test
403    * @param array $expected_properties
404    *   A keyed array of column names and values like has_children and depth.
405    * @param array $parents
406    *   An ordered array of the IDs of the menu links that are the parents.
407    * @param array $children
408    *   Array of child IDs that are visible (enabled == 1).
409    */
410   protected function assertMenuLink($id, array $expected_properties, array $parents = [], array $children = []) {
411     $query = $this->connection->select('menu_tree');
412     $query->fields('menu_tree');
413     $query->condition('id', $id);
414     foreach ($expected_properties as $field => $value) {
415       $query->condition($field, $value);
416     }
417     $all = $query->execute()->fetchAll(\PDO::FETCH_ASSOC);
418     $this->assertEqual(count($all), 1, "Found link $id matching all the expected properties");
419     $raw = reset($all);
420
421     // Put the current link onto the front.
422     array_unshift($parents, $raw['id']);
423
424     $query = $this->connection->select('menu_tree');
425     $query->fields('menu_tree', ['id', 'mlid']);
426     $query->condition('id', $parents, 'IN');
427     $found_parents = $query->execute()->fetchAllKeyed(0, 1);
428
429     $this->assertEqual(count($parents), count($found_parents), 'Found expected number of parents');
430     $this->assertEqual($raw['depth'], count($found_parents), 'Number of parents is the same as the depth');
431
432     $materialized_path = $this->treeStorage->getRootPathIds($id);
433     $this->assertEqual(array_values($materialized_path), array_values($parents), 'Parents match the materialized path');
434     // Check that the selected mlid values of the parents are in the correct
435     // column, including the link's own.
436     for ($i = $raw['depth']; $i >= 1; $i--) {
437       $parent_id = array_shift($parents);
438       $this->assertEqual($raw["p$i"], $found_parents[$parent_id], "mlid of parent matches at column p$i");
439     }
440     for ($i = $raw['depth'] + 1; $i <= $this->treeStorage->maxDepth(); $i++) {
441       $this->assertEqual($raw["p$i"], 0, "parent is 0 at column p$i greater than depth");
442     }
443     if ($parents) {
444       $this->assertEqual($raw['parent'], end($parents), 'Ensure that the parent field is set properly');
445     }
446     $found_children = array_keys($this->treeStorage->loadAllChildren($id));
447     // We need both these checks since the 2nd will pass if there are extra
448     // IDs loaded in $found_children.
449     $this->assertEqual(count($children), count($found_children), "Found expected number of children for $id");
450     $this->assertEqual(array_intersect($children, $found_children), $children, 'Child IDs match');
451   }
452
453 }