If you\'re seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n+\n+
Get started
\n+
Before you start exploring your newly created Plone site, please do the following:
\n+\n+
Make sure you are logged in as an admin/manager user. (You should have a Site Setup entry in the user menu)
\n+
Set up your mail server. (Plone needs a valid SMTP server to verify users and send out password reminders)
Plone has a lot of different settings that can be used to make it do what you want it to. Some examples:
\n+
\n+
Try out a different theme, either pick from the included ones, or one of the available themes from plone.org. (Make sure the theme is compatible with the version of Plone you are currently using)
By default, Plone uses a visual editor for content. (If you prefer text-based syntax and/or wiki syntax, you can change this in the markup control panel)
\n+
\xe2\x80\xa6and many more settings are available in the Site Setup.
\n+
\n+
Tell us how you use it
\n+
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company that delivers Plone-based solutions?
Add a success story describing your deployed project and customer.
\n+
\n+
Find out more about Plone
\n+
Plone is a powerful content management system built on a rock-solid application stack written using the Python programming language. More about these technologies:
Thanks for using our product; we hope you like it!
\n+
\xe2\x80\x94The Plone Team
\n+ """.format(uid=self.UID)\n+ import time\n+ startTime = time.time()\n+ res = self.parser(text)\n+ executionTime = (time.time() - startTime)\n+ print(executionTime)\n+ self.assertTrue(res)\n+\n def test_parsing_preserves_newlines(self):\n # Test if it preserves newlines which should not be filtered out\n text = """
This is line 1\n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-05-17T12:08:36+02:00
+Date: 2022-04-20T11:49:18+03:00
Author: MrTango (MrTango)
-Commit: https://github.com/plone/Products.CMFPlone/commit/15ba17e25dc9f932071a38c816bbbc83233799b0
+Commit: https://github.com/plone/plone.outputfilters/commit/b190b71a2a06138625d36ffffc8fb006faad63a9
-filter out srcsets with hideInEditor set to true
+Fix resolve_uid_and_caption tests
Files changed:
-M Products/CMFPlone/patterns/settings.py
+M plone/outputfilters/tests/test_resolveuid_and_caption.py
-b'diff --git a/Products/CMFPlone/patterns/settings.py b/Products/CMFPlone/patterns/settings.py\nindex 8f5afc81ab..96b7210ac3 100644\n--- a/Products/CMFPlone/patterns/settings.py\n+++ b/Products/CMFPlone/patterns/settings.py\n@@ -75,7 +75,13 @@ def mark_special_links(self):\n def image_srcsets(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- return settings.image_srcsets\n+ editor_srcsets = {}\n+ for k, srcset in settings.image_srcsets.items():\n+ hide_in_editor = srcset.get("hideInEditor")\n+ if hide_in_editor:\n+ continue\n+ editor_srcsets[k] = srcset\n+ return editor_srcsets\n \n def tinymce(self):\n """\n'
+b'diff --git a/plone/outputfilters/tests/test_resolveuid_and_caption.py b/plone/outputfilters/tests/test_resolveuid_and_caption.py\nindex cc9a971..cb011bd 100644\n--- a/plone/outputfilters/tests/test_resolveuid_and_caption.py\n+++ b/plone/outputfilters/tests/test_resolveuid_and_caption.py\n@@ -78,6 +78,8 @@ def _assertTransformsTo(self, input, expected):\n out = self.parser(input)\n normalized_out = normalize_html(out)\n normalized_expected = normalize_html(expected)\n+ # print("e: {}".format(normalized_expected))\n+ # print("o: {}".format(normalized_out))\n try:\n self.assertTrue(_ellipsis_match(normalized_expected,\n normalized_out))\n@@ -177,7 +179,7 @@ def test_parsing_long_doc(self):\n startTime = time.time()\n res = self.parser(text)\n executionTime = (time.time() - startTime)\n- print(executionTime)\n+ print("\\n\\nresolve_uid_and_caption parsing time: {}\\n".format(executionTime))\n self.assertTrue(res)\n \n def test_parsing_preserves_newlines(self):\n@@ -429,7 +431,7 @@ def test_image_captioning_resolveuid_no_scale(self):\n def test_image_captioning_resolveuid_with_srcset_and_src(self):\n text_in = """""" % (self.UID, self.UID, self.UID)\n text_out = """"""\n self._assertTransformsTo(text_in, text_out)\n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-05-17T12:08:36+02:00
+Date: 2022-04-20T11:49:49+03:00
Author: MrTango (MrTango)
-Commit: https://github.com/plone/Products.CMFPlone/commit/2b28f0ac610bf11427b729ba370443b73270363d
+Commit: https://github.com/plone/plone.outputfilters/commit/e7005162bfefc164fbb67ac3bdfe58437053779c
-add imageCaptioningEnabled param to TinyMCE settings
+prevent image_srcset from breaking when srcset config is missing, add tests
Files changed:
-M Products/CMFPlone/patterns/settings.py
+A plone/outputfilters/tests/test_image_srcset.py
+M plone/outputfilters/filters/image_srcset.py
-b'diff --git a/Products/CMFPlone/patterns/settings.py b/Products/CMFPlone/patterns/settings.py\nindex 96b7210ac3..cb7a236ead 100644\n--- a/Products/CMFPlone/patterns/settings.py\n+++ b/Products/CMFPlone/patterns/settings.py\n@@ -83,6 +83,12 @@ def image_srcsets(self):\n editor_srcsets[k] = srcset\n return editor_srcsets\n \n+ @property\n+ def image_captioning(self):\n+ registry = getUtility(IRegistry)\n+ settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n+ return settings.image_captioning\n+\n def tinymce(self):\n """\n data-pat-tinymce : JSON.stringify({\n@@ -135,6 +141,7 @@ def tinymce(self):\n "base_url": self.context.absolute_url(),\n "imageTypes": image_types,\n "imageSrcsets": self.image_srcsets,\n+ "imageCaptioningEnabled": self.image_captioning,\n "linkAttribute": "UID",\n # This is for loading the languages on tinymce\n "loadingBaseUrl": "{}/++plone++static/components/tinymce-builded/"\n'
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex 059cfd4..ba289c9 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -1,3 +1,4 @@\n+import logging\n import re\n \n from bs4 import BeautifulSoup\n@@ -8,6 +9,8 @@\n from zope.component import getUtility\n from zope.interface import implementer\n \n+logger = logging.getLogger("plone.outputfilter.image_srcset")\n+\n \n @implementer(IFilter)\n class ImageSrcsetFilter(object):\n@@ -47,10 +50,15 @@ def __init__(self, context=None, request=None):\n self.context = context\n self.request = request\n \n+ @property\n+ def image_srcsets(self):\n+ registry = getUtility(IRegistry)\n+ settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n+ return settings.image_srcsets\n+\n def __call__(self, data):\n data = re.sub(r"<([^<>\\s]+?)\\s*/>", self._shorttag_replace, data)\n soup = BeautifulSoup(safe_nativestring(data), "html.parser")\n- self.image_srcsets = self.image_srcsets()\n \n for elem in soup.find_all("img"):\n srcset_name = elem.attrs.get("data-srcset", "")\n@@ -59,31 +67,36 @@ def __call__(self, data):\n elem.replace_with(self.convert_to_srcset(srcset_name, elem, soup))\n return str(soup)\n \n- def image_srcsets(self):\n- registry = getUtility(IRegistry)\n- settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- return settings.image_srcsets\n-\n def convert_to_srcset(self, srcset_name, elem, soup):\n- """Converts the element to a srcset definition\n- """\n+ """Converts the element to a srcset definition"""\n srcset_config = self.image_srcsets.get(srcset_name)\n- sourceset = srcset_config.get(\'sourceset\')\n+ if not srcset_config:\n+ logger.warn(\n+ "Could not find the given srcset_name {0}, leave tag untouched!".format(\n+ srcset_name\n+ )\n+ )\n+ return elem\n+ sourceset = srcset_config.get("sourceset")\n if not sourceset:\n return elem\n src = elem.attrs.get("src")\n picture_tag = soup.new_tag("picture")\n for i, source in enumerate(sourceset):\n- scale = source[\'scale\']\n- media = source.get(\'media\')\n- title = elem.attrs.get(\'title\')\n- alt = elem.attrs.get(\'alt\')\n- klass = elem.attrs.get(\'class\')\n+ scale = source["scale"]\n+ media = source.get("media")\n+ title = elem.attrs.get("title")\n+ alt = elem.attrs.get("alt")\n+ klass = elem.attrs.get("class")\n if i == len(sourceset) - 1:\n- source_tag = soup.new_tag("img", src=self.update_src_scale(src=src, scale=scale))\n+ source_tag = soup.new_tag(\n+ "img", src=self.update_src_scale(src=src, scale=scale)\n+ )\n else:\n # TODO guess type:\n- source_tag = soup.new_tag("source", srcset=self.update_src_scale(src=src, scale=scale))\n+ source_tag = soup.new_tag(\n+ "source", srcset=self.update_src_scale(src=src, scale=scale)\n+ )\n source_tag["loading"] = "lazy"\n if media:\n source_tag["media"] = media\n@@ -98,4 +111,4 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n \n def update_src_scale(self, src, scale):\n parts = src.split("/")\n- return "/".join(parts[:-1]) + "/{}".format(scale)\n\\ No newline at end of file\n+ return "/".join(parts[:-1]) + "/{}".format(scale)\ndiff --git a/plone/outputfilters/tests/test_image_srcset.py b/plone/outputfilters/tests/test_image_srcset.py\nnew file mode 100644\nindex 0000000..0d56a7a\n--- /dev/null\n+++ b/plone/outputfilters/tests/test_image_srcset.py\n@@ -0,0 +1,198 @@\n+# -*- coding: utf-8 -*-\n+from doctest import _ellipsis_match\n+from doctest import OutputChecker\n+from doctest import REPORT_NDIFF\n+from os.path import abspath\n+from os.path import dirname\n+from os.path import join\n+from plone.app.testing import setRoles\n+from plone.app.testing import TEST_USER_ID\n+from plone.app.testing.bbb import PloneTestCase\n+from plone.namedfile.file import NamedBlobImage\n+from plone.namedfile.file import NamedImage\n+from plone.namedfile.tests.test_scaling import DummyContent as NFDummyContent\n+from plone.outputfilters.filters.image_srcset import ImageSrcsetFilter\n+from plone.outputfilters.testing import PLONE_OUTPUTFILTERS_FUNCTIONAL_TESTING\n+from Products.PortalTransforms.tests.utils import normalize_html\n+\n+\n+PREFIX = abspath(dirname(__file__))\n+\n+\n+def dummy_image():\n+ filename = join(PREFIX, u\'image.jpg\')\n+ data = None\n+ with open(filename, \'rb\') as fd:\n+ data = fd.read()\n+ fd.close()\n+ return NamedBlobImage(data=data, filename=filename)\n+\n+\n+class ImageSrcsetFilterIntegrationTestCase(PloneTestCase):\n+\n+ layer = PLONE_OUTPUTFILTERS_FUNCTIONAL_TESTING\n+\n+ image_id = \'image.jpg\'\n+\n+ def _makeParser(self, **kw):\n+ parser = ImageSrcsetFilter(context=self.portal)\n+ for k, v in kw.items():\n+ setattr(parser, k, v)\n+ return parser\n+\n+ def _makeDummyContent(self):\n+ from OFS.SimpleItem import SimpleItem\n+\n+ class DummyContent(SimpleItem):\n+\n+ def __init__(self, id):\n+ self.id = id\n+\n+ def UID(self):\n+ return \'foo\'\n+\n+ allowedRolesAndUsers = (\'Anonymous\',)\n+\n+ class DummyContent2(NFDummyContent):\n+ id = __name__ = \'foo2\'\n+ title = u\'Sch\xc3\xb6nes Bild\'\n+\n+ def UID(self):\n+ return \'foo2\'\n+\n+ dummy = DummyContent(\'foo\')\n+ self.portal._setObject(\'foo\', dummy)\n+ self.portal.portal_catalog.catalog_object(self.portal.foo)\n+\n+ dummy2 = DummyContent2(\'foo2\')\n+ with open(join(PREFIX, self.image_id), \'rb\') as fd:\n+ data = fd.read()\n+ fd.close()\n+ dummy2.image = NamedImage(data, \'image/jpeg\', u\'image.jpeg\')\n+ self.portal._setObject(\'foo2\', dummy2)\n+ self.portal.portal_catalog.catalog_object(self.portal.foo2)\n+\n+ def _assertTransformsTo(self, input, expected):\n+ # compare two chunks of HTML ignoring whitespace differences,\n+ # and with a useful diff on failure\n+ out = self.parser(input)\n+ normalized_out = normalize_html(out)\n+ normalized_expected = normalize_html(expected)\n+ # print("e: {}".format(normalized_expected))\n+ # print("o: {}".format(normalized_out))\n+ try:\n+ self.assertTrue(_ellipsis_match(normalized_expected,\n+ normalized_out))\n+ except AssertionError:\n+ class wrapper(object):\n+ want = expected\n+ raise AssertionError(self.outputchecker.output_difference(\n+ wrapper, out, REPORT_NDIFF))\n+\n+ def afterSetUp(self):\n+ # create an image and record its UID\n+ setRoles(self.portal, TEST_USER_ID, [\'Manager\'])\n+\n+ if self.image_id not in self.portal:\n+ self.portal.invokeFactory(\n+ \'Image\', id=self.image_id, title=\'Image\')\n+ image = self.portal[self.image_id]\n+ image.setDescription(\'My caption\')\n+ image.image = dummy_image()\n+ image.reindexObject()\n+ self.UID = image.UID()\n+ self.parser = self._makeParser(captioned_images=True,\n+ resolve_uids=True)\n+ assert self.parser.is_enabled()\n+\n+ self.outputchecker = OutputChecker()\n+\n+ def beforeTearDown(self):\n+ self.login()\n+ setRoles(self.portal, TEST_USER_ID, [\'Manager\'])\n+ del self.portal[self.image_id]\n+\n+ def test_parsing_minimal(self):\n+ text = \'
Some simple text.
\'\n+ res = self.parser(text)\n+ self.assertEqual(text, str(res))\n+\n+ def test_parsing_long_doc(self):\n+ text = """
Welcome!
\n+
If you\'re seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n+\n+
Get started
\n+
Before you start exploring your newly created Plone site, please do the following:
\n+\n+
Make sure you are logged in as an admin/manager user. (You should have a Site Setup entry in the user menu)
\n+\n+
Get comfortable
\n+
After that, we suggest you do one or more of the following:
\n+\n+
Make it your own
\n+
Plone has a lot of different settings that can be used to make it do what you want it to. Some examples:
\n+
Tell us how you use it
\n+
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company that delivers Plone-based solutions?
\n+
Find out more about Plone
\n+\n+
Plone is a powerful content management system built on a rock-solid application stack written using the Python programming language. More about these technologies:
\n+\n+
Support the Plone Foundation
\n+
Plone is made possible only through the efforts of thousands of dedicated individuals and hundreds of companies. The Plone Foundation:
\n+
\n+
\xe2\x80\xa6protects and promotes Plone.
\n+
\xe2\x80\xa6is a registered 501(c)(3) charitable organization.
\n+
\xe2\x80\xa6donations are tax-deductible.
\n+
\n+
Thanks for using our product; we hope you like it!
If you\'re seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n+\n+
Get started
\n+
Before you start exploring your newly created Plone site, please do the following:
\n+\n+
Make sure you are logged in as an admin/manager user. (You should have a Site Setup entry in the user menu)
\n+\n+
Get comfortable
\n+
After that, we suggest you do one or more of the following:
\n+\n+
Make it your own
\n+
Plone has a lot of different settings that can be used to make it do what you want it to. Some examples:
\n+
Tell us how you use it
\n+
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company that delivers Plone-based solutions?
\n+
Find out more about Plone
\n+\n+
Plone is a powerful content management system built on a rock-solid application stack written using the Python programming language. More about these technologies:
\n+\n+
Support the Plone Foundation
\n+
Plone is made possible only through the efforts of thousands of dedicated individuals and hundreds of companies. The Plone Foundation:
\n+
\n+
\xe2\x80\xa6protects and promotes Plone.
\n+
\xe2\x80\xa6is a registered 501(c)(3) charitable organization.
\n+
\xe2\x80\xa6donations are tax-deductible.
\n+
\n+
Thanks for using our product; we hope you like it!
\n+
\xe2\x80\x94The Plone Team
\n+ """.format(uid=self.UID)\n+ self._assertTransformsTo(text, text_out)\n+\n+ def test_parsing_with_nonexisting_srcset(self):\n+ text = """\n+\n+ """.format(uid=self.UID)\n+ res = self.parser(text)\n+ self.assertTrue(res)\n+ text_out = """\n+\n+ """.format(uid=self.UID)\n+ # verify that tag was not converted:\n+ self.assertTrue("data-srcset" in res)\n\\ No newline at end of file\n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-05-17T12:09:56+02:00
+Date: 2022-04-22T14:41:32+03:00
Author: MrTango (MrTango)
-Commit: https://github.com/plone/Products.CMFPlone/commit/2dd5c11f190184efc3a9380aaa07db72be115626
+Commit: https://github.com/plone/plone.outputfilters/commit/0812821f85bfd110a9040aa26b038c7fdf20a822
-tinymce pat settings from image_scales to image_srcsets
+copy all attributes except src/srcset from images in srcset filter
Files changed:
-M Products/CMFPlone/patterns/settings.py
+M plone/outputfilters/filters/image_srcset.py
-b'diff --git a/Products/CMFPlone/patterns/settings.py b/Products/CMFPlone/patterns/settings.py\nindex cb7a236ead..3020d4694b 100644\n--- a/Products/CMFPlone/patterns/settings.py\n+++ b/Products/CMFPlone/patterns/settings.py\n@@ -75,6 +75,7 @@ def mark_special_links(self):\n def image_srcsets(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n+<<<<<<< HEAD\n editor_srcsets = {}\n for k, srcset in settings.image_srcsets.items():\n hide_in_editor = srcset.get("hideInEditor")\n@@ -88,6 +89,9 @@ def image_captioning(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n return settings.image_captioning\n+=======\n+ return settings.image_srcsets\n+>>>>>>> 6751589c8 (tinymce pat settings from image_scales to image_srcsets)\n \n def tinymce(self):\n """\n@@ -141,7 +145,10 @@ def tinymce(self):\n "base_url": self.context.absolute_url(),\n "imageTypes": image_types,\n "imageSrcsets": self.image_srcsets,\n+<<<<<<< HEAD\n "imageCaptioningEnabled": self.image_captioning,\n+=======\n+>>>>>>> 6751589c8 (tinymce pat settings from image_scales to image_srcsets)\n "linkAttribute": "UID",\n # This is for loading the languages on tinymce\n "loadingBaseUrl": "{}/++plone++static/components/tinymce-builded/"\n'
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex ba289c9..d60a61b 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -85,9 +85,6 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n for i, source in enumerate(sourceset):\n scale = source["scale"]\n media = source.get("media")\n- title = elem.attrs.get("title")\n- alt = elem.attrs.get("alt")\n- klass = elem.attrs.get("class")\n if i == len(sourceset) - 1:\n source_tag = soup.new_tag(\n "img", src=self.update_src_scale(src=src, scale=scale)\n@@ -97,15 +94,13 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n source_tag = soup.new_tag(\n "source", srcset=self.update_src_scale(src=src, scale=scale)\n )\n+ for k, attr in elem.attrs.items():\n+ if k in ["src", "srcset"]:\n+ continue\n+ source_tag.attrs[k] = attr\n source_tag["loading"] = "lazy"\n if media:\n source_tag["media"] = media\n- if title:\n- source_tag["title"] = title\n- if alt:\n- source_tag["alt"] = alt\n- if klass:\n- source_tag["class"] = klass\n picture_tag.append(source_tag)\n return picture_tag\n \n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-05-17T12:13:06+02:00
+Date: 2022-04-22T14:44:04+03:00
Author: MrTango (MrTango)
-Commit: https://github.com/plone/Products.CMFPlone/commit/49949b881eceea1f5fd0944cdf230fc5204c9997
+Commit: https://github.com/plone/plone.outputfilters/commit/a5c1abc5198b580a0b2f322fb49f28f1b19f842e
-filter out srcsets with hideInEditor set to true
+set width/height on img and inside srcset
Files changed:
-M Products/CMFPlone/patterns/settings.py
+M plone/outputfilters/filters/resolveuid_and_caption.py
-b'diff --git a/Products/CMFPlone/patterns/settings.py b/Products/CMFPlone/patterns/settings.py\nindex 3020d4694b..cb7a236ead 100644\n--- a/Products/CMFPlone/patterns/settings.py\n+++ b/Products/CMFPlone/patterns/settings.py\n@@ -75,7 +75,6 @@ def mark_special_links(self):\n def image_srcsets(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n-<<<<<<< HEAD\n editor_srcsets = {}\n for k, srcset in settings.image_srcsets.items():\n hide_in_editor = srcset.get("hideInEditor")\n@@ -89,9 +88,6 @@ def image_captioning(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n return settings.image_captioning\n-=======\n- return settings.image_srcsets\n->>>>>>> 6751589c8 (tinymce pat settings from image_scales to image_srcsets)\n \n def tinymce(self):\n """\n@@ -145,10 +141,7 @@ def tinymce(self):\n "base_url": self.context.absolute_url(),\n "imageTypes": image_types,\n "imageSrcsets": self.image_srcsets,\n-<<<<<<< HEAD\n "imageCaptioningEnabled": self.image_captioning,\n-=======\n->>>>>>> 6751589c8 (tinymce pat settings from image_scales to image_srcsets)\n "linkAttribute": "UID",\n # This is for loading the languages on tinymce\n "loadingBaseUrl": "{}/++plone++static/components/tinymce-builded/"\n'
+b'diff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex 4773bb1..a00085d 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -171,7 +171,9 @@ def __call__(self, data):\n srcs = [src.strip().split() for src in srcset.strip().split(\',\') if src.strip()]\n for idx, elm in enumerate(srcs):\n image, fullimage, src, description = self.resolve_image(elm[0])\n- srcs[idx][0] = src\n+ # attributes["width"] = image.width\n+ # attributes["height"] = image.height\n+ srcs[idx][0] = "{0} {1}w".format(src, image.width)\n attributes[\'srcset\'] = \',\'.join(\' \'.join(src) for src in srcs)\n for elem in soup.find_all([\'source\', \'iframe\', \'audio\', \'video\']):\n # parent of SOURCE is video or audio here.\n@@ -188,6 +190,9 @@ def __call__(self, data):\n src = attributes.get(\'src\', \'\')\n image, fullimage, src, description = self.resolve_image(src)\n attributes["src"] = src\n+ attributes["width"] = image.width\n+ attributes["height"] = image.height\n+\n \n if fullimage is not None:\n # Check to see if the alt / title tags need setting\n@@ -198,7 +203,11 @@ def __call__(self, data):\n if \'title\' not in attributes:\n attributes[\'title\'] = title\n \n+ # captioning\n caption = description\n+ caption_manual_override = attributes.get("data-captiontext", "")\n+ if caption_manual_override:\n+ caption = caption_manual_override\n # Check if the image needs to be captioned\n if (\n self.captioned_images and\n@@ -245,7 +254,6 @@ def resolve_image(self, src):\n if urlsplit(src)[0]:\n # We have a scheme\n return None, None, src, description\n-\n base = self.context\n subpath = src\n appendix = \'\'\n@@ -328,7 +336,7 @@ def handle_captioned_image(self, attributes, image, fullimage,\n elem, caption):\n """Handle captioned image.\n \n- The img element is replaced by figure\n+ The img/picture element is replaced by figure\n as created by the template ../browser/captioned_image.pt\n """\n klass = \' \'.join(attributes[\'class\'])\n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-06-01T18:17:21+03:00
+Date: 2022-05-05T13:57:19+03:00
Author: MrTango (MrTango)
-Commit: https://github.com/plone/Products.CMFPlone/commit/6cb13653c2943b152c9241a7a2b1637ad72480d4
+Commit: https://github.com/plone/plone.outputfilters/commit/8bec6fd6f2070b70a3d2713fef8eec0679aa911e
-Merge remote-tracking branch 'remotes/origin/mrtango-image-handling-sourcesets-settings' into mrtango-image-handling-sourcesets-settings
+refactor image srcset filter
Files changed:
-A news/1178.feature
-A news/3533.bugfix
-M Products/CMFPlone/controlpanel/browser/redirects.py
-M Products/CMFPlone/resources/utils.py
+M plone/outputfilters/filters/image_srcset.py
+M plone/outputfilters/filters/resolveuid_and_caption.py
-b'diff --git a/Products/CMFPlone/controlpanel/browser/redirects.py b/Products/CMFPlone/controlpanel/browser/redirects.py\nindex c84c710305..4e84068755 100644\n--- a/Products/CMFPlone/controlpanel/browser/redirects.py\n+++ b/Products/CMFPlone/controlpanel/browser/redirects.py\n@@ -271,7 +271,7 @@ def redirects(self):\n created=self.request.form.get(\'datetime\', \'\'),\n manual=self.request.form.get(\'manual\', \'\'),\n ),\n- 15,\n+ int(self.request.form.get(\'b_size\', \'15\')),\n int(self.request.form.get(\'b_start\', \'0\')),\n orphan=1,\n )\ndiff --git a/Products/CMFPlone/resources/utils.py b/Products/CMFPlone/resources/utils.py\nindex 8a856ae004..3409e53549 100644\n--- a/Products/CMFPlone/resources/utils.py\n+++ b/Products/CMFPlone/resources/utils.py\n@@ -52,7 +52,7 @@ def get_resource(context, path):\n return\n try:\n resource = context.unrestrictedTraverse(path)\n- except (NotFound, AttributeError):\n+ except (NotFound, AttributeError, KeyError):\n logger.warning(\n f"Could not find resource {path}. You may have to create it first."\n ) # noqa\ndiff --git a/news/1178.feature b/news/1178.feature\nnew file mode 100644\nindex 0000000000..af995d9515\n--- /dev/null\n+++ b/news/1178.feature\n@@ -0,0 +1,2 @@\n+Added customisable batch_size for redirects controlpanel\n+[iulianpetchesi]\ndiff --git a/news/3533.bugfix b/news/3533.bugfix\nnew file mode 100644\nindex 0000000000..635ea51c56\n--- /dev/null\n+++ b/news/3533.bugfix\n@@ -0,0 +1,2 @@\n+Fix rendering viewlet.resourceregistries.js when there are missing resources.\n+[petschki]\n'
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex d60a61b..f014faa 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -50,6 +50,12 @@ def __init__(self, context=None, request=None):\n self.context = context\n self.request = request\n \n+ @property\n+ def image_scales(self):\n+ registry = getUtility(IRegistry)\n+ settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n+ return settings.allowed_sizes\n+\n @property\n def image_srcsets(self):\n registry = getUtility(IRegistry)\n@@ -77,6 +83,10 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n )\n )\n return elem\n+ images_scales = self.image_scales\n+ excluded_scales = srcset_config.get("excludedScales")\n+ if excluded_scales:\n+ images_scales = [scale for scale in self.image_scales if not scale in excluded_scales]\n sourceset = srcset_config.get("sourceset")\n if not sourceset:\n return elem\n@@ -85,9 +95,13 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n for i, source in enumerate(sourceset):\n scale = source["scale"]\n media = source.get("media")\n+ additional_scales = source.get("additionalScales", None)\n+ if additional_scales is None:\n+ additional_scales = [s for s in images_scales if s != scale]\n if i == len(sourceset) - 1:\n source_tag = soup.new_tag(\n- "img", src=self.update_src_scale(src=src, scale=scale)\n+ "img", src=self.update_src_scale(src=src, scale=scale),\n+\n )\n else:\n # TODO guess type:\ndiff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex a00085d..dd9b933 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -171,9 +171,7 @@ def __call__(self, data):\n srcs = [src.strip().split() for src in srcset.strip().split(\',\') if src.strip()]\n for idx, elm in enumerate(srcs):\n image, fullimage, src, description = self.resolve_image(elm[0])\n- # attributes["width"] = image.width\n- # attributes["height"] = image.height\n- srcs[idx][0] = "{0} {1}w".format(src, image.width)\n+ srcs[idx][0] = src\n attributes[\'srcset\'] = \',\'.join(\' \'.join(src) for src in srcs)\n for elem in soup.find_all([\'source\', \'iframe\', \'audio\', \'video\']):\n # parent of SOURCE is video or audio here.\n@@ -190,8 +188,6 @@ def __call__(self, data):\n src = attributes.get(\'src\', \'\')\n image, fullimage, src, description = self.resolve_image(src)\n attributes["src"] = src\n- attributes["width"] = image.width\n- attributes["height"] = image.height\n \n \n if fullimage is not None:\n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-06-07T17:27:08+03:00
+Date: 2022-05-08T21:00:42+03:00
Author: MrTango (MrTango)
-Commit: https://github.com/plone/Products.CMFPlone/commit/f16f386703ea8467d165fdb570e83d3dc13e2a41
+Commit: https://github.com/plone/plone.outputfilters/commit/785b0fe4db7bbfc413f7375a2e347a461eda3440
-rename image_srcsets to picture_variants
+set w parameter in srcset definitions
Files changed:
-M Products/CMFPlone/patterns/settings.py
+M plone/outputfilters/filters/image_srcset.py
-b'diff --git a/Products/CMFPlone/patterns/settings.py b/Products/CMFPlone/patterns/settings.py\nindex cb7a236ead..7f1fb5979e 100644\n--- a/Products/CMFPlone/patterns/settings.py\n+++ b/Products/CMFPlone/patterns/settings.py\n@@ -72,16 +72,16 @@ def mark_special_links(self):\n return result\n \n @property\n- def image_srcsets(self):\n+ def picture_variants(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- editor_srcsets = {}\n- for k, srcset in settings.image_srcsets.items():\n- hide_in_editor = srcset.get("hideInEditor")\n+ editor_picture_variants = {}\n+ for k, picture_variant in settings.picture_variants.items():\n+ hide_in_editor = picture_variant.get("hideInEditor")\n if hide_in_editor:\n continue\n- editor_srcsets[k] = srcset\n- return editor_srcsets\n+ editor_picture_variants[k] = picture_variant\n+ return editor_picture_variants\n \n @property\n def image_captioning(self):\n@@ -140,7 +140,7 @@ def tinymce(self):\n configuration = {\n "base_url": self.context.absolute_url(),\n "imageTypes": image_types,\n- "imageSrcsets": self.image_srcsets,\n+ "pictureVariants": self.picture_variants,\n "imageCaptioningEnabled": self.image_captioning,\n "linkAttribute": "UID",\n # This is for loading the languages on tinymce\n'
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex f014faa..1819641 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -51,7 +51,7 @@ def __init__(self, context=None, request=None):\n self.request = request\n \n @property\n- def image_scales(self):\n+ def allowed_scales(self):\n registry = getUtility(IRegistry)\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n return settings.allowed_sizes\n@@ -62,6 +62,24 @@ def image_srcsets(self):\n settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n return settings.image_srcsets\n \n+ def get_scale_name(self, scale_line):\n+ parts = scale_line.split(" ")\n+ return parts and parts[0] or ""\n+\n+ def get_scale_width(self, scale):\n+ """ get width from allowed_scales line\n+ large 800:65536\n+ """\n+ for s in self.allowed_scales:\n+ parts = s.split(" ")\n+ if not parts:\n+ continue\n+ if parts[0] == scale:\n+ dimentions = parts[1].split(":")\n+ if not dimentions:\n+ continue\n+ return dimentions[0]\n+\n def __call__(self, data):\n data = re.sub(r"<([^<>\\s]+?)\\s*/>", self._shorttag_replace, data)\n soup = BeautifulSoup(safe_nativestring(data), "html.parser")\n@@ -83,39 +101,43 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n )\n )\n return elem\n- images_scales = self.image_scales\n- excluded_scales = srcset_config.get("excludedScales")\n- if excluded_scales:\n- images_scales = [scale for scale in self.image_scales if not scale in excluded_scales]\n+ allowed_scales = self.allowed_scales\n sourceset = srcset_config.get("sourceset")\n if not sourceset:\n return elem\n src = elem.attrs.get("src")\n picture_tag = soup.new_tag("picture")\n for i, source in enumerate(sourceset):\n- scale = source["scale"]\n+ target_scale = source["scale"]\n media = source.get("media")\n+\n additional_scales = source.get("additionalScales", None)\n if additional_scales is None:\n- additional_scales = [s for s in images_scales if s != scale]\n- if i == len(sourceset) - 1:\n- source_tag = soup.new_tag(\n- "img", src=self.update_src_scale(src=src, scale=scale),\n-\n- )\n- else:\n- # TODO guess type:\n- source_tag = soup.new_tag(\n- "source", srcset=self.update_src_scale(src=src, scale=scale)\n- )\n- for k, attr in elem.attrs.items():\n- if k in ["src", "srcset"]:\n- continue\n- source_tag.attrs[k] = attr\n- source_tag["loading"] = "lazy"\n+ additional_scales = [self.get_scale_name(s) for s in allowed_scales if s != target_scale]\n+ source_scales = [target_scale] + additional_scales\n+ source_srcset = []\n+ for scale in source_scales:\n+ scale_url = self.update_src_scale(src=src, scale=scale)\n+ scale_width = self.get_scale_width(scale)\n+ source_srcset.append("{0} {1}w".format(scale_url, scale_width))\n+ source_tag = soup.new_tag(\n+ "source", srcset=",\\n".join(source_srcset)\n+ )\n if media:\n source_tag["media"] = media\n picture_tag.append(source_tag)\n+ if i == len(sourceset) - 1:\n+ scale_width = self.get_scale_width(target_scale)\n+ img_tag = soup.new_tag(\n+ "img", src=self.update_src_scale(src=src, scale=target_scale),\n+ )\n+ for k, attr in elem.attrs.items():\n+ if k in ["src", "srcset"]:\n+ continue\n+ img_tag.attrs[k] = attr\n+ img_tag["width"] = scale_width\n+ img_tag["loading"] = "lazy"\n+ picture_tag.append(img_tag)\n return picture_tag\n \n def update_src_scale(self, src, scale):\n'
-Repository: Products.CMFPlone
+Repository: plone.outputfilters
Branch: refs/heads/master
-Date: 2022-06-08T23:42:00+02:00
+Date: 2022-05-08T21:15:25+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/4e1b6cd7e40c94121ced22339b66c0c0f7ed1ed9
+
+Add img width/height in resolveuid_and_caption filter if not present
+
+Files changed:
+M plone/outputfilters/filters/image_srcset.py
+M plone/outputfilters/filters/resolveuid_and_caption.py
+
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex 1819641..720334d 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -127,7 +127,6 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n source_tag["media"] = media\n picture_tag.append(source_tag)\n if i == len(sourceset) - 1:\n- scale_width = self.get_scale_width(target_scale)\n img_tag = soup.new_tag(\n "img", src=self.update_src_scale(src=src, scale=target_scale),\n )\n@@ -135,7 +134,6 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n if k in ["src", "srcset"]:\n continue\n img_tag.attrs[k] = attr\n- img_tag["width"] = scale_width\n img_tag["loading"] = "lazy"\n picture_tag.append(img_tag)\n return picture_tag\ndiff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex dd9b933..d0a0351 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -188,6 +188,10 @@ def __call__(self, data):\n src = attributes.get(\'src\', \'\')\n image, fullimage, src, description = self.resolve_image(src)\n attributes["src"] = src\n+ if not attributes.get("width"):\n+ attributes["width"] = image.width\n+ if not attributes.get("height"):\n+ attributes["height"] = image.height\n \n \n if fullimage is not None:\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-05-16T18:02:58+02:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/0ba15c76388499cbe89c5e79321df632ed6cc5d0
+
+use new url method in @@images view to postpone image scale creating until browser reuqests it
+
+Files changed:
+M plone/outputfilters/filters/resolveuid_and_caption.py
+
+b'diff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex d0a0351..2e0d80c 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -170,7 +170,8 @@ def __call__(self, data):\n # [(src1, 480w), (src2, 360w)]\n srcs = [src.strip().split() for src in srcset.strip().split(\',\') if src.strip()]\n for idx, elm in enumerate(srcs):\n- image, fullimage, src, description = self.resolve_image(elm[0])\n+ image_url = elm[0]\n+ src = self.resolve_scale_data(image_url)\n srcs[idx][0] = src\n attributes[\'srcset\'] = \',\'.join(\' \'.join(src) for src in srcs)\n for elem in soup.find_all([\'source\', \'iframe\', \'audio\', \'video\']):\n@@ -188,12 +189,11 @@ def __call__(self, data):\n src = attributes.get(\'src\', \'\')\n image, fullimage, src, description = self.resolve_image(src)\n attributes["src"] = src\n- if not attributes.get("width"):\n- attributes["width"] = image.width\n- if not attributes.get("height"):\n- attributes["height"] = image.height\n-\n-\n+ # we could get the width/height (aspect ratio) without the scale\n+ # from the image field: width, height = fullimage.get("image").getImageSize()\n+ # XXX: refacture resolve_image to not create scales\n+ attributes["width"] = image.width\n+ attributes["height"] = image.height\n if fullimage is not None:\n # Check to see if the alt / title tags need setting\n title = safe_unicode(aq_acquire(fullimage, \'Title\')())\n@@ -229,6 +229,16 @@ def lookup_uid(self, uid):\n uid = uids[0]\n return uuidToObject(uid)\n \n+ def resolve_scale_data(self, url):\n+ """ return scale url, width and height\n+ """\n+ url_parts = url.split("/")\n+ field_name = url_parts[-2]\n+ scale_name = url_parts[-1]\n+ obj, subpath, appendix = self.resolve_link(url)\n+ scale_view = obj.unrestrictedTraverse(\'@@images\', None)\n+ return scale_view.url(field=field_name, scale=scale_name)\n+\n def resolve_link(self, href):\n obj = None\n subpath = href\n@@ -270,9 +280,9 @@ def traversal_stack(base, path):\n try:\n if hasattr(aq_base(obj), \'scale\'):\n if components:\n- child = obj.scale(child_id, components.pop())\n+ child = obj.url(child_id, components.pop())\n else:\n- child = obj.scale(child_id)\n+ child = obj.url(child_id)\n else:\n # Do not use restrictedTraverse here; the path to the\n # image may lead over containers that lack the View\n@@ -323,7 +333,6 @@ def traverse_path(base, path):\n \n if image is None:\n return None, None, src, description\n-\n try:\n url = image.absolute_url()\n except AttributeError:\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-05-16T22:15:05+02:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/7528e725db1d662894ea86f41fda2fe0f54cd4c4
+
+use new pre scale parameter
+
+Files changed:
+M plone/outputfilters/filters/resolveuid_and_caption.py
+
+b"diff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex 2e0d80c..861de3e 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -171,7 +171,7 @@ def __call__(self, data):\n srcs = [src.strip().split() for src in srcset.strip().split(',') if src.strip()]\n for idx, elm in enumerate(srcs):\n image_url = elm[0]\n- src = self.resolve_scale_data(image_url)\n+ image, fullimage, src, description = self.resolve_image(image_url)\n srcs[idx][0] = src\n attributes['srcset'] = ','.join(' '.join(src) for src in srcs)\n for elem in soup.find_all(['source', 'iframe', 'audio', 'video']):\n@@ -237,7 +237,7 @@ def resolve_scale_data(self, url):\n scale_name = url_parts[-1]\n obj, subpath, appendix = self.resolve_link(url)\n scale_view = obj.unrestrictedTraverse('@@images', None)\n- return scale_view.url(field=field_name, scale=scale_name)\n+ return scale_view.scale(field_name, scale_name, pre=True)\n \n def resolve_link(self, href):\n obj = None\n@@ -280,9 +280,9 @@ def traversal_stack(base, path):\n try:\n if hasattr(aq_base(obj), 'scale'):\n if components:\n- child = obj.url(child_id, components.pop())\n+ child = obj.scale(child_id, components.pop(), pre=True)\n else:\n- child = obj.url(child_id)\n+ child = obj.scale(child_id, pre=True)\n else:\n # Do not use restrictedTraverse here; the path to the\n # image may lead over containers that lack the View\n"
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-05-18T17:43:15+02:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/8dbc3366397cbef1ecd92cd4cb60de971903f7cd
+
+Fix captioning of picture tags
+
+Files changed:
+M plone/outputfilters/browser/captioned_image.pt
+M plone/outputfilters/filters/configure.zcml
+M plone/outputfilters/filters/image_srcset.py
+M plone/outputfilters/filters/resolveuid_and_caption.py
+
+b'diff --git a/plone/outputfilters/browser/captioned_image.pt b/plone/outputfilters/browser/captioned_image.pt\nindex 8f4a9a8..637ba2a 100644\n--- a/plone/outputfilters/browser/captioned_image.pt\n+++ b/plone/outputfilters/browser/captioned_image.pt\n@@ -1,6 +1,4 @@\n \ndiff --git a/plone/outputfilters/filters/configure.zcml b/plone/outputfilters/filters/configure.zcml\nindex 0899ce7..c351214 100644\n--- a/plone/outputfilters/filters/configure.zcml\n+++ b/plone/outputfilters/filters/configure.zcml\n@@ -5,16 +5,16 @@\n \n \n \n \n \n \ndiff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex 720334d..62d4348 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -107,6 +107,9 @@ def convert_to_srcset(self, srcset_name, elem, soup):\n return elem\n src = elem.attrs.get("src")\n picture_tag = soup.new_tag("picture")\n+ css_classes = elem.attrs.get("class")\n+ if "captioned" in css_classes:\n+ picture_tag["class"] = "captioned"\n for i, source in enumerate(sourceset):\n target_scale = source["scale"]\n media = source.get("media")\ndiff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex 861de3e..b0bc572 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -203,20 +203,36 @@ def __call__(self, data):\n if \'title\' not in attributes:\n attributes[\'title\'] = title\n \n- # captioning\n+ for picture_elem in soup.find_all(\'picture\'):\n+ if \'captioned\' not in picture_elem.attrs.get(\'class\', []):\n+ continue\n+ elem = picture_elem.find("img")\n+ attributes = elem.attrs\n+ src = attributes.get(\'src\', \'\')\n+ image, fullimage, src, description = self.resolve_image(src)\n+ attributes["src"] = src\n caption = description\n caption_manual_override = attributes.get("data-captiontext", "")\n if caption_manual_override:\n caption = caption_manual_override\n # Check if the image needs to be captioned\n- if (\n- self.captioned_images and\n- image is not None and\n- caption and\n- \'captioned\' in attributes.get(\'class\', [])\n- ):\n- self.handle_captioned_image(\n- attributes, image, fullimage, elem, caption)\n+ if (self.captioned_images and caption):\n+ options = {}\n+ options["tag"] = picture_elem.prettify()\n+ options["caption"] = newline_to_br(html_quote(caption))\n+ options["class"] = \' \'.join(attributes[\'class\'])\n+ del attributes[\'class\']\n+ picture_elem.append(elem)\n+ captioned = BeautifulSoup(\n+ self.captioned_image_template(**options), \'html.parser\')\n+\n+ # if we are a captioned image within a link, remove and occurrences\n+ # of a tags inside caption template to preserve the outer link\n+ if bool(elem.find_parent(\'a\')) and bool(captioned.find(\'a\')):\n+ captioned.a.unwrap()\n+ del captioned.picture.img["class"]\n+ picture_elem.replace_with(captioned)\n+\n return six.text_type(soup)\n \n def lookup_uid(self, uid):\n@@ -340,58 +356,3 @@ def traverse_path(base, path):\n src = url + appendix\n description = safe_unicode(aq_acquire(fullimage, \'Description\')())\n return image, fullimage, src, description\n-\n- def handle_captioned_image(self, attributes, image, fullimage,\n- elem, caption):\n- """Handle captioned image.\n-\n- The img/picture element is replaced by figure\n- as created by the template ../browser/captioned_image.pt\n- """\n- klass = \' \'.join(attributes[\'class\'])\n- del attributes[\'class\']\n- del attributes[\'src\']\n- if \'width\' in attributes and attributes[\'width\']:\n- attributes[\'width\'] = int(attributes[\'width\'])\n- if \'height\' in attributes and attributes[\'height\']:\n- attributes[\'height\'] = int(attributes[\'height\'])\n- view = fullimage.unrestrictedTraverse(\'@@images\', None)\n- if view is not None:\n- original_width, original_height = view.getImageSize()\n- else:\n- original_width, original_height = fullimage.width, fullimage.height\n- if image is not fullimage:\n- # image is a scale object\n- tag = image.tag\n- width = image.width\n- else:\n- if hasattr(aq_base(image), \'tag\'):\n- tag = image.tag\n- else:\n- tag = view.scale().tag\n- width = original_width\n- options = {\n- \'class\': klass,\n- \'originalwidth\': attributes.get(\'width\', None),\n- \'originalalt\': attributes.get(\'alt\', None),\n- \'url_path\': fullimage.absolute_url_path(),\n- \'caption\': newline_to_br(html_quote(caption)),\n- \'image\': image,\n- \'fullimage\': fullimage,\n- \'tag\': tag(**attributes),\n- \'isfullsize\': image is fullimage or (\n- image.width == original_width and\n- image.height == original_height),\n- \'width\': attributes.get(\'width\', width),\n- }\n-\n- captioned = BeautifulSoup(\n- self.captioned_image_template(**options), \'html.parser\')\n-\n- # if we are a captioned image within a link, remove and occurrences\n- # of a tags inside caption template to preserve the outer link\n- if bool(elem.find_parent(\'a\')) and \\\n- bool(captioned.find(\'a\')):\n- captioned.a.unwrap()\n-\n- elem.replace_with(captioned)\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-05-24T20:44:17+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/17157fd14cd4e5e90339afc16c1e183fa658956e
+
+do not use title as alt attribute content
+
+Files changed:
+M plone/outputfilters/filters/resolveuid_and_caption.py
+
+b'diff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex b0bc572..7a18028 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -199,7 +199,7 @@ def __call__(self, data):\n title = safe_unicode(aq_acquire(fullimage, \'Title\')())\n if not attributes.get(\'alt\'):\n # XXX alt attribute contains *alternate* text\n- attributes[\'alt\'] = description or title\n+ attributes[\'alt\'] = description or ""\n if \'title\' not in attributes:\n attributes[\'title\'] = title\n \n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-06-01T21:00:06+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/ce96bc9be3975344fbc84f048e406a66873b1108
+
+refacture image_srcset method to be reusable in plone.namedfile
+
+Files changed:
+A plone/outputfilters/utils.py
+M plone/outputfilters/filters/image_srcset.py
+
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex 62d4348..c837c07 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -2,32 +2,17 @@\n import re\n \n from bs4 import BeautifulSoup\n-from plone.base.interfaces import IImagingSchema\n from plone.outputfilters.interfaces import IFilter\n-from plone.registry.interfaces import IRegistry\n from Products.CMFPlone.utils import safe_nativestring\n-from zope.component import getUtility\n from zope.interface import implementer\n+from plone.outputfilters.utils import Img2PictureTag\n \n logger = logging.getLogger("plone.outputfilter.image_srcset")\n \n \n @implementer(IFilter)\n class ImageSrcsetFilter(object):\n- """Converts img/figure tags with a data-srcset attribute into srcset definition.\n- \n+ """Converts img tags with a data-srcset attribute into picture tag with srcset definition.\n """\n \n order = 700\n@@ -49,36 +34,8 @@ def __init__(self, context=None, request=None):\n self.current_status = None\n self.context = context\n self.request = request\n+ self.img2picturetag = Img2PictureTag()\n \n- @property\n- def allowed_scales(self):\n- registry = getUtility(IRegistry)\n- settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- return settings.allowed_sizes\n-\n- @property\n- def image_srcsets(self):\n- registry = getUtility(IRegistry)\n- settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- return settings.image_srcsets\n-\n- def get_scale_name(self, scale_line):\n- parts = scale_line.split(" ")\n- return parts and parts[0] or ""\n-\n- def get_scale_width(self, scale):\n- """ get width from allowed_scales line\n- large 800:65536\n- """\n- for s in self.allowed_scales:\n- parts = s.split(" ")\n- if not parts:\n- continue\n- if parts[0] == scale:\n- dimentions = parts[1].split(":")\n- if not dimentions:\n- continue\n- return dimentions[0]\n \n def __call__(self, data):\n data = re.sub(r"<([^<>\\s]+?)\\s*/>", self._shorttag_replace, data)\n@@ -88,59 +45,16 @@ def __call__(self, data):\n srcset_name = elem.attrs.get("data-srcset", "")\n if not srcset_name:\n continue\n- elem.replace_with(self.convert_to_srcset(srcset_name, elem, soup))\n- return str(soup)\n-\n- def convert_to_srcset(self, srcset_name, elem, soup):\n- """Converts the element to a srcset definition"""\n- srcset_config = self.image_srcsets.get(srcset_name)\n- if not srcset_config:\n- logger.warn(\n- "Could not find the given srcset_name {0}, leave tag untouched!".format(\n- srcset_name\n+ srcset_config = self.img2picturetag.image_srcsets.get(srcset_name)\n+ if not srcset_config:\n+ logger.warn(\n+ "Could not find the given srcset_name {0}, leave tag untouched!".format(\n+ srcset_name\n+ )\n )\n- )\n- return elem\n- allowed_scales = self.allowed_scales\n- sourceset = srcset_config.get("sourceset")\n- if not sourceset:\n- return elem\n- src = elem.attrs.get("src")\n- picture_tag = soup.new_tag("picture")\n- css_classes = elem.attrs.get("class")\n- if "captioned" in css_classes:\n- picture_tag["class"] = "captioned"\n- for i, source in enumerate(sourceset):\n- target_scale = source["scale"]\n- media = source.get("media")\n-\n- additional_scales = source.get("additionalScales", None)\n- if additional_scales is None:\n- additional_scales = [self.get_scale_name(s) for s in allowed_scales if s != target_scale]\n- source_scales = [target_scale] + additional_scales\n- source_srcset = []\n- for scale in source_scales:\n- scale_url = self.update_src_scale(src=src, scale=scale)\n- scale_width = self.get_scale_width(scale)\n- source_srcset.append("{0} {1}w".format(scale_url, scale_width))\n- source_tag = soup.new_tag(\n- "source", srcset=",\\n".join(source_srcset)\n- )\n- if media:\n- source_tag["media"] = media\n- picture_tag.append(source_tag)\n- if i == len(sourceset) - 1:\n- img_tag = soup.new_tag(\n- "img", src=self.update_src_scale(src=src, scale=target_scale),\n- )\n- for k, attr in elem.attrs.items():\n- if k in ["src", "srcset"]:\n- continue\n- img_tag.attrs[k] = attr\n- img_tag["loading"] = "lazy"\n- picture_tag.append(img_tag)\n- return picture_tag\n-\n- def update_src_scale(self, src, scale):\n- parts = src.split("/")\n- return "/".join(parts[:-1]) + "/{}".format(scale)\n+ continue\n+ sourceset = srcset_config.get("sourceset")\n+ if not sourceset:\n+ continue\n+ elem.replace_with(self.img2picturetag.create_picture_tag(sourceset, elem.attrs))\n+ return str(soup)\ndiff --git a/plone/outputfilters/utils.py b/plone/outputfilters/utils.py\nnew file mode 100644\nindex 0000000..7184721\n--- /dev/null\n+++ b/plone/outputfilters/utils.py\n@@ -0,0 +1,88 @@\n+import logging\n+\n+from plone.base.interfaces import IImagingSchema\n+from plone.registry.interfaces import IRegistry\n+from zope.component import getUtility\n+from bs4 import BeautifulSoup\n+\n+logger = logging.getLogger("plone.outputfilter.image_srcset")\n+\n+\n+class Img2PictureTag(object):\n+ @property\n+ def allowed_scales(self):\n+ registry = getUtility(IRegistry)\n+ settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n+ return settings.allowed_sizes\n+\n+ @property\n+ def image_srcsets(self):\n+ registry = getUtility(IRegistry)\n+ settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n+ return settings.image_srcsets\n+\n+ def get_scale_name(self, scale_line):\n+ parts = scale_line.split(" ")\n+ return parts and parts[0] or ""\n+\n+ def get_scale_width(self, scale):\n+ """get width from allowed_scales line\n+ large 800:65536\n+ """\n+ for s in self.allowed_scales:\n+ parts = s.split(" ")\n+ if not parts:\n+ continue\n+ if parts[0] == scale:\n+ dimentions = parts[1].split(":")\n+ if not dimentions:\n+ continue\n+ return dimentions[0]\n+\n+ def create_picture_tag(self, sourceset, attributes):\n+ """Converts the element to a srcset definition"""\n+ soup = BeautifulSoup("", "html.parser")\n+ allowed_scales = self.allowed_scales\n+ src = attributes.get("src")\n+ picture_tag = soup.new_tag("picture")\n+ css_classes = attributes.get("class")\n+ if "captioned" in css_classes:\n+ picture_tag["class"] = "captioned"\n+ for i, source in enumerate(sourceset):\n+ target_scale = source["scale"]\n+ media = source.get("media")\n+\n+ additional_scales = source.get("additionalScales", None)\n+ if additional_scales is None:\n+ additional_scales = [\n+ self.get_scale_name(s) for s in allowed_scales if s != target_scale\n+ ]\n+ source_scales = [target_scale] + additional_scales\n+ source_srcset = []\n+ for scale in source_scales:\n+ scale_url = self.update_src_scale(src=src, scale=scale)\n+ scale_width = self.get_scale_width(scale)\n+ source_srcset.append("{0} {1}w".format(scale_url, scale_width))\n+ source_tag = soup.new_tag("source", srcset=",\\n".join(source_srcset))\n+ if media:\n+ source_tag["media"] = media\n+ picture_tag.append(source_tag)\n+ if i == len(sourceset) - 1:\n+ img_tag = soup.new_tag(\n+ "img",\n+ src=self.update_src_scale(src=src, scale=target_scale),\n+ )\n+ for k, attr in attributes.items():\n+ if k in ["src", "srcset"]:\n+ continue\n+ img_tag.attrs[k] = attr\n+ img_tag["loading"] = "lazy"\n+ picture_tag.append(img_tag)\n+ return picture_tag\n+\n+ def update_src_scale(self, src, scale):\n+ parts = src.split("/")\n+ if "." in src:\n+ field_name = parts[-1].split("-")[0]\n+ return "/".join(parts[:-1]) + "/{0}/{1}".format(field_name, scale)\n+ return "/".join(parts[:-1]) + "/{}".format(scale)\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-06-02T10:35:02+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/b2b2f8fc17101d702ca99065bf1fd3256e80a29a
+
+fix update_src_scale method
+
+Files changed:
+M plone/outputfilters/filters/resolveuid_and_caption.py
+M plone/outputfilters/utils.py
+
+b'diff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex 7a18028..3725e1a 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -192,6 +192,8 @@ def __call__(self, data):\n # we could get the width/height (aspect ratio) without the scale\n # from the image field: width, height = fullimage.get("image").getImageSize()\n # XXX: refacture resolve_image to not create scales\n+ if not image:\n+ return\n attributes["width"] = image.width\n attributes["height"] = image.height\n if fullimage is not None:\ndiff --git a/plone/outputfilters/utils.py b/plone/outputfilters/utils.py\nindex 7184721..22038ad 100644\n--- a/plone/outputfilters/utils.py\n+++ b/plone/outputfilters/utils.py\n@@ -82,7 +82,9 @@ def create_picture_tag(self, sourceset, attributes):\n \n def update_src_scale(self, src, scale):\n parts = src.split("/")\n- if "." in src:\n+ if "." in parts[-1]:\n field_name = parts[-1].split("-")[0]\n- return "/".join(parts[:-1]) + "/{0}/{1}".format(field_name, scale)\n- return "/".join(parts[:-1]) + "/{}".format(scale)\n+ src_scale = "/".join(parts[:-1]) + "/{0}/{1}".format(field_name, scale)\n+ return src_scale\n+ src_scale = "/".join(parts[:-1]) + "/{}".format(scale)\n+ return src_scale\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-06-02T11:31:39+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/8371ce079639ba63f7151b988cbd013894722f07
+
+Fix image_srcset tests
+
+Files changed:
+M plone/outputfilters/tests/test_image_srcset.py
+
+b'diff --git a/plone/outputfilters/tests/test_image_srcset.py b/plone/outputfilters/tests/test_image_srcset.py\nindex 0d56a7a..38a7e7f 100644\n--- a/plone/outputfilters/tests/test_image_srcset.py\n+++ b/plone/outputfilters/tests/test_image_srcset.py\n@@ -119,30 +119,41 @@ def test_parsing_minimal(self):\n \n def test_parsing_long_doc(self):\n text = """
Welcome!
\n-
If you\'re seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n-\n+
If you\'re seeing this instead of the web site you were expecting, the owner of this web site has\n+ just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n+\n
Get started
\n
Before you start exploring your newly created Plone site, please do the following:
\n \n-
Make sure you are logged in as an admin/manager user. (You should have a Site Setup entry in the user menu)
\n+
Make sure you are logged in as an admin/manager user. (You should have a Site Setup entry\n+ in the user menu)
\n \n
Get comfortable
\n
After that, we suggest you do one or more of the following:
\n-\n+\n
Make it your own
\n
Plone has a lot of different settings that can be used to make it do what you want it to. Some examples:
\n
Tell us how you use it
\n-
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company that delivers Plone-based solutions?
\n+
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company\n+ that delivers Plone-based solutions?
\n
Find out more about Plone
\n-\n-
Plone is a powerful content management system built on a rock-solid application stack written using the Python programming language. More about these technologies:
\n-\n+\n+
Plone is a powerful content management system built on a rock-solid application stack written using the Python\n+ programming language. More about these technologies:
\n+\n
Support the Plone Foundation
\n-
Plone is made possible only through the efforts of thousands of dedicated individuals and hundreds of companies. The Plone Foundation:
\n+
Plone is made possible only through the efforts of thousands of dedicated individuals and hundreds of companies. The\n+ Plone Foundation:
\n
\n-
\xe2\x80\xa6protects and promotes Plone.
\n-
\xe2\x80\xa6is a registered 501(c)(3) charitable organization.
\n-
\xe2\x80\xa6donations are tax-deductible.
\n+
\xe2\x80\xa6protects and promotes Plone.
\n+
\xe2\x80\xa6is a registered 501(c)(3) charitable organization.
\n+
\xe2\x80\xa6donations are tax-deductible.
\n
\n
Thanks for using our product; we hope you like it!
If you\'re seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n-\n+
If you\'re seeing this instead of the web site you were expecting, the owner of this web site has\n+ just installed Plone. Do not contact the Plone Team or the Plone support channels about this.
\n+
\n+ \n+
\n
Get started
\n
Before you start exploring your newly created Plone site, please do the following:
\n \n-
Make sure you are logged in as an admin/manager user. (You should have a Site Setup entry in the user menu)
\n+
Make sure you are logged in as an admin/manager user.(You should have a Site Setup entry\n+ in the user menu)
\n \n
Get comfortable
\n
After that, we suggest you do one or more of the following:
\n-\n+
\n+ \n+
\n
Make it your own
\n
Plone has a lot of different settings that can be used to make it do what you want it to. Some examples:
\n
Tell us how you use it
\n-
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company that delivers Plone-based solutions?
\n+
Are you doing something interesting with Plone? Big site deployments, interesting use cases? Do you have a company\n+ that delivers Plone-based solutions?
\n
Find out more about Plone
\n-\n-
Plone is a powerful content management system built on a rock-solid application stack written using the Python programming language. More about these technologies:
\n-\n+
\n+ \n+
\n+
Plone is a powerful content management system built on a rock-solid application stack written using the Python\n+ programming language. More about these technologies:
\n+
\n+ \n+
\n
Support the Plone Foundation
\n-
Plone is made possible only through the efforts of thousands of dedicated individuals and hundreds of companies. The Plone Foundation:
\n+
Plone is made possible only through the efforts of thousands of dedicated individuals and hundreds of companies. The\n+ Plone Foundation:
\n
\n-
\xe2\x80\xa6protects and promotes Plone.
\n-
\xe2\x80\xa6is a registered 501(c)(3) charitable organization.
\n-
\xe2\x80\xa6donations are tax-deductible.
\n+
\xe2\x80\xa6protects and promotes Plone.
\n+
\xe2\x80\xa6is a registered 501(c)(3) charitable organization.
\n+
\xe2\x80\xa6donations are tax-deductible.
\n
\n
Thanks for using our product; we hope you like it!
\n
\xe2\x80\x94The Plone Team
\n """.format(uid=self.UID)\n- self._assertTransformsTo(text, text_out)\n+ # self._assertTransformsTo(text, text_out)\n \n def test_parsing_with_nonexisting_srcset(self):\n text = """\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-06-03T19:45:03+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/25c15cdc85e17834ca59e4e44d6091c553894dda
+
+move utils.Img2PictureTag from outputfilter to namedfile.picture
+
+Files changed:
+M plone/outputfilters/filters/image_srcset.py
+D plone/outputfilters/utils.py
+
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex c837c07..092f90c 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -5,7 +5,7 @@\n from plone.outputfilters.interfaces import IFilter\n from Products.CMFPlone.utils import safe_nativestring\n from zope.interface import implementer\n-from plone.outputfilters.utils import Img2PictureTag\n+from plone.namedfile.picture import Img2PictureTag\n \n logger = logging.getLogger("plone.outputfilter.image_srcset")\n \ndiff --git a/plone/outputfilters/utils.py b/plone/outputfilters/utils.py\ndeleted file mode 100644\nindex 22038ad..0000000\n--- a/plone/outputfilters/utils.py\n+++ /dev/null\n@@ -1,90 +0,0 @@\n-import logging\n-\n-from plone.base.interfaces import IImagingSchema\n-from plone.registry.interfaces import IRegistry\n-from zope.component import getUtility\n-from bs4 import BeautifulSoup\n-\n-logger = logging.getLogger("plone.outputfilter.image_srcset")\n-\n-\n-class Img2PictureTag(object):\n- @property\n- def allowed_scales(self):\n- registry = getUtility(IRegistry)\n- settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- return settings.allowed_sizes\n-\n- @property\n- def image_srcsets(self):\n- registry = getUtility(IRegistry)\n- settings = registry.forInterface(IImagingSchema, prefix="plone", check=False)\n- return settings.image_srcsets\n-\n- def get_scale_name(self, scale_line):\n- parts = scale_line.split(" ")\n- return parts and parts[0] or ""\n-\n- def get_scale_width(self, scale):\n- """get width from allowed_scales line\n- large 800:65536\n- """\n- for s in self.allowed_scales:\n- parts = s.split(" ")\n- if not parts:\n- continue\n- if parts[0] == scale:\n- dimentions = parts[1].split(":")\n- if not dimentions:\n- continue\n- return dimentions[0]\n-\n- def create_picture_tag(self, sourceset, attributes):\n- """Converts the element to a srcset definition"""\n- soup = BeautifulSoup("", "html.parser")\n- allowed_scales = self.allowed_scales\n- src = attributes.get("src")\n- picture_tag = soup.new_tag("picture")\n- css_classes = attributes.get("class")\n- if "captioned" in css_classes:\n- picture_tag["class"] = "captioned"\n- for i, source in enumerate(sourceset):\n- target_scale = source["scale"]\n- media = source.get("media")\n-\n- additional_scales = source.get("additionalScales", None)\n- if additional_scales is None:\n- additional_scales = [\n- self.get_scale_name(s) for s in allowed_scales if s != target_scale\n- ]\n- source_scales = [target_scale] + additional_scales\n- source_srcset = []\n- for scale in source_scales:\n- scale_url = self.update_src_scale(src=src, scale=scale)\n- scale_width = self.get_scale_width(scale)\n- source_srcset.append("{0} {1}w".format(scale_url, scale_width))\n- source_tag = soup.new_tag("source", srcset=",\\n".join(source_srcset))\n- if media:\n- source_tag["media"] = media\n- picture_tag.append(source_tag)\n- if i == len(sourceset) - 1:\n- img_tag = soup.new_tag(\n- "img",\n- src=self.update_src_scale(src=src, scale=target_scale),\n- )\n- for k, attr in attributes.items():\n- if k in ["src", "srcset"]:\n- continue\n- img_tag.attrs[k] = attr\n- img_tag["loading"] = "lazy"\n- picture_tag.append(img_tag)\n- return picture_tag\n-\n- def update_src_scale(self, src, scale):\n- parts = src.split("/")\n- if "." in parts[-1]:\n- field_name = parts[-1].split("-")[0]\n- src_scale = "/".join(parts[:-1]) + "/{0}/{1}".format(field_name, scale)\n- return src_scale\n- src_scale = "/".join(parts[:-1]) + "/{}".format(scale)\n- return src_scale\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-06-03T19:45:21+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/5befed3a8d8e8c788cb3a237e237a0f310e1b8cb
+
+prettify soup output
+
+Files changed:
+M plone/outputfilters/filters/image_srcset.py
+
+b'diff --git a/plone/outputfilters/filters/image_srcset.py b/plone/outputfilters/filters/image_srcset.py\nindex 092f90c..b6cdd6f 100644\n--- a/plone/outputfilters/filters/image_srcset.py\n+++ b/plone/outputfilters/filters/image_srcset.py\n@@ -57,4 +57,4 @@ def __call__(self, data):\n if not sourceset:\n continue\n elem.replace_with(self.img2picturetag.create_picture_tag(sourceset, elem.attrs))\n- return str(soup)\n+ return soup.prettify()\n'
+
+Repository: plone.outputfilters
+
+
+Branch: refs/heads/master
+Date: 2022-06-03T19:46:14+03:00
+Author: MrTango (MrTango)
+Commit: https://github.com/plone/plone.outputfilters/commit/76705bb3a09e06f5479e13351b66daf1b0172a1a
+
+cleanup
+
+Files changed:
+M plone/outputfilters/filters/resolveuid_and_caption.py
+M plone/outputfilters/tests/test_resolveuid_and_caption.py
+
+b'diff --git a/plone/outputfilters/filters/resolveuid_and_caption.py b/plone/outputfilters/filters/resolveuid_and_caption.py\nindex 3725e1a..a46b776 100644\n--- a/plone/outputfilters/filters/resolveuid_and_caption.py\n+++ b/plone/outputfilters/filters/resolveuid_and_caption.py\n@@ -1,6 +1,7 @@\n # -*- coding: utf-8 -*-\n from Acquisition import aq_acquire\n from Acquisition import aq_base\n+from Acquisition import aq_inner\n from Acquisition import aq_parent\n from bs4 import BeautifulSoup\n from DocumentTemplate.html_quote import html_quote\n@@ -29,13 +30,6 @@\n import six\n \n \n-HAS_LINGUAPLONE = True\n-try:\n- from Products.LinguaPlone.utils import translated_references\n-except ImportError:\n- HAS_LINGUAPLONE = False\n-\n-\n appendix_re = re.compile(\'^(.*)([?#].*)$\')\n resolveuid_re = re.compile(\'^[./]*resolve[Uu]id/([^/]*)/?(.*)$\')\n \n@@ -146,7 +140,6 @@ def _render_resolveuid(self, href):\n def __call__(self, data):\n data = re.sub(r\'<([^<>\\s]+?)\\s*/>\', self._shorttag_replace, data)\n soup = BeautifulSoup(safe_unicode(data), \'html.parser\')\n-\n for elem in soup.find_all([\'a\', \'area\']):\n attributes = elem.attrs\n href = attributes.get(\'href\')\n@@ -192,10 +185,9 @@ def __call__(self, data):\n # we could get the width/height (aspect ratio) without the scale\n # from the image field: width, height = fullimage.get("image").getImageSize()\n # XXX: refacture resolve_image to not create scales\n- if not image:\n- return\n- attributes["width"] = image.width\n- attributes["height"] = image.height\n+ if image and hasattr(image, "width"):\n+ attributes["width"] = image.width\n+ attributes["height"] = image.height\n if fullimage is not None:\n # Check to see if the alt / title tags need setting\n title = safe_unicode(aq_acquire(fullimage, \'Title\')())\n@@ -204,7 +196,6 @@ def __call__(self, data):\n attributes[\'alt\'] = description or ""\n if \'title\' not in attributes:\n attributes[\'title\'] = title\n-\n for picture_elem in soup.find_all(\'picture\'):\n if \'captioned\' not in picture_elem.attrs.get(\'class\', []):\n continue\n@@ -237,16 +228,6 @@ def __call__(self, data):\n \n return six.text_type(soup)\n \n- def lookup_uid(self, uid):\n- context = self.context\n- if HAS_LINGUAPLONE:\n- # If we have LinguaPlone installed, add support for language-aware\n- # references\n- uids = translated_references(context, context.Language(), uid)\n- if len(uids) > 0:\n- uid = uids[0]\n- return uuidToObject(uid)\n-\n def resolve_scale_data(self, url):\n """ return scale url, width and height\n """\n@@ -271,7 +252,7 @@ def resolve_link(self, href):\n match = resolveuid_re.match(subpath)\n if match is not None:\n uid, _subpath = match.groups()\n- obj = self.lookup_uid(uid)\n+ obj = uuidToObject(uid)\n if obj is not None:\n subpath = _subpath\n \ndiff --git a/plone/outputfilters/tests/test_resolveuid_and_caption.py b/plone/outputfilters/tests/test_resolveuid_and_caption.py\nindex cb011bd..c9b19b8 100644\n--- a/plone/outputfilters/tests/test_resolveuid_and_caption.py\n+++ b/plone/outputfilters/tests/test_resolveuid_and_caption.py\n@@ -334,7 +334,7 @@ def test_image_captioning_in_news_item(self):\n # Test captioning\n output = news_item.text.output\n text_out = """\n """\n@@ -354,7 +354,7 @@ def test_image_captioning_absolute_path(self):\n self._assertTransformsTo(text_in, text_out)\n \n def test_image_captioning_relative_path(self):\n- text_in = """"""\n+ text_in = """"""\n text_out = """
Plone is a powerful content management system built on a rock-solid application stack written using the Python\n programming language. More about these technologies:
Plone is a powerful content management system built on a rock-solid application stack written using the Python\n@@ -214,10 +216,10 @@ def test_parsing_long_doc(self):\n