This is a cross-post of https://www.drupal.org/node/2691929 since the issue lies in Pathauto rather than Token.

As described on that issue, re: using a Pathauto pattern with ':menu-link:parent' token/s, when updating the alias for an entity that has a menu link that is a parent, the aliases for the child menu links are not updated.

The attached patch adds a call at the end of PathautoGenerator::createEntityAlias() for insert or update operations (if alias changed) to update the aliases for any child menu link/s (if any) of the entity's menu link/s (if any). It only runs this update if any saved pathauto pattern contains ':menu-link:parent/s' tokens. Perhaps an admin setting for this might be preferred?

NB this only operates on entity insert/update, not on menu link hierarchy restructuring.

NB tested successfully with pattern '[node:menu-link:parent:url:relative]/[node:title]'. The pattern '[node:menu-link:parents:join-path]/[node:title]' seemed to replace with stale aliases, not sure of the issue.

Issue fork pathauto-3016532

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

bgilhome created an issue. See original summary.

bgilhome’s picture

bgilhome’s picture

Issue summary: View changes
ducktape’s picture

Status: Needs review » Needs work

The use case of menu hierarchy changes need to be covered as well.

I don't think the for-each will scale very well, maybe switch to a queue for processing.

mpp’s picture

Priority: Normal » Major

Hi @bgilhome, thanks for your patch.

I consider this a major bug (or missing feature). Changing the priority.

Like @ducktape mentioned:
Note that the foreach code in #2 will cause performance issues for sites with large menu's (I've seen sites with 50k+ menu items) where a webmaster can move a large subtree in the menu. I would consider using a queue for this: fill it up with all the children when a node's parent menu is moved, then process a configurable amount on cron.

ducktape’s picture

Status: Needs work » Needs review
StatusFileSize
new7.22 KB

I rerolled that patch against the latest dev. I have also added support for menu tree restructuring or menu link updates.

I have not added a queue yet, as the processing seems to be fine, even for large trees, however I did not have a 50k+ menu to my disposal to test. @mpp, can you have that tested? :)

fernly’s picture

The patch in #6 outputs a fatal error when there is no parsable request url because the exception class in the catch statement is not recognised, namespacewise (Exception => \Exception).

Added an updated patch.

fernly’s picture

StatusFileSize
new7.23 KB

Removed the txt extension from the patch file.

gremy’s picture

Status: Needs review » Reviewed & tested by the community

We were were running into this problem with one of our clients, and decided to write a custom module to solve this issue. After running into some issues with our custom module, we found this issue and tried pathauto-update_child_aliases-3016532-8.patch instead of our module.
It works great! Thank you!

0livier’s picture

Patch updated for 8.x-1.5

berdir’s picture

Issue tags: +Needs tests

This is quite a lot of non-trivial code, I didn't review in depth yet but some test coverage would be very helpful in getting this committed. to ensure that this doesn't get broken in the future.

almunnings’s picture

Getting some strange behaviour with this patch with pathauto 1.8 & Drupal 8.9.6
Removing patch #10 stops this happening.

On creating a new node:
Select 'Provide a menu link' and save.

Node saves, but the link in the menu gets the url node/[uuid] rather than the alias. Menu seems to save incorrectly.

So not sure whats going on here. Race condition?

Edit. My issue appears to be unrelated to this patch. Issue could be memcached 2.2.

https://www.drupal.org/project/memcache/issues/3176451

0livier’s picture

StatusFileSize
new7.45 KB
new720 bytes

Fix child recursivity loop

sylvain lavielle’s picture

The #13 patch worked for me but I had to do something more to make the whole thing to work in my case.

The problem that I faced was the Pathauto generation for content related to menu item was triggered to early while menu item update and before the menu tree was updated. Hence, the ":menu-link:parent" pathauto token generated an old version of the alias due to a non-updated menu tree structure.

I fixed it in our project by applying a patch to the core in the menu link manager (core/lib/Drupal/Core/Menu/MenuLinkManager.php), updating the updateDefinition this way :

  public function updateDefinition($id, array $new_definition_values, $persist = TRUE) {
    $instance = $this->createInstance($id);
    if ($instance) {
      $new_definition_values['id'] = $id;

      // Update link just to get the definition but do not persist menu link entity.
      $changed_definition = $instance->updateLink($new_definition_values, FALSE);

      // Update tree using the definition.
      $this->treeStorage->save($changed_definition);

      // Update link and persist.
      // This MenuLinkInterface::updateLink re-call is made to persist menu-link after menu tree update and in order to fix the
      // following pathauto related issue : https://www.drupal.org/project/pathauto/issues/3016532
      if($persist === TRUE) {
        $instance->updateLink($new_definition_values, TRUE);
      }
    }
    return $instance;
  }

I'm aware that is certainly not the good manner. That's only a quick fix in order to make it work. But there is somehow a tricky point there that appears only in case of using :menu-link:parent token/s :

Is pathauto have to change the way it works to handle it ?
Is the core have to change something about the way it works while updating menu tree / menu items ?
an what is the best way to do this (or that)

I thing I'm not deeply aware about those subjects to handle this further right now.

I let you - the aware guys :) - react on it and suggest the best solution

kevineinarsson’s picture

In #13, the logic that determines whether or not to update a child looks like this:

($child_entity->id() !== $entity->id && $child_entity->getEntityTypeId() !== $entity->getEntityTypeId())

By changing the condition to be an || instead makes more sense to me. That would mean entities of different types with the same ID would evaluate to true as well as entities of the same type but with different ID:s.

kevineinarsson’s picture

Status: Reviewed & tested by the community » Needs review
fengtan’s picture

A combination of #16 with #3181070-2: Possible need to change the way MenuLinkManager update menu-item and menu-tree seems to work.

However, given that the latter requires to patch core, we implemented the following workaround instead as suggested by #3068943-2: Menu navigation change does not affect node path:


// mymodule.module

/**
 * Implements hook_cron().
 */
function mymodule_cron() {
  $batch = [
    'title' => t('Bulk updating URL aliases'),
    'operations' => [
      ['Drupal\pathauto\Form\PathautoBulkUpdateForm::batchStart', []],
      ['Drupal\pathauto\Form\PathautoBulkUpdateForm::batchProcess',
        [
          "canonical_entities:node",
          "update",
        ],
      ],
    ],
    'finished' => 'Drupal\pathauto\Form\PathautoBulkUpdateForm::batchFinished',
    'progressive' => FALSE,
  ];

  batch_set($batch);
  if (PHP_SAPI === 'cli') {
    drush_backend_batch_process();
  }
}

The paths get updated only on cron (instead of right after updating a node or menu item), but that is acceptable for us.

daniel korte’s picture

Status: Needs review » Needs work
StatusFileSize
new7.45 KB
new505 bytes

I’m seeing duplicate path aliases created when updating a node by adding it to a menu via the “Menu settings” fieldset on the node edit page. This is because there is an “insert” operation on the MenuLinkContentInterface entity which prevents createEntityAlias() from setting $existing_alias causing a duplicate alias to be created.

Changing to “Needs work” because tests are still needed and I’m not sure that hardcoding the operation to “update” here is the best solution.

brandon mok’s picture

StatusFileSize
new7.4 KB
new675 bytes

In the patch from #16, I noticed the check that determines if a child needs to be updated was always evaluating to TRUE for this particular condition $child_entity->id() !== $entity->id. The accessing of the entity's id through $entity->id was observed to return a NULL value every time despite there being a parent entity; thereby, resulting in a truthy check.

Based on the #16 patch, the included patch and interdiff updates the condition so that the accessing of the entity's id is through the id() method of the EntityInterface: $entity->id().

penyaskito’s picture

I don't know why #20 ignored the patch at #19 (thought they might be crossposting, but they have more than 1 year between them), but with #20 I also get duplicated path aliases all over the place.

sjpeters79’s picture

Updated patch to work with latest pathauto dev release

mably’s picture

MR analysis summary

When a node's alias changes (insert or update) and the node is referenced by a menu link, child menu links' target entities are not re-aliased — even when their pathauto pattern includes :menu-link:parent tokens. This means the child nodes keep stale aliases that reference the old parent path.

Changes in src/PathautoGenerator.php

1. Child menu link alias propagation (createEntityAlias())

After saving an alias, if the operation is insert or update (with a changed alias), and at least one pathauto pattern contains :menu-link:parent tokens, the new method updateEntityMenuLinkChildAliases() is called.

2. New updateEntityMenuLinkChildAliases() method

  • Finds the entity's menu link(s) via MenuLinkManager::loadLinksByRoute().
  • Iterates over each menu link's child links via getChildIds().
  • For each child link that points to an entity (entity.* route), loads the target entity.
  • Handles translations: if the child entity has a translation matching the parent's language, it uses it.
  • Checks that the child entity's pattern actually contains :menu-link:parent tokens before regenerating its alias.
  • Invalidates the child entity's cache tags after updating.

3. New getPatternsWithMenuTokens() method

Queries and caches all pathauto patterns whose pattern string contains :menu-link:parent. Used as a fast check to skip the entire menu link traversal when no pattern uses menu tokens.

4. Menu link content handling (updateEntityAlias())

When a MenuLinkContentInterface entity is saved, the method resolves its URL to the linked entity via the router and triggers updateEntityAlias() on that entity, ensuring alias regeneration cascades from menu link changes.

5. Cache reset

resetCaches() now also clears $patternsWithMenuTokens.

mably’s picture

Category: Bug report » Feature request