Repository: plone/Products.CMFPlone

Issue:
POST to @@actions-form ignores validation error
The edit for for actions ignores validation errors upon saving/applying.

I used an URL like `https://some.com` as `ACTION URL`.

The backend raises

```
2019-01-07 15:02:26,589 ERROR [Zope.SiteErrorLog:250][waitress] 1546873346.5875420.1529143591444001 https://python3.plone-demo.info/portal_actions/site_actions/XXXXX/@@action-form
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 128, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 276, in publish_module
  Module ZPublisher.WSGIPublisher, line 210, in publish
  Module ZPublisher.mapply, line 85, in mapply
  Module ZPublisher.WSGIPublisher, line 57, in call_object
  Module z3c.form.form, line 233, in __call__
  Module plone.z3cform.fieldsets.extensible, line 65, in update
  Module plone.z3cform.patch, line 30, in GroupForm_update
  Module z3c.form.group, line 145, in update
  Module plone.app.z3cform.csrf, line 22, in execute
  Module z3c.form.action, line 98, in execute
  Module z3c.form.button, line 315, in __call__
  Module z3c.form.button, line 170, in __call__
  Module z3c.form.form, line 312, in handleApply
  Module z3c.form.group, line 114, in applyChanges
  Module z3c.form.form, line 51, in applyChanges
  Module z3c.form.datamanager, line 91, in set
  Module Products.CMFPlone.controlpanel.browser.actions, line 121, in set_url_expr
  Module Products.CMFCore.ActionInformation, line 127, in _setPropValue
  Module Products.CMFCore.Expression, line 43, in __init__
  Module zope.tales.tales, line 640, in compile
zope.tales.tales.CompilerError: Unrecognized expression type "https".
```

The JS call receives a 500 response but does not report the failure on the UI...

Verified for Plone 5.2a2, unverified for Plone 5.1.X


Discussion:
@zopyx . it is mentioned in description to use string:${globals_view/navigationRootUrl}/page .
We can add a url by adding string:https://www.google.com instead of https://www.google.com.
Should i add this in description? If yes , i would like to work on this.


Resolving patch:
diff --git a/Products/CMFPlone/interfaces/controlpanel.py b/Products/CMFPlone/interfaces/controlpanel.py
--- a/Products/CMFPlone/interfaces/controlpanel.py
+++ b/Products/CMFPlone/interfaces/controlpanel.py
@@ -1,13 +1,16 @@
 # -*- coding: utf-8 -*-
+from Products.CMFCore.utils import getToolByName
 from Products.CMFPlone import PloneMessageFactory as _
 from Products.CMFPlone._compat import dump_json_to_text
 from Products.CMFPlone.interfaces.basetool import IPloneBaseTool
 from zope import schema
 from zope.deferredimport import deprecated
+from zope.component.hooks import getSite
 from zope.interface import Attribute
 from zope.interface import implementer
 from zope.interface import Interface
 from zope.interface import Invalid
+from zope.interface import invariant
 from zope.schema.vocabulary import SimpleTerm
 from zope.schema.vocabulary import SimpleVocabulary
 
@@ -1559,6 +1562,20 @@ class ILinkSchema(Interface):
         required=False)
 
 
+def _check_tales_expression(value):
+    from Products.PageTemplates.Expressions import getEngine
+    try:
+        getEngine().compile(value)
+    except Exception:
+        raise Invalid(
+            _(
+                'The expression "${value}" is invalid',
+                mapping={'value': value},
+            )
+        )
+    return True
+
+
 class IActionSchema(Interface):
 
     category = schema.Choice(
@@ -1586,7 +1603,9 @@ class IActionSchema(Interface):
             default=u'An expression producing the called URL. '
             u'Example: string:${globals_view/navigationRootUrl}/page'
         ),
-        required=True)
+        required=True,
+        constraint=_check_tales_expression,
+    )
 
     available_expr = schema.ASCIILine(
         title=_(u'action_condition_heading', default=u'Condition'),
@@ -1629,6 +1648,29 @@ class INewActionSchema(Interface):
         title=_(u'Id'),
         required=True)
 
+    @invariant
+    def validate_category_id(data):
+        categoryid = data.category
+        pa = getToolByName(getSite(), 'portal_actions')
+        category = pa.get(categoryid, {})
+        actionid = data.id
+        if actionid in category:
+            raise Invalid(
+                _(
+                    'An action with the id "${actionid}" already exists',
+                    mapping={'actionid': actionid},
+                )
+            )
+        try:
+            category._checkId(actionid)
+        except Exception:
+            raise Invalid(
+                _(
+                    'The id "${actionid}" is invalid',
+                    mapping={'actionid': actionid},
+                )
+            )
+
 
 class IPloneControlPanelView(Interface):
     """A marker interface for views showing a controlpanel.


Test patch: