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