1fffa4d081654ad911a77540b5b91ba9a536b9f4
[yaffs-website] / web / modules / contrib / pathauto / tests / src / Kernel / PathautoKernelTest.php
1 <?php
2
3 namespace Drupal\Tests\pathauto\Kernel;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Language\Language;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\language\Entity\ConfigurableLanguage;
9 use Drupal\field\Entity\FieldConfig;
10 use Drupal\field\Entity\FieldStorageConfig;
11 use Drupal\node\Entity\NodeType;
12 use Drupal\pathauto\PathautoGeneratorInterface;
13 use Drupal\pathauto\PathautoState;
14 use Drupal\pathauto\Tests\PathautoTestHelperTrait;
15 use Drupal\KernelTests\KernelTestBase;
16 use Drupal\taxonomy\Entity\Term;
17 use Drupal\taxonomy\Entity\Vocabulary;
18 use Drupal\node\Entity\Node;
19 use Drupal\user\Entity\User;
20
21 /**
22  * Unit tests for Pathauto functions.
23  *
24  * @group pathauto
25  */
26 class PathautoKernelTest extends KernelTestBase {
27
28   use PathautoTestHelperTrait;
29
30   public static $modules = array('system', 'field', 'text', 'user', 'node', 'path', 'pathauto', 'taxonomy', 'token', 'filter', 'ctools', 'language');
31
32   protected $currentUser;
33
34   /**
35    * @var \Drupal\pathauto\PathautoPatternInterface
36    */
37   protected $nodePattern;
38
39   /**
40    * @var \Drupal\pathauto\PathautoPatternInterface
41    */
42   protected $userPattern;
43
44   public function setUp() {
45     parent::setup();
46
47     $this->installConfig(array('pathauto', 'taxonomy', 'system', 'node'));
48
49     $this->installEntitySchema('user');
50     $this->installEntitySchema('node');
51     $this->installEntitySchema('taxonomy_term');
52
53     ConfigurableLanguage::createFromLangcode('fr')->save();
54
55     $this->installSchema('node', array('node_access'));
56     $this->installSchema('system', array('url_alias', 'sequences', 'router'));
57
58     $type = NodeType::create(['type' => 'page']);
59     $type->save();
60     node_add_body_field($type);
61
62     $this->nodePattern = $this->createPattern('node', '/content/[node:title]');
63     $this->userPattern = $this->createPattern('user', '/users/[user:name]');
64
65     \Drupal::service('router.builder')->rebuild();
66
67     $this->currentUser = User::create(array('name' => $this->randomMachineName()));
68     $this->currentUser->save();
69   }
70
71   /**
72    * Test _pathauto_get_schema_alias_maxlength().
73    */
74   public function testGetSchemaAliasMaxLength() {
75     $this->assertIdentical(\Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength(), 255);
76   }
77
78   /**
79    * Test pathauto_pattern_load_by_entity().
80    */
81   public function testPatternLoadByEntity() {
82     $pattern = $this->createPattern('node', '/article/[node:title]', -1);
83     $this->addBundleCondition($pattern, 'node', 'article');
84     $pattern->save();
85
86     $pattern = $this->createPattern('node', '/article/en/[node:title]', -2);
87     $this->addBundleCondition($pattern, 'node', 'article');
88     $pattern->addSelectionCondition(
89       [
90         'id' => 'language',
91         'langcodes' => [
92           'en' => 'en',
93         ],
94         'negate' => FALSE,
95         'context_mapping' => [
96           'language' => 'node:langcode:language',
97         ]
98       ]
99     );
100
101     $pattern->addRelationship('node:langcode:language');
102     $pattern->save();
103
104     $pattern = $this->createPattern('node', '/[node:title]', -1);
105     $this->addBundleCondition($pattern, 'node', 'page');
106     $pattern->save();
107
108     $tests = array(
109       array(
110         'entity' => 'node',
111         'values' => [
112           'title' => 'Article fr',
113           'type' => 'article',
114           'langcode' => 'fr',
115         ],
116         'expected' => '/article/[node:title]',
117       ),
118       array(
119         'entity' => 'node',
120         'values' => [
121           'title' => 'Article en',
122           'type' => 'article',
123           'langcode' => 'en',
124         ],
125         'expected' => '/article/en/[node:title]',
126       ),
127       array(
128         'entity' => 'node',
129         'values' => [
130           'title' => 'Article und',
131           'type' => 'article',
132           'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
133         ],
134         'expected' => '/article/[node:title]',
135       ),
136       array(
137         'entity' => 'node',
138         'values' => [
139           'title' => 'Page',
140           'type' => 'page',
141         ],
142         'expected' => '/[node:title]',
143       ),
144       array(
145         'entity' => 'user',
146         'values' => [
147           'name' => 'User',
148         ],
149         'expected' => '/users/[user:name]',
150       ),
151     );
152     foreach ($tests as $test) {
153       $entity = \Drupal::entityTypeManager()->getStorage($test['entity'])->create($test['values']);
154       $entity->save();
155       $actual = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
156       $this->assertIdentical($actual->getPattern(), $test['expected'], t("Correct pattern returned for @entity_type with @values", array(
157         '@entity' => $test['entity'],
158         '@values' => print_r($test['values'], TRUE),
159       )));
160     }
161   }
162
163   /**
164    * Test potential conflicts with the same alias in different languages.
165    */
166   public function testSameTitleDifferentLanguages() {
167     // Create two English articles with the same title.
168     $edit = [
169       'title' => 'Sample page',
170       'type' => 'page',
171       'langcode' => 'en',
172     ];
173     $node1 = $this->drupalCreateNode($edit);
174     $this->assertEntityAlias($node1, '/content/sample-page', 'en');
175
176     $node2 = $this->drupalCreateNode($edit);
177     $this->assertEntityAlias($node2, '/content/sample-page-0', 'en');
178
179     // Now, create a French article with the same title, and verify that it gets
180     // the basic alias with the correct langcode.
181     $edit['langcode'] = 'fr';
182     $node3 = $this->drupalCreateNode($edit);
183     $this->assertEntityAlias($node3, '/content/sample-page', 'fr');
184   }
185
186   /**
187    * Test pathauto_cleanstring().
188    */
189   public function testCleanString() {
190
191     // Test with default settings defined in pathauto.settings.yml.
192     $this->installConfig(array('pathauto'));
193     \Drupal::service('pathauto.generator')->resetCaches();
194
195     $tests = array();
196
197     // Test the 'ignored words' removal.
198     $tests['this'] = 'this';
199     $tests['this with that'] = 'this-with-that';
200     $tests['this thing with that thing'] = 'thing-thing';
201
202     // Test 'ignored words' removal and duplicate separator removal.
203     $tests[' - Pathauto is the greatest - module ever - '] = 'pathauto-greatest-module-ever';
204
205     // Test length truncation and lowering of strings.
206     $long_string = $this->randomMachineName(120);
207     $tests[$long_string] = strtolower(substr($long_string, 0, 100));
208
209     // Test that HTML tags are removed.
210     $tests['This <span class="text">text</span> has <br /><a href="http://example.com"><strong>HTML tags</strong></a>.'] = 'text-has-html-tags';
211     $tests[Html::escape('This <span class="text">text</span> has <br /><a href="http://example.com"><strong>HTML tags</strong></a>.')] = 'text-has-html-tags';
212
213     // Transliteration.
214     $tests['ľščťžýáíéňô'] = 'lsctzyaieno';
215
216     foreach ($tests as $input => $expected) {
217       $output = \Drupal::service('pathauto.alias_cleaner')->cleanString($input);
218       $this->assertEqual($output, $expected, t("Drupal::service('pathauto.alias_cleaner')->cleanString('@input') expected '@expected', actual '@output'", array(
219         '@input' => $input,
220         '@expected' => $expected,
221         '@output' => $output,
222       )));
223     }
224   }
225
226   /**
227    * Test pathauto_clean_alias().
228    */
229   public function testCleanAlias() {
230     $tests = array();
231     $tests['one/two/three'] = '/one/two/three';
232     $tests['/one/two/three/'] = '/one/two/three';
233     $tests['one//two///three'] = '/one/two/three';
234     $tests['one/two--three/-/--/-/--/four---five'] = '/one/two-three/four-five';
235     $tests['one/-//three--/four'] = '/one/three/four';
236
237     foreach ($tests as $input => $expected) {
238       $output = \Drupal::service('pathauto.alias_cleaner')->cleanAlias($input);
239       $this->assertEqual($output, $expected, t("Drupal::service('pathauto.generator')->cleanAlias('@input') expected '@expected', actual '@output'", array(
240         '@input' => $input,
241         '@expected' => $expected,
242         '@output' => $output,
243       )));
244     }
245   }
246
247   /**
248    * Test pathauto_path_delete_multiple().
249    */
250   public function testPathDeleteMultiple() {
251     $this->saveAlias('/node/1', '/node-1-alias');
252     $this->saveAlias('/node/1/view', '/node-1-alias/view');
253     $this->saveAlias('/node/1', '/node-1-alias-en', 'en');
254     $this->saveAlias('/node/1', '/node-1-alias-fr', 'fr');
255     $this->saveAlias('/node/2', '/node-2-alias');
256     $this->saveAlias('/node/10', '/node-10-alias');
257
258     \Drupal::service('pathauto.alias_storage_helper')->deleteBySourcePrefix('/node/1');
259     $this->assertNoAliasExists(array('source' => "/node/1"));
260     $this->assertNoAliasExists(array('source' => "/node/1/view"));
261     $this->assertAliasExists(array('source' => "/node/2"));
262     $this->assertAliasExists(array('source' => "/node/10"));
263   }
264
265   /**
266    * Test the different update actions in \Drupal::service('pathauto.generator')->createEntityAlias().
267    */
268   public function testUpdateActions() {
269     $config = $this->config('pathauto.settings');
270
271     // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'insert'.
272     $config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW);
273     $config->save();
274     $node = $this->drupalCreateNode(array('title' => 'First title'));
275     $this->assertEntityAlias($node, '/content/first-title');
276
277     $node->path->pathauto = PathautoState::CREATE;
278
279     // Default action is PATHAUTO_UPDATE_ACTION_DELETE.
280     $config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_DELETE);
281     $config->save();
282     $node->setTitle('Second title');
283     $node->save();
284     $this->assertEntityAlias($node, '/content/second-title');
285     $this->assertNoAliasExists(array('alias' => '/content/first-title'));
286
287     // Test PATHAUTO_UPDATE_ACTION_LEAVE.
288     $config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_LEAVE);
289     $config->save();
290     $node->setTitle('Third title');
291     $node->save();
292     $this->assertEntityAlias($node, '/content/third-title');
293     $this->assertAliasExists(array('source' => '/' . $node->toUrl()->getInternalPath(), 'alias' => '/content/second-title'));
294
295     $config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_DELETE);
296     $config->save();
297     $node->setTitle('Fourth title');
298     $node->save();
299     $this->assertEntityAlias($node, '/content/fourth-title');
300     $this->assertNoAliasExists(array('alias' => '/content/third-title'));
301     // The older second alias is not deleted yet.
302     $older_path = $this->assertAliasExists(array('source' => '/' . $node->toUrl()->getInternalPath(), 'alias' => '/content/second-title'));
303     \Drupal::service('path.alias_storage')->delete($older_path);
304
305     $config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW);
306     $config->save();
307     $node->setTitle('Fifth title');
308     $node->save();
309     $this->assertEntityAlias($node, '/content/fourth-title');
310     $this->assertNoAliasExists(array('alias' => '/content/fifth-title'));
311
312     // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'update'.
313     $this->deleteAllAliases();
314     $node->save();
315     $this->assertEntityAlias($node, '/content/fifth-title');
316
317     // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'bulkupdate'.
318     $this->deleteAllAliases();
319     $node->setTitle('Sixth title');
320     \Drupal::service('pathauto.generator')->updateEntityAlias($node, 'bulkupdate');
321     $this->assertEntityAlias($node, '/content/sixth-title');
322   }
323
324   /**
325    * Test that \Drupal::service('pathauto.generator')->createEntityAlias() will
326    * not create an alias for a pattern that does not get any tokens replaced.
327    */
328   public function testNoTokensNoAlias() {
329     $this->installConfig(['filter']);
330     $this->nodePattern
331       ->setPattern('/content/[node:body]')
332       ->save();
333
334     $node = $this->drupalCreateNode();
335     $this->assertNoEntityAliasExists($node);
336
337     $node->body->value = 'hello';
338     $node->save();
339     $this->assertEntityAlias($node, '/content/hello');
340   }
341
342   /**
343    * Test the handling of path vs non-path tokens in pathauto_clean_token_values().
344    */
345   public function testPathTokens() {
346     $this->createPattern('taxonomy_term', '/[term:parent:url:path]/[term:name]');
347
348     $vocab = $this->addVocabulary();
349
350     $term1 = $this->addTerm($vocab, array('name' => 'Parent term'));
351     $this->assertEntityAlias($term1, '/parent-term');
352
353     $term2 = $this->addTerm($vocab, array('name' => 'Child term', 'parent' => $term1->id()));
354     $this->assertEntityAlias($term2, '/parent-term/child-term');
355
356     $this->saveEntityAlias($term1, '/My Crazy/Alias/');
357     $term2->save();
358     $this->assertEntityAlias($term2, '/My Crazy/Alias/child-term');
359   }
360
361   /**
362    * Test using fields for path structures.
363    */
364   function testParentChildPathTokens() {
365     // First create a field which will be used to create the path. It must
366     // begin with a letter.
367     $this->installEntitySchema('taxonomy_term');
368
369     Vocabulary::create(['vid' => 'tags'])->save();
370
371     $fieldname = 'a' . mb_strtolower($this->randomMachineName());
372     $field_storage = FieldStorageConfig::create(['entity_type' => 'taxonomy_term', 'field_name' => $fieldname, 'type' => 'string']);
373     $field_storage->save();
374     $field = FieldConfig::create(['field_storage' => $field_storage, 'bundle' => 'tags']);
375     $field->save();
376
377     $display = entity_get_display('taxonomy_term', 'tags', 'default');
378     $display->setComponent($fieldname, ['type' => 'string']);
379     $display->save();
380
381     // Make the path pattern of a field use the value of this field appended
382     // to the parent taxonomy term's pattern if there is one.
383     $this->createPattern('taxonomy_term', '/[term:parents:join-path]/[term:' . $fieldname . ']');
384
385     // Start by creating a parent term.
386     $parent = Term::create(['vid' => 'tags', $fieldname => $this->randomMachineName(), 'name' => $this->randomMachineName()]);
387     $parent->save();
388
389     // Create the child term.
390     $child = Term::create(['vid' => 'tags', $fieldname => $this->randomMachineName(), 'parent' => $parent, 'name' => $this->randomMachineName()]);
391     $child->save();
392     $this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));
393
394     // Re-saving the parent term should not modify the child term's alias.
395     $parent->save();
396     $this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));
397   }
398
399   /**
400    * Tests aliases on taxonomy terms.
401    */
402   public function testTaxonomyPattern() {
403     // Create a vocabulary and test that it's pattern variable works.
404     $vocab = $this->addVocabulary(array('vid' => 'name'));
405     $this->createPattern('taxonomy_term', 'base');
406     $pattern = $this->createPattern('taxonomy_term', 'bundle', -1);
407     $this->addBundleCondition($pattern, 'taxonomy_term', 'name');
408     $pattern->save();
409     $this->assertEntityPattern('taxonomy_term', 'name', Language::LANGCODE_NOT_SPECIFIED, 'bundle');
410   }
411
412   function testNoExistingPathAliases() {
413     $this->config('pathauto.settings')
414       ->set('punctuation.period', PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING)
415       ->save();
416
417     $this->nodePattern
418       ->setPattern('[node:title]')
419       ->save();
420
421     // Check that Pathauto does not create an alias of '/admin'.
422     $node = $this->drupalCreateNode(array('title' => 'Admin', 'type' => 'page'));
423     $this->assertEntityAlias($node, '/admin-0');
424
425     // Check that Pathauto does not create an alias of '/modules'.
426     $node->setTitle('Modules');
427     $node->save();
428     $this->assertEntityAlias($node, '/modules-0');
429
430     // Check that Pathauto does not create an alias of '/index.php'.
431     $node->setTitle('index.php');
432     $node->save();
433     $this->assertEntityAlias($node, '/index.php-0');
434
435     // Check that a safe value gets an automatic alias. This is also a control
436     // to ensure the above tests work properly.
437     $node->setTitle('Safe value');
438     $node->save();
439     $this->assertEntityAlias($node, '/safe-value');
440   }
441
442   /**
443    * Test programmatic entity creation for aliases.
444    */
445   function testProgrammaticEntityCreation() {
446     $this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
447     $node = $this->drupalCreateNode(array('title' => 'Test node', 'path' => array('pathauto' => TRUE)));
448     $this->assertEntityAlias($node, '/content/test-node');
449
450     $vocabulary = $this->addVocabulary(array('name' => 'Tags'));
451     $term = $this->addTerm($vocabulary, array('name' => 'Test term', 'path' => array('pathauto' => TRUE)));
452     $this->assertEntityAlias($term, '/tags/test-term');
453
454     $edit['name'] = 'Test user';
455     $edit['mail'] = 'test-user@example.com';
456     $edit['pass']   = user_password();
457     $edit['path'] = array('pathauto' => TRUE);
458     $edit['status'] = 1;
459     $account = User::create($edit);
460     $account->save();
461     $this->assertEntityAlias($account, '/users/test-user');
462   }
463
464   /**
465    * Tests word safe alias truncating.
466    */
467   function testPathAliasUniquifyWordsafe() {
468     $this->config('pathauto.settings')
469       ->set('max_length', 26)
470       ->save();
471
472     $node_1 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
473     $node_2 = $this->drupalCreateNode(array('title' => 'thequick brownfox jumpedover thelazydog', 'type' => 'page'));
474
475     // Check that alias uniquifying is truncating with $wordsafe param set to
476     // TRUE.
477     // If it doesn't path alias result would be content/thequick-brownf-0
478     $this->assertEntityAlias($node_1, '/content/thequick-brownfox');
479     $this->assertEntityAlias($node_2, '/content/thequick-0');
480   }
481
482   /**
483    * Test if aliases are (not) generated with enabled/disabled patterns.
484    */
485   function testPatternStatus() {
486     // Create a node to get an alias for.
487     $title = 'Pattern enabled';
488     $alias = '/content/pattern-enabled';
489     $node1 = $this->drupalCreateNode(['title' => $title, 'type' => 'page']);
490     $this->assertEntityAlias($node1, $alias);
491
492     // Disable the pattern, save the node again and make sure the alias is still
493     // working.
494     $this->nodePattern->setStatus(FALSE)->save();
495
496     $node1->save();
497     $this->assertEntityAlias($node1, $alias);
498
499     // Create a new node with disabled pattern and make sure there is no new
500     // alias created.
501     $title = 'Pattern disabled';
502     $node2 = $this->drupalCreateNode(['title' => $title, 'type' => 'page']);
503     $this->assertNoEntityAlias($node2);
504   }
505
506   /**
507    * Tests that enabled entity types generates the necessary fields and plugins.
508    */
509   public function testSettingChangeInvalidatesCache() {
510
511     $this->installConfig(['pathauto']);
512
513     $this->enableModules(['entity_test']);
514
515     $definitions = \Drupal::service('plugin.manager.alias_type')->getDefinitions();
516     $this->assertFalse(isset($definitions['canonical_entities:entity_test']));
517
518     $fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('entity_test');
519     $this->assertFalse(isset($fields['path']));
520
521     $this->config('pathauto.settings')
522       ->set('enabled_entity_types', ['user', 'entity_test'])
523       ->save();
524
525     $definitions = \Drupal::service('plugin.manager.alias_type')->getDefinitions();
526     $this->assertTrue(isset($definitions['canonical_entities:entity_test']));
527
528     $fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('entity_test');
529     $this->assertTrue(isset($fields['path']));
530
531   }
532
533   /**
534    * Tests that aliases are only generated for default revisions.
535    */
536   public function testDefaultRevision() {
537     $node1 = $this->drupalCreateNode(['title' => 'Default revision', 'type' => 'page']);
538     $this->assertEntityAlias($node1, '/content/default-revision');
539
540     $node1->setNewRevision(TRUE);
541     $node1->isDefaultRevision(FALSE);
542     $node1->setTitle('New non-default-revision');
543     $node1->save();
544
545     $this->assertEntityAlias($node1, '/content/default-revision');
546   }
547
548   /**
549    * Tests that the pathauto state property gets set to CREATED for new nodes.
550    *
551    * In some cases, this can trigger $node->path to be set up with no default
552    * value for the pathauto property.
553    */
554   public function testCreateNodeWhileAccessingPath() {
555     $node = Node::create([
556       'type' => 'article',
557       'title' => 'TestAlias',
558     ]);
559     $node->path->langcode;
560     $node->save();
561     $this->assertEntityAlias($node, '/content/testalias');
562   }
563
564   /**
565    * Creates a node programmatically.
566    *
567    * @param array $settings
568    *   The array of values for the node.
569    *
570    * @return \Drupal\node\Entity\Node
571    *   The created node.
572    */
573   protected function drupalCreateNode(array $settings = array()) {
574     // Populate defaults array.
575     $settings += array(
576       'title'     => $this->randomMachineName(8),
577       'type'      => 'page',
578     );
579
580     $node = Node::create($settings);
581     $node->save();
582
583     return $node;
584   }
585
586 }