Problem/Motivation

wse_menu keeps a separate menu tree table per workspace. When that tree is rebuilt, the table is dropped, recreated, re-seeded from Live, and the workspace's tracked menu-link changes are replayed on top.

The replay is not fault-isolated: WseMenuTreeStorage::rebuildWorkspaceMenuTree() calls getPluginDefinition() on every tracked menu link in a single loop. If one tracked link throws there (e.g. a malformed or unresolvable link URI), the whole rebuild aborts after the table was already dropped and re-seeded, so every workspace-only menu change is lost from that workspace's tree.

Two secondary defects make it worse:

  1. rebuildWorkspaceMenuTree() restores $this->table only at the end of the replay (no try/finally), so on a throw the shared tree-storage service is left pointing at the workspace table for the rest of the request.
  2. WseMenuRequestSubscriber::onKernelRequest() clears the "needs rebuild" flag only on success, so a persistently failing rebuild refails on every subsequent request.

Related: WseMenuSubscriber calls MenuLinkManager::updateDefinition() unguarded in its publish/revert handlers; if a definition is not in the Live tree at that moment, the publish/revert aborts with the same PluginNotFoundException.

Steps to reproduce

On a site with workspaces + wse + wse_menu:

  1. Switch into a workspace.
  2. Create two menu links in that workspace: a good one (internal:/good) and a second one to corrupt.
  3. Make the second link's tracked (workspace) revision throw on getPluginDefinition() by setting a malformed internal: URI (no leading slash), e.g. update menu_link_content_field_revision.link__uri to internal:badnoslash for that revision. Url::fromUri('internal:badnoslash') throws InvalidArgumentException, which surfaces through getUrlObject()getPluginDefinition().
  4. Call \Drupal::service('wse_menu.tree_storage')->rebuildWorkspaceMenuTree($workspace) inside that workspace.

Before the fix: the call throws and the good link's definition is gone from the workspace tree. After the fix: the call completes, the bad link is skipped and logged, and the good link is still present.

Proposed resolution

  • Add a per-link try/catch around getPluginDefinition() and the resave step in the replay: a single bad tracked link is logged and skipped, not fatal.
  • Restore $this->table via try/finally.
  • Guard the updateDefinition() calls in WseMenuSubscriber::onWorkspacePostPublish() and onWorkspacePostRevert() with hasDefinition() (removeDefinition() already no-ops on a missing definition).
  • Inject a wse_menu logger channel into WseMenuTreeStorage to log skipped links.

Issue fork wse-3595541

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

amateescu created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review

  • amateescu committed 69fcba6c on 3.0.x
    fix: #3595541 Workspace menu tree rebuild aborts entirely if one tracked...
amateescu’s picture

Status: Needs review » Fixed

Merged.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.