From 5704d03ceb25e54ba61ddd69bf6a05bb874ca02a Mon Sep 17 00:00:00 2001 From: Peter Mathis Date: Wed, 24 Sep 2025 12:36:42 +0200 Subject: [PATCH 1/6] remove "strip_id" svg modifier --- Products/CMFPlone/browser/icons.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Products/CMFPlone/browser/icons.py b/Products/CMFPlone/browser/icons.py index 72de48ddff..8e142813c5 100644 --- a/Products/CMFPlone/browser/icons.py +++ b/Products/CMFPlone/browser/icons.py @@ -49,14 +49,6 @@ def _add_css_class(svgtree, cfg): SVG_MODIFER["add_css_class"] = _add_css_class -def _strip_id(svgtree, cfg): - for el in svgtree.getroot().xpath("//*[@id]"): - del el.attrib["id"] - - -SVG_MODIFER["strip_id"] = _strip_id - - @implementer(IPublishTraverse) class IconsView(BrowserView): prefix = "plone.icon." From 96b70e91c35c70e9d721438fcd1be9ed3322611c Mon Sep 17 00:00:00 2001 From: Peter Mathis Date: Wed, 24 Sep 2025 12:36:47 +0200 Subject: [PATCH 2/6] changenote --- news/4224.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/4224.bugfix diff --git a/news/4224.bugfix b/news/4224.bugfix new file mode 100644 index 0000000000..16464bad0b --- /dev/null +++ b/news/4224.bugfix @@ -0,0 +1 @@ +Remove the svn modifier "strip_id" from "@@iconresolver" view to fix SVGs with repeatable elements. @petschki From 4d2ce7450c9ad5ebdffe32fc9f8260a54359a9fb Mon Sep 17 00:00:00 2001 From: Peter Mathis Date: Thu, 9 Oct 2025 14:40:18 +0200 Subject: [PATCH 3/6] fix unique ids for inline rendered SVG also fix the "aria-labelledby" by adding a unique id to the title tag --- Products/CMFPlone/browser/icons.py | 33 +++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/Products/CMFPlone/browser/icons.py b/Products/CMFPlone/browser/icons.py index 8e142813c5..c19111b710 100644 --- a/Products/CMFPlone/browser/icons.py +++ b/Products/CMFPlone/browser/icons.py @@ -8,7 +8,9 @@ from zope.interface import implementer from zope.publisher.interfaces import IPublishTraverse +import hashlib import logging +import time logger = logging.getLogger(__name__) @@ -28,8 +30,9 @@ def _add_aria_title(svgtree, cfg): title = etree.Element("title") root.append(title) title.text = cfg["title"] + title.attrib["id"] = f"title-{cfg['hash']}" # add aria attr - root.attrib["aria-labelledby"] = "title" + root.attrib["aria-labelledby"] = f"title-{cfg['hash']}" SVG_MODIFER["add_aria_title"] = _add_aria_title @@ -49,6 +52,29 @@ def _add_css_class(svgtree, cfg): SVG_MODIFER["add_css_class"] = _add_css_class +def _unique_id(svgtree, cfg): + # for references to work + # the ids in the svg must be unique in the document + root = svgtree.getroot() + for el in root.xpath("//*[@id]"): + _id = el.attrib["id"] + if cfg["hash"] in _id: + # might be already unique + continue + _unique_id = f"{_id}-{cfg['hash']}" + el.attrib["id"] = _unique_id + refs = root.xpath(f"//*[@* = '#{_id}' or @* = 'url(#{_id})']") + for ref in refs: + for attr, attr_val in ref.attrib.items(): + # there are multiple attributes that might contain a reference + # to an id, e.g. href, xlink:href, fill, filter, clip-path, ... + if f"#{_id}" in attr_val: + ref.attrib[attr] = attr_val.replace(f"#{_id}", f"#{_unique_id}") + + +SVG_MODIFER["unique_id"] = _unique_id + + @implementer(IPublishTraverse) class IconsView(BrowserView): prefix = "plone.icon." @@ -122,8 +148,9 @@ def tag(self, name, tag_class="", tag_alt=""): modifier_cfg = { "cssclass": tag_class, "title": tag_alt, + "hash": hashlib.md5(str(time.time()).encode()).hexdigest(), } - for name, modifier in SVG_MODIFER.items(): - __traceback_info__ = name + for mod_name, modifier in SVG_MODIFER.items(): + __traceback_info__ = mod_name modifier(svgtree, modifier_cfg) return etree.tostring(svgtree) From 33e9d3ae0279a10088acbadfc2c2df16deb976c5 Mon Sep 17 00:00:00 2001 From: Peter Mathis Date: Thu, 9 Oct 2025 14:49:52 +0200 Subject: [PATCH 4/6] udpate changenote --- news/4224.bugfix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/news/4224.bugfix b/news/4224.bugfix index 16464bad0b..129fe7cc00 100644 --- a/news/4224.bugfix +++ b/news/4224.bugfix @@ -1 +1,3 @@ -Remove the svn modifier "strip_id" from "@@iconresolver" view to fix SVGs with repeatable elements. @petschki +Make referencing IDs of inline rendered SVGs unique. +Fix ``aria-labelledby`` parameter when providing ``tag_alt``. +@petschki From 395ed9b041501dab65b21ed2df35de04911037f5 Mon Sep 17 00:00:00 2001 From: Peter Mathis Date: Fri, 10 Oct 2025 00:36:18 +0200 Subject: [PATCH 5/6] make id hash predictable --- Products/CMFPlone/browser/icons.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Products/CMFPlone/browser/icons.py b/Products/CMFPlone/browser/icons.py index c19111b710..e3ee99f5e4 100644 --- a/Products/CMFPlone/browser/icons.py +++ b/Products/CMFPlone/browser/icons.py @@ -1,3 +1,4 @@ +from collections import defaultdict from lxml import etree from OFS.Image import File from plone.registry.interfaces import IRegistry @@ -10,7 +11,6 @@ import hashlib import logging -import time logger = logging.getLogger(__name__) @@ -81,6 +81,10 @@ class IconsView(BrowserView): defaulticon = "++plone++icons/plone.svg" name = "" + def __init__(self, context, request): + super().__init__(context, request) + self._svg_count = defaultdict(int) + def publishTraverse(self, request, name): if self.name: # fix traversing to eg. "contenttype/document" @@ -145,10 +149,11 @@ def tag(self, name, tag_class="", tag_alt=""): raise ValueError( f"SVG file content root tag mismatch (not svg but {svgtree.docinfo.root_name}): {iconfile.path}" ) + self._svg_count[name] += 1 modifier_cfg = { "cssclass": tag_class, "title": tag_alt, - "hash": hashlib.md5(str(time.time()).encode()).hexdigest(), + "hash": hashlib.md5(f"{name}{self._svg_count[name]}".encode()).hexdigest(), } for mod_name, modifier in SVG_MODIFER.items(): __traceback_info__ = mod_name From 88f78fdc9e33bde097b29925c8a18f1957f96a51 Mon Sep 17 00:00:00 2001 From: Peter Mathis Date: Mon, 16 Feb 2026 16:01:18 +0100 Subject: [PATCH 6/6] use uuid4 as unique svg hash --- Products/CMFPlone/browser/icons.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Products/CMFPlone/browser/icons.py b/Products/CMFPlone/browser/icons.py index e3ee99f5e4..af927564b7 100644 --- a/Products/CMFPlone/browser/icons.py +++ b/Products/CMFPlone/browser/icons.py @@ -1,4 +1,3 @@ -from collections import defaultdict from lxml import etree from OFS.Image import File from plone.registry.interfaces import IRegistry @@ -9,8 +8,8 @@ from zope.interface import implementer from zope.publisher.interfaces import IPublishTraverse -import hashlib import logging +import uuid logger = logging.getLogger(__name__) @@ -30,9 +29,9 @@ def _add_aria_title(svgtree, cfg): title = etree.Element("title") root.append(title) title.text = cfg["title"] - title.attrib["id"] = f"title-{cfg['hash']}" + title.attrib["id"] = cfg["hash"] # add aria attr - root.attrib["aria-labelledby"] = f"title-{cfg['hash']}" + root.attrib["aria-labelledby"] = cfg["hash"] SVG_MODIFER["add_aria_title"] = _add_aria_title @@ -81,10 +80,6 @@ class IconsView(BrowserView): defaulticon = "++plone++icons/plone.svg" name = "" - def __init__(self, context, request): - super().__init__(context, request) - self._svg_count = defaultdict(int) - def publishTraverse(self, request, name): if self.name: # fix traversing to eg. "contenttype/document" @@ -149,11 +144,10 @@ def tag(self, name, tag_class="", tag_alt=""): raise ValueError( f"SVG file content root tag mismatch (not svg but {svgtree.docinfo.root_name}): {iconfile.path}" ) - self._svg_count[name] += 1 modifier_cfg = { "cssclass": tag_class, "title": tag_alt, - "hash": hashlib.md5(f"{name}{self._svg_count[name]}".encode()).hexdigest(), + "hash": str(uuid.uuid4()), } for mod_name, modifier in SVG_MODIFER.items(): __traceback_info__ = mod_name