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:
rebuildWorkspaceMenuTree()restores$this->tableonly at the end of the replay (notry/finally), so on a throw the shared tree-storage service is left pointing at the workspace table for the rest of the request.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:
- Switch into a workspace.
- Create two menu links in that workspace: a good one (
internal:/good) and a second one to corrupt. - Make the second link's tracked (workspace) revision throw on
getPluginDefinition()by setting a malformedinternal:URI (no leading slash), e.g. updatemenu_link_content_field_revision.link__uritointernal:badnoslashfor that revision.Url::fromUri('internal:badnoslash')throwsInvalidArgumentException, which surfaces throughgetUrlObject()→getPluginDefinition(). - 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/catcharoundgetPluginDefinition()and the resave step in the replay: a single bad tracked link is logged and skipped, not fatal. - Restore
$this->tableviatry/finally. - Guard the
updateDefinition()calls inWseMenuSubscriber::onWorkspacePostPublish()andonWorkspacePostRevert()withhasDefinition()(removeDefinition()already no-ops on a missing definition). - Inject a
wse_menulogger channel intoWseMenuTreeStorageto log skipped links.
Issue fork wse-3595541
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
Comment #3
amateescu commentedComment #5
amateescu commentedMerged.