Skip to content

Commit e26b660

Browse files
jensensdavisagli
andauthored
Return 404 when content rules are not activated (PLIP 4119) (#2067)
* Return 404 when content rules are not activated (PLIP 4119) Assisted-by: Claude Sonnet 5.5, Claude Opus 5.5 * Tests: run fieldset test on rules layer, strengthen inactive-profile 404 tests Assisted-by: Claude Sonnet 5.5, Claude Opus 5.5 --------- Co-authored-by: David Glick <david@glicksoftware.com>
1 parent aa542d1 commit e26b660

17 files changed

Lines changed: 166 additions & 4 deletions

‎news/+controlpanel404.bugfix‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
``POST``, ``PATCH`` and ``DELETE`` on ``@controlpanels`` with an unknown control panel name return 404 instead of 500.
2+
@jensens

‎news/+plip4119.feature‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Content rules endpoints return 404 when content rules are not activated in the site, instead of failing with an error.
2+
Preparation for PLIP https://github.com/plone/Products.CMFPlone/issues/4119.
3+
@jensens

‎setup.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
TEST_REQUIRES = [
2222
"collective.MockMailHost",
2323
"plone.app.caching",
24+
"plone.app.contentrules",
2425
"plone.app.contenttypes[test]",
2526
"plone.app.iterate",
2627
"plone.app.discussion[test]",

‎src/plone/restapi/controlpanels/rules.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from plone.contentrules.engine.interfaces import IRuleStorage
56
from plone.restapi.controlpanels import RegistryConfigletPanel
67
from plone.restapi.controlpanels.interfaces import IContentRulesControlpanel
78
from plone.restapi.deserializer import json_body
@@ -15,6 +16,7 @@
1516
from zExceptions import NotFound
1617
from zope.component import adapter
1718
from zope.component import queryMultiAdapter
19+
from zope.component import queryUtility
1820
from zope.interface import alsoProvides
1921
from zope.interface import implementer
2022
from zope.interface import Interface
@@ -342,3 +344,15 @@ def _delete_rule(self, rule_id: str) -> None:
342344
# calls authorize directly, so we need to override that here
343345
cpanel.authorize = lambda: True
344346
cpanel.delete_rule()
347+
348+
349+
@adapter(Interface, IControlpanelLayer)
350+
@implementer(IContentRulesControlpanel)
351+
def content_rules_controlpanel(context, request):
352+
"""Return the content rules control panel, or None when content rules
353+
are not activated (no storage or no add-on browser layer)."""
354+
if queryUtility(IRuleStorage) is None:
355+
return None
356+
if queryMultiAdapter((context, request), name="rules-controlpanel") is None:
357+
return None
358+
return ContentRulesControlpanel(context, request)

‎src/plone/restapi/services/controlpanels/add.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from plone.restapi.interfaces import IControlpanelLayer
33
from plone.restapi.services import Service
44
from zExceptions import BadRequest
5+
from zExceptions import NotFound
56
from zope.component import getAdapters
67
from zope.interface import alsoProvides
78
from zope.interface import implementer
@@ -35,6 +36,8 @@ def reply(self):
3536
raise BadRequest("Missing parameter controlpanelname")
3637

3738
panel = self.panel_by_name(self.params[0])
39+
if panel is None:
40+
raise NotFound(self.context, self.params[0], self.request)
3841
res = panel.add(self.params[1:])
3942

4043
self.request.response.setStatus(201)

‎src/plone/restapi/services/controlpanels/configure.zcml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
<!-- Rules Controlpanel -->
5959
<adapter
60-
factory="plone.restapi.controlpanels.rules.ContentRulesControlpanel"
60+
factory="plone.restapi.controlpanels.rules.content_rules_controlpanel"
6161
provides="plone.restapi.controlpanels.interfaces.IContentRulesControlpanel"
6262
name="content-rules"
6363
zcml:condition="installed plone.app.contentrules"

‎src/plone/restapi/services/controlpanels/delete.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from plone.restapi.interfaces import IControlpanelLayer
33
from plone.restapi.services import Service
44
from zExceptions import BadRequest
5+
from zExceptions import NotFound
56
from zope.component import getAdapters
67
from zope.interface import alsoProvides
78
from zope.interface import implementer
@@ -35,6 +36,8 @@ def reply(self):
3536
raise BadRequest("Can't delete Control Panel: %s" % self.params)
3637

3738
panel = self.panel_by_name(self.params[0])
39+
if panel is None:
40+
raise NotFound(self.context, self.params[0], self.request)
3841
panel.delete(self.params[1:])
3942

4043
return self.reply_no_content()

‎src/plone/restapi/services/controlpanels/update.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from plone.restapi.interfaces import IDeserializeFromJson
44
from plone.restapi.services import Service
55
from zExceptions import BadRequest
6+
from zExceptions import NotFound
67
from zope.component import getAdapters
78
from zope.interface import alsoProvides
89
from zope.interface import implementer
@@ -36,6 +37,8 @@ def reply(self):
3637
raise BadRequest("Missing parameter controlpanelname")
3738

3839
panel = self.panel_by_name(self.params[0])
40+
if panel is None:
41+
raise NotFound(self.context, self.params[0], self.request)
3942
if len(self.params) > 1:
4043
# Update panel child
4144
panel.update(self.params[1:])
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from plone.contentrules.engine.interfaces import IRuleStorage
2+
from zExceptions import NotFound
3+
from zope.component import queryMultiAdapter
4+
from zope.component import queryUtility
5+
6+
7+
def ensure_rules_activated(context, request):
8+
"""Raise NotFound unless content rules are activated in this site.
9+
10+
Activated means: the rule storage exists and the management view is
11+
available for this request (the add-on browser layer is active).
12+
"""
13+
if queryUtility(IRuleStorage) is None:
14+
raise NotFound("Content rules are not activated in this site.")
15+
if queryMultiAdapter((context, request), name="manage-content-rules") is None:
16+
raise NotFound("Content rules are not activated in this site.")

‎src/plone/restapi/services/rules/add.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from plone.restapi.services import Service
2+
from plone.restapi.services.rules import ensure_rules_activated
23
from zExceptions import BadRequest
34
from zope.component import queryMultiAdapter
45
from zope.interface import alsoProvides
@@ -22,6 +23,7 @@ def publishTraverse(self, request, name):
2223
return self
2324

2425
def reply(self):
26+
ensure_rules_activated(self.context, self.request)
2527

2628
if not self.params:
2729
raise BadRequest("Missing parameter rule_id")

0 commit comments

Comments
 (0)