Skip to content

Commit df1b635

Browse files
authored
fix: Support django CMS 5 data bridge for text-enabled plugins (#87)
1 parent ffa847f commit df1b635

File tree

7 files changed

+43
-49
lines changed

7 files changed

+43
-49
lines changed

djangocms_text/cms_plugins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ def get_plugins(self, obj=None):
623623
page=page,
624624
)
625625
child_plugins = (get_plugin(name) for name in child_plugin_types)
626-
template = getattr(self.page, "template", None)
626+
template = getattr(page, "template", None)
627627

628628
modules = get_placeholder_conf("plugin_modules", plugin.placeholder.slot, template, default={})
629629
names = get_placeholder_conf("plugin_labels", plugin.placeholder.slot, template, default={})

djangocms_text/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def __init__(self, *args, **kwargs):
2323
def clean(self, value):
2424
value = super().clean(value)
2525
value = render_dynamic_attributes(value, admin_objects=False, remove_attr=False)
26-
clean_value = clean_html(value, full=False)
26+
clean_value = clean_html(value)
2727

2828
# We `mark_safe` here (as well as in the correct places) because Django
2929
# Parler cache's the value directly from the in-memory object as it

djangocms_text/html.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def __call__(self) -> dict[str, Union[dict[str, set[str]], set[str], None]]:
9595
#: An instance of NH3Parser with the default configuration for CMS text content.
9696

9797

98-
def clean_html(data: str, full: bool = False, cleaner: NH3Parser = None) -> str:
98+
def clean_html(data: str, full: Optional[bool] = None, cleaner: NH3Parser = None) -> str:
9999
"""
100100
Cleans HTML from XSS vulnerabilities using nh3
101101
If full is False, only the contents inside <body> will be returned (without
@@ -105,11 +105,12 @@ def clean_html(data: str, full: bool = False, cleaner: NH3Parser = None) -> str:
105105
if settings.TEXT_HTML_SANITIZE is False:
106106
return data
107107

108-
warnings.warn(
109-
"full argument is deprecated and will be removed",
110-
category=DeprecationWarning,
111-
stacklevel=2,
112-
)
108+
if full is not None:
109+
warnings.warn(
110+
"full argument is deprecated and will be removed",
111+
category=DeprecationWarning,
112+
stacklevel=2,
113+
)
113114
cleaner = cleaner or cms_parser
114115
return nh3.clean(data, **cleaner())
115116

djangocms_text/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def save(self, *args, **kwargs):
7979
super().save(*args, **kwargs)
8080
body = self.body
8181
body = extract_images(body, self)
82-
body = clean_html(body, full=False)
82+
body = clean_html(body)
8383
if settings.TEXT_AUTO_HYPHENATE:
8484
try:
8585
body = hyphenate(body, language=self.language)

private/js/cms.editor.js

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -449,24 +449,13 @@ class CMSEditor {
449449
}
450450

451451
}
452-
const script = dom.querySelector('script#data-bridge');
453452
el.dataset.changed = 'false';
454-
if (script && script.textContent.length > 2) {
455-
this.CMS.API.Helpers.dataBridge = JSON.parse(script.textContent);
456-
} else {
457-
const regex1 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\s=\s(.*?);$/gmu.exec(body);
458-
const regex2 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\.structure\s=\s(.*?);$/gmu.exec(body);
459-
if (regex1 && regex2 && this.CMS) {
460-
this.CMS.API.Helpers.dataBridge = JSON.parse(regex1[1]);
461-
this.CMS.API.Helpers.dataBridge.structure = JSON.parse(regex2[1]);
462-
} else {
463-
// No databridge found: reload
464-
this.CMS.API.Helpers.reloadBrowser('REFRESH_PAGE');
465-
return;
466-
}
453+
this.processDataBridge(dom);
454+
if (!this.CMS.API.Helpers.dataBridge) {
455+
// No databridge found
456+
this.CMS.API.Helpers.reloadBrowser('REFRESH_PAGE');
457+
return;
467458
}
468-
// Additional content for the page disrupts inline editing and needs to be removed
469-
delete this.CMS.API.Helpers.dataBridge.structure?.content;
470459

471460
if (this.CMS.settings.version.startsWith('3.')) {
472461
/* Reflect dirty flag in django CMS < 4 */
@@ -497,6 +486,27 @@ class CMSEditor {
497486
}
498487
}
499488

489+
processDataBridge(dom) {
490+
const script = dom.querySelector('script#data-bridge');
491+
492+
if (script && script.textContent.length > 2) {
493+
this.CMS.API.Helpers.dataBridge = JSON.parse(script.textContent);
494+
} else {
495+
const regex1 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\s=\s(.*?);$/gmu.exec(dom.innerHTML);
496+
const regex2 = /^\s*Window\.CMS\.API\.Helpers\.dataBridge\.structure\s=\s(.*?);$/gmu.exec(dom.innerHTML);
497+
498+
if (regex1 && regex2 && this.CMS) {
499+
this.CMS.API.Helpers.dataBridge = JSON.parse(regex1[1]);
500+
this.CMS.API.Helpers.dataBridge.structure = JSON.parse(regex2[1]);
501+
} else {
502+
// No databridge found
503+
this.CMS.API.Helpers.dataBridge = null;
504+
}
505+
}
506+
// Additional content for the page disrupts inline editing and needs to be removed
507+
delete this.CMS.API.Helpers.dataBridge.structure?.content;
508+
}
509+
500510
// CMS Editor: addPluginForm
501511
// Get form for a new child plugin
502512
addPluginForm (plugin_type, iframe, el , onLoad, onSave) {
@@ -554,19 +564,13 @@ class CMSEditor {
554564
el.dataset.changed = 'true';
555565
// Hook into the django CMS dataBridge to get the details of the newly created or saved
556566
// plugin. For new plugins we need their id to get the content.
567+
557568
if (!this.CMS.API.Helpers.dataBridge) {
558-
// The dataBridge sets a timer, so typically it will not yet be present
559-
setTimeout(() => {
560-
// Needed to update StructureBoard
561-
if (onSave) {
562-
onSave(el, form, this.CMS.API.Helpers.dataBridge);
563-
}
564-
}, 100);
565-
} else {
566-
// Needed to update StructureBoard
567-
if (onSave) {
568-
onSave(el, form, this.CMS.API.Helpers.dataBridge);
569-
}
569+
this.processDataBridge(form);
570+
}
571+
// Needed to update StructureBoard
572+
if (onSave && this.CMS.API.Helpers.dataBridge) {
573+
onSave(el, form, this.CMS.API.Helpers.dataBridge);
570574
}
571575
// Do callback
572576
} else if (onLoad) {

tests/test_html.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ def test_default_tag_removal(self):
5353
settings.TEXT_ADDITIONAL_ATTRIBUTES = {}
5454
text = html.clean_html(
5555
'<iframe src="rtmp://testurl.com/"></iframe>',
56-
full=False,
5756
cleaner=NH3Parser(),
5857
)
5958
self.assertNotIn("iframe", NH3Parser().ALLOWED_TAGS)
@@ -65,7 +64,6 @@ def test_default_tag_removal(self):
6564
def test_custom_tag_enabled(self):
6665
text = html.clean_html(
6766
'<iframe src="https://testurl.com/"></iframe>',
68-
full=False,
6967
cleaner=NH3Parser(
7068
additional_attributes={"iframe": {"src"}},
7169
),
@@ -78,7 +76,6 @@ def test_custom_tag_enabled(self):
7876
def test_default_attribute_escaping(self):
7977
text = html.clean_html(
8078
'<span test-attr="2">foo</span>',
81-
full=False,
8279
cleaner=NH3Parser(),
8380
)
8481
self.assertEqual(
@@ -89,7 +86,6 @@ def test_default_attribute_escaping(self):
8986
def test_custom_attribute_enabled(self):
9087
text = html.clean_html(
9188
'<span test-attr="2">foo</span>',
92-
full=False,
9389
cleaner=NH3Parser(
9490
additional_attributes={
9591
"span": {"test-attr"},
@@ -105,14 +101,13 @@ def test_default_protocol_removal(self):
105101
settings.TEXT_ADDITIONAL_PROTOCOLS = []
106102
text = html.clean_html(
107103
'<source src="rtmp://testurl.com/">',
108-
full=False,
109104
cleaner=NH3Parser(),
110105
)
111106
self.assertEqual("<source>", text)
112107

113108
def test_custom_protocol_enabled(self):
114109
settings.TEXT_ADDITIONAL_PROTOCOLS = ["rtmp"]
115-
text = html.clean_html('<source src="rtmp://testurl.com/">', full=False, cleaner=NH3Parser())
110+
text = html.clean_html('<source src="rtmp://testurl.com/">', cleaner=NH3Parser())
116111
self.assertEqual('<source src="rtmp://testurl.com/">', text)
117112

118113
def test_clean_html_with_sanitize_enabled(self):
@@ -122,7 +117,6 @@ def test_clean_html_with_sanitize_enabled(self):
122117
original = '<span test-attr="2">foo</span>'
123118
cleaned = html.clean_html(
124119
original,
125-
full=False,
126120
)
127121
try:
128122
self.assertHTMLEqual("<span>foo</span>", cleaned)
@@ -136,7 +130,6 @@ def test_clean_html_with_sanitize_disabled(self):
136130
original = '<span test-attr="2" onclick="alert();">foo</span>'
137131
cleaned = html.clean_html(
138132
original,
139-
full=False,
140133
)
141134
try:
142135
self.assertHTMLEqual(original, cleaned)
@@ -147,23 +140,20 @@ def test_clean_html_preserves_aria_attributes(self):
147140
original = '<span aria-label="foo">foo</span>'
148141
cleaned = html.clean_html(
149142
original,
150-
full=False,
151143
)
152144
self.assertHTMLEqual(original, cleaned)
153145

154146
def test_clean_html_preserves_data_attributes(self):
155147
original = '<span data-test-attr="foo">foo</span>'
156148
cleaned = html.clean_html(
157149
original,
158-
full=False,
159150
)
160151
self.assertHTMLEqual(original, cleaned)
161152

162153
def test_clean_html_preserves_role_attribute(self):
163154
original = '<span role="button">foo</span>'
164155
cleaned = html.clean_html(
165156
original,
166-
full=False,
167157
)
168158
self.assertHTMLEqual(original, cleaned)
169159

tests/test_widget.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ def test_sub_plugin_config(self):
2828
body="some text",
2929
)
3030
endpoint = self.get_change_plugin_uri(plugin)
31-
3231
with self.login_user_context(self.super_user):
3332
response = self.client.get(endpoint)
3433
self.assertContains(response, '"group": "Extra"')

0 commit comments

Comments
 (0)