Problem/Motivation
When a parent uses a Child menu label token pattern (for example [node:title] [node:field_subtitle]), Autopilot writes the resolved string through Token::replace(). That API treats replacements as markup and HTML-escapes special characters: ampersands, quotes, and angle brackets. Menu link titles are plain text, so those characters are stored as HTML entities instead of the characters themselves.
Admin and any consumer that reads the stored title (including GraphQL MenuItem.title via graphql_compose, which is $link->getTitle()) then show the entities. Editing the menu link back to the real characters does not stick: the next sync overwrites it from the pattern.
Parents that do not set a title pattern copy $node->label() and keep the real characters. That is why some sections look fine and others do not.
Steps to reproduce
- Enable automatic children on a parent and set Child menu label to
[node:title](or any pattern that includes a field containing an ampersand or other HTML-special character). - Publish a node whose title is
Foo & Bar. - The managed child title is stored as the HTML-escaped form of that string.
- Edit the menu link back to the real characters and save. Reconcile again (save the node or the parent). The escaped title returns.
Proposed resolution
Resolve the pattern with Token::replacePlain() so replacements stay plain text. A kernel test asserts a title containing an ampersand is stored without HTML escaping. The same path covers quotes and angle brackets, because replace() escaped all of them.
Remaining tasks
- Failing kernel test — done
- Switch
linkTitle()toreplacePlain()— done - 1.2.2 release — done
User interface changes
None. Existing escaped titles are rewritten on the next sync.
API changes
None.
Data model changes
None.
Comments
Comment #2
jmcerdaWorking the fix against 1.x. This issue is the public record.
Comment #3
jmcerdaCorrected the summary: the first draft over-escaped HTML entities (Filtered HTML already encodes them), and the bug is not ampersand-only. Token::replace() markup-escapes every HTML special (ampersands, quotes, and angle brackets). 1.2.2 uses Token::replacePlain() so replacements stay plain text. Existing escaped titles rewrite on the next parent or child sync.
https://www.drupal.org/project/menu_autopilot/releases/1.2.2