Skip to content

Commit 79ef152

Browse files
committed
fix: Only allow turning plugins into alias if they can be a root plugin
1 parent 47ffd16 commit 79ef152

File tree

5 files changed

+136
-20
lines changed

5 files changed

+136
-20
lines changed

djangocms_alias/cms_plugins.py

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from cms.models import Page
22
from cms.plugin_base import CMSPluginBase, PluginMenuItem
33
from cms.plugin_pool import plugin_pool
4-
from cms.toolbar.utils import get_object_edit_url
4+
from cms.toolbar.utils import get_object_edit_url, get_plugin_toolbar_info, get_plugin_tree
55
from cms.utils import get_language_from_request
66
from cms.utils.permissions import (
77
get_model_permission_codename,
@@ -43,6 +43,13 @@ def get_render_template(self, context, instance, placeholder):
4343
return "djangocms_alias/alias_recursive.html"
4444
return f"djangocms_alias/{instance.template}/alias.html"
4545

46+
@classmethod
47+
def _get_allowed_root_plugins(cls):
48+
if not hasattr(cls, "_cached_allowed_root_plugins"):
49+
cls._cached_allowed_root_plugins = set(plugin_pool.get_all_plugins(root_plugin=True))
50+
print("==>", cls._cached_allowed_root_plugins)
51+
return cls._cached_allowed_root_plugins
52+
4653
@classmethod
4754
def get_extra_plugin_menu_items(cls, request, plugin):
4855
if plugin.plugin_type == cls.__name__:
@@ -82,15 +89,19 @@ def get_extra_plugin_menu_items(cls, request, plugin):
8289
"plugin": plugin.pk,
8390
"language": get_language_from_request(request),
8491
}
85-
endpoint = add_url_parameters(admin_reverse(CREATE_ALIAS_URL_NAME), **data)
86-
return [
87-
PluginMenuItem(
88-
_("Create Alias"),
89-
endpoint,
90-
action="modal",
91-
attributes={"cms-icon": "alias"},
92-
),
93-
]
92+
# Check if the plugin can become root: Should be allowed as a root plugin (in the alias)
93+
can_become_alias = plugin.get_plugin_class() in cls._get_allowed_root_plugins()
94+
if can_become_alias:
95+
endpoint = add_url_parameters(admin_reverse(CREATE_ALIAS_URL_NAME), **data)
96+
return [
97+
PluginMenuItem(
98+
_("Create Alias"),
99+
endpoint,
100+
action="modal",
101+
attributes={"cms-icon": "alias"},
102+
),
103+
]
104+
return []
94105

95106
@classmethod
96107
def get_extra_placeholder_menu_items(cls, request, placeholder):
@@ -150,6 +161,7 @@ def detach_alias_plugin(cls, plugin, language):
150161
source_plugins = plugin.alias.get_plugins(language, show_draft_content=True) # We're in edit mode
151162
target_placeholder = plugin.placeholder
152163
plugin_position = plugin.position
164+
plugin_parent = plugin.parent
153165
target_placeholder.delete_plugin(plugin)
154166
if source_plugins:
155167
if target_last_plugin := target_placeholder.get_last_plugin(plugin.language):
@@ -163,6 +175,7 @@ def detach_alias_plugin(cls, plugin, language):
163175
source_plugins,
164176
placeholder=target_placeholder,
165177
language=language,
178+
root_plugin=plugin_parent,
166179
start_positions={language: plugin_position},
167180
)
168181
return []
@@ -242,10 +255,13 @@ def create_alias_view(self, request):
242255
emit_content_change([alias_content])
243256

244257
if replace:
245-
return self.render_close_frame(
258+
plugin = create_form.cleaned_data.get("plugin")
259+
placeholder = create_form.cleaned_data.get("placeholder")
260+
return self.render_replace_response(
246261
request,
247-
obj=alias_plugin,
248-
action="reload",
262+
new_plugins=[alias_plugin],
263+
source_placeholder=placeholder,
264+
source_plugin=plugin,
249265
)
250266
return TemplateResponse(request, "admin/cms/page/close_frame.html")
251267

@@ -276,17 +292,54 @@ def detach_alias_plugin_view(self, request, plugin_pk):
276292
if not can_detach:
277293
raise PermissionDenied
278294

279-
self.detach_alias_plugin(
295+
copied_plugins = self.detach_alias_plugin(
280296
plugin=instance,
281297
language=language,
282298
)
283299

300+
return self.render_replace_response(
301+
request,
302+
new_plugins=copied_plugins,
303+
source_plugin=instance,
304+
)
305+
284306
return self.render_close_frame(
285307
request,
286308
obj=instance,
287309
action="reload",
288310
)
289311

312+
def render_replace_response(self, request, new_plugins, source_placeholder=None, source_plugin=None):
313+
move_plugins, add_plugins = [], []
314+
for plugin in new_plugins:
315+
root = plugin.parent.get_bound_plugin() if plugin.parent else plugin
316+
317+
plugins = [root] + list(root.get_descendants())
318+
319+
plugin_order = plugin.placeholder.get_plugin_tree_order(
320+
plugin.language,
321+
parent_id=plugin.parent_id,
322+
)
323+
plugin_tree = get_plugin_tree(request, plugins)
324+
move_data = get_plugin_toolbar_info(plugin)
325+
move_data["plugin_order"] = plugin_order
326+
move_data.update(plugin_tree)
327+
move_plugins.append(move_data)
328+
add_plugins.append({**get_plugin_toolbar_info(plugin), "structure": plugin_tree})
329+
data = {
330+
"addedPlugins": add_plugins,
331+
"movedPlugins": move_plugins,
332+
"is_popup": True,
333+
}
334+
if source_plugin is not None:
335+
data["replacedPlugin"] = get_plugin_toolbar_info(source_plugin)
336+
if source_placeholder is not None:
337+
data["replacedPlaceholder"] = {
338+
"placeholder_id": source_placeholder.pk,
339+
"deleted": True,
340+
}
341+
return self.render_close_frame(request, obj=None, action="ALIAS_REPLACE", extra_data=data)
342+
290343
def alias_usage_view(self, request, pk):
291344
if not request.user.is_staff:
292345
raise PermissionDenied

djangocms_alias/forms.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,23 @@ class CreateAliasForm(BaseCreateAliasForm):
104104
required=False,
105105
)
106106

107-
def __init__(self, *args, **kwargs):
107+
def __init__(self, *args, initial=None, **kwargs):
108108
self.user = kwargs.pop("user")
109109

110-
super().__init__(*args, **kwargs)
110+
super().__init__(*args, initial=initial, **kwargs)
111111

112+
# Remove the replace option, if user does not have permission to add "Alias"
112113
if not has_plugin_permission(self.user, "Alias", "add"):
113114
self.fields["replace"].widget = forms.HiddenInput()
114115

116+
# Remove the replace option, if "Alias" cannot be a child of parent plugin
117+
plugin = initial.get("plugin")
118+
if plugin and plugin.parent:
119+
plugin_class = plugin.parent.get_plugin_class()
120+
allowed_children = plugin_class.get_child_classes(plugin.placeholder.slot, instance=plugin.parent)
121+
if allowed_children and "Alias" not in allowed_children:
122+
self.fields["replace"].widget = forms.HiddenInput()
123+
115124
self.set_category_widget(self.user)
116125
self.fields["site"].initial = get_current_site()
117126

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
(function () {
2+
3+
function processDataBridge(data) {
4+
let actionsPerformed = 0;
5+
window.parent.console.log('Processing data bridge:', data);
6+
if (data.replacedPlaceholder) {
7+
CMS.API.StructureBoard.invalidateState('CLEAR_PLACEHOLDER', data.replacedPlaceholder);
8+
actionsPerformed++;
9+
}
10+
if (data.replacedPlugin) {
11+
CMS.API.StructureBoard.invalidateState('DELETE', data.replacedPlugin);
12+
actionsPerformed++;
13+
}
14+
if (data.addedPlugins) {
15+
for (const addedPlugin of data.addedPlugins) {
16+
CMS.API.StructureBoard.invalidateState('ADD', addedPlugin);
17+
actionsPerformed++;
18+
}
19+
}
20+
if (data.movedPlugins) {
21+
for (const movedPlugin of data.movedPlugins) {
22+
CMS.API.StructureBoard.invalidateState('MOVE', movedPlugin);
23+
actionsPerformed++;
24+
}
25+
}
26+
return actionsPerformed;
27+
}
28+
29+
const iframe = window.parent.document.querySelector('.cms-modal-frame > iframe');
30+
const CMS = window.parent.CMS;
31+
32+
// Register the event handler in the capture phase to increase the chance it runs first
33+
iframe.addEventListener('load', function (event) {
34+
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
35+
const dataBridge = iframeDocument.body.querySelector('script#data-bridge');
36+
if (dataBridge) {
37+
window.parent.console.log('iframe loaded', dataBridge);
38+
try {
39+
data = JSON.parse(dataBridge.textContent);
40+
if (data.action === 'ALIAS_REPLACE') {
41+
event.stopPropagation();
42+
dataBridge.parentNode.removeChild(dataBridge);
43+
if (processDataBridge(data)) {
44+
}
45+
iframe.dispatchEvent(new Event('load')); // re-dispatch load event to trigger modal close
46+
}
47+
} catch (error) {
48+
window.parent.console.error('Error parsing data bridge script:', error);
49+
}
50+
iframeDocument.body.textContent = JSON.stringify(dataBridge);
51+
}
52+
}, true); // 'true' sets the event to be handled in the capture phase before the modals handler
53+
})();

djangocms_alias/templates/djangocms_alias/create_alias.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{% extends "admin/change_form.html" %}
2-
{% load cms_admin cms_static i18n %}
2+
{% load cms_admin cms_static i18n static %}
33

44
{% block content %}
55
<h1>{% trans "Create alias" %}</h1>
@@ -31,4 +31,5 @@ <h1>{% trans "Create alias" %}</h1>
3131
<input type="submit" value="{% trans "Create" %}" class="default" name="create">
3232
</div>
3333
</form>
34+
<script src="{% static 'djangocms_alias/js/databridge.js' %}"></script>
3435
{% endblock %}

djangocms_alias/templates/djangocms_alias/detach_alias.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{% extends "admin/delete_confirmation.html" %}
2-
{% load i18n admin_urls cms_tags %}
3-
2+
{% load i18n admin_urls cms_tags static %}
43
{% block breadcrumbs %}{% endblock %}
54

65
{% block content %}
@@ -14,7 +13,8 @@
1413
<input type="hidden" name="post" value="yes" />
1514
<div class="submit-row">
1615
<a href="#" class="button cancel-link">{% trans "No, take me back" %}</a>
17-
<input type="submit" class="deletelink" value="{% trans "Yes, I'm sure" %}" />
16+
<input type="submit" class="deletelink" value="{% translate "Yes, I'm sure" %}" />
1817
</div>
1918
</form>
19+
<script src="{% static 'djangocms_alias/js/databridge.js' %}"></script>
2020
{% endblock %}

0 commit comments

Comments
 (0)