Commit ea4c655b authored by jweowu's avatar jweowu Committed by Sascha Grossenbacher
Browse files

Issue #3107144 by justcaldwell, joseph.olstad, eleonel, hkirsman: Duplicate...

Issue #3107144 by justcaldwell, joseph.olstad, eleonel, hkirsman: Duplicate alias entities created with 'Create a new alias. Leave the existing alias functioning' setting
parent ed2d4d30
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -114,14 +114,14 @@ class AliasStorageHelper implements AliasStorageHelperInterface {
      return NULL;
    }

    // Update the existing alias if there is one and the configuration is set to
    // replace it.
    if ($existing_alias && $config->get('update_action') == PathautoGeneratorInterface::UPDATE_ACTION_DELETE) {
      // Skip replacing the current alias with an identical alias.
      if ($existing_alias->getAlias() == $alias) {
    // Don't create a new alias if it is identical to the current alias.
    if ($existing_alias && $existing_alias->getAlias() == $alias) {
      return NULL;
    }

    // Update the existing alias if there is one and the configuration is set to
    // replace it.
    if ($existing_alias && $config->get('update_action') == PathautoGeneratorInterface::UPDATE_ACTION_DELETE) {
      $old_alias = $existing_alias->getAlias();
      $existing_alias->setAlias($alias)->save();

+67 −5
Original line number Diff line number Diff line
@@ -7,9 +7,9 @@ use Drupal\Core\Language\Language;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\pathauto\Entity\PathautoPattern;
use Drupal\pathauto\PathautoPatternInterface;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Tests\Traits\Core\PathAliasTestTrait;

/**
@@ -65,18 +65,29 @@ trait PathautoTestHelperTrait {
        '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]));
    $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) {
@@ -85,6 +96,9 @@ trait PathautoTestHelperTrait {
    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) {
@@ -93,10 +107,16 @@ trait PathautoTestHelperTrait {
    $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) {
@@ -105,6 +125,9 @@ trait PathautoTestHelperTrait {
    $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)) {
@@ -113,6 +136,9 @@ trait PathautoTestHelperTrait {
    $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();
@@ -123,26 +149,53 @@ trait PathautoTestHelperTrait {
      ['%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));
@@ -156,6 +209,9 @@ trait PathautoTestHelperTrait {
    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)),
@@ -167,6 +223,9 @@ trait PathautoTestHelperTrait {
    return $term;
  }

  /**
   * Helper for testTaxonomyPattern().
   */
  public function assertEntityPattern($entity_type, $bundle, $langcode, $expected) {

    $values = [
@@ -179,9 +238,12 @@ trait PathautoTestHelperTrait {
    $this->assertSame($expected, $pattern->getPattern());
  }

  /**
   * Load a taxonomy term by name.
   */
  public function drupalGetTermByName($name, $reset = FALSE) {
    if ($reset) {
      // @todo - implement cache reset.
      // @todo implement cache reset.
    }
    $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadByProperties(['name' => $name]);
    return !empty($terms) ? reset($terms) : FALSE;
+119 −26
Original line number Diff line number Diff line
@@ -2,23 +2,22 @@

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\language\Entity\ConfigurableLanguage;
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\Tests\pathauto\Functional\PathautoTestHelperTrait;
use Drupal\KernelTests\KernelTestBase;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\node\Entity\Node;
use Drupal\Tests\pathauto\Functional\PathautoTestHelperTrait;
use Drupal\user\Entity\User;
use Drupal\Component\Render\FormattableMarkup;


/**
 * Unit tests for Pathauto functions.
@@ -29,20 +28,51 @@ class PathautoKernelTest extends KernelTestBase {

  use PathautoTestHelperTrait;

  protected static $modules = ['system', 'field', 'text', 'user', 'node', 'path', 'path_alias', 'pathauto', 'pathauto_custom_punctuation_test', 'taxonomy', 'token', 'filter', 'language'];
  /**
   * 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();

@@ -52,7 +82,12 @@ class PathautoKernelTest extends KernelTestBase {
    if ($this->container->get('entity_type.manager')->hasDefinition('path_alias')) {
      $this->installEntitySchema('path_alias');
    }
    $this->installConfig(['pathauto', 'taxonomy', 'system', 'node']);
    $this->installConfig([
      'pathauto',
      'taxonomy',
      'system',
      'node',
    ]);

    ConfigurableLanguage::createFromLangcode('fr')->save();

@@ -98,7 +133,7 @@ class PathautoKernelTest extends KernelTestBase {
        'negate' => FALSE,
        'context_mapping' => [
          'language' => 'node:langcode:language',
        ]
        ],
      ]
    );

@@ -275,7 +310,9 @@ class PathautoKernelTest extends KernelTestBase {
  }

  /**
   * Test the different update actions in \Drupal::service('pathauto.generator')->createEntityAlias().
   * Test the different update actions in createEntityAlias().
   *
   * Tests \Drupal::service('pathauto.generator')->createEntityAlias().
   */
  public function testUpdateActions() {
    $config = $this->config('pathauto.settings');
@@ -302,7 +339,18 @@ class PathautoKernelTest extends KernelTestBase {
    $node->setTitle('Third title');
    $node->save();
    $this->assertEntityAlias($node, '/content/third-title');
    $this->assertAliasExists(['path' => '/' . $node->toUrl()->getInternalPath(), 'alias' => '/content/second-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();
@@ -311,7 +359,10 @@ class PathautoKernelTest extends KernelTestBase {
    $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']);
    $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);
@@ -334,6 +385,8 @@ class PathautoKernelTest extends KernelTestBase {
  }

  /**
   * Test createEntityAlias().
   *
   * Test that \Drupal::service('pathauto.generator')->createEntityAlias() will
   * not create an alias for a pattern that does not get any tokens replaced.
   */
@@ -352,7 +405,7 @@ class PathautoKernelTest extends KernelTestBase {
  }

  /**
   * Test the handling of path vs non-path tokens in pathauto_clean_token_values().
   * Test path vs non-path tokens in pathauto_clean_token_values().
   */
  public function testPathTokens() {
    $this->createPattern('taxonomy_term', '/[term:parent:url:path]/[term:name]');
@@ -362,7 +415,10 @@ class PathautoKernelTest extends KernelTestBase {
    $term1 = $this->addTerm($vocab, ['name' => 'Parent term']);
    $this->assertEntityAlias($term1, '/parent-term');

    $term2 = $this->addTerm($vocab, ['name' => 'Child term', 'parent' => $term1->id()]);
    $term2 = $this->addTerm($vocab, [
      'name' => 'Child term',
      'parent' => $term1->id(),
    ]);
    $this->assertEntityAlias($term2, '/parent-term/child-term');

    $this->saveEntityAlias($term1, '/My Crazy/Alias/');
@@ -381,9 +437,16 @@ class PathautoKernelTest extends KernelTestBase {
    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 = FieldStorageConfig::create([
      'entity_type' => 'taxonomy_term',
      'field_name' => $fieldname,
      'type' => 'string',
    ]);
    $field_storage->save();
    $field = FieldConfig::create(['field_storage' => $field_storage, 'bundle' => 'tags']);
    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'tags',
    ]);
    $field->save();

    $display = \Drupal::service('entity_display.repository')->getViewDisplay('taxonomy_term', 'tags');
@@ -395,11 +458,20 @@ class PathautoKernelTest extends KernelTestBase {
    $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 = 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 = Term::create([
      'vid' => 'tags',
      $fieldname => $this->randomMachineName(),
      'parent' => $parent,
      'name' => $this->randomMachineName(),
    ]);
    $child->save();
    $this->assertEntityAlias($child, '/' . mb_strtolower($parent->getName() . '/' . $child->$fieldname->value));

@@ -421,6 +493,9 @@ class PathautoKernelTest extends KernelTestBase {
    $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)
@@ -455,7 +530,10 @@ class PathautoKernelTest extends KernelTestBase {
   * Test programmatic entity creation for aliases.
   */
  public function testProgrammaticEntityCreation() {
    $node = $this->drupalCreateNode(['title' => 'Test node', 'path' => ['pathauto' => TRUE]]);
    $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
@@ -463,12 +541,18 @@ class PathautoKernelTest extends KernelTestBase {
    // \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']]);
    $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]]);
    $term = $this->addTerm($vocabulary, [
      'name' => 'Test term',
      'path' => ['pathauto' => TRUE],
    ]);
    $this->assertEntityAlias($term, '/tags/test-term');

    $edit['name'] = 'Test user';
@@ -489,12 +573,18 @@ class PathautoKernelTest extends KernelTestBase {
      ->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']);
    $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
    // 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');
  }
@@ -554,7 +644,10 @@ class PathautoKernelTest extends KernelTestBase {
   * Tests that aliases are only generated for default revisions.
   */
  public function testDefaultRevision() {
    $node1 = $this->drupalCreateNode(['title' => 'Default revision', 'type' => 'page']);
    $node1 = $this->drupalCreateNode([
      'title' => 'Default revision',
      'type' => 'page',
    ]);
    $this->assertEntityAlias($node1, '/content/default-revision');

    $node1->setNewRevision(TRUE);