Products.CMFPlone/robodoc: Merge pull request #2098 from plone/midsummersprint_fix_tran

GitHub <jenkins-z4DKO/[email protected]> Fri, 28 Jul 2017 05:39:14 -0700 (PDT)
Newsgroups gmane.comp.web.zope.plone.cvs
Message-ID <[email protected]>
Repository: Products.CMFPlone
Branch: refs/heads/robodoc
Date: 2017-07-28T13:26:58+02:00
Author: agitator (agitator) <[email protected]>
Commit: https://github.com/plone/Products.CMFPlone/commit/fdb0ba5b58964df1ad27f203507afaa2af9010d1

Merge pull request #2098 from plone/midsummersprint_fix_transforms

refactor filter control panel and tests

Files changed:
M CHANGES.rst
M Products/CMFPlone/controlpanel/bbb/filter.py
M Products/CMFPlone/controlpanel/browser/filter.py
M Products/CMFPlone/controlpanel/tests/test_controlpanel_bbb_filter_adapter.py
M Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_filter.py
M Products/CMFPlone/controlpanel/tests/test_controlpanel_filter.py
M Products/CMFPlone/interfaces/controlpanel.py
M Products/CMFPlone/patterns/settings.py
M Products/CMFPlone/patterns/tinymce.py
M Products/CMFPlone/tests/robot/test_controlpanel_filter.robot
M Products/CMFPlone/tests/testPortalCreation.py
M Products/CMFPlone/tests/test_z3c_form_widgets.py
M setup.py

diff --git a/CHANGES.rst b/CHANGES.rst
index 6235df51a..26bbfb77c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -15,6 +15,9 @@ Breaking changes:
   Introduced PyScss as a drop in replacement that could also do more things.
   Discussion on that at https://github.com/plone/Products.CMFPlone/issues/1800
   [loechel]
+- Fix and migrate safe_html filter completly into Plone registry and sync settings with TinyMCE.
+  Also some unused options in controlpanel where removed, like stripped_combinations and class_blacklist.
+  [MrTango]
 
 New features:
 
diff --git a/Products/CMFPlone/controlpanel/bbb/filter.py b/Products/CMFPlone/controlpanel/bbb/filter.py
index a8ea0b1c6..ed40da042 100644
--- a/Products/CMFPlone/controlpanel/bbb/filter.py
+++ b/Products/CMFPlone/controlpanel/bbb/filter.py
@@ -28,49 +28,20 @@ def get_nasty_tags(self):
     def set_nasty_tags(self, value):
         self.settings.nasty_tags = value
 
-    def get_stripped_tags(self):
-        return self.settings.stripped_tags
+    def get_valid_tags(self):
+        return self.settings.valid_tags
 
-    def set_stripped_tags(self, value):
-        self.settings.stripped_tags = value
+    def set_valid_tags(self, value):
+        self.settings.valid_tags = value
 
-    def get_custom_tags(self):
-        return self.settings.custom_tags
+    def get_custom_attributes(self):
+        return self.settings.custom_attributes
 
-    def set_custom_tags(self, value):
-        self.settings.custom_tags = value
+    def set_custom_attributes(self, value):
+        self.settings.custom_attributes = value
 
-    def get_stripped_attributes(self):
-        return self.settings.stripped_attributes
-
-    def set_stripped_attributes(self, value):
-        self.settings.stripped_attributes = value
-
-    def get_stripped_combinations(self):
-        return self.settings.stripped_combinations
-
-    def set_stripped_combinations(self, value):
-        self.settings.stripped_combinations = value
-
-    def get_style_whitelist(self):
-        return self.settings.style_whitelist
-
-    def set_style_whitelist(self, value):
-        self.settings.style_whitelist = value
-
-    def get_class_blacklist(self):
-        return self.settings.class_blacklist
-
-    def set_class_blacklist(self, value):
-        self.settings.class_blacklist = value
-
-    class_blacklist = property(get_class_blacklist, set_class_blacklist)
-    style_whitelist = property(get_style_whitelist, set_style_whitelist)
-    stripped_combinations = property(get_stripped_combinations,
-                                     set_stripped_combinations)
-    stripped_attributes = property(get_stripped_attributes,
-                                   set_stripped_attributes)
-    custom_tags = property(get_custom_tags, set_custom_tags)
-    stripped_tags = property(get_stripped_tags, set_stripped_tags)
+    custom_attributes = property(
+        get_custom_attributes, set_custom_attributes)
+    valid_tags = property(get_valid_tags, set_valid_tags)
     nasty_tags = property(get_nasty_tags, set_nasty_tags)
     disable_filtering = property(get_disable_filtering, set_disable_filtering)
diff --git a/Products/CMFPlone/controlpanel/browser/filter.py b/Products/CMFPlone/controlpanel/browser/filter.py
index 7a479dae3..8c822d855 100644
--- a/Products/CMFPlone/controlpanel/browser/filter.py
+++ b/Products/CMFPlone/controlpanel/browser/filter.py
@@ -1,31 +1,23 @@
 # -*- coding: utf-8 -*-
-from Products.CMFCore.utils import getToolByName
+from plone.app.registry.browser import controlpanel
+from plone.z3cform import layout
 from Products.CMFPlone import PloneMessageFactory as _  # NOQA
 from Products.CMFPlone.interfaces import IFilterSchema
 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
 from Products.statusmessages.interfaces import IStatusMessage
-from plone.autoform.form import AutoExtensibleForm
-from plone.z3cform import layout
 from z3c.form import button
-from z3c.form import form
-from Products.PortalTransforms.transforms.safe_html import VALID_TAGS
-from Products.PortalTransforms.transforms.safe_html import NASTY_TAGS
 
 
-class FilterControlPanel(AutoExtensibleForm, form.EditForm):
+class FilterControlPanel(controlpanel.RegistryEditForm):
     id = "FilterControlPanel"
     label = _(u"HTML Filtering Settings")
-    description = ""
+    description = _("Keep in mind that editors like TinyMCE might have "
+                    "additional filters.")
     schema = IFilterSchema
+    schema_prefix = "plone"
     form_name = _(u"HTML Filtering Settings")
     control_panel_view = "filter-controlpanel"
 
-    def updateActions(self):  # NOQA
-        """Have to override this because we only have Save, not Cancel
-        """
-        super(FilterControlPanel, self).updateActions()
-        self.actions['save'].addClass("context")
-
     @button.buttonAndHandler(_(u"Save"), name='save')
     def handleSave(self, action):  # NOQA
         data, errors = self.extractData()
@@ -33,54 +25,6 @@ def handleSave(self, action):  # NOQA
             self.status = self.formErrorsMessage
             return
 
-        # Save in portal tools
-        safe_html = getattr(
-            getToolByName(self.context, 'portal_transforms'),
-            'safe_html',
-            None)
-
-        nasty_tags = data['nasty_tags']
-        custom_tags = data['custom_tags']
-        stripped_tags = data['stripped_tags']
-
-        valid = safe_html._config['valid_tags']
-
-        # start with removing tags that do not belong in valid tags
-        for value in nasty_tags + stripped_tags:
-            if value in valid:
-                del valid[value]
-        # add in custom tags
-        for custom in custom_tags:
-            if value not in valid:
-                valid[custom] = 1
-        # then, check if something was previously prevented but is no longer
-        for tag in set(VALID_TAGS.keys()) - set(valid.keys()):
-            if tag not in nasty_tags and tag not in stripped_tags:
-                valid[tag] = VALID_TAGS[tag]
-
-        # nasty tags are simple, just set the value here
-        nasty_value = {tag: NASTY_TAGS.get(tag, VALID_TAGS.get(tag, 1)) for tag in nasty_tags}
-        safe_html._config['nasty_tags'] = nasty_value
-
-        disable_filtering = int(data['disable_filtering'])
-        if disable_filtering != safe_html._config['disable_transform']:
-            safe_html._config['disable_transform'] = disable_filtering
-
-        for attr in ('stripped_combinations', 'class_blacklist',
-                     'stripped_attributes', 'style_whitelist'):
-            value = data[attr]
-            if value is None:
-                if attr == 'stripped_combinations':
-                    value = {}
-                else:
-                    value = []
-            if value != safe_html._config[attr]:
-                safe_html._config[attr] = value
-
-        # always reload the transform
-        safe_html._p_changed = True
-        safe_html.reload()
-
         self.applyChanges(data)
         IStatusMessage(self.request).addStatusMessage(
             _(u"Changes saved."),
@@ -92,6 +36,15 @@ def handleSave(self, action):  # NOQA
             "warning")
         self.request.response.redirect(self.request.getURL())
 
+    @button.buttonAndHandler(_(u"Cancel"), name='cancel')
+    def handleCancel(self, action):
+        IStatusMessage(self.request).addStatusMessage(
+            _(u"Changes canceled."),
+            "info")
+        self.request.response.redirect("%s/%s" % (
+            self.context.absolute_url(),
+            self.control_panel_view))
+
 
 class ControlPanelFormWrapper(layout.FormWrapper):
     """Use this form as the plone.z3cform layout wrapper to get the control
diff --git a/Products/CMFPlone/controlpanel/tests/test_controlpanel_bbb_filter_adapter.py b/Products/CMFPlone/controlpanel/tests/test_controlpanel_bbb_filter_adapter.py
index 45166579c..523d1271c 100644
--- a/Products/CMFPlone/controlpanel/tests/test_controlpanel_bbb_filter_adapter.py
+++ b/Products/CMFPlone/controlpanel/tests/test_controlpanel_bbb_filter_adapter.py
@@ -37,78 +37,32 @@ def test_set_nasty_tags(self):
             [u'foo', u'bar']
         )
 
-    def test_get_stripped_tags(self):
-        self.settings.stripped_tags = [u'foo', u'bar']
+    def test_get_valid_tags(self):
+        self.settings.valid_tags = [u'foo', u'bar']
         self.assertEquals(
-            getAdapter(self.portal, IFilterSchema).stripped_tags,
+            getAdapter(self.portal, IFilterSchema).valid_tags,
             [u'foo', u'bar']
         )
 
-    def test_set_stripped_tags(self):
-        getAdapter(self.portal, IFilterSchema).stripped_tags = [u'foo', u'bar']
+    def test_set_valid_tags(self):
+        getAdapter(self.portal, IFilterSchema).valid_tags = [u'foo', u'bar']
         self.assertEquals(
-            self.settings.stripped_tags,
+            self.settings.valid_tags,
             [u'foo', u'bar']
         )
 
-    def test_get_custom_tags(self):
-        self.settings.custom_tags = [u'foo', u'bar']
+    def test_get_custom_attributes(self):
+        self.settings.custom_attributes = [u'foo', u'bar']
         self.assertEquals(
-            getAdapter(self.portal, IFilterSchema).custom_tags,
+            getAdapter(self.portal, IFilterSchema).custom_attributes,
             [u'foo', u'bar']
         )
 
-    def test_set_custom_tags(self):
-        getAdapter(self.portal, IFilterSchema).custom_tags = [u'foo', u'bar']
-        self.assertEquals(
-            self.settings.custom_tags,
-            [u'foo', u'bar']
-        )
-
-    def test_get_stripped_attributes(self):
-        self.settings.stripped_attributes = [u'foo', u'bar']
-        self.assertEquals(
-            getAdapter(self.portal, IFilterSchema).stripped_attributes,
-            [u'foo', u'bar']
-        )
-
-    def test_set_stripped_attributes(self):
-        getAdapter(self.portal, IFilterSchema).stripped_attributes = [
-            u'foo', u'bar'
-        ]
-        self.assertEquals(
-            self.settings.stripped_attributes,
-            [u'foo', u'bar']
-        )
-
-    def test_get_style_whitelist(self):
-        self.settings.style_whitelist = [u'foo', u'bar']
-        self.assertEquals(
-            getAdapter(self.portal, IFilterSchema).style_whitelist,
-            [u'foo', u'bar']
-        )
-
-    def test_set_style_whitelist(self):
-        getAdapter(self.portal, IFilterSchema).style_whitelist = [
-            u'foo', u'bar'
-        ]
-        self.assertEquals(
-            self.settings.style_whitelist,
-            [u'foo', u'bar']
-        )
-
-    def test_get_class_blacklist(self):
-        self.settings.class_blacklist = [u'foo', u'bar']
-        self.assertEquals(
-            getAdapter(self.portal, IFilterSchema).class_blacklist,
-            [u'foo', u'bar']
-        )
-
-    def test_set_class_blacklist(self):
-        getAdapter(self.portal, IFilterSchema).class_blacklist = [
+    def test_set_custom_attributes(self):
+        getAdapter(self.portal, IFilterSchema).custom_attributes = [
             u'foo', u'bar'
         ]
         self.assertEquals(
-            self.settings.class_blacklist,
+            self.settings.custom_attributes,
             [u'foo', u'bar']
         )
diff --git a/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_filter.py b/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_filter.py
index 09bff81d0..5769032b9 100644
--- a/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_filter.py
+++ b/Products/CMFPlone/controlpanel/tests/test_controlpanel_browser_filter.py
@@ -1,11 +1,13 @@
 # -*- coding: utf-8 -*-
+from plone.app.testing import SITE_OWNER_NAME, SITE_OWNER_PASSWORD
+from plone.registry.interfaces import IRegistry
+from plone.testing.z2 import Browser
 from Products.CMFCore.utils import getToolByName
-# from Products.CMFPlone.interfaces import IFilterSchema
+from Products.CMFPlone.interfaces import IFilterSchema
 from Products.CMFPlone.testing import PRODUCTS_CMFPLONE_FUNCTIONAL_TESTING
 from Products.PortalTransforms.data import datastream
-from plone.app.testing import SITE_OWNER_NAME, SITE_OWNER_PASSWORD
-from plone.testing.z2 import Browser
 from zope.component import getMultiAdapter
+from zope.component import getUtility
 import unittest
 
 
@@ -21,6 +23,9 @@ def setUp(self):
         self.portal = self.layer['portal']
         self.request = self.layer['request']
         self.portal_url = self.portal.absolute_url()
+        registry = getUtility(IRegistry)
+        self.settings = registry.forInterface(
+            IFilterSchema, prefix="plone")
         self.browser = Browser(self.app)
         self.browser.handleErrors = False
         self.browser.addHeader(
@@ -64,7 +69,7 @@ def test_disable_filtering(self):
 
         # test that the transform is disabled
         self.assertEqual(
-            self.safe_html._config['disable_transform'],
+            self.settings.disable_filtering,
             1)
 
         # anything passes
@@ -81,49 +86,18 @@ def test_nasty_tags(self):
         self.browser.getControl(
             name='form.widgets.nasty_tags'
         ).value = 'div\r\na'
+        valid_tags = self.browser.getControl(
+            name='form.widgets.valid_tags').value
+        valid_tags = valid_tags.replace('a\r\n', '')
+        valid_tags = self.browser.getControl(
+            name='form.widgets.valid_tags').value = valid_tags
         self.browser.getControl('Save').click()
 
         # test that <a> is filtered
-        self.assertFalse(self.safe_html._config['disable_transform'])
+        self.assertFalse(self.settings.disable_filtering)
         good_html = '<p><a href="http://example.com">harmless link</a></p>'
         ds = datastream('dummy_name')
         self.assertEqual(
             str(self.safe_html.convert(good_html, ds)),
-            ''
+            '<p/>'
         )
-
-    @unittest.skip('This functionality was broken with formlib already. Needs fix.')  # noqa
-    def test_stripped_combinations(self):
-        # test a combination that isn't normally filtered
-        self.assertFalse(self.safe_html._config['disable_transform'])
-        html = '<p class="wow">lala</p>'
-        ds = datastream('dummy_name')
-        self.assertEqual(
-            str(self.safe_html.convert(html, ds)),
-            html)
-
-        # we can set stripped combinations
-        self.browser.open(
-            "%s/@@filter-controlpanel" % self.portal_url)
-        self.browser.getControl(
-            name='form.widgets.stripped_combinations.buttons.add').click()
-        self.browser.getControl(
-            name='form.widgets.stripped_combinations.key.0'
-        ).value = 'mytag1 p'
-        self.browser.getControl(
-            name='form.widgets.stripped_combinations.0'
-        ).value = 'myattr1 class'
-        self.browser.getControl('Save').click()
-
-        # stripped combinations are stored on the transform
-        self.assertIn(
-            'mytag1 p',
-            self.safe_html._config['stripped_combinations'])
-        self.assertEqual(
-            'myattr1 class',
-            self.safe_html._config['stripped_combinations']['mytag1 p'])
-
-        # test that combination is now filtered
-        self.assertEqual(
-            str(self.safe_html.convert(html, ds)),
-            '<p>lala</p>')
diff --git a/Products/CMFPlone/controlpanel/tests/test_controlpanel_filter.py b/Products/CMFPlone/controlpanel/tests/test_controlpanel_filter.py
index df8c35da6..fd5d2f016 100644
--- a/Products/CMFPlone/controlpanel/tests/test_controlpanel_filter.py
+++ b/Products/CMFPlone/controlpanel/tests/test_controlpanel_filter.py
@@ -40,20 +40,8 @@ def test_disable_filtering_setting(self):
     def test_nasty_tags_setting(self):
         self.assertTrue(hasattr(self.settings, 'nasty_tags'))
 
-    def test_stripped_tags_setting(self):
-        self.assertTrue(hasattr(self.settings, 'stripped_tags'))
+    def test_valid_tags_setting(self):
+        self.assertTrue(hasattr(self.settings, 'valid_tags'))
 
-    def test_custom_tags_setting(self):
-        self.assertTrue(hasattr(self.settings, 'custom_tags'))
-
-    def test_stripped_attributes_setting(self):
-        self.assertTrue(hasattr(self.settings, 'stripped_attributes'))
-
-    # def test_stripped_combinations_setting(self):
-    #     self.assertTrue(hasattr(self.settings, 'stripped_combinations'))
-
-    def test_style_whitelist_setting(self):
-        self.assertTrue(hasattr(self.settings, 'style_whitelist'))
-
-    def test_class_blacklist_setting(self):
-        self.assertTrue(hasattr(self.settings, 'class_blacklist'))
+    def test_custom_attributes_setting(self):
+        self.assertTrue(hasattr(self.settings, 'custom_attributes'))
diff --git a/Products/CMFPlone/interfaces/controlpanel.py b/Products/CMFPlone/interfaces/controlpanel.py
index e6526b644..58a811fed 100644
--- a/Products/CMFPlone/interfaces/controlpanel.py
+++ b/Products/CMFPlone/interfaces/controlpanel.py
@@ -355,75 +355,112 @@ class IFilterSchema(Interface):
     nasty_tags = schema.List(
         title=_(u'Nasty tags'),
         description=_(u'These tags and their content are completely blocked '
-                      'when a page is saved or rendered.'),
+                      'when a page is saved or rendered. They are only deleted'
+                      ' if they are not marked as valid_tags'),
         default=[u'style', u'object', u'embed', u'applet', u'script', u'meta'],
         value_type=schema.TextLine(),
         missing_value=[],
         required=False)
 
-    stripped_tags = schema.List(
-        title=_(u'Stripped tags'),
-        description=_(u'These tags are stripped when saving or rendering, '
-                      'but any content is preserved.'),
-        default=[u'font', ],
-        value_type=schema.TextLine(),
-        missing_value=[],
-        required=False)
-
-    custom_tags = schema.List(
-        title=_(u'Custom tags'),
-        description=_(u'Add tag names here for tags which are not part of '
-                      'XHTML but which should be permitted.'),
-        default=[],
-        value_type=schema.TextLine(),
-        missing_value=[],
-        required=False)
-
-    # class IFilterAttributesSchema(Interface):
-
-    stripped_attributes = schema.List(
-        title=_(u'Stripped attributes'),
-        description=_(u'These attributes are stripped from any tag when '
-                      'saving.'),
-        default=(u'dir lang valign halign border frame rules cellspacing '
-                 'cellpadding bgcolor').split(),
-        value_type=schema.TextLine(),
-        missing_value=[],
-        required=False)
-
-    stripped_combinations = schema.Dict(
-        title=_(u'Stripped combinations'),
-        description=_(u'These attributes are stripped from those tags when '
-                      'saving.'),
-        key_type=schema.TextLine(title=u'tags'),
-        value_type=schema.TextLine(title=u'attributes'),
-        default={'table th td': 'width height'},
-        missing_value={},
-        required=False)
-
-    # class IFilterEditorSchema(Interface):
-
-    style_whitelist = schema.List(
-        title=_(u'Permitted properties'),
-        description=_(
-            u'These CSS properties are allowed in style attributes.'),
+    valid_tags = schema.List(
+        title=_(u'Valid tags'),
+        description=_(u'A list of valid tags which will be not filtered out.'),
         default=[
-            u'text-align',
-            u'list-style-type',
-            u'float padding-left',
-            u'text-decoration'
+            u'a',
+            u'abbr',
+            u'acronym',
+            u'address',
+            u'article',
+            u'aside',
+            u'audio',
+            u'b',
+            u'bdo',
+            u'big',
+            u'blockquote',
+            u'body',
+            u'br',
+            u'canvas',
+            u'caption',
+            u'cite',
+            u'code',
+            u'col',
+            u'colgroup',
+            u'command',
+            u'datalist',
+            u'dd',
+            u'del',
+            u'details',
+            u'dfn',
+            u'dialog',
+            u'div',
+            u'dl',
+            u'dt',
+            u'em',
+            u'figure',
+            u'footer',
+            u'h1',
+            u'h2',
+            u'h3',
+            u'h4',
+            u'h5',
+            u'h6',
+            u'head',
+            u'header',
+            u'hgroup',
+            u'html',
+            u'i',
+            u'iframe',
+            u'img',
+            u'ins',
+            u'kbd',
+            u'keygen',
+            u'li',
+            u'map',
+            u'mark',
+            u'meter',
+            u'nav',
+            u'ol',
+            u'output',
+            u'p',
+            u'pre',
+            u'progress',
+            u'q',
+            u'rp',
+            u'rt',
+            u'ruby',
+            u'samp',
+            u'section',
+            u'small',
+            u'source',
+            u'span',
+            u'strong',
+            u'sub',
+            u'sup',
+            u'table',
+            u'tbody',
+            u'td',
+            u'tfoot',
+            u'th',
+            u'thead',
+            u'time',
+            u'title',
+            u'tr',
+            u'tt',
+            u'u',
+            u'ul',
+            u'var',
+            u'video',
         ],
         value_type=schema.TextLine(),
         missing_value=[],
         required=False)
 
-    class_blacklist = schema.List(
-        title=_(u'Filtered classes'),
-        description=_(u'These class names are not allowed in class '
-                      'attributes.'),
+    custom_attributes = schema.List(
+        title=_(u'Custom attributes'),
+        description=_(u'These attributes are additionally allowed.'),
         default=[],
-        missing_value=[],
         value_type=schema.TextLine(),
+        missing_value=[],
         required=False)
 
 
@@ -1271,8 +1308,8 @@ class ISiteSchema(Interface):
     default_page = schema.List(
         title=_(u'Default page IDs'),
         description=_(
-            u'Select which IDs (short names) can act as fallback default pages for',
-            u'a container.'),
+            u'Select which IDs (short names) can act as fallback '
+            u'default pages for a container.'),
         required=True,
         default=[
             u'index_html',
diff --git a/Products/CMFPlone/patterns/settings.py b/Products/CMFPlone/patterns/settings.py
index c568c07fc..5f3ae140f 100644
--- a/Products/CMFPlone/patterns/settings.py
+++ b/Products/CMFPlone/patterns/settings.py
@@ -3,7 +3,6 @@
 from Acquisition import aq_parent
 from borg.localrole.interfaces import IFactoryTempFolder
 from plone.app.content.browser.interfaces import IFolderContentsView
-from plone.app.layout.navigation.root import getNavigationRootObject
 from plone.app.widgets.utils import get_relateditems_options
 from plone.app.z3cform.utils import call_callables
 from plone.registry.interfaces import IRegistry
@@ -81,7 +80,9 @@ def image_scales(self):
             'plone.app.vocabularies.ImagesScales'
         )
         vocabulary = factory(self.context)
-        ret = [{'title': translate(it.title), 'value': it.value} for it in vocabulary]
+        ret = [{
+            'title': translate(it.title), 'value': it.value}
+            for it in vocabulary]
         ret = sorted(ret, key=lambda it: it['title'])
         return json.dumps(ret)
 
diff --git a/Products/CMFPlone/patterns/tinymce.py b/Products/CMFPlone/patterns/tinymce.py
index 5e9e5dc14..c7bdab7ba 100644
--- a/Products/CMFPlone/patterns/tinymce.py
+++ b/Products/CMFPlone/patterns/tinymce.py
@@ -1,8 +1,10 @@
 # -*- coding: utf-8 -*-
+from lxml import html
 from plone.app.layout.navigation.root import getNavigationRootObject
 from plone.app.theming.utils import theming_policy
 from plone.registry.interfaces import IRegistry
 from Products.CMFCore.utils import getToolByName
+from Products.CMFPlone.interfaces import IFilterSchema
 from Products.CMFPlone.interfaces import ITinyMCESchema
 from Products.CMFPlone.utils import get_portal
 from zope.component import getUtility
@@ -20,6 +22,11 @@ def __init__(self, context, request):
             prefix="plone",
             check=False
         )
+        self.filter_settings = getUtility(IRegistry).forInterface(
+            IFilterSchema,
+            prefix="plone",
+            check=False
+        )
         self.nav_root = getNavigationRootObject(
             self.context,
             get_portal(),
@@ -197,6 +204,25 @@ def get_tiny_config(self):
             except ValueError:
                 pass
 
+        # add safe_html settings, which are useed in backend for filtering:
+        if not self.filter_settings.disable_filtering:
+            valid_tags = self.filter_settings.valid_tags
+            nasty_tags = self.filter_settings.nasty_tags
+            custom_attributes = self.filter_settings.custom_attributes
+            safe_attributes = [attr.decode() for attr in html.defs.safe_attrs]
+            valid_attributes = safe_attributes + custom_attributes
+            # valid_elements : 'a[href|target=_blank],strong/b,div[align],br'
+            tiny_valid_elements = []
+            for tag in valid_tags:
+                tag_str = "%s[%s]" % (tag, "|".join(valid_attributes))
+                tiny_valid_elements.append(tag_str)
+            # We want to remove the nasty tag including the content in the
+            # backend, so TinyMCE should allow them here.
+            for tag in nasty_tags:
+                tag_str = "%s[%s]" % (tag, "|".join(valid_attributes))
+                tiny_valid_elements.append(tag_str)
+            tiny_config['valid_elements'] = ",".join(tiny_valid_elements)
+
         if settings.other_settings:
             try:
                 tiny_config.update(json.loads(settings.other_settings))
diff --git a/Products/CMFPlone/tests/robot/test_controlpanel_filter.robot b/Products/CMFPlone/tests/robot/test_controlpanel_filter.robot
index 53695d65b..26399060f 100644
--- a/Products/CMFPlone/tests/robot/test_controlpanel_filter.robot
+++ b/Products/CMFPlone/tests/robot/test_controlpanel_filter.robot
@@ -14,6 +14,7 @@ Resource  plone/app/robotframework/keywords.robot
 Resource  plone/app/robotframework/saucelabs.robot
 
 Library  Remote  ${PLONE_URL}/RobotRemote
+Library  Collections
 
 Resource  keywords.robot
 
@@ -26,48 +27,26 @@ Test Teardown  Run keywords  Plone Test Teardown
 Scenario: Configure Filter Control Panel to filter out nasty tags
   Given a logged-in site administrator
     and the filter control panel
-   When I add 'h1' to the nasty tags list
+   When I add 'h1' to the nasty tags list and remove it from the valid tags list
    Then the 'h1' tag is filtered out when a document is saved
 
 Scenario: Configure Filter Control Panel to strip out tags
   Given a logged-in site administrator
     and the filter control panel
-   When I add 'h1' to the stripped tags list
+   When I remove 'h1' from the valid tags list
    Then the 'h1' tag is stripped when a document is saved
 
 Scenario: Configure Filter Control Panel to allow custom tags
-  Pass Execution  This test currently fails because TinyMCE filters out the marquee tag and ignores the filter control panel settings.
   Given a logged-in site administrator
     and the filter control panel
-   When I add 'marquee' to the custom tags list
-   Then the 'marquee' tag is preserved when a document is saved
+   When I add 'foobar' to the valid tags list
+   Then the 'foobar' tag is preserved when a document is saved
 
-Scenario: Configure Filter Control Panel to strip out attributes
-  Pass Execution  Functionality is broken. Maybe even in Plone 4.3?
+Scenario: Configure Filter Control Panel to allow custom attributes
   Given a logged-in site administrator
     and the filter control panel
-   When I add 'data-stripme' to the stripped attributes list
-   Then the 'data-stripme' attribute is stripped when a document is saved
-
-Scenario: Configure Filter Control Panel to strip out combinations
-  Pass Execution  Functionality is broken. Maybe even in Plone 4.3?
-  Given a logged-in site administrator
-    and the filter control panel
-   When I add 'div h3' and 'data-foo' to the stripped out combinations
-   Then the 'data-foo' attribute of a 'div h3' combination is stripped when a document is saved
-
-Scenario: Configure Filter Control Panel to allow style attributes
-  Given a logged-in site administrator
-    and the filter control panel
-   When I add 'display' to the allowed style attributes
-   Then the 'display' style attribute is preserved when a document is saved
-
-Scenario: Configure Filter Control Panel to filter out classes
-  Pass Execution  Functionality is broken. Maybe even in Plone 4.3?
-  Given a logged-in site administrator
-    and the filter control panel
-   When I add 'foobar' to the filtered classes
-   Then the 'foobar' class is filtered out when a document is saved
+   When I add 'foo-foo' to the custom attributes list
+   Then the 'foo-foo' attribute is preserved when a document is saved
 
 Scenario: Filter Control Panel displays information regarding caching when saved
   Given a logged-in site administrator
@@ -93,39 +72,24 @@ Input RichText
 
 # --- WHEN -------------------------------------------------------------------
 
-I add '${tag}' to the nasty tags list
+I add '${tag}' to the nasty tags list and remove it from the valid tags list
   Input Text  name=form.widgets.nasty_tags  ${tag}
+  Remove line from textarea  form.widgets.valid_tags  ${tag}
   Click Button  Save
   Wait until page contains  Changes saved
 
-I add '${tag}' to the stripped tags list
-  Input Text  name=form.widgets.stripped_tags  ${tag}
-  Click Button  Save
-  Wait until page contains  Changes saved
-
-I add '${tag}' to the custom tags list
-  Input Text  name=form.widgets.custom_tags  ${tag}
-  Click Button  Save
-  Wait until page contains  Changes saved
-
-I add '${tag}' to the stripped attributes list
-  Input Text  name=form.widgets.stripped_attributes  ${tag}
+I remove '${tag}' from the valid tags list
+  Remove line from textarea  form.widgets.valid_tags  ${tag}
   Click Button  Save
   Wait until page contains  Changes saved
 
-I add '${tag}' to the filtered classes
-  Input Text  name=form.widgets.class_blacklist  ${tag}
+I add '${tag}' to the valid tags list
+  Input Text  name=form.widgets.valid_tags  ${tag}
   Click Button  Save
   Wait until page contains  Changes saved
 
-I add '${tag}' to the allowed style attributes
-  Input text  name=form.widgets.style_whitelist  ${tag}
-  Click Button  Save
-  Wait until page contains  Changes saved
-
-I add '${tags}' and '${attributes}' to the stripped out combinations
-  Input text  name=form.stripped_combinations.1.tags  ${tags}
-  Input text  name=form.stripped_combinations.1.attributes  ${attributes}
+I add '${tag}' to the custom attributes list
+  Input Text  name=form.widgets.custom_attributes  ${tag}
   Click Button  Save
   Wait until page contains  Changes saved
 
@@ -155,62 +119,23 @@ the 'h1' tag is stripped when a document is saved
   Page should contain  heading
   XPath Should Match X Times  //div[@id='content-core']//h1  0  message=h1 should have been stripped out
 
-the 'marquee' tag is preserved when a document is saved
-  ${doc1_uid}=  Create content  id=doc1  title=Document 1  type=Document
-  Go To  ${PLONE_URL}/doc1/edit
-  patterns are loaded
-  Input RichText  <marquee>lorem ipsum</marquee>
-  Click Button  Save
-  Wait until page contains  Changes saved
-  XPath Should Match X Times  //div[@id='content-core']//marquee  1  message=the marquee tag should have been preserved
-
-the 'data-stripme' attribute is stripped when a document is saved
-  ${doc1_uid}=  Create content  id=doc1  title=Document 1  type=Document
-  Go To  ${PLONE_URL}/doc1/edit
-  patterns are loaded
-  Input RichText  <h4 data-stripme="foo">lorem ipsum</h4>
-  Click Button  Save
-  Wait until page contains  Changes saved
-  Page should contain  lorem ipsum
-
-  XPath Should Match X Times  //*[@id='content-core']//h4  1  message=h4 tag should be present
-  XPath Should Match X Times  //*[@id='content-core']//h4[@data-stripme='foo']  0  message=data-stripme attribute should have been filtered out
-
-the 'foobar' class is filtered out when a document is saved
-  ${doc1_uid}=  Create content  id=doc1  title=Document 1  type=Document
-  Go To  ${PLONE_URL}/doc1/edit
-  patterns are loaded
-  Input RichText  <h4 class="foobar">lorem ipsum</h4>
-  Click Button  Save
-  Wait until page contains  Changes saved
-  Page should contain  lorem ipsum
-
-  XPath Should Match X Times  //*[@id='content-core']//h4  1  message=h4 tag should be present
-  XPath Should Match X Times  //*[@id='content-core']//h4[@class='foobar']  0  message=class foobar should have been filtered out
-
-the 'data-foo' attribute of a 'div h3' combination is stripped when a document is saved
+the '${tag}' tag is preserved when a document is saved
   ${doc1_uid}=  Create content  id=doc1  title=Document 1  type=Document
   Go To  ${PLONE_URL}/doc1/edit
   patterns are loaded
-  Input RichText  <div><h3 data-foo="50">lorem ipsum</h3></div>
+  Input RichText  <${tag}>lorem ipsum</${tag}>
   Click Button  Save
   Wait until page contains  Changes saved
-  Page should contain  lorem ipsum
-  XPath Should Match X Times  //*[@id='content-core']//div/h3  1  message=h4 tag should be present
-  XPath Should Match X Times  //*[@id='content-core']//div/h3[@data-foo]  0  message=the data-foo attribute class should have been filtered out
+  XPath Should Match X Times  //div[@id='content-core']//${tag}  1  message=the ${tag} tag should have been preserved
 
-
-the 'display' style attribute is preserved when a document is saved
+the '${attribute}' attribute is preserved when a document is saved
   ${doc1_uid}=  Create content  id=doc1  title=Document 1  type=Document
   Go To  ${PLONE_URL}/doc1/edit
   patterns are loaded
-  Input RichText  <h4 style="display: block">lorem ipsum</h4>
+  Input RichText  <span ${attribute}="foo">lorem ipsum</span>
   Click Button  Save
   Wait until page contains  Changes saved
-  Page should contain  lorem ipsum
-
-  XPath Should Match X Times  //*[@id='content-core']//h4  1  message=h4 tag should be present
-  XPath Should Match X Times  //*[@id='content-core']//h4[@style]  1  message=style attribute with display:block should be present
+  XPath Should Match X Times  //span[@${attribute}]  1  message=the ${attribute} tag should have been preserved
 
 success message should contain information regarding caching
   Element Should Contain  css=.portalMessage.warning  HTML generation is heavily cached across Plone. You may have to edit existing content or restart your server to see the changes.
diff --git a/Products/CMFPlone/tests/testPortalCreation.py b/Products/CMFPlone/tests/testPortalCreation.py
index be2a5dcca..9139f60a8 100644
--- a/Products/CMFPlone/tests/testPortalCreation.py
+++ b/Products/CMFPlone/tests/testPortalCreation.py
@@ -19,6 +19,7 @@
 from Products.CMFCore.utils import getToolByName
 from Products.CMFPlone import setuphandlers
 from Products.CMFPlone.factory import _DEFAULT_PROFILE
+from Products.CMFPlone.interfaces import IFilterSchema
 from Products.CMFPlone.interfaces import INavigationSchema
 from Products.CMFPlone.interfaces import ISearchSchema
 from Products.CMFPlone.UnicodeSplitter import Splitter, I18NNormalizer
@@ -186,7 +187,7 @@ def testNavTreeProperties(self):
         self.assertFalse(
             self.properties.navtree_properties.hasProperty('showAllParents'))
         self.assertFalse(
-            self.properties.navtree_properties.hasProperty('metaTypesNotToList'))
+            self.properties.navtree_properties.hasProperty('metaTypesNotToList'))  # noqa
         self.assertFalse(
             self.properties.navtree_properties.hasProperty('sortAttribute'))
         self.assertFalse(
@@ -558,7 +559,7 @@ def testObjectButtonActionsOnDefaultDocumentDoNotApplyToParent(self):
         urls = [a['url'] for a in buttons]
         for url in urls:
             self.assertFalse('index_html' not in url,
-                             'Action wrongly applied to parent object %s' % url)
+                             'Action wrongly applied to parent object %s' % url)  # noqa
 
     def testObjectButtonActionsPerformCorrectAction(self):
         # only a manager would have proper permissions
@@ -641,11 +642,14 @@ def testFolderHasAlbumView(self):
         self.assertTrue('atct_album_view' in self.types.Folder.view_methods)
 
     def testConfigurableSafeHtmlTransform(self):
+        registry = getUtility(IRegistry)
+        settings = registry.forInterface(
+            IFilterSchema, prefix="plone")
         # The safe_html transformation should be configurable
         try:
-            self.transforms.safe_html.get_parameter_value('disable_transform')
+            settings.disable_filtering
         except (AttributeError, KeyError):
-            self.fail('safe_html transformation not updated')
+            self.fail('Disabling of safe_html should be possible!')
 
     def testvcXMLRPCRemoved(self):
         # vcXMLRPC.js should no longer be registered
diff --git a/Products/CMFPlone/tests/test_z3c_form_widgets.py b/Products/CMFPlone/tests/test_z3c_form_widgets.py
index 2dd0e8669..32328fe3d 100644
--- a/Products/CMFPlone/tests/test_z3c_form_widgets.py
+++ b/Products/CMFPlone/tests/test_z3c_form_widgets.py
@@ -24,7 +24,7 @@ class FakeForm(object):
 class TestAttackVector(unittest.TestCase):
     layer = PRODUCTS_CMFPLONE_FUNCTIONAL_TESTING
     _widgets_to_test = WIDGETS_TO_TEST
-    _attack = '</textarea><script>alert("form.widgets.class_blacklist")</script>'  # noqa
+    _attack = '</textarea><script>alert("form.widgets")</script>'  # noqa
 
     def _terms(self):
         # For the SequenceWidget we need basic terms.
diff --git a/setup.py b/setup.py
index 2764017a2..1819071b8 100644
--- a/setup.py
+++ b/setup.py
@@ -37,6 +37,7 @@
             'lxml',
             'mock',
             'plone.app.robotframework>0.9.16',
+            'robotframework-debuglibrary',
             'plone.app.testing',
             'zope.globalrequest',
             'zope.testing',



------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot