Skip to content

Fix xml schema editor for fields and actions. #387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ Changelog
4.1.2 (unreleased)
------------------

- Nothing changed yet.
- Fix xml schema editor for fields and actions.
Fixes `issue 366 <https://github.com/collective/collective.easyform/issues/366>`_.
[maurits]



4.1.1 (2022-10-28)
Expand Down Expand Up @@ -76,7 +79,7 @@ Enhancements:

Breaking change:

- This is for Plone 6 only. At least this is the only version that is tested.
- This is for Plone 6 only. At least this is the only version that is tested.
(Changelog edited later to avoid misunderstandings, use 3.x for Plone 5.2)
[maurits]

Expand Down
65 changes: 57 additions & 8 deletions src/collective/easyform/browser/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
from plone.schemaeditor.browser.schema.listing import SchemaListing
from plone.schemaeditor.browser.schema.listing import SchemaListingPage
from plone.schemaeditor.browser.schema.traversal import SchemaContext
from plone.supermodel import loadString
from plone.supermodel.parser import SupermodelParseError
from Products.CMFPlone.utils import safe_bytes
from Products.CMFPlone.utils import safe_text
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from z3c.form import button
Expand All @@ -30,6 +32,7 @@

import html


NAMESPACE = "{http://namespaces.plone.org/supermodel/schema}"

try:
Expand Down Expand Up @@ -144,21 +147,63 @@ class ModelEditorView(BrowserView):

title = _(u"Edit XML Fields Model")

@property
def escaped_model_source(self):
# Return the HTML escaped model source.
source = self.modelSource()
# html.escape only accepts string.
# We expect to have this, but let's be careful.
if isinstance(source, bytes):
source = source.decode("utf-8")
return html.escape(source, False)

def modelSource(self):
return self.context.aq_parent.fields_model

def _unescaped_source_from_request(self):
"""Unescape the source from the request.

We expect that the source we get from the request is escaped html.
If we pass this directly to the lxml parser, we get:
Error: XMLSyntaxError: Start tag expected
See https://github.com/plone/Products.CMFPlone/issues/3695
So we need to unescape it.

There is a danger that we unescape too much. If we somehow get already
unescaped xml, this may contain escaped html. If we then call html.unescape,
this html gets unescaped, which is not what we want.
The source likely starts with one of these strings:

&lt;?xml
&lt;model

We check if it starts with '&lt;' and we only unescape then.
"""
source = self.request.form.get("source")
if not source:
return
# If you let the source start with spaces, it actually becomes invisible
# in the code editor. So strip it to be safe.
source = source.strip()
if source.startswith("&lt;"):
source = html.unescape(source)
return source

def authorized(self, context, request):
authenticator = queryMultiAdapter((context, request), name=u"authenticator")
authenticator = queryMultiAdapter((context, request), name="authenticator")
return authenticator and authenticator.verify()

def save(self, source):
# We must save a string, but we probably get bytes.
if isinstance(source, bytes):
source = source.decode("utf-8")
self.context.aq_parent.fields_model = source

def __call__(self):
"""View and eventually save the form."""

save = "form.button.save" in self.request.form
source = self.request.form.get("source")
source = self._unescaped_source_from_request()
if save and source:

# First, check for authenticator
Expand All @@ -176,35 +221,35 @@ def __call__(self):
root = etree.fromstring(source, parser=parser)
except etree.XMLSyntaxError as e:
IStatusMessage(self.request).addStatusMessage(
"XMLSyntaxError: {0}".format(html.escape(safe_unicode(e.args[0]))),
f"XMLSyntaxError: {html.escape(safe_text(e.args[0]))}",
"error",
)
return super().__call__()

# a little more sanity checking, look at first two element levels
if root.tag != NAMESPACE + "model":
IStatusMessage(self.request).addStatusMessage(
_(u"Error: root tag must be 'model'"),
_("Error: root tag must be 'model'"),
"error",
)
return super().__call__()

for element in root.getchildren():
if element.tag != NAMESPACE + "schema":
IStatusMessage(self.request).addStatusMessage(
_(u"Error: all model elements must be 'schema'"),
_("Error: all model elements must be 'schema'"),
"error",
)
return super().__call__()

# can supermodel parse it?
# This is mainly good for catching bad dotted names.
try:
plone.supermodel.loadString(source, policy=u"dexterity")
loadString(source, policy="dexterity")
except SupermodelParseError as e:
message = e.args[0].replace('\n File "<unknown>"', "")
IStatusMessage(self.request).addStatusMessage(
u"SuperModelParseError: {0}".format(html.escape(message)),
f"SuperModelParseError: {html.escape(message)}",
"error",
)
return super().__call__()
Expand All @@ -215,5 +260,9 @@ def __call__(self):
)
# and save
self.save(source)
# import pdb; pdb.set_trace()
IStatusMessage(self.request).addStatusMessage(
_("Changes saved."),
"info",
)

return self.template()
5 changes: 1 addition & 4 deletions src/collective/easyform/browser/modeleditor.pt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
<metal:main fill-slot="content">
<tal:main-macro metal:define-macro="content">
<div id="content">
<style type="text/css"
tal:content="string:@import url(${portal_url}/++resource++plone.app.dexterity.modeleditor.css);"
></style>
<div id="page-intro">
<h1 class="documentFirstHeading"
tal:content="view/title"
Expand Down Expand Up @@ -51,7 +48,7 @@
<textarea
name="source"
class="modeleditor__source pat-code-editor"
data-pat-code-editor="language: xml; theme: okaidia">${view/modelSource}</textarea>
data-pat-code-editor="language: xml; theme: okaidia">${view/escaped_model_source}</textarea>

</form>
</div>
Expand Down