Render plugin nav icons supplied as a file path - #19794
Conversation
📚 Storybook previews@craftcms/ui — open Storybook No changed components detected in this Storybook. resources/js — open Storybook No changed components detected in this Storybook. |
# Conflicts: # CHANGELOG.md
|
This PR had conflicts with Claude's response
|
A plugin's control panel nav item never showed its icon.
Cause
A plugin's icon arrives as an absolute filesystem path —
cpNavIconPath()returns the path to its ownicon-mask.svg— andgetCpNavItem()puts that in the nav item'siconfield.But
iconis handed tocraft-nav-itemas an icon name (ActionList.vue,navAttrs()), so the path gets looked up as though it were a system icon and resolves to nothing. The nav's only other route for an icon isiconSvg, which it renders as inline markup — and nothing converted one to the other.This isn't specific to any one plugin, or to the Yii adapter:
InteractsWithCp::getCpNavItem()does the same thing for plugins written for Craft 6, so no plugin's nav icon could render. Craft's own nav entries pass real names ('plug','custom-icons/graphql'), which is why only plugins looked broken.Fix
Navigation::normalize()— which already walks every item and recurses into subnavs, and runs insidebuildTree()so the result lands in the cached tree rather than re-rendering per request — now renders a path-shaped icon throughIcons::svg()and moves the result toiconSvg.Icons::svg()already handled both shapes, so this is only wiring the two together.Deliberately narrow in two ways:
.svgare touched, so the named icons the rest of the nav uses stay names. Inlining every icon would weigh down a tree that's cached and shared across every control panel page.Icons::svg()logs and returns an empty string for anything it can't read, so a missing path keeps whatever was there rather than substituting a blank icon.No plugin has to change, whether it was written for Craft 6 or comes through the Yii adapter — both arrive as a path in the same field.
Verification
361 tests across
tests/Unit/Cp(includingNavigationTest) andtests/Feature/Integrationpass, along with Pint and PHPStan. Those suites were run against this change in a full checkout; this branch's working copy has novendor/, so onlyphp -land Pint were run here directly — the applied hunk is byte-identical.Worth noting for anyone testing by hand:
getTree()is cached with a one-hour TTL, so an icon may not appear until the nav cache turns over.Navigation::flushCache()forces it.