first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
name: 'Pathauto custom punctuation testing module'
type: module
description: 'Add some uncommon punctuation to the replacement list.'
package: Testing
# Information added by Drupal.org packaging script on 2024-05-18
version: '8.x-1.12+6-dev'
project: 'pathauto'
datestamp: 1716065615

View File

@@ -0,0 +1,8 @@
<?php
/**
* Implements hook_pathauto_punctuation_chars_alter().
*/
function pathauto_custom_punctuation_test_pathauto_punctuation_chars_alter(array &$punctuation) {
$punctuation['copyright'] = ['value' => '©', 'name' => t('Copyright symbol')];
}

View File

@@ -0,0 +1,11 @@
name: 'Pathauto testing module'
type: module
description: 'Pathauto for Entity with string ID.'
package: Testing
dependencies:
- token:token
# Information added by Drupal.org packaging script on 2024-05-18
version: '8.x-1.12+6-dev'
project: 'pathauto'
datestamp: 1716065615

View File

@@ -0,0 +1,50 @@
<?php
namespace Drupal\pathauto_string_id_test\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* Defines a test entity with a string ID.
*
* @ContentEntityType(
* id = "pathauto_string_id_test",
* label = @Translation("Test entity with string ID"),
* handlers = {
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* },
* },
* base_table = "pathauto_string_id_test",
* entity_keys = {
* "id" = "id",
* "label" = "name",
* },
* links = {
* "canonical" = "/pathauto_string_id_test/{pathauto_string_id_test}",
* },
* )
*/
class PathautoStringIdTest extends ContentEntityBase {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['id'] = BaseFieldDefinition::create('string')
->setLabel('ID')
->setReadOnly(TRUE)
// A bigger value will not be allowed to build the index.
->setSetting('max_length', 191);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel('Name');
$fields['path'] = BaseFieldDefinition::create('path')
->setLabel('Path')
->setComputed(TRUE);
return $fields;
}
}

View File

@@ -0,0 +1,11 @@
name: 'Views Test Config'
type: module
description: 'Provides default views for tests.'
package: Testing
dependencies:
- drupal:views
# Information added by Drupal.org packaging script on 2024-05-18
version: '8.x-1.12+6-dev'
project: 'pathauto'
datestamp: 1716065615

View File

@@ -0,0 +1,169 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\pathauto\PathautoGeneratorInterface;
use Drupal\pathauto\PathautoState;
use Drupal\Tests\BrowserTestBase;
/**
* Bulk update functionality tests.
*
* @group pathauto
*/
class PathautoBulkUpdateTest extends BrowserTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'pathauto', 'forum'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* The created nodes.
*
* @var \Drupal\node\NodeInterface
*/
protected $nodes;
/**
* The created patterns.
*
* @var \Drupal\pathauto\PathautoPatternInterface
*/
protected $patterns;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Allow other modules to add additional permissions for the admin user.
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'administer forums',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
$this->patterns = [];
$this->patterns['node'] = $this->createPattern('node', '/content/[node:title]');
$this->patterns['user'] = $this->createPattern('user', '/users/[user:name]');
$this->patterns['forum'] = $this->createPattern('forum', '/forums/[term:name]');
}
public function testBulkUpdate() {
// Create some nodes.
$this->nodes = [];
for ($i = 1; $i <= 5; $i++) {
$node = $this->drupalCreateNode();
$this->nodes[$node->id()] = $node;
}
// Clear out all aliases.
$this->deleteAllAliases();
// Bulk create aliases.
$edit = [
'update[canonical_entities:node]' => TRUE,
'update[canonical_entities:user]' => TRUE,
'update[forum]' => TRUE,
];
$this->drupalGet('admin/config/search/path/update_bulk');
$this->submitForm($edit, 'Update');
// This has generated 8 aliases: 5 nodes, 2 users and 1 forum.
$this->assertSession()->pageTextContains('Generated 8 URL aliases.');
// Check that aliases have actually been created.
foreach ($this->nodes as $node) {
$this->assertEntityAliasExists($node);
}
$this->assertEntityAliasExists($this->adminUser);
// This is the default "General discussion" forum.
$this->assertAliasExists(['path' => '/taxonomy/term/1']);
// Add a new node.
$new_node = $this->drupalCreateNode(['path' => ['alias' => '', 'pathauto' => PathautoState::SKIP]]);
// Run the update again which should not run against any nodes.
$this->drupalGet('admin/config/search/path/update_bulk');
$this->submitForm($edit, 'Update');
$this->assertSession()->pageTextContains('No new URL aliases to generate.');
$this->assertNoEntityAliasExists($new_node);
// Make sure existing aliases can be overridden.
$this->drupalGet('admin/config/search/path/settings');
$this->submitForm(['update_action' => (string) PathautoGeneratorInterface::UPDATE_ACTION_DELETE], 'Save configuration');
// Patterns did not change, so no aliases should be regenerated.
$edit['action'] = 'all';
$this->drupalGet('admin/config/search/path/update_bulk');
$this->submitForm($edit, 'Update');
$this->assertSession()->pageTextContains('No new URL aliases to generate.');
// Update the node pattern, and leave other patterns alone. Existing nodes
// should get a new alias, except the node above whose alias is manually
// set. Other aliases must be left alone.
$this->patterns['node']->delete();
$this->patterns['node'] = $this->createPattern('node', '/archive/node-[node:nid]');
$this->drupalGet('admin/config/search/path/update_bulk');
$this->submitForm($edit, 'Update');
$this->assertSession()->pageTextContains('Generated 5 URL aliases.');
// Prevent existing aliases to be overridden. The bulk generate page should
// only offer to create an alias for paths which have none.
$this->drupalGet('admin/config/search/path/settings');
$this->submitForm(['update_action' => (string) PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW], 'Save configuration');
$this->drupalGet('admin/config/search/path/update_bulk');
$this->assertSession()->fieldValueEquals('action', 'create');
$this->assertSession()->pageTextContains('Pathauto settings are set to ignore paths which already have a URL alias.');
$this->assertSession()->fieldValueNotEquals('action', 'update');
$this->assertSession()->fieldValueNotEquals('action', 'all');
}
/**
* Tests alias generation for nodes that existed before installing Pathauto.
*/
public function testBulkUpdateExistingContent() {
// Create a node.
$node = $this->drupalCreateNode();
// Delete its alias and Pathauto metadata.
\Drupal::service('pathauto.alias_storage_helper')->deleteEntityPathAll($node);
$node->path->first()->get('pathauto')->purge();
\Drupal::entityTypeManager()->getStorage('node')->resetCache([$node->id()]);
// Execute bulk generation.
// Bulk create aliases.
$edit = [
'update[canonical_entities:node]' => TRUE,
];
$this->drupalGet('admin/config/search/path/update_bulk');
$this->submitForm($edit, 'Update');
// Verify that the alias was created for the node.
$this->assertSession()->pageTextContains('Generated 1 URL alias.');
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Tests\BrowserTestBase;
/**
* Tests pathauto settings form.
*
* @group pathauto
*/
class PathautoEnablingEntityTypesTest extends BrowserTestBase {
use PathautoTestHelperTrait;
use CommentTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'pathauto', 'comment'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalCreateContentType(['type' => 'article']);
$this->addDefaultCommentField('node', 'article');
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'administer nodes',
'post comments',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
}
/**
* A suite of tests to verify if the feature to enable and disable the
* ability to define alias patterns for a given entity type works. Test with
* the comment module, as it is not enabled by default.
*/
public function testEnablingEntityTypes() {
// Verify that the comment entity type is not available when trying to add
// a new pattern, nor "broken".
$this->drupalGet('/admin/config/search/path/patterns/add');
$this->assertCount(0, $this->cssSelect('option[value = "canonical_entities:comment"]:contains(Comment)'));
$this->assertCount(0, $this->cssSelect('option:contains(Broken)'));
// Enable the entity type and create a pattern for it.
$this->drupalGet('/admin/config/search/path/settings');
$edit = [
'enabled_entity_types[comment]' => TRUE,
];
$this->submitForm($edit, "Save configuration" );
$this->createPattern('comment', '/comment/[comment:body]');
// Create a node, a comment type and a comment entity.
$node = $this->drupalCreateNode(['type' => 'article']);
$this->drupalGet('/node/' . $node->id());
$edit = [
'comment_body[0][value]' => 'test-body',
];
$this->submitForm($edit, 'Save');
// Verify that an alias has been generated and that the type can no longer
// be disabled.
$this->assertAliasExists(['alias' => '/comment/test-body']);
$this->drupalGet('/admin/config/search/path/settings');
$this->assertCount(1, $this->cssSelect('input[name = "enabled_entity_types[comment]"][disabled = "disabled"]'));
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\pathauto\PathautoState;
use Drupal\Tests\BrowserTestBase;
/**
* Mass delete functionality tests.
*
* @group pathauto
*/
class PathautoMassDeleteTest extends BrowserTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'taxonomy', 'pathauto'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* The test nodes.
*
* @var \Drupal\node\NodeInterface
*/
protected $nodes;
/**
* The test accounts.
*
* @var \Drupal\user\UserInterface
*/
protected $accounts;
/**
* The test terms.
*
* @var \Drupal\taxonomy\TermInterface
*/
protected $terms;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
$this->createPattern('node', '/content/[node:title]');
$this->createPattern('user', '/users/[user:name]');
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
}
/**
* Tests the deletion of all the aliases.
*/
public function testDeleteAll() {
/** @var \Drupal\pathauto\AliasStorageHelperInterface $alias_storage_helper */
$alias_storage_helper = \Drupal::service('pathauto.alias_storage_helper');
// 1. Test that deleting all the aliases, of any type, works.
$this->generateAliases();
$edit = [
'delete[all_aliases]' => TRUE,
'options[keep_custom_aliases]' => FALSE,
];
$this->drupalGet('admin/config/search/path/delete_bulk');
$this->submitForm($edit, 'Delete aliases now!');
$this->assertSession()->pageTextContains('All of your path aliases have been deleted.');
$this->assertSession()->addressEquals('admin/config/search/path/delete_bulk');
// Make sure that all of them are actually deleted.
$this->assertEquals(0, $alias_storage_helper->countAll(), 'All the aliases have been deleted.');
// 2. Test deleting only specific (entity type) aliases.
$manager = $this->container->get('plugin.manager.alias_type');
$pathauto_plugins = [
'canonical_entities:node' => 'nodes',
'canonical_entities:taxonomy_term' => 'terms',
'canonical_entities:user' => 'accounts',
];
foreach ($pathauto_plugins as $pathauto_plugin => $attribute) {
$this->generateAliases();
$edit = [
'delete[plugins][' . $pathauto_plugin . ']' => TRUE,
'options[keep_custom_aliases]' => FALSE,
];
$this->drupalGet('admin/config/search/path/delete_bulk');
$this->submitForm($edit, 'Delete aliases now!');
$alias_type = $manager->createInstance($pathauto_plugin);
$this->assertSession()->responseContains(new FormattableMarkup('All of your %label path aliases have been deleted.', ['%label' => $alias_type->getLabel()]));
// Check that the aliases were actually deleted.
foreach ($this->{$attribute} as $entity) {
$this->assertNoEntityAlias($entity);
}
// Check that the other aliases are not deleted.
foreach ($pathauto_plugins as $_pathauto_plugin => $_attribute) {
// Skip the aliases that should be deleted.
if ($_pathauto_plugin == $pathauto_plugin) {
continue;
}
foreach ($this->{$_attribute} as $entity) {
$this->assertEntityAliasExists($entity);
}
}
}
// 3. Test deleting automatically generated aliases only.
$this->generateAliases();
$edit = [
'delete[all_aliases]' => TRUE,
'options[keep_custom_aliases]' => TRUE,
];
$this->drupalGet('admin/config/search/path/delete_bulk');
$this->submitForm($edit, 'Delete aliases now!');
$this->assertSession()->pageTextContains('All of your automatically generated path aliases have been deleted.');
$this->assertSession()->addressEquals('admin/config/search/path/delete_bulk');
// Make sure that only custom aliases and aliases with no information about
// their state still exist.
$this->assertEquals(3, $alias_storage_helper->countAll(), 'Custom aliases still exist.');
$this->assertEquals('/node/101', $alias_storage_helper->loadBySource('/node/101', 'en')['source']);
$this->assertEquals('/node/104', $alias_storage_helper->loadBySource('/node/104', 'en')['source']);
$this->assertEquals('/node/105', $alias_storage_helper->loadBySource('/node/105', 'en')['source']);
}
/**
* Helper function to generate aliases.
*/
public function generateAliases() {
// Delete all aliases to avoid duplicated aliases. They will be recreated
// below.
$this->deleteAllAliases();
// We generate a bunch of aliases for nodes, users and taxonomy terms. If
// the entities are already created we just update them, otherwise we create
// them.
if (empty($this->nodes)) {
// Create a large number of nodes (100+) to make sure that the batch code
// works.
for ($i = 1; $i <= 105; $i++) {
// Set the alias of two nodes manually.
$settings = ($i > 103) ? ['path' => ['alias' => "/custom_alias_$i", 'pathauto' => PathautoState::SKIP]] : [];
$node = $this->drupalCreateNode($settings);
$this->nodes[$node->id()] = $node;
}
}
else {
foreach ($this->nodes as $node) {
if ($node->id() > 103) {
// The alias is set manually.
$node->set('path', ['alias' => '/custom_alias_' . $node->id()]);
}
$node->save();
}
}
// Delete information about the state of an alias to make sure that aliases
// with no such data are left alone by default.
\Drupal::keyValue('pathauto_state.node')->delete(101);
if (empty($this->accounts)) {
for ($i = 1; $i <= 5; $i++) {
$account = $this->drupalCreateUser();
$this->accounts[$account->id()] = $account;
}
}
else {
foreach ($this->accounts as $account) {
$account->save();
}
}
if (empty($this->terms)) {
$vocabulary = $this->addVocabulary(['name' => 'test vocabulary', 'vid' => 'test_vocabulary']);
for ($i = 1; $i <= 5; $i++) {
$term = $this->addTerm($vocabulary);
$this->terms[$term->id()] = $term;
}
}
else {
foreach ($this->terms as $term) {
$term->save();
}
}
// Check that we have aliases for the entities.
foreach (['nodes', 'accounts', 'terms'] as $attribute) {
foreach ($this->{$attribute} as $entity) {
$this->assertEntityAliasExists($entity);
}
}
}
}

View File

@@ -0,0 +1,392 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\node\Entity\Node;
use Drupal\pathauto\Entity\PathautoPattern;
use Drupal\pathauto\PathautoState;
use Drupal\Tests\BrowserTestBase;
/**
* Tests pathauto node UI integration.
*
* @group pathauto
*/
class PathautoNodeWebTest extends BrowserTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'pathauto', 'views', 'taxonomy', 'pathauto_views_test'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->drupalCreateContentType(['type' => 'article']);
// Allow other modules to add additional permissions for the admin user.
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'bypass node access',
'access content overview',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
$this->createPattern('node', '/content/[node:title]');
}
/**
* Tests editing nodes with different settings.
*/
public function testNodeEditing() {
// Ensure that the Pathauto checkbox is checked by default on the node add
// form.
$this->drupalGet('node/add/page');
$this->assertSession()->checkboxChecked('edit-path-0-pathauto');
// Create a node by saving the node form.
$title = ' Testing: node title [';
$automatic_alias = '/content/testing-node-title';
$this->submitForm(['title[0][value]' => $title], 'Save');
$node = $this->drupalGetNodeByTitle($title);
// Look for alias generated in the form.
$this->drupalGet("node/{$node->id()}/edit");
$this->assertSession()->checkboxChecked('edit-path-0-pathauto');
$this->assertSession()->fieldValueEquals('path[0][alias]', $automatic_alias);
// Check whether the alias actually works.
$this->assertSession()->responseContains($title);
// Manually set the node's alias.
$manual_alias = '/content/' . $node->id();
$edit = [
'path[0][pathauto]' => FALSE,
'path[0][alias]' => $manual_alias,
];
$this->drupalGet($node->toUrl('edit-form'));
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains(new FormattableMarkup('@title has been updated.', ['@title' => $title]));
// Check that the automatic alias checkbox is now unchecked by default.
$this->drupalGet("node/{$node->id()}/edit");
$this->assertSession()->checkboxNotChecked('edit-path-0-pathauto');
$this->assertSession()->fieldValueEquals('path[0][alias]', $manual_alias);
// Submit the node form with the default values.
$this->submitForm(['path[0][pathauto]' => FALSE], 'Save');
$this->assertSession()->pageTextContains(new FormattableMarkup('@title has been updated.', ['@title' => $title]));
// Test that the old (automatic) alias has been deleted and only accessible
// through the new (manual) alias.
$this->drupalGet($automatic_alias);
$this->assertSession()->statusCodeEquals(404);
$this->drupalGet($manual_alias);
$this->assertSession()->pageTextContains($title);
// Test that the manual alias is not kept for new nodes when the pathauto
// checkbox is ticked.
$title = 'Automatic Title';
$edit = [
'title[0][value]' => $title,
'path[0][pathauto]' => TRUE,
'path[0][alias]' => '/should-not-get-created',
];
$this->drupalGet('node/add/page');
$this->submitForm( $edit, 'Save');
$this->assertNoAliasExists(['alias' => 'should-not-get-created']);
$node = $this->drupalGetNodeByTitle($title);
$this->assertEntityAlias($node, '/content/automatic-title');
// Remove the pattern for nodes, the pathauto checkbox should not be
// displayed.
$ids = \Drupal::entityQuery('pathauto_pattern')
->condition('type', 'canonical_entities:node')
->accessCheck(TRUE)
->execute();
foreach (PathautoPattern::loadMultiple($ids) as $pattern) {
$pattern->delete();
}
$this->drupalGet('node/add/article');
$this->assertSession()->fieldNotExists('edit-path-0-pathauto');
$this->assertSession()->fieldValueEquals('path[0][alias]', '');
$edit = [];
$edit['title'] = 'My test article';
$this->drupalCreateNode($edit);
$node = $this->drupalGetNodeByTitle($edit['title']);
// Pathauto checkbox should still not exist.
$this->drupalGet($node->toUrl('edit-form'));
$this->assertSession()->fieldNotExists('edit-path-0-pathauto');
$this->assertSession()->fieldValueEquals('path[0][alias]', '');
$this->assertNoEntityAlias($node);
}
/**
* Test node operations.
*/
public function testNodeOperations() {
$node1 = $this->drupalCreateNode(['title' => 'node1']);
$node2 = $this->drupalCreateNode(['title' => 'node2']);
// Delete all current URL aliases.
$this->deleteAllAliases();
$this->drupalGet('admin/content');
// Check which of the two nodes is first.
if (strpos($this->getTextContent(), 'node1') < strpos($this->getTextContent(), 'node2')) {
$index = 0;
}
else {
$index = 1;
}
$edit = [
'action' => 'pathauto_update_alias_node',
'node_bulk_form[' . $index . ']' => TRUE,
];
$this->submitForm($edit, 'Apply to selected items');
$this->assertSession()->pageTextContains('Update URL alias was applied to 1 item.');
$this->assertEntityAlias($node1, '/content/' . $node1->getTitle());
$this->assertEntityAlias($node2, '/node/' . $node2->id());
}
/**
* @todo Merge this with existing node test methods?
*/
public function testNodeState() {
$nodeNoAliasUser = $this->drupalCreateUser(['bypass node access']);
$nodeAliasUser = $this->drupalCreateUser(['bypass node access', 'create url aliases']);
$node = $this->drupalCreateNode([
'title' => 'Node version one',
'type' => 'page',
'path' => [
'pathauto' => PathautoState::SKIP,
],
]);
$this->assertNoEntityAlias($node);
// Set a manual path alias for the node.
$node->path->alias = '/test-alias';
$node->save();
// Ensure that the pathauto field was saved to the database.
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
$node = Node::load($node->id());
$this->assertSame(PathautoState::SKIP, $node->path->pathauto);
// Ensure that the manual path alias was saved and an automatic alias was not generated.
$this->assertEntityAlias($node, '/test-alias');
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
// Save the node as a user who does not have access to path fieldset.
$this->drupalLogin($nodeNoAliasUser);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertSession()->fieldNotExists('path[0][pathauto]');
$edit = ['title[0][value]' => 'Node version two'];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Basic page Node version two has been updated.');
$this->assertEntityAlias($node, '/test-alias');
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
$this->assertNoEntityAliasExists($node, '/content/node-version-two');
// Load the edit node page and check that the Pathauto checkbox is unchecked.
$this->drupalLogin($nodeAliasUser);
$this->drupalGet('node/' . $node->id() . '/edit');
$this->assertSession()->checkboxNotChecked('edit-path-0-pathauto');
// Edit the manual alias and save the node.
$edit = [
'title[0][value]' => 'Node version three',
'path[0][alias]' => '/manually-edited-alias',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Basic page Node version three has been updated.');
$this->assertEntityAlias($node, '/manually-edited-alias');
$this->assertNoEntityAliasExists($node, '/test-alias');
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
$this->assertNoEntityAliasExists($node, '/content/node-version-two');
$this->assertNoEntityAliasExists($node, '/content/node-version-three');
// Programatically save the node with an automatic alias.
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
$node = Node::load($node->id());
$node->path->pathauto = PathautoState::CREATE;
$node->save();
// Ensure that the pathauto field was saved to the database.
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
$node = Node::load($node->id());
$this->assertSame(PathautoState::CREATE, $node->path->pathauto);
$this->assertEntityAlias($node, '/content/node-version-three');
$this->assertNoEntityAliasExists($node, '/manually-edited-alias');
$this->assertNoEntityAliasExists($node, '/test-alias');
$this->assertNoEntityAliasExists($node, '/content/node-version-one');
$this->assertNoEntityAliasExists($node, '/content/node-version-two');
$node->delete();
$this->assertNull(\Drupal::keyValue('pathauto_state.node')->get($node->id()), 'Pathauto state was deleted');
}
/**
* Tests that nodes without a Pathauto pattern can set custom aliases.
*/
public function testCustomAliasWithoutPattern() {
// First, delete all patterns to be sure that there will be no match.
$entities = PathautoPattern::loadMultiple(NULL);
foreach ($entities as $entity) {
$entity->delete();
}
// Next, create a node with a custom alias.
$edit = [
'title[0][value]' => 'Sample article',
'path[0][alias]' => '/sample-article',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('article Sample article has been created.');
// Test the alias.
$this->assertAliasExists(['alias' => '/sample-article']);
$this->drupalGet('sample-article');
$this->assertSession()->statusCodeEquals(200);
// Now create a node through the API.
$node = Node::create([
'type' => 'article',
'title' => 'Sample article API',
'path' => ['alias' => '/sample-article-api'],
]);
$node->save();
// Test the alias.
$this->assertAliasExists(['alias' => '/sample-article-api']);
$this->drupalGet('sample-article-api');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests that nodes with an automatic alias can get a custom alias.
*/
public function testCustomAliasAfterAutomaticAlias() {
// Create a pattern.
$this->createPattern('node', '/content/[node:title]');
// Create a node with an automatic alias.
$edit = [
'title[0][value]' => 'Sample article',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('article Sample article has been created.');
// Ensure that the automatic alias got created.
$this->assertAliasExists(['alias' => '/content/sample-article']);
$this->drupalGet('/content/sample-article');
$this->assertSession()->statusCodeEquals(200);
// Now edit the node, set a custom alias.
$edit = [
'path[0][pathauto]' => 0,
'path[0][alias]' => '/sample-pattern-for-article',
];
$this->drupalGet('node/1/edit');
$this->submitForm($edit, 'Save');
// Assert that the new alias exists and the old one does not.
$this->assertAliasExists(['alias' => '/sample-pattern-for-article']);
$this->assertNoAliasExists(['alias' => '/content/sample-article']);
$this->drupalGet('sample-pattern-for-article');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests setting custom alias for nodes after removing pattern.
*
* Tests that nodes that had an automatic alias can get a custom alias after
* the pathauto pattern on which the automatic alias was based, is removed.
*/
public function testCustomAliasAfterRemovingPattern() {
// Create a pattern.
$this->createPattern('node', '/content/[node:title]');
// Create a node with an automatic alias.
$edit = [
'title[0][value]' => 'Sample article',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('article Sample article has been created.');
// Ensure that the automatic alias got created.
$this->assertAliasExists(['alias' => '/content/sample-article']);
$this->drupalGet('/content/sample-article');
$this->assertSession()->statusCodeEquals(200);
// Go to the edit the node form and confirm that the pathauto checkbox
// exists.
$this->drupalGet('node/1/edit');
$this->assertSession()->elementExists('css', '#edit-path-0-pathauto');
// Delete all patterns to be sure that there will be no match.
$entities = PathautoPattern::loadMultiple(NULL);
foreach ($entities as $entity) {
$entity->delete();
}
// Reload the node edit form and confirm that the pathauto checkbox no
// longer exists.
$this->drupalGet('node/1/edit');
$this->assertSession()->elementNotExists('css', '#edit-path-0-pathauto');
// Set a custom alias. We cannot disable the pathauto checkbox, because
// there is none.
$edit = [
'path[0][alias]' => '/sample-alias-for-article',
];
$this->submitForm($edit, 'Save');
// Check that the new alias exists and the old one does not.
$this->assertAliasExists(['alias' => '/sample-alias-for-article']);
$this->assertNoAliasExists(['alias' => '/content/sample-article']);
$this->drupalGet('sample-alias-for-article');
$this->assertSession()->statusCodeEquals(200);
}
}

View File

@@ -0,0 +1,247 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\pathauto\PathautoGeneratorInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Tests pathauto settings form.
*
* @group pathauto
*/
class PathautoSettingsFormWebTest extends BrowserTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'pathauto'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Form values that are set by default.
*
* @var array
*/
protected $defaultFormValues = [
'verbose' => FALSE,
'separator' => '-',
'case' => '1',
'max_length' => '100',
'max_component_length' => '100',
'update_action' => '2',
'transliterate' => '1',
'reduce_ascii' => FALSE,
'ignore_words' => 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with',
];
/**
* Punctuation form items with default values.
*
* @var array
*/
protected $defaultPunctuations = [
'punctuation[double_quotes]' => '0',
'punctuation[quotes]' => '0',
'punctuation[backtick]' => '0',
'punctuation[comma]' => '0',
'punctuation[period]' => '0',
'punctuation[hyphen]' => '1',
'punctuation[underscore]' => '0',
'punctuation[colon]' => '0',
'punctuation[semicolon]' => '0',
'punctuation[pipe]' => '0',
'punctuation[left_curly]' => '0',
'punctuation[left_square]' => '0',
'punctuation[right_curly]' => '0',
'punctuation[right_square]' => '0',
'punctuation[plus]' => '0',
'punctuation[equal]' => '0',
'punctuation[asterisk]' => '0',
'punctuation[ampersand]' => '0',
'punctuation[percent]' => '0',
'punctuation[caret]' => '0',
'punctuation[dollar]' => '0',
'punctuation[hash]' => '0',
'punctuation[exclamation]' => '0',
'punctuation[tilde]' => '0',
'punctuation[left_parenthesis]' => '0',
'punctuation[right_parenthesis]' => '0',
'punctuation[question_mark]' => '0',
'punctuation[less_than]' => '0',
'punctuation[greater_than]' => '0',
'punctuation[slash]' => '0',
'punctuation[back_slash]' => '0',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalCreateContentType(['type' => 'article']);
$permissions = [
'administer pathauto',
'notify of path changes',
'administer url aliases',
'create url aliases',
'bypass node access',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
$this->createPattern('node', '/content/[node:title]');
}
/**
* Test if the default values are shown correctly in the form.
*/
public function testDefaultFormValues() {
$this->drupalGet('/admin/config/search/path/settings');
$this->assertSession()->checkboxNotChecked('edit-verbose');
$this->assertSession()->fieldExists('edit-separator');
$this->assertSession()->checkboxChecked('edit-case');
$this->assertSession()->fieldExists('edit-max-length');
$this->assertSession()->fieldExists('edit-max-component-length');
$this->assertSession()->checkboxChecked('edit-update-action-2');
$this->assertSession()->checkboxChecked('edit-transliterate');
$this->assertSession()->checkboxNotChecked('edit-reduce-ascii');
$this->assertSession()->fieldExists('edit-ignore-words');
}
/**
* Test the verbose option.
*/
public function testVerboseOption() {
$edit = ['verbose' => '1'];
$this->drupalGet('/admin/config/search/path/settings');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->pageTextContains('The configuration options have been saved.');
$this->assertSession()->checkboxChecked('edit-verbose');
$title = 'Verbose settings test';
$this->drupalGet('/node/add/article');
$this->assertSession()->checkboxChecked('edit-path-0-pathauto');
$this->submitForm(['title[0][value]' => $title], 'Save');
$this->assertSession()->pageTextContains('Created new alias /content/verbose-settings-test for');
$node = $this->drupalGetNodeByTitle($title);
$this->drupalGet('/node/' . $node->id() . '/edit');
$this->submitForm(['title[0][value]' => 'Updated title'], 'Save');
$this->assertSession()->pageTextContains('Created new alias /content/updated-title for');
$this->assertSession()->pageTextContains('replacing /content/verbose-settings-test.');
}
/**
* Tests generating aliases with different settings.
*/
public function testSettingsForm() {
// Ensure the separator settings apply correctly.
$this->checkAlias('My awesome content', '/content/my.awesome.content', ['separator' => '.']);
// Ensure the character case setting works correctly.
// Leave case the same as source token values.
$this->checkAlias('My awesome Content', '/content/My-awesome-Content', ['case' => FALSE]);
$this->checkAlias('Change Lower', '/content/change-lower', ['case' => '1']);
// Ensure the maximum alias length is working.
$this->checkAlias('My awesome Content', '/content/my-awesome', ['max_length' => '23']);
// Ensure the maximum component length is working.
$this->checkAlias('My awesome Content', '/content/my', ['max_component_length' => '2']);
// Ensure transliteration option is working.
$this->checkAlias('è é àl ö äl ü', '/content/e-e-al-o-al-u', ['transliterate' => '1']);
$this->checkAlias('è é àl äl ö ü', '/content/è-é-àl-äl-ö-ü', ['transliterate' => FALSE]);
$ignore_words = 'a, new, very, should';
$this->checkAlias('a very new alias to test', '/content/alias-to-test', ['ignore_words' => $ignore_words]);
}
/**
* Test the punctuation setting form items.
*/
public function testPunctuationSettings() {
// Test the replacement of punctuations.
$settings = [];
foreach ($this->defaultPunctuations as $key => $punctuation) {
$settings[$key] = PathautoGeneratorInterface::PUNCTUATION_REPLACE;
}
$title = 'aa"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
$alias = '/content/aa-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-1-2-3';
$this->checkAlias($title, $alias, $settings);
// Test the removal of punctuations.
$settings = [];
foreach ($this->defaultPunctuations as $key => $punctuation) {
$settings[$key] = PathautoGeneratorInterface::PUNCTUATION_REMOVE;
}
$title = 'a"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
$alias = '/content/abcdefghijklmnopqrstuvwxyz123';
$this->checkAlias($title, $alias, $settings);
// Keep all punctuations in alias.
$settings = [];
foreach ($this->defaultPunctuations as $key => $punctuation) {
$settings[$key] = PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING;
}
$title = 'al"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
$alias = '/content/al"b`c,d.e-f_g:h;i|j{k[l}m]n+o=p*q%r^s$t#u!v~w(x)y?z>1/2\3';
$this->checkAlias($title, $alias, $settings);
}
/**
* Helper method to check the an aliases.
*
* @param string $title
* The node title to build the aliases from.
* @param string $alias
* The expected alias.
* @param array $settings
* The form values the alias should be generated with.
*/
protected function checkAlias($title, $alias, $settings = []) {
// Submit the settings form.
$edit = array_merge($this->defaultFormValues + $this->defaultPunctuations, $settings);
$this->drupalGet('/admin/config/search/path/settings');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->pageTextContains('The configuration options have been saved.');
// If we do not clear the caches here, AliasCleaner will use its
// cleanStringCache instance variable. Due to that the creation of aliases
// with $this->createNode() will only work correctly on the first call.
\Drupal::service('pathauto.generator')->resetCaches();
// Create a node and check if the settings applied.
$node = $this->createNode(
[
'title' => $title,
'type' => 'article',
]
);
$this->drupalGet($alias);
$this->assertSession()->statusCodeEquals(200);
$this->assertEntityAlias($node, $alias);
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests pathauto taxonomy UI integration.
*
* @group pathauto
*/
class PathautoTaxonomyWebTest extends BrowserTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['taxonomy', 'pathauto', 'views'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Allow other modules to add additional permissions for the admin user.
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'administer taxonomy',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
}
/**
* Basic functional testing of Pathauto with taxonomy terms.
*/
public function testTermEditing() {
$this->drupalGet('admin/structure');
$this->drupalGet('admin/structure/taxonomy');
// Add vocabulary "tags".
$this->addVocabulary(['name' => 'tags', 'vid' => 'tags']);
// Create term for testing.
$name = 'Testing: term name [';
$automatic_alias = '/tags/testing-term-name';
$this->drupalGet('admin/structure/taxonomy/manage/tags/add');
$this->submitForm(['name[0][value]' => $name], 'Save');
$name = trim($name);
$this->assertSession()->pageTextContains("Created new term $name.");
$term = $this->drupalGetTermByName($name);
// Look for alias generated in the form.
$this->drupalGet("taxonomy/term/{$term->id()}/edit");
$this->assertSession()->checkboxChecked('edit-path-0-pathauto');
$this->assertSession()->fieldValueEquals('path[0][alias]', $automatic_alias);
// Check whether the alias actually works.
$this->drupalGet($automatic_alias);
$this->assertSession()->pageTextContains($name);
// Manually set the term's alias.
$manual_alias = '/tags/' . $term->id();
$edit = [
'path[0][pathauto]' => FALSE,
'path[0][alias]' => $manual_alias,
];
$this->drupalGet("taxonomy/term/{$term->id()}/edit");
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("Updated term $name.");
// Check that the automatic alias checkbox is now unchecked by default.
$this->drupalGet("taxonomy/term/{$term->id()}/edit");
$this->assertSession()->checkboxNotChecked('edit-path-0-pathauto');
$this->assertSession()->fieldValueEquals('path[0][alias]', $manual_alias);
// Submit the term form with the default values.
$this->submitForm(['path[0][pathauto]' => FALSE], 'Save');
$this->assertSession()->pageTextContains("Updated term $name.");
// Test that the old (automatic) alias has been deleted and only accessible
// through the new (manual) alias.
$this->drupalGet($automatic_alias);
$this->assertSession()->statusCodeEquals(404);
$this->drupalGet($manual_alias);
$this->assertSession()->pageTextContains($name);
}
}

View File

@@ -0,0 +1,252 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\Language;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\pathauto\Entity\PathautoPattern;
use Drupal\pathauto\PathautoPatternInterface;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
/**
* Helper test class with some added functions for testing.
*/
trait PathautoTestHelperTrait {
use PathAliasTestTrait;
/**
* Creates a pathauto pattern.
*
* @param string $entity_type_id
* The entity type.
* @param string $pattern
* The path pattern.
* @param int $weight
* (optional) The pattern weight.
*
* @return \Drupal\pathauto\PathautoPatternInterface
* The created pattern.
*/
protected function createPattern($entity_type_id, $pattern, $weight = 10) {
$type = ($entity_type_id == 'forum') ? 'forum' : 'canonical_entities:' . $entity_type_id;
$pattern = PathautoPattern::create([
'id' => mb_strtolower($this->randomMachineName()),
'type' => $type,
'pattern' => $pattern,
'weight' => $weight,
]);
$pattern->save();
return $pattern;
}
/**
* Add a bundle condition to a pathauto pattern.
*
* @param \Drupal\pathauto\PathautoPatternInterface $pattern
* The pattern.
* @param string $entity_type
* The entity type ID.
* @param string $bundle
* The bundle.
*/
protected function addBundleCondition(PathautoPatternInterface $pattern, $entity_type, $bundle) {
$pattern->addSelectionCondition(
[
'id' => 'entity_bundle:' . $entity_type,
'bundles' => [
$bundle => $bundle,
],
'negate' => FALSE,
'context_mapping' => [
$entity_type => $entity_type,
],
]
);
}
/**
* Assert the expected value for a token.
*/
public function assertToken($type, $object, $token, $expected) {
$bubbleable_metadata = new BubbleableMetadata();
$tokens = \Drupal::token()->generate($type, [$token => $token], [$type => $object], [], $bubbleable_metadata);
$tokens += [$token => ''];
$this->assertSame($tokens[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", [
'@type' => $type,
'@token' => $token,
'@actual' => $tokens[$token],
'@expected' => $expected,
]));
}
/**
* Create a path alias for an entity.
*/
public function saveEntityAlias(EntityInterface $entity, $alias, $langcode = NULL) {
// By default, use the entity language.
if (!$langcode) {
$langcode = $entity->language()->getId();
}
return $this->createPathAlias('/' . $entity->toUrl()->getInternalPath(), $alias, $langcode);
}
/**
* Assert the expected value for an entity path alias.
*/
public function assertEntityAlias(EntityInterface $entity, $expected_alias, $langcode = NULL) {
// By default, use the entity language.
if (!$langcode) {
$langcode = $entity->language()->getId();
}
$this->assertAlias('/' . $entity->toUrl()->getInternalPath(), $expected_alias, $langcode);
}
/**
* Assert that an alias exists for the given entity's internal path.
*/
public function assertEntityAliasExists(EntityInterface $entity) {
return $this->assertAliasExists(['path' => '/' . $entity->toUrl()->getInternalPath()]);
}
/**
* Assert that the given entity does not have a path alias.
*/
public function assertNoEntityAlias(EntityInterface $entity, $langcode = NULL) {
// By default, use the entity language.
if (!$langcode) {
$langcode = $entity->language()->getId();
}
$this->assertEntityAlias($entity, '/' . $entity->toUrl()->getInternalPath(), $langcode);
}
/**
* Assert that no alias exists matching the given entity path/alias.
*/
public function assertNoEntityAliasExists(EntityInterface $entity, $alias = NULL) {
$path = ['path' => '/' . $entity->toUrl()->getInternalPath()];
if (!empty($alias)) {
$path['alias'] = $alias;
}
$this->assertNoAliasExists($path);
}
/**
* Assert the expected alias for the given source/language.
*/
public function assertAlias($source, $expected_alias, $langcode = Language::LANGCODE_NOT_SPECIFIED) {
\Drupal::service('path_alias.manager')->cacheClear($source);
$entity_type_manager = \Drupal::entityTypeManager();
if ($entity_type_manager->hasDefinition('path_alias')) {
$entity_type_manager->getStorage('path_alias')->resetCache();
}
$this->assertEquals($expected_alias, \Drupal::service('path_alias.manager')->getAliasByPath($source, $langcode), t("Alias for %source with language '@language' is correct.",
['%source' => $source, '@language' => $langcode]));
}
/**
* Assert that an alias exists for the given conditions.
*/
public function assertAliasExists($conditions) {
$path = $this->loadPathAliasByConditions($conditions);
$this->assertNotEmpty($path, t('Alias with conditions @conditions found.', ['@conditions' => var_export($conditions, TRUE)]));
return $path;
}
/**
* Assert that no alias exists for the given conditions.
*/
public function assertNoAliasExists($conditions) {
$alias = $this->loadPathAliasByConditions($conditions);
$this->assertEmpty($alias, t('Alias with conditions @conditions not found.', ['@conditions' => var_export($conditions, TRUE)]));
}
/**
* Assert that exactly one alias matches the given conditions.
*/
public function assertAliasIsUnique($conditions) {
$storage = \Drupal::entityTypeManager()->getStorage('path_alias');
$query = $storage->getQuery()->accessCheck(FALSE);
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
$entities = $storage->loadMultiple($query->execute());
return $this->assertCount(1, $entities);
}
/**
* Delete all path aliases.
*/
public function deleteAllAliases() {
\Drupal::service('pathauto.alias_storage_helper')->deleteAll();
\Drupal::service('path_alias.manager')->cacheClear();
}
/**
* Create a new vocabulary.
*
* @param array $values
* Vocabulary properties.
*
* @return \Drupal\taxonomy\VocabularyInterface
* The Vocabulary object.
*/
public function addVocabulary(array $values = []) {
$name = mb_strtolower($this->randomMachineName(5));
$values += [
'name' => $name,
'vid' => $name,
];
$vocabulary = Vocabulary::create($values);
$vocabulary->save();
return $vocabulary;
}
/**
* Add a new taxonomy term to the given vocabulary.
*/
public function addTerm(VocabularyInterface $vocabulary, array $values = []) {
$values += [
'name' => mb_strtolower($this->randomMachineName(5)),
'vid' => $vocabulary->id(),
];
$term = Term::create($values);
$term->save();
return $term;
}
/**
* Helper for testTaxonomyPattern().
*/
public function assertEntityPattern($entity_type, $bundle, $langcode, $expected) {
$values = [
'langcode' => $langcode,
\Drupal::entityTypeManager()->getDefinition($entity_type)->getKey('bundle') => $bundle,
];
$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->create($values);
$pattern = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
$this->assertSame($expected, $pattern->getPattern());
}
/**
* Load a taxonomy term by name.
*/
public function drupalGetTermByName($name, $reset = FALSE) {
if ($reset) {
// @todo implement cache reset.
}
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['name' => $name]);
return !empty($terms) ? reset($terms) : FALSE;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\pathauto\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\views\Views;
/**
* Tests pathauto user UI integration.
*
* @group pathauto
*/
class PathautoUserWebTest extends BrowserTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['pathauto', 'views'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Allow other modules to add additional permissions for the admin user.
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'administer users',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
$this->createPattern('user', '/users/[user:name]');
}
/**
* Basic functional testing of Pathauto with users.
*/
public function testUserEditing() {
// There should be no Pathauto checkbox on user forms.
$this->drupalGet('user/' . $this->adminUser->id() . '/edit');
$this->assertSession()->fieldValueNotEquals('path[0][pathauto]', '');
}
/**
* Test user operations.
*/
public function testUserOperations() {
$account = $this->drupalCreateUser();
// Delete all current URL aliases.
$this->deleteAllAliases();
// Find the position of just created account in the user_admin_people view.
$view = Views::getView('user_admin_people');
$view->initDisplay();
$view->preview('page_1');
foreach ($view->result as $key => $row) {
if ($view->field['name']->getValue($row) == $account->getDisplayName()) {
break;
}
}
$edit = [
'action' => 'pathauto_update_alias_user',
"user_bulk_form[$key]" => TRUE,
];
$this->drupalGet('admin/people');
$this->submitForm($edit, 'Apply to selected items');
$this->assertSession()->pageTextContains('Update URL alias was applied to 1 item.');
$this->assertEntityAlias($account, '/users/' . mb_strtolower($account->getDisplayName()));
$this->assertEntityAlias($this->adminUser, '/user/' . $this->adminUser->id());
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace Drupal\Tests\pathauto\FunctionalJavascript;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\pathauto\PathautoState;
use Drupal\Tests\pathauto\Functional\PathautoTestHelperTrait;
/**
* Test pathauto functionality with localization and translation.
*
* @group pathauto
*/
class PathautoLocaleTest extends WebDriverTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'pathauto', 'locale', 'content_translation'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create Article node type.
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
}
/**
* Test that when an English node is updated, its old English alias is
* updated and its newer French alias is left intact.
*/
public function testLanguageAliases() {
$this->createPattern('node', '/content/[node:title]');
// Add predefined French language.
ConfigurableLanguage::createFromLangcode('fr')->save();
$node = [
'title' => 'English node',
'langcode' => 'en',
'path' => [[
'alias' => '/english-node',
'pathauto' => FALSE,
]],
];
$node = $this->drupalCreateNode($node);
$english_alias = $this->loadPathAliasByConditions(['alias' => '/english-node', 'langcode' => 'en']);
$this->assertNotEmpty($english_alias, 'Alias created with proper language.');
// Also save a French alias that should not be left alone, even though
// it is the newer alias.
$this->saveEntityAlias($node, '/french-node', 'fr');
// Add an alias with the soon-to-be generated alias, causing the upcoming
// alias update to generate a unique alias with the '-0' suffix.
$this->createPathAlias('/node/invalid', '/content/english-node', Language::LANGCODE_NOT_SPECIFIED);
// Update the node, triggering a change in the English alias.
$node->path->pathauto = PathautoState::CREATE;
$node->save();
// Check that the new English alias replaced the old one.
$this->assertEntityAlias($node, '/content/english-node-0', 'en');
$this->assertEntityAlias($node, '/french-node', 'fr');
$this->assertAliasExists(['id' => $english_alias->id(), 'alias' => '/content/english-node-0']);
// Create a new node with the same title as before but without
// specifying a language.
$node = $this->drupalCreateNode(['title' => 'English node', 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]);
// Check that the new node had a unique alias generated with the '-0'
// suffix.
$this->assertEntityAlias($node, '/content/english-node-0', LanguageInterface::LANGCODE_NOT_SPECIFIED);
}
/**
* Test that patterns work on multilingual content.
*/
public function testLanguagePatterns() {
// Allow other modules to add additional permissions for the admin user.
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'bypass node access',
'access content overview',
'administer languages',
'translate any entity',
'administer content translation',
'create content translations',
];
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Add French language.
$edit = [
'predefined_langcode' => 'fr',
];
$this->drupalGet('admin/config/regional/language/add');
$this->submitForm($edit, 'Add language');
$this->enableArticleTranslation();
// Create a pattern for English articles.
$this->drupalGet('admin/config/search/path/patterns/add');
$session = $this->getSession();
$page = $session->getPage();
$page->fillField('type', 'canonical_entities:node');
$this->assertSession()->assertWaitOnAjaxRequest();
sleep(1);
$page->fillField('label', 'English articles');
$this->assertSession()->waitForElementVisible('css', '#edit-label-machine-name-suffix .machine-name-value');
$edit = [
'bundles[article]' => TRUE,
'languages[en]' => TRUE,
'pattern' => '/the-articles/[node:title]',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Pattern English articles saved.');
// Create a pattern for French articles.
$this->drupalGet('admin/config/search/path/patterns/add');
$page->fillField('type', 'canonical_entities:node');
$this->assertSession()->assertWaitOnAjaxRequest();
$page->fillField('label', 'French articles');
$this->assertSession()->waitForElementVisible('css', '#edit-label-machine-name-suffix .machine-name-value');
$edit = [
'bundles[article]' => TRUE,
'languages[fr]' => TRUE,
'pattern' => '/les-articles/[node:title]',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Pattern French articles saved.');
// Create a node and its translation. Assert aliases.
$edit = [
'title[0][value]' => 'English node',
'langcode[0][value]' => 'en',
];
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
$node = $this->drupalGetNodeByTitle('English node');
$this->assertAlias('/node/' . $node->id(), '/the-articles/english-node', 'en');
$this->drupalGet('node/' . $node->id() . '/translations');
$this->clickLink('Add');
$edit = [
'title[0][value]' => 'French node',
];
$this->submitForm($edit, 'Save (this translation)');
$this->rebuildContainer();
$this->assertAlias('/node/' . $node->id(), '/les-articles/french-node', 'fr');
// Bulk delete and Bulk generate patterns. Assert aliases.
$this->deleteAllAliases();
// Bulk create aliases.
$edit = [
'update[canonical_entities:node]' => TRUE,
];
$this->drupalGet('admin/config/search/path/update_bulk');
$this->submitForm($edit, 'Update');
$this->assertSession()->waitForText('Generated 2 URL aliases.');
$this->assertAlias('/node/' . $node->id(), '/the-articles/english-node', 'en');
$this->assertAlias('/node/' . $node->id(), '/les-articles/french-node', 'fr');
}
/**
* Tests the alias created for a node with language Not Applicable.
*/
public function testLanguageNotApplicable() {
$this->drupalLogin($this->rootUser);
$this->enableArticleTranslation();
// Create a pattern for nodes.
$pattern = $this->createPattern('node', '/content/[node:title]', -1);
$pattern->save();
// Create a node with language Not Applicable.
$node = $this->createNode([
'type' => 'article',
'title' => 'Test node',
'langcode' => LanguageInterface::LANGCODE_NOT_APPLICABLE,
]);
// Check that the generated alias has language Not Specified.
$alias = \Drupal::service('pathauto.alias_storage_helper')->loadBySource('/node/' . $node->id());
$this->assertEquals(LanguageInterface::LANGCODE_NOT_SPECIFIED, $alias['langcode'], 'PathautoGenerator::createEntityAlias() adjusts the alias langcode from Not Applicable to Not Specified.');
// Check that the alias works.
$this->drupalGet('content/test-node');
$this->assertSession()->pageTextContains('Test node');
}
/**
* Enables content translation on articles.
*/
protected function enableArticleTranslation() {
// Enable content translation on articles.
$this->drupalGet('admin/config/regional/content-language');
// Enable translation for node.
$this->assertSession()->fieldExists('entity_types[node]')->check();
// Open details for Content settings in Drupal 10.2.
$nodeSettings = $this->getSession()->getPage()->find('css', '#edit-settings-node summary');
if ($nodeSettings) {
$nodeSettings->click();
}
$this->assertSession()->fieldExists('settings[node][article][translatable]')->check();
$this->assertSession()->fieldExists('settings[node][article][settings][language][language_alterable]')->check();
$this->getSession()->getPage()->pressButton('Save configuration');
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace Drupal\Tests\pathauto\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\pathauto\Entity\PathautoPattern;
use Drupal\Tests\pathauto\Functional\PathautoTestHelperTrait;
/**
* Test basic pathauto functionality.
*
* @group pathauto
*/
class PathautoUiTest extends WebDriverTestBase {
use PathautoTestHelperTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['pathauto', 'node', 'block'];
/**
* Admin user.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->drupalCreateContentType(['type' => 'article']);
// Allow other modules to add additional permissions for the admin user.
$permissions = [
'administer pathauto',
'administer url aliases',
'create url aliases',
'administer nodes',
'bypass node access',
'access content overview',
];
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
}
public function testSettingsValidation() {
$this->drupalGet('/admin/config/search/path/settings');
$this->assertSession()->fieldExists('max_length');
$this->assertSession()->elementAttributeContains('css', '#edit-max-length', 'min', '1');
$this->assertSession()->fieldExists('max_component_length');
$this->assertSession()->elementAttributeContains('css', '#edit-max-component-length', 'min', '1');
}
public function testPatternsWorkflow() {
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'local-tasks-block']);
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('page_title_block');
$this->drupalGet('admin/config/search/path');
$this->assertSession()->elementContains('css', '#block-local-tasks-block', 'Patterns');
$this->assertSession()->elementContains('css', '#block-local-tasks-block', 'Settings');
$this->assertSession()->elementContains('css', '#block-local-tasks-block', 'Bulk generate');
$this->assertSession()->elementContains('css', '#block-local-tasks-block', 'Delete aliases');
$this->drupalGet('admin/config/search/path/patterns');
$this->clickLink('Add Pathauto pattern');
$session = $this->getSession();
$session->getPage()->fillField('type', 'canonical_entities:node');
$this->assertSession()->assertWaitOnAjaxRequest();
$edit = [
'type' => 'canonical_entities:node',
'bundles[page]' => TRUE,
'label' => 'Page pattern',
'pattern' => '[node:title]/[user:name]/[term:name]',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->waitForElementVisible('css', '[name="id"]');
if (version_compare(\Drupal::VERSION, '10.1', '<')) {
$edit += [
'id' => 'page_pattern',
];
$this->submitForm($edit, 'Save');
}
$this->assertSession()->pageTextContains('Path pattern is using the following invalid tokens: [user:name], [term:name].');
$this->assertSession()->pageTextNotContains('The configuration options have been saved.');
// We do not need ID anymore, it is already set in previous step and made a label by browser.
unset($edit['id']);
$edit['pattern'] = '#[node:title]';
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('The Path pattern is using the following invalid characters: #.');
$this->assertSession()->pageTextNotContains('The configuration options have been saved.');
// Checking whitespace ending of the string.
$edit['pattern'] = '[node:title] ';
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("The Path pattern doesn't allow the patterns ending with whitespace.");
$this->assertSession()->pageTextNotContains('The configuration options have been saved.');
// Fix the pattern, then check that it gets saved successfully.
$edit['pattern'] = '[node:title]';
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Pattern Page pattern saved.');
\Drupal::service('pathauto.generator')->resetCaches();
// Create a node with pattern enabled and check if the pattern applies.
$title = 'Page Pattern enabled';
$alias = '/page-pattern-enabled';
$node = $this->createNode(['title' => $title, 'type' => 'page']);
$this->drupalGet($alias);
$this->assertSession()->pageTextContains($title);
$this->assertEntityAlias($node, $alias);
// Edit workflow, set a new label and weight for the pattern.
$this->drupalGet('/admin/config/search/path/patterns');
$session->getPage()->pressButton('Show row weights');
$this->submitForm(['entities[page_pattern][weight]' => '4'], 'Save');
$session->getPage()->find('css', '.dropbutton-toggle > button')->press();
$this->clickLink('Edit');
$destination_query = ['query' => ['destination' => Url::fromRoute('entity.pathauto_pattern.collection')->toString()]];
$address = Url::fromRoute('entity.pathauto_pattern.edit_form', ['pathauto_pattern' => 'page_pattern'], [$destination_query]);
$this->assertSession()->addressEquals($address);
$this->assertSession()->fieldValueEquals('pattern', '[node:title]');
$this->assertSession()->fieldValueEquals('label', 'Page pattern');
$this->assertSession()->checkboxChecked('edit-status');
$this->assertSession()->linkExists('Delete');
$edit = ['label' => 'Test'];
$this->drupalGet('/admin/config/search/path/patterns/page_pattern');
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Pattern Test saved.');
// Check that the pattern weight did not change.
$this->assertSession()->optionExists('edit-entities-page-pattern-weight', '4');
$this->drupalGet('/admin/config/search/path/patterns/page_pattern/duplicate');
$session->getPage()->pressButton('Edit');
$edit = ['label' => 'Test Duplicate', 'id' => 'page_pattern_test_duplicate'];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains('Pattern Test Duplicate saved.');
PathautoPattern::load('page_pattern_test_duplicate')->delete();
// Disable workflow.
$this->drupalGet('/admin/config/search/path/patterns');
$session->getPage()->find('css', '.dropbutton-toggle > button')->press();
$this->assertSession()->linkNotExists('Enable');
$this->clickLink('Disable');
$this->assertSession()->addressEquals('/admin/config/search/path/patterns/page_pattern/disable');
$this->submitForm([], 'Disable');
$this->assertSession()->pageTextContains('Disabled pattern Test.');
// Load the pattern from storage and check if its disabled.
$pattern = PathautoPattern::load('page_pattern');
$this->assertFalse($pattern->status());
\Drupal::service('pathauto.generator')->resetCaches();
// Create a node with pattern disabled and check that we have no new alias.
$title = 'Page Pattern disabled';
$node = $this->createNode(['title' => $title, 'type' => 'page']);
$this->assertNoEntityAlias($node);
// Enable workflow.
$this->drupalGet('/admin/config/search/path/patterns');
$this->assertSession()->linkNotExists('Disable');
$this->clickLink('Enable');
$address = Url::fromRoute('entity.pathauto_pattern.enable', ['pathauto_pattern' => 'page_pattern'], [$destination_query]);
$this->assertSession()->addressEquals($address);
$this->submitForm([], 'Enable');
$this->assertSession()->pageTextContains('Enabled pattern Test.');
// Reload pattern from storage and check if its enabled.
$pattern = PathautoPattern::load('page_pattern');
$this->assertTrue($pattern->status());
// Delete workflow.
$this->drupalGet('/admin/config/search/path/patterns');
$session->getPage()->find('css', '.dropbutton-toggle > button')->press();
$this->clickLink('Delete');
$this->assertSession()->assertWaitOnAjaxRequest();
if (version_compare(\Drupal::VERSION, '10.1', '>=')) {
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '#drupal-modal'));
$this->assertSession()->elementContains('css', '#drupal-modal', 'This action cannot be undone.');
$this->assertSession()->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Delete');
}
else {
$address = Url::fromRoute('entity.pathauto_pattern.delete_form', ['pathauto_pattern' => 'page_pattern'], [$destination_query]);
$this->assertSession()->addressEquals($address);
$this->submitForm([], 'Delete');
}
$this->assertSession()->pageTextContains('The pathauto pattern Test has been deleted.');
$this->assertEmpty(PathautoPattern::load('page_pattern'));
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace Drupal\Tests\pathauto\Kernel;
use Drupal\Component\Serialization\PhpSerialize;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\KeyValueStore\KeyValueDatabaseFactory;
use Drupal\KernelTests\KernelTestBase;
use Drupal\pathauto\PathautoState;
use Drupal\pathauto_string_id_test\Entity\PathautoStringIdTest;
use Drupal\Tests\pathauto\Functional\PathautoTestHelperTrait;
/**
* Tests auto-aliasing of entities that use string IDs.
*
* @group pathauto
*/
class PathautoEntityWithStringIdTest extends KernelTestBase {
use PathautoTestHelperTrait;
/**
* The alias type plugin instance.
*
* @var \Drupal\pathauto\AliasTypeBatchUpdateInterface
*/
protected $aliasType;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'user',
'field',
'token',
'path',
'path_alias',
'pathauto',
'pathauto_string_id_test',
];
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
// Kernel tests are using the 'keyvalue.memory' store but we want to test
// against the 'keyvalue.database'.
$container
->register('keyvalue.database', KeyValueDatabaseFactory::class)
->addArgument(new PhpSerialize())
->addArgument($container->get('database'))
->addTag('persist');
$container->setAlias('keyvalue', 'keyvalue.database');
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['system', 'pathauto']);
$this->installEntitySchema('path_alias');
$this->installEntitySchema('pathauto_string_id_test');
$this->createPattern('pathauto_string_id_test', '/[pathauto_string_id_test:name]');
/** @var \Drupal\pathauto\AliasTypeManager $alias_type_manager */
$alias_type_manager = $this->container->get('plugin.manager.alias_type');
$this->aliasType = $alias_type_manager->createInstance('canonical_entities:pathauto_string_id_test');
}
/**
* Test aliasing entities with long string ID.
*
* @dataProvider entityWithStringIdProvider
*
* @param string|int $id
* The entity ID
* @param string $expected_key
* The expected key for 'pathauto_state.*' collections.
*/
public function testEntityWithStringId($id, $expected_key) {
$entity = PathautoStringIdTest::create([
'id' => $id,
'name' => $name = $this->randomMachineName(),
]);
$entity->save();
// Check that the path was generated.
$this->assertEntityAlias($entity, mb_strtolower("/$name"));
// Check that the path auto state was saved with the expected key.
$value = \Drupal::keyValue('pathauto_state.pathauto_string_id_test')->get($expected_key);
$this->assertEquals(PathautoState::CREATE, $value);
$context = [];
// Batch delete uses the key-value store collection 'pathauto_state.*. We
// test that after a bulk delete all aliases are removed. Running only once
// the batch delete process is enough as the batch size is 100.
$this->aliasType->batchDelete($context);
// Check that the paths were removed on batch delete.
$this->assertNoEntityAliasExists($entity, "/$name");
}
/**
* Provides test cases for ::testEntityWithStringId().
*
* @see \Drupal\Tests\pathauto\Kernel\PathautoEntityWithStringIdTest::testEntityWithStringId()
*/
public function entityWithStringIdProvider() {
return [
'ascii with less or equal 128 chars' => [
str_repeat('a', 128), str_repeat('a', 128),
],
'ascii with over 128 chars' => [
str_repeat('a', 191), Crypt::hashBase64(str_repeat('a', 191)),
],
'non-ascii with less or equal 128 chars' => [
str_repeat('社', 128), Crypt::hashBase64(str_repeat('社', 128)),
],
'non-ascii with over 128 chars' => [
str_repeat('社', 191), Crypt::hashBase64(str_repeat('社', 191)),
],
'simulating an integer id' => [
123, '123',
],
];
}
}

View File

@@ -0,0 +1,699 @@
<?php
namespace Drupal\Tests\pathauto\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\pathauto\PathautoGeneratorInterface;
use Drupal\pathauto\PathautoState;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\pathauto\Functional\PathautoTestHelperTrait;
use Drupal\user\Entity\User;
/**
* Unit tests for Pathauto functions.
*
* @group pathauto
*/
class PathautoKernelTest extends KernelTestBase {
use PathautoTestHelperTrait;
/**
* Modules.
*
* @var string[]
*/
protected static $modules = [
'system',
'field',
'text',
'user',
'node',
'path',
'path_alias',
'pathauto',
'pathauto_custom_punctuation_test',
'taxonomy',
'token',
'filter',
'language',
];
/**
* The current user.
*
* @var \Drupal\user\Entity\UserInterface
*/
protected $currentUser;
/**
* Node pattern.
*
* @var \Drupal\pathauto\PathautoPatternInterface
*/
protected $nodePattern;
/**
* User pattern.
*
* @var \Drupal\pathauto\PathautoPatternInterface
*/
protected $userPattern;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setup();
$this->installEntitySchema('user');
$this->installEntitySchema('node');
$this->installEntitySchema('taxonomy_term');
if ($this->container->get('entity_type.manager')->hasDefinition('path_alias')) {
$this->installEntitySchema('path_alias');
}
$this->installConfig([
'pathauto',
'taxonomy',
'system',
'node',
]);
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->installSchema('node', ['node_access']);
$this->installSchema('system', ['sequences']);
$type = NodeType::create(['type' => 'page']);
$type->save();
node_add_body_field($type);
$this->nodePattern = $this->createPattern('node', '/content/[node:title]');
$this->userPattern = $this->createPattern('user', '/users/[user:name]');
\Drupal::service('router.builder')->rebuild();
$this->currentUser = User::create(['name' => $this->randomMachineName()]);
$this->currentUser->save();
}
/**
* Test _pathauto_get_schema_alias_maxlength().
*/
public function testGetSchemaAliasMaxLength() {
$this->assertSame(\Drupal::service('pathauto.alias_storage_helper')->getAliasSchemaMaxlength(), 255);
}
/**
* Test pathauto_pattern_load_by_entity().
*/
public function testPatternLoadByEntity() {
$pattern = $this->createPattern('node', '/article/[node:title]', -1);
$this->addBundleCondition($pattern, 'node', 'article');
$pattern->save();
$pattern = $this->createPattern('node', '/article/en/[node:title]', -2);
$this->addBundleCondition($pattern, 'node', 'article');
$pattern->addSelectionCondition(
[
'id' => 'language',
'langcodes' => [
'en' => 'en',
],
'negate' => FALSE,
'context_mapping' => [
'language' => 'node:langcode:language',
],
]
);
$pattern->addRelationship('node:langcode:language');
$pattern->save();
$pattern = $this->createPattern('node', '/[node:title]', -1);
$this->addBundleCondition($pattern, 'node', 'page');
$pattern->save();
$tests = [
[
'entity' => 'node',
'values' => [
'title' => 'Article fr',
'type' => 'article',
'langcode' => 'fr',
],
'expected' => '/article/[node:title]',
],
[
'entity' => 'node',
'values' => [
'title' => 'Article en',
'type' => 'article',
'langcode' => 'en',
],
'expected' => '/article/en/[node:title]',
],
[
'entity' => 'node',
'values' => [
'title' => 'Article und',
'type' => 'article',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
],
'expected' => '/article/[node:title]',
],
[
'entity' => 'node',
'values' => [
'title' => 'Page',
'type' => 'page',
],
'expected' => '/[node:title]',
],
[
'entity' => 'user',
'values' => [
'name' => 'User',
],
'expected' => '/users/[user:name]',
],
];
foreach ($tests as $test) {
$entity = \Drupal::entityTypeManager()->getStorage($test['entity'])->create($test['values']);
$entity->save();
$actual = \Drupal::service('pathauto.generator')->getPatternByEntity($entity);
$this->assertSame($actual->getPattern(), $test['expected'], new FormattableMarkup("Correct pattern returned for @entity_type with @values", [
'@entity' => $test['entity'],
'@values' => print_r($test['values'], TRUE),
]));
}
}
/**
* Test potential conflicts with the same alias in different languages.
*/
public function testSameTitleDifferentLanguages() {
// Create two English articles with the same title.
$edit = [
'title' => 'Sample page',
'type' => 'page',
'langcode' => 'en',
];
$node1 = $this->drupalCreateNode($edit);
$this->assertEntityAlias($node1, '/content/sample-page', 'en');
$node2 = $this->drupalCreateNode($edit);
$this->assertEntityAlias($node2, '/content/sample-page-0', 'en');
// Now, create a French article with the same title, and verify that it gets
// the basic alias with the correct langcode.
$edit['langcode'] = 'fr';
$node3 = $this->drupalCreateNode($edit);
$this->assertEntityAlias($node3, '/content/sample-page', 'fr');
}
/**
* Test pathauto_cleanstring().
*/
public function testCleanString() {
// Test with default settings defined in pathauto.settings.yml.
$this->installConfig(['pathauto']);
// Add a custom setting for the copyright symbol defined in
// pathauto_custom_punctuation_test_pathauto_punctuation_chars_alter().
$this->config('pathauto.settings')->set('punctuation.copyright', PathautoGeneratorInterface::PUNCTUATION_REMOVE);
\Drupal::service('pathauto.generator')->resetCaches();
$tests = [];
// Test the 'ignored words' removal.
$tests['this'] = 'this';
$tests['this with that'] = 'this-with-that';
$tests['this thing with that thing'] = 'thing-thing';
// Test 'ignored words' removal and duplicate separator removal.
$tests[' - Pathauto is the greatest - module ever - '] = 'pathauto-greatest-module-ever';
// Test length truncation and lowering of strings.
$long_string = $this->randomMachineName(120);
$tests[$long_string] = strtolower(substr($long_string, 0, 100));
// Test that HTML tags are removed.
$tests['This <span class="text">text</span> has <br /><a href="http://example.com"><strong>HTML tags</strong></a>.'] = 'text-has-html-tags';
$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';
// Transliteration.
$tests['ľščťžýáíéňô'] = 'lsctzyaieno';
// Transliteration of special chars that are converted to punctuation.
$tests['© “Drupal”'] = 'drupal';
foreach ($tests as $input => $expected) {
$output = \Drupal::service('pathauto.alias_cleaner')->cleanString($input);
$this->assertEquals($expected, $output, new FormattableMarkup("Drupal::service('pathauto.alias_cleaner')->cleanString('@input') expected '@expected', actual '@output'", [
'@input' => $input,
'@expected' => $expected,
'@output' => $output,
]));
}
}
/**
* Test pathauto_clean_alias().
*/
public function testCleanAlias() {
$tests = [];
$tests['one/two/three'] = '/one/two/three';
$tests['/one/two/three/'] = '/one/two/three';
$tests['one//two///three'] = '/one/two/three';
$tests['one/two--three/-/--/-/--/four---five'] = '/one/two-three/four-five';
$tests['one/-//three--/four'] = '/one/three/four';
foreach ($tests as $input => $expected) {
$output = \Drupal::service('pathauto.alias_cleaner')->cleanAlias($input);
$this->assertEquals($expected, $output, new FormattableMarkup("Drupal::service('pathauto.generator')->cleanAlias('@input') expected '@expected', actual '@output'", [
'@input' => $input,
'@expected' => $expected,
'@output' => $output,
]));
}
}
/**
* Test pathauto_path_delete_multiple().
*/
public function testPathDeleteMultiple() {
$this->createPathAlias('/node/1', '/node-1-alias');
$this->createPathAlias('/node/1/view', '/node-1-alias/view');
$this->createPathAlias('/node/1', '/node-1-alias-en', 'en');
$this->createPathAlias('/node/1', '/node-1-alias-fr', 'fr');
$this->createPathAlias('/node/2', '/node-2-alias');
$this->createPathAlias('/node/10', '/node-10-alias');
\Drupal::service('pathauto.alias_storage_helper')->deleteBySourcePrefix('/node/1');
$this->assertNoAliasExists(['path' => "/node/1"]);
$this->assertNoAliasExists(['path' => "/node/1/view"]);
$this->assertAliasExists(['path' => "/node/2"]);
$this->assertAliasExists(['path' => "/node/10"]);
}
/**
* Test the different update actions in createEntityAlias().
*
* Tests \Drupal::service('pathauto.generator')->createEntityAlias().
*/
public function testUpdateActions() {
$config = $this->config('pathauto.settings');
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'insert'.
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW);
$config->save();
$node = $this->drupalCreateNode(['title' => 'First title']);
$this->assertEntityAlias($node, '/content/first-title');
$node->path->pathauto = PathautoState::CREATE;
// Default action is PATHAUTO_UPDATE_ACTION_DELETE.
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_DELETE);
$config->save();
$node->setTitle('Second title');
$node->save();
$this->assertEntityAlias($node, '/content/second-title');
$this->assertNoAliasExists(['alias' => '/content/first-title']);
// Test PATHAUTO_UPDATE_ACTION_LEAVE.
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_LEAVE);
$config->save();
$node->setTitle('Third title');
$node->save();
$this->assertEntityAlias($node, '/content/third-title');
$this->assertAliasExists([
'path' => '/' . $node->toUrl()->getInternalPath(),
'alias' => '/content/second-title',
]);
// Confirm that aliases are not duplicated when entities are re-saved.
$node->save();
$this->assertEntityAlias($node, '/content/third-title');
$this->assertAliasIsUnique([
'path' => '/' . $node->toUrl()->getInternalPath(),
'alias' => '/content/third-title',
]);
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_DELETE);
$config->save();
$node->setTitle('Fourth title');
$node->save();
$this->assertEntityAlias($node, '/content/fourth-title');
$this->assertNoAliasExists(['alias' => '/content/third-title']);
// The older second alias is not deleted yet.
$older_path = $this->assertAliasExists([
'path' => '/' . $node->toUrl()->getInternalPath(),
'alias' => '/content/second-title',
]);
\Drupal::service('entity_type.manager')->getStorage('path_alias')->delete([$older_path]);
$config->set('update_action', PathautoGeneratorInterface::UPDATE_ACTION_NO_NEW);
$config->save();
$node->setTitle('Fifth title');
$node->save();
$this->assertEntityAlias($node, '/content/fourth-title');
$this->assertNoAliasExists(['alias' => '/content/fifth-title']);
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'update'.
$this->deleteAllAliases();
$node->save();
$this->assertEntityAlias($node, '/content/fifth-title');
// Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'bulkupdate'.
$this->deleteAllAliases();
$node->setTitle('Sixth title');
\Drupal::service('pathauto.generator')->updateEntityAlias($node, 'bulkupdate');
$this->assertEntityAlias($node, '/content/sixth-title');
}
/**
* Test createEntityAlias().
*
* Test that \Drupal::service('pathauto.generator')->createEntityAlias() will
* not create an alias for a pattern that does not get any tokens replaced.
*/
public function testNoTokensNoAlias() {
$this->installConfig(['filter']);
$this->nodePattern
->setPattern('/content/[node:body]')
->save();
$node = $this->drupalCreateNode();
$this->assertNoEntityAliasExists($node);
$node->body->value = 'hello';
$node->save();
$this->assertEntityAlias($node, '/content/hello');
}
/**
* Test path vs non-path tokens in pathauto_clean_token_values().
*/
public function testPathTokens() {
$this->createPattern('taxonomy_term', '/[term:parent:url:path]/[term:name]');
$vocab = $this->addVocabulary();
$term1 = $this->addTerm($vocab, ['name' => 'Parent term']);
$this->assertEntityAlias($term1, '/parent-term');
$term2 = $this->addTerm($vocab, [
'name' => 'Child term',
'parent' => $term1->id(),
]);
$this->assertEntityAlias($term2, '/parent-term/child-term');
$this->saveEntityAlias($term1, '/My Crazy/Alias/');
$term2->save();
$this->assertEntityAlias($term2, '/My Crazy/Alias/child-term');
}
/**
* Test using fields for path structures.
*/
public function testParentChildPathTokens() {
// First create a field which will be used to create the path. It must
// begin with a letter.
$this->installEntitySchema('taxonomy_term');
Vocabulary::create(['vid' => 'tags'])->save();
$fieldname = 'a' . mb_strtolower($this->randomMachineName());
$field_storage = FieldStorageConfig::create([
'entity_type' => 'taxonomy_term',
'field_name' => $fieldname,
'type' => 'string',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'tags',
]);
$field->save();
$display = \Drupal::service('entity_display.repository')->getViewDisplay('taxonomy_term', 'tags');
$display->setComponent($fieldname, ['type' => 'string']);
$display->save();
// Make the path pattern of a field use the value of this field appended
// to the parent taxonomy term's pattern if there is one.
$this->createPattern('taxonomy_term', '/[term:parents:join-path]/[term:' . $fieldname . ']');
// Start by creating a parent term.
$parent = Term::create([
'vid' => 'tags',
$fieldname => $this->randomMachineName(),
'name' => $this->randomMachineName(),
]);
$parent->save();
// Create the child term.
$child = Term::create([
'vid' => 'tags',
$fieldname => $this->randomMachineName(),
'parent' => $parent,
'name' => $this->randomMachineName(),
]);
$child->save();
$this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));
// Re-saving the parent term should not modify the child term's alias.
$parent->save();
$this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));
}
/**
* Tests aliases on taxonomy terms.
*/
public function testTaxonomyPattern() {
// Create a vocabulary and test that it's pattern variable works.
$this->addVocabulary(['vid' => 'name']);
$this->createPattern('taxonomy_term', 'base');
$pattern = $this->createPattern('taxonomy_term', 'bundle', -1);
$this->addBundleCondition($pattern, 'taxonomy_term', 'name');
$pattern->save();
$this->assertEntityPattern('taxonomy_term', 'name', Language::LANGCODE_NOT_SPECIFIED, 'bundle');
}
/**
* Test that aliases matching existing paths are not generated.
*/
public function testNoExistingPathAliases() {
$this->config('pathauto.settings')
->set('punctuation.period', PathautoGeneratorInterface::PUNCTUATION_DO_NOTHING)
->save();
$this->nodePattern
->setPattern('[node:title]')
->save();
// Check that Pathauto does not create an alias of '/admin'.
$node = $this->drupalCreateNode(['title' => 'Admin', 'type' => 'page']);
$this->assertEntityAlias($node, '/admin-0');
// Check that Pathauto does not create an alias of '/modules'.
$node->setTitle('Modules');
$node->save();
$this->assertEntityAlias($node, '/modules-0');
// Check that Pathauto does not create an alias of '/index.php'.
$node->setTitle('index.php');
$node->save();
$this->assertEntityAlias($node, '/index.php-0');
// Check that a safe value gets an automatic alias. This is also a control
// to ensure the above tests work properly.
$node->setTitle('Safe value');
$node->save();
$this->assertEntityAlias($node, '/safe-value');
}
/**
* Test programmatic entity creation for aliases.
*/
public function testProgrammaticEntityCreation() {
$node = $this->drupalCreateNode([
'title' => 'Test node',
'path' => ['pathauto' => TRUE],
]);
$this->assertEntityAlias($node, '/content/test-node');
// Check the case when the pathauto widget is hidden, so it can not populate
// the 'pathauto' property, and
// \Drupal\path\Plugin\Field\FieldType\PathFieldItemList::computeValue()
// populates the 'path' field with a 'langcode' property, for example during
// an AJAX call on the entity form.
$node = $this->drupalCreateNode([
'title' => 'Test node 2',
'path' => ['langcode' => 'en'],
]);
$this->assertEntityAlias($node, '/content/test-node-2');
$this->createPattern('taxonomy_term', '/[term:vocabulary]/[term:name]');
$vocabulary = $this->addVocabulary(['name' => 'Tags']);
$term = $this->addTerm($vocabulary, [
'name' => 'Test term',
'path' => ['pathauto' => TRUE],
]);
$this->assertEntityAlias($term, '/tags/test-term');
$edit['name'] = 'Test user';
$edit['mail'] = 'test-user@example.com';
$edit['pass'] = \Drupal::service('password_generator')->generate();
$edit['path'] = ['pathauto' => TRUE];
$edit['status'] = 1;
$account = User::create($edit);
$account->save();
$this->assertEntityAlias($account, '/users/test-user');
}
/**
* Tests word safe alias truncating.
*/
public function testPathAliasUniquifyWordsafe() {
$this->config('pathauto.settings')
->set('max_length', 26)
->save();
$node_1 = $this->drupalCreateNode([
'title' => 'thequick brownfox jumpedover thelazydog',
'type' => 'page',
]);
$node_2 = $this->drupalCreateNode([
'title' => 'thequick brownfox jumpedover thelazydog',
'type' => 'page',
]);
// Check that alias uniquifying is truncating with $wordsafe param set to
// TRUE.
// If it doesn't path alias result would be content/thequick-brownf-0.
$this->assertEntityAlias($node_1, '/content/thequick-brownfox');
$this->assertEntityAlias($node_2, '/content/thequick-0');
}
/**
* Test if aliases are (not) generated with enabled/disabled patterns.
*/
public function testPatternStatus() {
// Create a node to get an alias for.
$title = 'Pattern enabled';
$alias = '/content/pattern-enabled';
$node1 = $this->drupalCreateNode(['title' => $title, 'type' => 'page']);
$this->assertEntityAlias($node1, $alias);
// Disable the pattern, save the node again and make sure the alias is still
// working.
$this->nodePattern->setStatus(FALSE)->save();
$node1->save();
$this->assertEntityAlias($node1, $alias);
// Create a new node with disabled pattern and make sure there is no new
// alias created.
$title = 'Pattern disabled';
$node2 = $this->drupalCreateNode(['title' => $title, 'type' => 'page']);
$this->assertNoEntityAlias($node2);
}
/**
* Tests that enabled entity types generates the necessary fields and plugins.
*/
public function testSettingChangeInvalidatesCache() {
$this->installConfig(['pathauto']);
$this->enableModules(['entity_test']);
$definitions = \Drupal::service('plugin.manager.alias_type')->getDefinitions();
$this->assertFalse(isset($definitions['canonical_entities:entity_test']));
$fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('entity_test');
$this->assertFalse(isset($fields['path']));
$this->config('pathauto.settings')
->set('enabled_entity_types', ['user', 'entity_test'])
->save();
$definitions = \Drupal::service('plugin.manager.alias_type')->getDefinitions();
$this->assertTrue(isset($definitions['canonical_entities:entity_test']));
$fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('entity_test');
$this->assertTrue(isset($fields['path']));
}
/**
* Tests that aliases are only generated for default revisions.
*/
public function testDefaultRevision() {
$node1 = $this->drupalCreateNode([
'title' => 'Default revision',
'type' => 'page',
]);
$this->assertEntityAlias($node1, '/content/default-revision');
$node1->setNewRevision(TRUE);
$node1->isDefaultRevision(FALSE);
$node1->setTitle('New non-default-revision');
$node1->save();
$this->assertEntityAlias($node1, '/content/default-revision');
}
/**
* Tests that the pathauto state property gets set to CREATED for new nodes.
*
* In some cases, this can trigger $node->path to be set up with no default
* value for the pathauto property.
*/
public function testCreateNodeWhileAccessingPath() {
$node = Node::create([
'type' => 'article',
'title' => 'TestAlias',
]);
$node->path->langcode;
$node->save();
$this->assertEntityAlias($node, '/content/testalias');
}
/**
* Creates a node programmatically.
*
* @param array $settings
* The array of values for the node.
*
* @return \Drupal\node\Entity\Node
* The created node.
*/
protected function drupalCreateNode(array $settings = []) {
// Populate defaults array.
$settings += [
'title' => $this->randomMachineName(8),
'type' => 'page',
];
$node = Node::create($settings);
$node->save();
return $node;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Drupal\Tests\pathauto\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests tokens provided by Pathauto.
*
* @group pathauto
*/
class PathautoTokenTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['system', 'token', 'path_alias', 'pathauto'];
public function testPathautoTokens() {
$this->installConfig(['pathauto']);
$array = [
'test first arg',
'The Array / value',
];
$tokens = [
'join-path' => 'test-first-arg/array-value',
];
$data['array'] = $array;
$replacements = $this->assertTokens('array', $data, $tokens);
// Ensure that the cleanTokenValues() method does not alter this token value.
/* @var \Drupal\pathauto\AliasCleanerInterface $alias_cleaner */
$alias_cleaner = \Drupal::service('pathauto.alias_cleaner');
$alias_cleaner->cleanTokenValues($replacements, $data, []);
$this->assertEquals('test-first-arg/array-value', $replacements['[array:join-path]']);
// Test additional token cleaning and its configuration.
$safe_tokens = $this->config('pathauto.settings')->get('safe_tokens');
$safe_tokens[] = 'safe';
$this->config('pathauto.settings')
->set('safe_tokens', $safe_tokens)
->save();
$safe_tokens = [
'[example:path]',
'[example:url]',
'[example:url-brief]',
'[example:login-url]',
'[example:login-url:relative]',
'[example:url:relative]',
'[example:safe]',
'[safe:example]',
];
$unsafe_tokens = [
'[example:path_part]',
'[example:something_url]',
'[example:unsafe]',
];
foreach ($safe_tokens as $token) {
$replacements = [
$token => 'this/is/a/path',
];
$alias_cleaner->cleanTokenValues($replacements);
$this->assertEquals('this/is/a/path', $replacements[$token], "Token $token cleaned.");
}
foreach ($unsafe_tokens as $token) {
$replacements = [
$token => 'This is not a / path',
];
$alias_cleaner->cleanTokenValues($replacements);
$this->assertEquals('not-path', $replacements[$token], "Token $token not cleaned.");
}
}
/**
* Function copied from TokenTestHelper::assertTokens().
*/
public function assertTokens($type, array $data, array $tokens, array $options = []) {
$input = $this->mapTokenNames($type, array_keys($tokens));
$bubbleable_metadata = new BubbleableMetadata();
$replacements = \Drupal::token()->generate($type, $input, $data, $options, $bubbleable_metadata);
foreach ($tokens as $name => $expected) {
$token = $input[$name];
if (!isset($expected)) {
$this->assertTrue(!isset($values[$token]), new FormattableMarkup("Token value for @token was not generated.", [
'@type' => $type,
'@token' => $token,
]));
}
elseif (!isset($replacements[$token])) {
$this->fail(new FormattableMarkup("Token value for @token was not generated.", [
'@type' => $type,
'@token' => $token,
]));
}
elseif (!empty($options['regex'])) {
$this->assertTrue(preg_match('/^' . $expected . '$/', $replacements[$token]), new FormattableMarkup("Token value for @token was '@actual', matching regular expression pattern '@expected'.", [
'@type' => $type,
'@token' => $token,
'@actual' => $replacements[$token],
'@expected' => $expected,
]));
}
else {
$this->assertSame($expected, $replacements[$token], new FormattableMarkup("Token value for @token was '@actual', expected value '@expected'.", [
'@type' => $type,
'@token' => $token,
'@actual' => $replacements[$token],
'@expected' => $expected,
]));
}
}
return $replacements;
}
public function mapTokenNames($type, array $tokens = []) {
$return = [];
foreach ($tokens as $token) {
$return[$token] = "[$type:$token]";
}
return $return;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\pathauto\Unit;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\pathauto\VerboseMessenger;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\pathauto\VerboseMessenger
* @group pathauto
*/
class VerboseMessengerTest extends UnitTestCase {
/**
* The messenger under test.
*
* @var \Drupal\pathauto\VerboseMessenger
*/
protected $messenger;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$config_factory = $this->getConfigFactoryStub(['pathauto.settings' => ['verbose' => TRUE]]);
$account = $this->createMock(AccountInterface::class);
$account->expects($this->once())
->method('hasPermission')
->withAnyParameters()
->willReturn(TRUE);
$messenger = $this->createMock(MessengerInterface::class);
$this->messenger = new VerboseMessenger($config_factory, $account, $messenger);
}
/**
* Tests add messages.
*
* @covers ::addMessage
*/
public function testAddMessage() {
$this->assertTrue($this->messenger->addMessage("Test message"), "The message was added");
}
/**
* @covers ::addMessage
*/
public function testDoNotAddMessageWhileBulkupdate() {
$this->assertFalse($this->messenger->addMessage("Test message", "bulkupdate"), "The message was NOT added");
}
}