diff --git a/djangocms_text/cms_plugins.py b/djangocms_text/cms_plugins.py index bc3045c3..c6015bc2 100644 --- a/djangocms_text/cms_plugins.py +++ b/djangocms_text/cms_plugins.py @@ -623,7 +623,7 @@ def get_plugins(self, obj=None): page=page, ) child_plugins = (get_plugin(name) for name in child_plugin_types) - template = getattr(self.page, "template", None) + template = getattr(page, "template", None) modules = get_placeholder_conf("plugin_modules", plugin.placeholder.slot, template, default={}) names = get_placeholder_conf("plugin_labels", plugin.placeholder.slot, template, default={}) diff --git a/djangocms_text/fields.py b/djangocms_text/fields.py index b8fb9959..54b8a95a 100644 --- a/djangocms_text/fields.py +++ b/djangocms_text/fields.py @@ -23,7 +23,7 @@ def __init__(self, *args, **kwargs): def clean(self, value): value = super().clean(value) value = render_dynamic_attributes(value, admin_objects=False, remove_attr=False) - clean_value = clean_html(value, full=False) + clean_value = clean_html(value) # We `mark_safe` here (as well as in the correct places) because Django # Parler cache's the value directly from the in-memory object as it diff --git a/djangocms_text/html.py b/djangocms_text/html.py index 38057f0e..ddec8b68 100644 --- a/djangocms_text/html.py +++ b/djangocms_text/html.py @@ -95,7 +95,7 @@ def __call__(self) -> dict[str, Union[dict[str, set[str]], set[str], None]]: #: An instance of NH3Parser with the default configuration for CMS text content. -def clean_html(data: str, full: bool = False, cleaner: NH3Parser = None) -> str: +def clean_html(data: str, full: Optional[bool] = None, cleaner: NH3Parser = None) -> str: """ Cleans HTML from XSS vulnerabilities using nh3 If full is False, only the contents inside will be returned (without @@ -105,11 +105,12 @@ def clean_html(data: str, full: bool = False, cleaner: NH3Parser = None) -> str: if settings.TEXT_HTML_SANITIZE is False: return data - warnings.warn( - "full argument is deprecated and will be removed", - category=DeprecationWarning, - stacklevel=2, - ) + if full is not None: + warnings.warn( + "full argument is deprecated and will be removed", + category=DeprecationWarning, + stacklevel=2, + ) cleaner = cleaner or cms_parser return nh3.clean(data, **cleaner()) diff --git a/djangocms_text/models.py b/djangocms_text/models.py index d54676a9..768970f6 100644 --- a/djangocms_text/models.py +++ b/djangocms_text/models.py @@ -79,7 +79,7 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) body = self.body body = extract_images(body, self) - body = clean_html(body, full=False) + body = clean_html(body) if settings.TEXT_AUTO_HYPHENATE: try: body = hyphenate(body, language=self.language) diff --git a/private/js/cms.editor.js b/private/js/cms.editor.js index e2210f7f..8b2f806c 100644 --- a/private/js/cms.editor.js +++ b/private/js/cms.editor.js @@ -449,24 +449,13 @@ class CMSEditor { } } - const script = dom.querySelector('script#data-bridge'); el.dataset.changed = 'false'; - if (script && script.textContent.length > 2) { - this.CMS.API.Helpers.dataBridge = JSON.parse(script.textContent); - } else { - const regex1 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\s=\s(.*?);$/gmu.exec(body); - const regex2 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\.structure\s=\s(.*?);$/gmu.exec(body); - if (regex1 && regex2 && this.CMS) { - this.CMS.API.Helpers.dataBridge = JSON.parse(regex1[1]); - this.CMS.API.Helpers.dataBridge.structure = JSON.parse(regex2[1]); - } else { - // No databridge found: reload - this.CMS.API.Helpers.reloadBrowser('REFRESH_PAGE'); - return; - } + this.processDataBridge(dom); + if (!this.CMS.API.Helpers.dataBridge) { + // No databridge found + this.CMS.API.Helpers.reloadBrowser('REFRESH_PAGE'); + return; } - // Additional content for the page disrupts inline editing and needs to be removed - delete this.CMS.API.Helpers.dataBridge.structure?.content; if (this.CMS.settings.version.startsWith('3.')) { /* Reflect dirty flag in django CMS < 4 */ @@ -497,6 +486,27 @@ class CMSEditor { } } + processDataBridge(dom) { + const script = dom.querySelector('script#data-bridge'); + + if (script && script.textContent.length > 2) { + this.CMS.API.Helpers.dataBridge = JSON.parse(script.textContent); + } else { + const regex1 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\s=\s(.*?);$/gmu.exec(dom.innerHTML); + const regex2 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\.structure\s=\s(.*?);$/gmu.exec(dom.innerHTML); + + if (regex1 && regex2 && this.CMS) { + this.CMS.API.Helpers.dataBridge = JSON.parse(regex1[1]); + this.CMS.API.Helpers.dataBridge.structure = JSON.parse(regex2[1]); + } else { + // No databridge found + this.CMS.API.Helpers.dataBridge = null; + } + } + // Additional content for the page disrupts inline editing and needs to be removed + delete this.CMS.API.Helpers.dataBridge.structure?.content; + } + // CMS Editor: addPluginForm // Get form for a new child plugin addPluginForm (plugin_type, iframe, el , onLoad, onSave) { @@ -554,19 +564,13 @@ class CMSEditor { el.dataset.changed = 'true'; // Hook into the django CMS dataBridge to get the details of the newly created or saved // plugin. For new plugins we need their id to get the content. + if (!this.CMS.API.Helpers.dataBridge) { - // The dataBridge sets a timer, so typically it will not yet be present - setTimeout(() => { - // Needed to update StructureBoard - if (onSave) { - onSave(el, form, this.CMS.API.Helpers.dataBridge); - } - }, 100); - } else { - // Needed to update StructureBoard - if (onSave) { - onSave(el, form, this.CMS.API.Helpers.dataBridge); - } + this.processDataBridge(form); + } + // Needed to update StructureBoard + if (onSave && this.CMS.API.Helpers.dataBridge) { + onSave(el, form, this.CMS.API.Helpers.dataBridge); } // Do callback } else if (onLoad) { diff --git a/tests/test_html.py b/tests/test_html.py index c8b9b595..4ade1c65 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -53,7 +53,6 @@ def test_default_tag_removal(self): settings.TEXT_ADDITIONAL_ATTRIBUTES = {} text = html.clean_html( '', - full=False, cleaner=NH3Parser(), ) self.assertNotIn("iframe", NH3Parser().ALLOWED_TAGS) @@ -65,7 +64,6 @@ def test_default_tag_removal(self): def test_custom_tag_enabled(self): text = html.clean_html( '', - full=False, cleaner=NH3Parser( additional_attributes={"iframe": {"src"}}, ), @@ -78,7 +76,6 @@ def test_custom_tag_enabled(self): def test_default_attribute_escaping(self): text = html.clean_html( 'foo', - full=False, cleaner=NH3Parser(), ) self.assertEqual( @@ -89,7 +86,6 @@ def test_default_attribute_escaping(self): def test_custom_attribute_enabled(self): text = html.clean_html( 'foo', - full=False, cleaner=NH3Parser( additional_attributes={ "span": {"test-attr"}, @@ -105,14 +101,13 @@ def test_default_protocol_removal(self): settings.TEXT_ADDITIONAL_PROTOCOLS = [] text = html.clean_html( '', - full=False, cleaner=NH3Parser(), ) self.assertEqual("", text) def test_custom_protocol_enabled(self): settings.TEXT_ADDITIONAL_PROTOCOLS = ["rtmp"] - text = html.clean_html('', full=False, cleaner=NH3Parser()) + text = html.clean_html('', cleaner=NH3Parser()) self.assertEqual('', text) def test_clean_html_with_sanitize_enabled(self): @@ -122,7 +117,6 @@ def test_clean_html_with_sanitize_enabled(self): original = 'foo' cleaned = html.clean_html( original, - full=False, ) try: self.assertHTMLEqual("foo", cleaned) @@ -136,7 +130,6 @@ def test_clean_html_with_sanitize_disabled(self): original = 'foo' cleaned = html.clean_html( original, - full=False, ) try: self.assertHTMLEqual(original, cleaned) @@ -147,7 +140,6 @@ def test_clean_html_preserves_aria_attributes(self): original = 'foo' cleaned = html.clean_html( original, - full=False, ) self.assertHTMLEqual(original, cleaned) @@ -155,7 +147,6 @@ def test_clean_html_preserves_data_attributes(self): original = 'foo' cleaned = html.clean_html( original, - full=False, ) self.assertHTMLEqual(original, cleaned) @@ -163,7 +154,6 @@ def test_clean_html_preserves_role_attribute(self): original = 'foo' cleaned = html.clean_html( original, - full=False, ) self.assertHTMLEqual(original, cleaned) diff --git a/tests/test_widget.py b/tests/test_widget.py index 631c2420..733946ca 100644 --- a/tests/test_widget.py +++ b/tests/test_widget.py @@ -28,7 +28,6 @@ def test_sub_plugin_config(self): body="some text", ) endpoint = self.get_change_plugin_uri(plugin) - with self.login_user_context(self.super_user): response = self.client.get(endpoint) self.assertContains(response, '"group": "Extra"')