Merge pull request #115 from plone/apply-hotfix-20161129-master

Apply hotfix 20161129 [master]
This commit is contained in:
Maurits van Rees 2017-01-16 16:39:32 +01:00 committed by GitHub
commit 31e7fab26c
17 changed files with 272 additions and 46 deletions

View File

@ -14,7 +14,8 @@ New features:
Bug fixes:
- *add item here*
- Make comment on private content not publicly available in search results.
Part of PloneHotfix20161129. [vangheem, maurits]
2.4.19 (2017-01-02)

View File

@ -125,7 +125,7 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
output.append('globally_enabled')
# Comment moderation
one_state_worklow_disabled = 'one_state_workflow' not in workflow_chain
one_state_worklow_disabled = 'comment_one_state_workflow' not in workflow_chain
comment_review_workflow_disabled = \
'comment_review_workflow' not in workflow_chain
if one_state_worklow_disabled and comment_review_workflow_disabled:
@ -174,7 +174,7 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
"""
wftool = getToolByName(self.context, 'portal_workflow', None)
workflow_chain = wftool.getChainForPortalType('Discussion Item')
one_state_workflow_enabled = 'one_state_workflow' in workflow_chain
one_state_workflow_enabled = 'comment_one_state_workflow' in workflow_chain
comment_review_workflow_enabled = \
'comment_review_workflow' in workflow_chain
if one_state_workflow_enabled or comment_review_workflow_enabled:
@ -199,7 +199,7 @@ def notify_configuration_changed(event):
else:
# Disable moderation workflow
wftool.setChainForPortalTypes(('Discussion Item',),
'one_state_workflow')
'comment_one_state_workflow')
if IConfigurationChangedEvent.providedBy(event):
# Types control panel setting changed
@ -209,7 +209,7 @@ def notify_configuration_changed(event):
workflow_chain = wftool.getChainForPortalType('Discussion Item')
if workflow_chain:
workflow = workflow_chain[0]
if workflow == 'one_state_workflow':
if workflow == 'comment_one_state_workflow':
settings.moderation_enabled = False
elif workflow == 'comment_review_workflow':
settings.moderation_enabled = True

View File

@ -40,26 +40,7 @@
provides="Products.GenericSetup.interfaces.EXTENSION"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
/>
<genericsetup:upgradeStep
title="edit comments and delete own comments"
description="reload registry config to enable new fields edit_comment_enabled and delete_own_comment_enabled"
source="100"
destination="101"
handler=".upgrades.update_registry"
sortkey="1"
profile="plone.app.discussion:default"
/>
<genericsetup:upgradeStep
title="delete comments and delete own comments"
description="reload rolemap config to enable new permissions 'Delete comments' and 'Delete own comments'"
source="101"
destination="102"
handler=".upgrades.update_rolemap"
sortkey="1"
profile="plone.app.discussion:default"
/>
<!-- For upgrade steps see upgrades.zcml. -->
<!-- Comments -->

View File

@ -1,6 +1,6 @@
<metadata>
<version>102</version>
<version>1000</version>
<dependencies>
<dependency>profile-plone.app.registry:default</dependency>
</dependencies>
</metadata>
</metadata>

View File

@ -1,9 +1,10 @@
<?xml version="1.0"?>
<object name="portal_workflow" meta_type="Plone Workflow Tool">
<object name="comment_review_workflow" meta_type="Workflow"/>
<object name="comment_one_state_workflow" meta_type="Workflow"/>
<bindings>
<type type_id="Discussion Item">
<bound-workflow workflow_id="one_state_workflow" />
<bound-workflow workflow_id="comment_one_state_workflow" />
</type>
</bindings>
</object>

View File

@ -0,0 +1,80 @@
<?xml version="1.0"?>
<dc-workflow workflow_id="comment_one_state_workflow" title="Single State Workflow" description="- Essentially a workflow with no transitions, but has a Published state, so portlets and applications that expect that state will continue to work." state_variable="review_state" initial_state="published" manager_bypass="False" >
<permission>Access contents information</permission>
<permission>Change portal events</permission>
<permission>Modify portal content</permission>
<permission>View</permission>
<state state_id="published" title="Published" >
<description>Visible to everyone, editable by the owner.</description>
<permission-map name="Access contents information" acquired="False">
<permission-role>Anonymous</permission-role>
</permission-map>
<permission-map name="Change portal events" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Editor</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Editor</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="True">
</permission-map>
</state>
<variable variable_id="action" for_catalog="False" for_status="True" update_always="True" >
<description>Previous transition</description>
<default>
<expression>transition/getId|nothing</expression>
</default>
<guard >
</guard>
</variable>
<variable variable_id="actor" for_catalog="False" for_status="True" update_always="True" >
<description>The ID of the user who performed the previous transition</description>
<default>
<expression>user/getId</expression>
</default>
<guard >
</guard>
</variable>
<variable variable_id="comments" for_catalog="False" for_status="True" update_always="True" >
<description>Comment about the last transition</description>
<default>
<expression>python:state_change.kwargs.get('comment', '')</expression>
</default>
<guard >
</guard>
</variable>
<variable variable_id="review_history" for_catalog="False" for_status="False" update_always="False" >
<description>Provides access to workflow history</description>
<default>
<expression>state_change/getHistory</expression>
</default>
<guard >
<guard-permission>Request review</guard-permission>
<guard-permission>Review portal content</guard-permission>
</guard>
</variable>
<variable variable_id="time" for_catalog="False" for_status="True" update_always="True" >
<description>When the previous transition was performed</description>
<default>
<expression>state_change/getDateTime</expression>
</default>
<guard >
</guard>
</variable>
</dc-workflow>

View File

@ -41,14 +41,12 @@
<description i18n:translate="">
Visible to everyone, non-editable.
</description>
<permission-map name="Access contents information" acquired="False">
<permission-role>Anonymous</permission-role>
<permission-map name="Access contents information" acquired="True">
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Manager</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Anonymous</permission-role>
<permission-map name="View" acquired="True">
</permission-map>
<permission-map name="Reply to item" acquired="True">
</permission-map>

View File

@ -59,6 +59,10 @@ class ConversationCatalogTest(unittest.TestCase):
def setUp(self):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
self.catalog = getToolByName(self.portal, 'portal_catalog')
conversation = IConversation(self.portal.doc1)
comment1 = createObject('plone.Comment')

View File

@ -27,6 +27,9 @@ class CommentTest(unittest.TestCase):
self.portal = self.layer['portal']
self.request = self.layer['request']
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.catalog = getToolByName(self.portal, 'portal_catalog')
self.document_brain = self.catalog.searchResults(
@ -351,6 +354,9 @@ class RepliesTest(unittest.TestCase):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
def test_add_comment(self):
# Add comments to a CommentReplies adapter

View File

@ -458,7 +458,7 @@ class TestCommentsViewlet(unittest.TestCase):
)
self.workflowTool = getToolByName(self.portal, 'portal_workflow')
self.workflowTool.setDefaultChain('one_state_workflow')
self.workflowTool.setDefaultChain('comment_one_state_workflow')
self.membershipTool = getToolByName(self.folder, 'portal_membership')
self.memberdata = self.portal.portal_memberdata

View File

@ -163,9 +163,9 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
the 'comment_moderation' setting in the discussion control panel
changes.
"""
# By default the one_state_workflow without moderation is enabled
# By default the comment_one_state_workflow without moderation is enabled
self.assertEqual(
('one_state_workflow',),
('comment_one_state_workflow',),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item'
)
@ -185,7 +185,7 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
# And back
self.settings.moderation_enabled = False
self.assertEqual(
('one_state_workflow',),
('comment_one_state_workflow',),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item'
)
@ -211,7 +211,7 @@ class ConfigurationChangedSubscriberTest(unittest.TestCase):
# Enable the 'comment_review_workflow' with moderation enabled
self.portal.portal_workflow.setChainForPortalTypes(
('Discussion Item',),
('one_state_workflow',)
('comment_one_state_workflow',)
)
self.settings.moderation_enabled = True

View File

@ -50,6 +50,9 @@ class ConversationTest(unittest.TestCase):
settings = registry.forInterface(IDiscussionSettings)
settings.globally_enabled = True
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
def test_add_comment(self):
# Create a conversation. In this case we doesn't assign it to an
# object, as we just want to check the Conversation object API.
@ -744,6 +747,9 @@ class RepliesTest(unittest.TestCase):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
def test_add_comment(self):
# Add comments to a ConversationReplies adapter

View File

@ -35,6 +35,9 @@ class ConversationIndexersTest(unittest.TestCase):
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
workflow = self.portal.portal_workflow
workflow.doActionFor(self.portal.doc1, 'publish')
# Create a conversation.
conversation = IConversation(self.portal.doc1)

View File

@ -46,9 +46,9 @@ class ModerationViewTest(unittest.TestCase):
# If workflow is not set, enabled must return False
self.wf_tool.setChainForPortalTypes(('Discussion Item',), ())
self.assertEqual(self.view.moderation_enabled(), False)
# The one_state_workflow does not have a 'pending' state
# The comment_one_state_workflow does not have a 'pending' state
self.wf_tool.setChainForPortalTypes(('Discussion Item',),
('one_state_workflow,'))
('comment_one_state_workflow,'))
self.assertEqual(self.view.moderation_enabled(), False)
# The comment_review_workflow does have a 'pending' state
self.wf_tool.setChainForPortalTypes(('Discussion Item',),

View File

@ -9,6 +9,7 @@ from plone.app.testing import login
from plone.app.testing import logout
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from plone.app.testing import TEST_USER_NAME
from Products.CMFCore.permissions import View
from Products.CMFCore.utils import _checkPermission as checkPerm
from zope.component import createObject
@ -35,7 +36,7 @@ class WorkflowSetupTest(unittest.TestCase):
def test_workflows_installed(self):
"""Make sure both comment workflows have been installed properly.
"""
self.assertTrue('one_state_workflow' in
self.assertTrue('comment_one_state_workflow' in
self.portal.portal_workflow.objectIds())
self.assertTrue('comment_review_workflow' in
self.portal.portal_workflow.objectIds())
@ -44,7 +45,7 @@ class WorkflowSetupTest(unittest.TestCase):
"""Make sure one_state_workflow is the default workflow.
"""
self.assertEqual(
('one_state_workflow',),
('comment_one_state_workflow',),
self.portal.portal_workflow.getChainForPortalType(
'Discussion Item'
)
@ -102,7 +103,7 @@ class PermissionsSetupTest(unittest.TestCase):
class CommentOneStateWorkflowTest(unittest.TestCase):
"""Test the one_state_workflow that ships with plone.app.discussion.
"""Test the comment_one_state_workflow that ships with plone.app.discussion.
"""
layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING
@ -114,8 +115,6 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
self.folder = self.portal['test-folder']
self.catalog = self.portal.portal_catalog
self.workflow = self.portal.portal_workflow
self.workflow.setChainForPortalTypes(['Document'],
'one_state_workflow')
self.folder.invokeFactory('Document', 'doc1')
self.doc = self.folder.doc1
@ -137,10 +136,10 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
self.portal.acl_users._doAddUser('reader', 'secret', ['Reader'], [])
def test_initial_workflow_state(self):
"""Make sure the initial workflow state of a comment is 'published'.
"""Make sure the initial workflow state of a comment is 'private'.
"""
self.assertEqual(self.workflow.getInfoFor(self.doc, 'review_state'),
'published')
'private')
def test_view_comments(self):
"""Make sure published comments can be viewed by everyone.
@ -149,6 +148,10 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
# self.login(default_user)
# self.assertTrue(checkPerm(View, self.doc))
# Member is allowed
login(self.portal, TEST_USER_NAME)
workflow = self.portal.portal_workflow
workflow.doActionFor(self.doc, 'publish')
login(self.portal, 'member')
self.assertTrue(checkPerm(View, self.comment))
# Reviewer is allowed
@ -164,6 +167,30 @@ class CommentOneStateWorkflowTest(unittest.TestCase):
login(self.portal, 'reader')
self.assertTrue(checkPerm(View, self.comment))
def test_comment_on_private_content_not_visible_to_world(self):
logout()
self.assertFalse(checkPerm(View, self.comment))
def test_migration(self):
from plone.app.discussion.upgrades import upgrade_comment_workflows
# Fake permission according to earlier one_comment_workflow.
self.comment._View_Permission = ('Anonymous',)
# Anonymous can see the comment.
logout()
self.assertTrue(checkPerm(View, self.comment))
# Run the upgrade.
login(self.portal, TEST_USER_NAME)
upgrade_comment_workflows(self.portal.portal_setup)
# The workflow chain is still what we want.
self.assertEqual(
self.portal.portal_workflow.getChainFor('Discussion Item'),
('comment_one_state_workflow',))
# A Manager can still see the comment.
self.assertTrue(checkPerm(View, self.comment))
# Anonymous cannot see the comment.
logout()
self.assertFalse(checkPerm(View, self.comment))
class CommentReviewWorkflowTest(unittest.TestCase):
"""Test the comment_review_workflow that ships with plone.app.discussion.
@ -269,3 +296,35 @@ class CommentReviewWorkflowTest(unittest.TestCase):
'review_state'
)
)
def test_publish_comment_on_private_content_not_visible_to_world(self):
logout()
self.assertFalse(checkPerm(View, self.comment))
# publish comment and check again
login(self.portal, TEST_USER_NAME)
workflow = self.portal.portal_workflow
workflow.doActionFor(self.comment, 'publish')
logout()
self.assertFalse(checkPerm(View, self.comment))
def test_migration(self):
from plone.app.discussion.upgrades import upgrade_comment_workflows
# Fake permission according to earlier comment_review_workflow.
self.comment._View_Permission = ('Anonymous',)
# Anonymous can see the comment.
logout()
self.assertTrue(checkPerm(View, self.comment))
# Run the upgrade.
login(self.portal, TEST_USER_NAME)
upgrade_comment_workflows(self.portal.portal_setup)
# The workflow chain is still what we want.
self.assertEqual(
self.portal.portal_workflow.getChainFor('Discussion Item'),
('comment_review_workflow',))
# A Manager can still see the comment.
self.assertTrue(checkPerm(View, self.comment))
# Anonymous cannot see the comment.
logout()
self.assertFalse(checkPerm(View, self.comment))

View File

@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.registry.interfaces import IRegistry
from Products.CMFCore.utils import getToolByName
from zope.component import getUtility
import logging
default_profile = 'profile-plone.app.discussion:default'
logger = logging.getLogger('plone.app.discussion')
def update_registry(context):
@ -14,3 +18,46 @@ def update_registry(context):
def update_rolemap(context):
context.runImportStepFromProfile(default_profile, 'rolemap')
def upgrade_comment_workflows(context):
# If the current comment workflow is the one_state_workflow, running our
# import step will change it to comment_one_state_workflow. This is good.
# If it was anything else, we should restore this. So get the original
# chain.
portal_type = 'Discussion Item'
wf_tool = getToolByName(context, 'portal_workflow')
orig_chain = list(wf_tool.getChainFor(portal_type))
# Run the workflow step. This sets the chain to
# comment_one_state_workflow.
context.runImportStepFromProfile(default_profile, 'workflow')
# Restore original workflow chain if needed.
old_workflow = 'one_state_workflow'
if old_workflow not in orig_chain:
# Restore the chain. Probably comment_review_workflow.
wf_tool.setChainForPortalTypes([portal_type], orig_chain)
elif len(orig_chain) > 1:
# This is strange, but I guess it could happen.
if old_workflow in orig_chain:
# Replace with new one.
idx = orig_chain.index(old_workflow)
orig_chain[idx] = 'comment_one_state_workflow'
# Restore the chain.
wf_tool.setChainForPortalTypes([portal_type], orig_chain)
new_chain = list(wf_tool.getChainFor(portal_type))
workflows = [wf_tool.getWorkflowById(wf_id)
for wf_id in new_chain]
# Now go over the comments, update their role mappings, and reindex the
# allowedRolesAndUsers index.
catalog = getToolByName(context, 'portal_catalog')
for brain in catalog.unrestrictedSearchResults(portal_type=portal_type):
try:
comment = brain.getObject()
for wf in workflows:
wf.updateRoleMappingsFor(comment)
comment.reindexObjectSecurity()
except (AttributeError, KeyError):
logger.info('Could not reindex comment %s' % brain.getURL())

View File

@ -11,4 +11,44 @@
handler=".upgrades.update_registry"
/>
<genericsetup:upgradeStep
title="edit comments and delete own comments"
description="reload registry config to enable new fields edit_comment_enabled and delete_own_comment_enabled"
source="100"
destination="101"
handler=".upgrades.update_registry"
sortkey="1"
profile="plone.app.discussion:default"
/>
<genericsetup:upgradeStep
title="delete comments and delete own comments"
description="reload rolemap config to enable new permissions 'Delete comments' and 'Delete own comments'"
source="101"
destination="102"
handler=".upgrades.update_rolemap"
sortkey="1"
profile="plone.app.discussion:default"
/>
<genericsetup:upgradeSteps
source="102"
destination="1000"
profile="plone.app.discussion:default">
<!-- Apply the update rolemap step again, to avoid missing it when
updating from plone.app.discussion 2.2.x. When originally
adding this step in the 2.3.x release, we should have made a
bigger metadata revision increase to leave some room for new
upgrade steps in 2.2.x. -->
<genericsetup:upgradeStep
title="delete comments and delete own comments"
description="reload rolemap config to enable new permissions 'Delete comments' and 'Delete own comments'"
handler=".upgrades.update_rolemap"
/>
<genericsetup:upgradeStep
title="Update plone.app.discussion workflows"
handler=".upgrades.upgrade_comment_workflows"
/>
</genericsetup:upgradeSteps>
</configure>