def test_add_comment(self):
        # Add comments to a CommentReplies adapter

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        # Add a comment to the conversation
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        # Add a reply to the CommentReplies adapter of the first comment
        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'

        replies = IReplies(comment)

        new_re_id = replies.addComment(re_comment)

        # check that replies provides the IReplies interface
        self.assertTrue(IReplies.providedBy(replies))

        # Make sure our comment was added
        self.assertTrue(new_re_id in replies)

        # Make sure it is also reflected in the conversation
        self.assertTrue(new_re_id in conversation)

        # Make sure the conversation has the correct comment id
        self.assertEqual(conversation[new_re_id].comment_id, new_re_id)
Example #2
0
    def setUp(self):
        # Setup sandbox
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])

        self.document = self.portal['doc1']
        conversation = IConversation(self.document)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'This is a comment'
        new_id = replies.addComment(comment)
        comment = self.document.restrictedTraverse(
            '++conversation++default/{0}'.format(new_id)
        )

        re_comment = createObject('plone.Comment')
        re_comment.text = 'This is a reply'
        re_comment.author_username = '******'
        re_comment.author_name = 'Juliana'
        re_comment.author_email = '*****@*****.**'

        replies = IReplies(comment)
        replies.addComment(re_comment)
Example #3
0
    def handleComment(self, action):
        context = aq_inner(self.context)

        # Check if conversation is enabled on this content object
        if not self.__parent__.restrictedTraverse(
                '@@conversation_view').enabled():
            raise Unauthorized(
                'Discussion is not enabled for this content object.')

        # Validation form
        data, errors = self.extractData()
        if errors:
            return

        # Validate Captcha
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        portal_membership = getToolByName(self.context, 'portal_membership')
        captcha_enabled = settings.captcha != 'disabled'
        anonymous_comments = settings.anonymous_comments
        anon = portal_membership.isAnonymousUser()
        if captcha_enabled and anonymous_comments and anon:
            if 'captcha' not in data:
                data['captcha'] = u''
            captcha = CaptchaValidator(self.context, self.request, None,
                                       ICaptcha['captcha'], None)
            captcha.validate(data['captcha'])

        # Create comment
        comment = self.create_comment(data)

        # Add comment to conversation
        conversation = IConversation(self.__parent__)
        if data['in_reply_to']:
            # Add a reply to an existing comment
            conversation_to_reply_to = conversation.get(data['in_reply_to'])
            replies = IReplies(conversation_to_reply_to)
            comment_id = replies.addComment(comment)
        else:
            # Add a comment to the conversation
            comment_id = conversation.addComment(comment)

        # Redirect after form submit:
        # If a user posts a comment and moderation is enabled, a message is
        # shown to the user that his/her comment awaits moderation. If the user
        # has 'review comments' permission, he/she is redirected directly
        # to the comment.
        can_review = getSecurityManager().checkPermission(
            'Review comments', context)
        workflowTool = getToolByName(context, 'portal_workflow')
        comment_review_state = workflowTool.getInfoFor(comment, 'review_state',
                                                       None)
        if comment_review_state == 'pending' and not can_review:
            # Show info message when comment moderation is enabled
            IStatusMessage(self.context.REQUEST).addStatusMessage(
                _('Your comment awaits moderator approval.'), type='info')
            self.request.response.redirect(self.action)
        else:
            # Redirect to comment (inside a content object page)
            self.request.response.redirect(self.action + '#' + str(comment_id))
Example #4
0
    def test_delete_comment(self):
        # Add and remove a comment to a CommentReplies adapter

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        # Add a comment to the conversation
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_id)
        )

        # Add a reply to the CommentReplies adapter of the first comment
        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'

        replies = IReplies(comment)

        new_re_id = replies.addComment(re_comment)

        # Remove the reply to the CommentReplies adapter
        del replies[new_re_id]

        # Make sure there is no comment left in CommentReplies
        self.assertEqual(len(replies), 0)

        # Make sure the first comment is still in the conversation
        self.assertEqual(conversation.total_comments(), 1)
Example #5
0
    def setUp(self):
        self.app = self.layer["app"]
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.portal_url = self.portal.absolute_url()

        # Allow discussion
        registry = getUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.globally_enabled = True
        settings.edit_comment_enabled = True
        settings.delete_own_comment_enabled = True

        # doc with comments
        self.doc = api.content.create(
            container=self.portal,
            type="Document",
            id="doc_with_comments",
            title="Document with comments",
            allow_discussion=True,
        )
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Comment"
        self.comment = self.replies[self.replies.addComment(comment)]

        comment = createObject("plone.Comment")
        comment.text = "Comment 2"
        self.replies.addComment(comment)
Example #6
0
    def setUp(self):
        # Setup session manager
        ztc.utils.setupCoreSessions(self.layer['app'])

        # Setup sandbox
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        name = self.portal.invokeFactory(
            id='doc1',
            title='Document 1',
            type_name='Document')

        self.document = self.portal[name]
        conversation = IConversation(self.document)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'This is a comment'
        new_id = replies.addComment(comment)
        comment = self.document.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        re_comment = createObject('plone.Comment')
        re_comment.text = 'This is a reply'
        re_comment.author_username = "******"
        re_comment.author_name = "Juliana"
        re_comment.author_email = "*****@*****.**"

        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)
Example #7
0
    def test_comment_with_no_author_image(self):
        setRoles(self.portal, TEST_USER_ID, ["Manager"])
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Hey ho, let's go!"
        comment.author_username = TEST_USER_ID
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)
        self.assertEqual(
            None,
            serializer().get("author_image"),
        )
Example #8
0
    def test_comment_with_mimetype_text_plain(self):
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Hey, I am plain text!"
        comment.mime_type = "text/plain"
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        # serializer should return HTML with a clickable link
        self.assertEqual(
            "Hey, I am plain text!",
            serializer()["text"]["data"],
        )
        # serializer should return mimetype = text/x-web-intelligent
        self.assertEqual("text/plain", serializer()["text"]["mime-type"])
Example #9
0
    def test_delete_recursive(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.
        conversation = IConversation(self.portal.doc1)

        IReplies(conversation)

        # Create a nested comment structure:
        #
        # Conversation
        # +- Comment 1
        #    +- Comment 1_1
        #    |  +- Comment 1_1_1
        #    +- Comment 1_2
        # +- Comment 2
        #    +- Comment 2_1

        # Create all comments
        comment1 = createObject('plone.Comment')
        comment1.text = 'Comment text'

        comment1_1 = createObject('plone.Comment')
        comment1_1.text = 'Comment text'

        comment1_1_1 = createObject('plone.Comment')
        comment1_1_1.text = 'Comment text'

        comment1_2 = createObject('plone.Comment')
        comment1_2.text = 'Comment text'

        comment2 = createObject('plone.Comment')
        comment2.text = 'Comment text'

        comment2_1 = createObject('plone.Comment')
        comment2_1.text = 'Comment text'

        # Create the nested comment structure
        new_id_1 = conversation.addComment(comment1)
        new_id_2 = conversation.addComment(comment2)

        comment1_1.in_reply_to = new_id_1
        new_id_1_1 = conversation.addComment(comment1_1)

        comment1_1_1.in_reply_to = new_id_1_1
        conversation.addComment(comment1_1_1)

        comment1_2.in_reply_to = new_id_1
        conversation.addComment(comment1_2)

        comment2_1.in_reply_to = new_id_2
        new_id_2_1 = conversation.addComment(comment2_1)

        del conversation[new_id_1]

        self.assertEqual(
            [{'comment': comment2,     'depth': 0, 'id': new_id_2},
             {'comment': comment2_1,   'depth': 1, 'id': new_id_2_1},
            ], list(conversation.getThreads()))
    def test_add_comment(self):
        # Add comments to a CommentReplies adapter

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        # Add a comment to the conversation
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_id), )

        # Add a reply to the CommentReplies adapter of the first comment
        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'

        replies = IReplies(comment)

        new_re_id = replies.addComment(re_comment)

        # check that replies provides the IReplies interface
        self.assertTrue(IReplies.providedBy(replies))

        # Make sure our comment was added
        self.assertTrue(new_re_id in replies)

        # Make sure it is also reflected in the conversation
        self.assertTrue(new_re_id in conversation)

        # Make sure the conversation has the correct comment id
        self.assertEqual(conversation[new_re_id].comment_id, new_re_id)
Example #11
0
    def setUp(self):
        # Setup sandbox
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])

        self.document = self.portal['doc1']
        conversation = IConversation(self.document)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'This is a comment'
        new_id = replies.addComment(comment)
        comment = self.document.restrictedTraverse(
            '++conversation++default/{0}'.format(new_id)
        )

        re_comment = createObject('plone.Comment')
        re_comment.text = 'This is a reply'
        re_comment.author_username = '******'
        re_comment.author_name = 'Juliana'
        re_comment.author_email = '*****@*****.**'

        replies = IReplies(comment)
        replies.addComment(re_comment)
Example #12
0
    def test_comment_with_author_image(self):
        setRoles(self.portal, TEST_USER_ID, ["Manager"])
        # set member portrait
        membertool = getToolByName(self.portal, "portal_memberdata")
        membertool._setPortrait(
            Image(id=TEST_USER_ID, file=dummy.File(), title=""), TEST_USER_ID)
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Hey ho, let's go!"
        comment.author_username = TEST_USER_ID
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request),
                                     ISerializeToJson)
        self.assertEqual(
            f"{self.portal_url}/portal_memberdata/portraits/test_user_1_",
            serializer().get("author_image"),
        )
Example #13
0
    def test_removedEvent(self):
        self.assertFalse(self.registry.replyRemoved)

        conversation = IConversation(self.portal.doc1)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'
        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)

        del replies[new_re_id]
        self.assertTrue(self.registry.replyRemoved)
Example #14
0
    def test_addEvent(self):
        self.assertFalse(self.registry.replyAdded)

        conversation = IConversation(self.document)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.document.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'

        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)

        self.assertTrue(self.registry.replyAdded)
    def setUp(self):
        # Setup sandbox
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        name = self.portal.invokeFactory(id='doc1',
                                         title='Document 1',
                                         type_name='Document')

        self.document = self.portal[name]
        conversation = IConversation(self.document)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'This is a comment'
        new_id = replies.addComment(comment)
        comment = self.document.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        re_comment = createObject('plone.Comment')
        re_comment.text = 'This is a reply'
        re_comment.author_username = "******"
        re_comment.author_name = "Juliana"
        re_comment.author_email = "*****@*****.**"

        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)
    def test_traversal(self):
        # Create a nested structure of comment replies and check the traversal

        # make sure comments are traversable, have an id, absolute_url and
        # physical path
        conversation = IConversation(self.portal.doc1)

        comment1 = createObject('plone.Comment')
        comment1.text = 'Comment text'

        conversation.addComment(comment1)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = conversation.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        # Add a reply to the CommentReplies adapter of the first comment
        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'
        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)
        re_comment = self.portal.doc1.restrictedTraverse(
                '++conversation++default/%s' % new_re_id)

        # Add a reply to the reply
        re_re_comment = createObject('plone.Comment')
        re_re_comment.text = 'Comment text'
        replies = IReplies(re_comment)
        new_re_re_id = replies.addComment(re_re_comment)
        re_re_comment = self.portal.doc1.restrictedTraverse(
                '++conversation++default/%s' % new_re_re_id)

        # Add a reply to the replies reply
        re_re_re_comment = createObject('plone.Comment')
        re_re_re_comment.text = 'Comment text'
        replies = IReplies(re_re_comment)
        new_re_re_re_id = replies.addComment(re_re_re_comment)
        re_re_re_comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_re_re_re_id)

        self.assertEqual(('', 'plone', 'doc1', '++conversation++default',
                           str(new_id)), comment.getPhysicalPath())
        self.assertEqual('http://nohost/plone/doc1/++conversation++default/' +
                          str(new_id), comment.absolute_url())
        self.assertEqual(('', 'plone', 'doc1', '++conversation++default',
                           str(new_re_id)), re_comment.getPhysicalPath())
        self.assertEqual('http://nohost/plone/doc1/++conversation++default/' +
                          str(new_re_id), re_comment.absolute_url())
        self.assertEqual(('', 'plone', 'doc1', '++conversation++default',
                           str(new_re_re_id)), re_re_comment.getPhysicalPath())
        self.assertEqual('http://nohost/plone/doc1/++conversation++default/' +
                          str(new_re_re_id), re_re_comment.absolute_url())
        self.assertEqual(('', 'plone', 'doc1', '++conversation++default',
                           str(new_re_re_re_id)),
                           re_re_re_comment.getPhysicalPath())
        self.assertEqual('http://nohost/plone/doc1/++conversation++default/' +
                          str(new_re_re_re_id),
                          re_re_re_comment.absolute_url())
    def test_delete_comment(self):
        # Add and remove a comment to a CommentReplies adapter

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        # Add a comment to the conversation
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_id), )

        # Add a reply to the CommentReplies adapter of the first comment
        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'

        replies = IReplies(comment)

        new_re_id = replies.addComment(re_comment)

        # Remove the reply to the CommentReplies adapter
        del replies[new_re_id]

        # Make sure there is no comment left in CommentReplies
        self.assertEqual(len(replies), 0)

        # Make sure the first comment is still in the conversation
        self.assertEqual(conversation.total_comments(), 1)
Example #18
0
    def test_comment_with_mimetype_html(self):
        # Set text transform to text/html
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.text_transform = "text/html"

        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Go to <a href='https://www.plone.org'>Plone</a>"
        comment.mime_type = "text/html"
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        # serializer should return HTML
        self.assertEqual(
            'Go to <a href="https://www.plone.org">Plone</a>',
            serializer()["text"]["data"],
        )
        # serializer should return mimetype = text/html
        self.assertEqual("text/html", serializer()["text"]["mime-type"])
    def test_add_comment(self):
        # Add comments to a ConversationReplies adapter

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'

        new_id = replies.addComment(comment)

        # check that replies provides the IReplies interface
        self.assertTrue(IReplies.providedBy(replies))

        # Make sure our comment was added
        self.assertTrue(new_id in replies)

        # Make sure it is also reflected in the conversation
        self.assertTrue(new_id in conversation)

        self.assertEqual(conversation[new_id].comment_id, new_id)
    def test_delete_comment(self):
        # Create and remove a comment and check if the replies adapter
        # has been updated accordingly

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        replies = IReplies(conversation)

        # Add a comment.
        comment = createObject('plone.Comment')
        comment.text = 'Comment text'

        new_id = replies.addComment(comment)

        # make sure the comment has been added
        self.assertEqual(len(replies), 1)

        # delete the comment we just created
        del replies[new_id]

        # make sure there is no comment left in the conversation
        self.assertEqual(len(replies), 0)
Example #21
0
    def test_delete_comment(self):
        # Create and remove a comment and check if the replies adapter
        # has been updated accordingly

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        replies = IReplies(conversation)

        # Add a comment.
        comment = createObject('plone.Comment')
        comment.text = 'Comment text'

        new_id = replies.addComment(comment)

        # make sure the comment has been added
        self.assertEqual(len(replies), 1)

        # delete the comment we just created
        del replies[new_id]

        # make sure there is no comment left in the conversation
        self.assertEqual(len(replies), 0)
Example #22
0
    def test_comment_with_mimetype_intelligent_text(self):
        # Set text transform to intelligent text
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.text_transform = "text/x-web-intelligent"

        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Go to https://www.plone.org"
        comment.mime_type = "text/x-web-intelligent"
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request),
                                     ISerializeToJson)

        # serializer should return HTML with a clickable link
        self.assertEqual(
            'Go to <a href="https://www.plone.org" ' +
            'rel="nofollow">https://www.plone.org</a>',
            normalize_html(serializer()["text"]["data"]),
        )
        # serializer should return mimetype = text/html
        self.assertEqual("text/html", serializer()["text"]["mime-type"])
Example #23
0
    def test_add_comment(self):
        # Add comments to a ConversationReplies adapter

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'

        new_id = replies.addComment(comment)

        # check that replies provides the IReplies interface
        self.assertTrue(IReplies.providedBy(replies))

        # Make sure our comment was added
        self.assertTrue(new_id in replies)

        # Make sure it is also reflected in the conversation
        self.assertTrue(new_id in conversation)

        self.assertEqual(conversation[new_id].comment_id, new_id)
Example #24
0
    def create_comments(self, document):
        document.allow_discussion = True

        conversation = IConversation(document)
        replies = IReplies(conversation)
        comments = []
        for x in range(1, 2):
            comment = createObject("plone.Comment")
            comment.text = "Comment %d" % x
            comment = replies[replies.addComment(comment)]

            comment_replies = IReplies(comment)
            for y in range(1, 2):
                comment = createObject("plone.Comment")
                comment.text = "Comment %d.%d" % (x, y)
                comment_replies.addComment(comment)
                comments.append(comment)

        return comments
    def test_addEvent(self):
        self.assertFalse(self.registry.replyAdded)

        conversation = IConversation(self.document)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.document.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'

        replies = IReplies(comment)
        replies.addComment(re_comment)

        self.assertTrue(self.registry.replyAdded)
    def test_removedEvent(self):
        self.assertFalse(self.registry.replyRemoved)

        conversation = IConversation(self.portal.doc1)
        replies = IReplies(conversation)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = replies.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id)

        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'
        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)

        del replies[new_re_id]
        self.assertTrue(self.registry.replyRemoved)
Example #27
0
    def test_dict_api(self):
        # This test is for the ConversationReplies as well as the
        # CommentReplies adapter.
        #
        # Ensure all operations use only top-level comments. Add some
        # deeper children and ensure that these are not exposed through the
        # IReplies dict.

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        replies = IReplies(conversation)

        # Create a nested comment structure:
        #
        # Conversation
        # +- Comment 1
        #    +- Comment 1_1
        #    |  +- Comment 1_1_1
        #    +- Comment 1_2
        # +- Comment 2
        #    +- Comment 2_1

        # Create all comments
        comment1 = createObject('plone.Comment')
        comment1.text = 'Comment text'

        comment1_1 = createObject('plone.Comment')
        comment1_1.text = 'Comment text'

        comment1_1_1 = createObject('plone.Comment')
        comment1_1_1.text = 'Comment text'

        comment1_2 = createObject('plone.Comment')
        comment1_2.text = 'Comment text'

        comment2 = createObject('plone.Comment')
        comment2.text = 'Comment text'

        comment2_1 = createObject('plone.Comment')
        comment2_1.text = 'Comment text'

        # Create the nested comment structure
        new_id_1 = replies.addComment(comment1)
        comment1 = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id_1)
        replies_to_comment1 = IReplies(comment1)
        new_id_2 = replies.addComment(comment2)
        comment2 = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id_2)
        replies_to_comment2 = IReplies(comment2)

        new_id_1_1 = replies_to_comment1.addComment(comment1_1)
        comment1_1 = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id_1_1)
        replies_to_comment1_1 = IReplies(comment1_1)
        replies_to_comment1_1.addComment(comment1_1_1)

        replies_to_comment1.addComment(comment1_2)

        replies_to_comment2.addComment(comment2_1)

        # check that replies only contain the direct comments
        # and no comments deeper than 1
        self.assertEqual(conversation.total_comments, 6)
        self.assertEqual(len(replies), 2)
        self.assertEqual(len(replies_to_comment1), 2)
        self.assertEqual(len(replies_to_comment1_1), 1)
        self.assertEqual(len(replies_to_comment2), 1)
    def handleComment(self, action):
        context = aq_inner(self.context)

        # Check if conversation is enabled on this content object
        if not self.__parent__.restrictedTraverse(
            '@@conversation_view',
        ).enabled():
            raise Unauthorized(
                'Discussion is not enabled for this content object.',
            )

        # Validation form
        data, errors = self.extractData()
        if errors:
            return

        # Validate Captcha
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        portal_membership = getToolByName(self.context, 'portal_membership')
        captcha_enabled = settings.captcha != 'disabled'
        anonymous_comments = settings.anonymous_comments
        anon = portal_membership.isAnonymousUser()
        if captcha_enabled and anonymous_comments and anon:
            if 'captcha' not in data:
                data['captcha'] = u''
            captcha = CaptchaValidator(self.context,
                                       self.request,
                                       None,
                                       ICaptcha['captcha'],
                                       None)
            captcha.validate(data['captcha'])

        # Create comment
        comment = self.create_comment(data)

        # Add comment to conversation
        conversation = IConversation(self.__parent__)
        if data['in_reply_to']:
            # Add a reply to an existing comment
            conversation_to_reply_to = conversation.get(data['in_reply_to'])
            replies = IReplies(conversation_to_reply_to)
            comment_id = replies.addComment(comment)
        else:
            # Add a comment to the conversation
            comment_id = conversation.addComment(comment)

        # Redirect after form submit:
        # If a user posts a comment and moderation is enabled, a message is
        # shown to the user that his/her comment awaits moderation. If the user
        # has 'review comments' permission, he/she is redirected directly
        # to the comment.
        can_review = getSecurityManager().checkPermission('Review comments',
                                                          context)
        workflowTool = getToolByName(context, 'portal_workflow')
        comment_review_state = workflowTool.getInfoFor(
            comment,
            'review_state',
            None,
        )
        if comment_review_state == 'pending' and not can_review:
            # Show info message when comment moderation is enabled
            IStatusMessage(self.context.REQUEST).addStatusMessage(
                _('Your comment awaits moderator approval.'),
                type='info')
            self.request.response.redirect(self.action)
        else:
            # Redirect to comment (inside a content object page)
            self.request.response.redirect(self.action + '#' + str(comment_id))
Example #29
0
class TestCommentsSerializers(TestCase):
    layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING

    def setUp(self):
        self.app = self.layer["app"]
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.portal_url = self.portal.absolute_url()

        # Allow discussion
        registry = getUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.globally_enabled = True
        settings.edit_comment_enabled = True
        settings.delete_own_comment_enabled = True

        # doc with comments
        self.doc = api.content.create(
            container=self.portal,
            type="Document",
            id="doc_with_comments",
            title="Document with comments",
            allow_discussion=True,
        )
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Comment"
        self.comment = self.replies[self.replies.addComment(comment)]

        comment = createObject("plone.Comment")
        comment.text = "Comment 2"
        self.replies.addComment(comment)

    def test_conversation(self):
        serializer = getMultiAdapter(
            (self.conversation, self.request), ISerializeToJson
        )

        output = serializer()
        self.assertEqual(set(output), set(["@id", "items_total", "items"]))

    def test_conversation_batched(self):
        self.request.form["b_size"] = 1
        serializer = getMultiAdapter(
            (self.conversation, self.request), ISerializeToJson
        )

        output = serializer()
        self.assertIn("batching", output)

    def test_comment(self):
        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        output = serializer()

        expected = [
            "@id",
            "@type",
            "@parent",
            "comment_id",
            "in_reply_to",
            "text",
            "user_notification",
            "author_username",
            "author_name",
            "creation_date",
            "modification_date",
            "is_editable",
            "is_deletable",
        ]
        self.assertEqual(set(output), set(expected))

        self.assertEqual(set(output["text"]), set(["data", "mime-type"]))
    def test_dict_api(self):
        # This test is for the ConversationReplies as well as the
        # CommentReplies adapter.
        #
        # Ensure all operations use only top-level comments. Add some
        # deeper children and ensure that these are not exposed through the
        # IReplies dict.

        # Create a conversation. In this case we doesn't assign it to an
        # object, as we just want to check the Conversation object API.
        conversation = IConversation(self.portal.doc1)

        replies = IReplies(conversation)

        # Create a nested comment structure:
        #
        # Conversation
        # +- Comment 1
        #    +- Comment 1_1
        #    |  +- Comment 1_1_1
        #    +- Comment 1_2
        # +- Comment 2
        #    +- Comment 2_1

        # Create all comments
        comment1 = createObject('plone.Comment')
        comment1.text = 'Comment text'

        comment1_1 = createObject('plone.Comment')
        comment1_1.text = 'Comment text'

        comment1_1_1 = createObject('plone.Comment')
        comment1_1_1.text = 'Comment text'

        comment1_2 = createObject('plone.Comment')
        comment1_2.text = 'Comment text'

        comment2 = createObject('plone.Comment')
        comment2.text = 'Comment text'

        comment2_1 = createObject('plone.Comment')
        comment2_1.text = 'Comment text'

        # Create the nested comment structure
        new_id_1 = replies.addComment(comment1)
        comment1 = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id_1)
        replies_to_comment1 = IReplies(comment1)
        new_id_2 = replies.addComment(comment2)
        comment2 = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id_2)
        replies_to_comment2 = IReplies(comment2)

        new_id_1_1 = replies_to_comment1.addComment(comment1_1)
        comment1_1 = self.portal.doc1.restrictedTraverse(
            '++conversation++default/%s' % new_id_1_1)
        replies_to_comment1_1 = IReplies(comment1_1)
        replies_to_comment1_1.addComment(comment1_1_1)

        replies_to_comment1.addComment(comment1_2)

        replies_to_comment2.addComment(comment2_1)

        # check that replies only contain the direct comments
        # and no comments deeper than 1
        self.assertEqual(conversation.total_comments, 6)
        self.assertEqual(len(replies), 2)
        self.assertEqual(len(replies_to_comment1), 2)
        self.assertEqual(len(replies_to_comment1_1), 1)
        self.assertEqual(len(replies_to_comment2), 1)
class TestCommentsSerializers(TestCase):
    layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING

    def setUp(self):
        self.app = self.layer['app']
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        self.portal_url = self.portal.absolute_url()

        # Allow discussion
        registry = getUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.globally_enabled = True
        settings.edit_comment_enabled = True
        settings.delete_own_comment_enabled = True

        # doc with comments
        self.doc = api.content.create(
            container=self.portal,
            type='Document',
            id='doc_with_comments',
            title='Document with comments',
            allow_discussion=True
        )
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject('plone.Comment')
        comment.text = 'Comment'
        self.comment = self.replies[self.replies.addComment(comment)]

        comment = createObject('plone.Comment')
        comment.text = 'Comment 2'
        self.replies.addComment(comment)

    def test_conversation(self):
        serializer = getMultiAdapter(
            (self.conversation, self.request),
            ISerializeToJson
        )

        output = serializer()
        self.assertEqual(
            set(output),
            set(['@id', 'items_total', 'items'])
        )

    def test_conversation_batched(self):
        self.request.form['b_size'] = 1
        serializer = getMultiAdapter(
            (self.conversation, self.request),
            ISerializeToJson
        )

        output = serializer()
        self.assertIn('batching', output)

    def test_comment(self):
        serializer = getMultiAdapter(
            (self.comment, self.request),
            ISerializeToJson
        )

        output = serializer()

        expected = [
            '@id',
            '@type',
            '@parent',
            'comment_id',
            'in_reply_to',
            'text',
            'user_notification',
            'author_username',
            'author_name',
            'creation_date',
            'modification_date',
            'is_editable',
            'is_deletable'
        ]
        self.assertEqual(
            set(output),
            set(expected)
        )

        self.assertEqual(
            set(output['text']),
            set(['data', 'mime-type'])
        )
Example #32
0
        def migrate_replies(context,
                            in_reply_to,
                            replies,
                            depth=0,
                            just_delete=0):
            # Recursive function to migrate all direct replies
            # of a comment. Returns True if there are no replies to
            # this comment left, and therefore the comment can be removed.
            if len(replies) == 0:
                return True

            workflow = context.portal_workflow
            oldchain = workflow.getChainForPortalType('Discussion Item')
            new_workflow = workflow.comment_review_workflow

            if type(oldchain) == TupleType and len(oldchain) > 0:
                oldchain = oldchain[0]

            for reply in replies:
                # log
                indent = "  "
                for i in range(depth):
                    indent += "  "
                log("%smigrate_reply: '%s'." % (indent, reply.title))

                should_migrate = True
                if filter_callback and not filter_callback(reply):
                    should_migrate = False
                if just_delete:
                    should_migrate = False

                new_in_reply_to = None
                if should_migrate:

                    # create a reply object
                    comment = CommentFactory()
                    comment.title = reply.Title()
                    comment.text = reply.cooked_text
                    comment.mime_type = 'text/html'
                    comment.creator = reply.Creator()

                    email = reply.getProperty('email', None)
                    if email:
                        comment.author_email = email

                    comment.creation_date = DT2dt(reply.creation_date)
                    comment.modification_date = DT2dt(reply.modification_date)

                    comment.reply_to = in_reply_to

                    if in_reply_to == 0:
                        # Direct reply to a content object
                        new_in_reply_to = conversation.addComment(comment)
                    else:
                        # Reply to another comment
                        comment_to_reply_to = conversation.get(in_reply_to)
                        replies = IReplies(comment_to_reply_to)
                        new_in_reply_to = replies.addComment(comment)

                    # migrate the review state
                    old_status = workflow.getStatusOf(oldchain, reply)
                    new_status = {
                        'action':
                        None,
                        'actor':
                        None,
                        'comment':
                        'Migrated workflow state',
                        'review_state':
                        old_status and old_status.get(
                            'review_state', new_workflow.initial_state)
                        or 'published',
                        'time':
                        DateTime()
                    }
                    workflow.setStatusOf('comment_review_workflow', comment,
                                         new_status)

                    auto_transition = new_workflow._findAutomaticTransition(
                        comment, new_workflow._getWorkflowStateOf(comment))
                    if auto_transition is not None:
                        new_workflow._changeStateOf(comment, auto_transition)
                    else:
                        new_workflow.updateRoleMappingsFor(comment)
                    comment.reindexObject(
                        idxs=['allowedRolesAndUsers', 'review_state'])

                self.total_comments_migrated += 1

                # migrate all talkbacks of the reply
                talkback = getattr(reply, 'talkback', None)
                no_replies_left = migrate_replies(
                    context,
                    new_in_reply_to,
                    talkback.getReplies(),
                    depth=depth + 1,
                    just_delete=not should_migrate)

                if no_replies_left:
                    # remove reply and talkback
                    talkback.deleteReply(reply.id)
                    obj = aq_parent(talkback)
                    obj.talkback = None
                    log("%sremove %s" % (indent, reply.id))
                    self.total_comments_deleted += 1

            # Return True when all comments on a certain level have been
            # migrated.
            return True
Example #33
0
class TestCommentsSerializers(TestCase):
    layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING

    def setUp(self):
        self.app = self.layer["app"]
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.portal_url = self.portal.absolute_url()

        # Allow discussion
        registry = getUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.globally_enabled = True
        settings.edit_comment_enabled = True
        settings.delete_own_comment_enabled = True

        # doc with comments
        self.doc = api.content.create(
            container=self.portal,
            type="Document",
            id="doc_with_comments",
            title="Document with comments",
            allow_discussion=True,
        )
        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Comment"
        self.comment = self.replies[self.replies.addComment(comment)]

        comment = createObject("plone.Comment")
        comment.text = "Comment 2"
        self.replies.addComment(comment)

    def test_conversation(self):
        serializer = getMultiAdapter(
            (self.conversation, self.request), ISerializeToJson
        )

        output = serializer()
        self.assertEqual(set(output), set(["@id", "items_total", "items"]))

    def test_conversation_batched(self):
        self.request.form["b_size"] = 1
        serializer = getMultiAdapter(
            (self.conversation, self.request), ISerializeToJson
        )

        output = serializer()
        self.assertIn("batching", output)

    def test_comment(self):
        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        output = serializer()

        expected = [
            "@id",
            "@type",
            "@parent",
            "comment_id",
            "in_reply_to",
            "text",
            "user_notification",
            "author_username",
            "author_name",
            "creation_date",
            "modification_date",
            "is_editable",
            "is_deletable",
        ]
        self.assertEqual(set(output), set(expected))

        self.assertEqual(set(output["text"]), set(["data", "mime-type"]))

    def test_comment_with_mimetype_text_plain(self):

        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Hey, I am plain text!"
        comment.mime_type = "text/plain"
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        # serializer should return HTML with a clickable link
        self.assertEqual(
            'Hey, I am plain text!',
            serializer()["text"]["data"],
        )
        # serializer should return mimetype = text/x-web-intelligent
        self.assertEqual("text/plain", serializer()["text"]["mime-type"])

    def test_comment_with_mimetype_intelligent_text(self):
        # Set text transform to intelligent text
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.text_transform = "text/x-web-intelligent"

        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Go to https://www.plone.org"
        comment.mime_type = "text/x-web-intelligent"
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        # serializer should return HTML with a clickable link
        self.assertEqual(
            'Go to <a href="https://www.plone.org" '
            + 'rel="nofollow">https://www.plone.org</a>',
            serializer()["text"]["data"],
        )
        # serializer should return mimetype = text/html
        self.assertEqual("text/html", serializer()["text"]["mime-type"])

    def test_comment_with_mimetype_html(self):
        # Set text transform to text/html
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        settings.text_transform = "text/html"

        self.conversation = IConversation(self.doc)
        self.replies = IReplies(self.conversation)
        comment = createObject("plone.Comment")
        comment.text = "Go to <a href='https://www.plone.org'>Plone</a>"
        comment.mime_type = "text/html"
        self.comment = self.replies[self.replies.addComment(comment)]

        serializer = getMultiAdapter((self.comment, self.request), ISerializeToJson)

        # serializer should return HTML
        self.assertEqual(
            'Go to <a href="https://www.plone.org">Plone</a>',
            serializer()["text"]["data"],
        )
        # serializer should return mimetype = text/html
        self.assertEqual("text/html", serializer()["text"]["mime-type"])
Example #34
0
    def __call__(self, site, msg):
        recipient = email.Utils.parseaddr(msg.get('To'))[1]

        settings = getSettings()
        if settings is None or settings.sender is None:
            # Cannot handle email unless we have a dedicated address for it
            return False 

        if recipient.lower() != settings.sender.strip().lower():
            # It is not addressed to us
            return False

        sender = msg.get('From')
        sender = email.Utils.parseaddr(sender)[1].lower()

        # Drop privileges to the right user
        acl_users = getToolByName(site, 'acl_users')
        pm = getToolByName(site, 'portal_membership')
        props = getToolByName(site, 'portal_properties').site_properties
        user = None
        if props.getProperty('use_email_as_login'):
            # If email logins are used, the user's email might be his id
            user = pm.getMemberById(sender)
            if user is not None:
                if user.getProperty('email') == sender:
                    user = user.getUser()
                else:
                    user = None

        if user is None:
            potential = pm.searchMembers('email', sender)
            # Email might match more than one user, check for exact match
            for u in potential:
                if sender == u['email']:
                    user = pm.getMemberById(u['username']).getUser()
                    break

        if user is None:
            raise NotFoundError(_("Sender is not a valid user"))

        newSecurityManager(None, user.__of__(acl_users))

        # Find relevant comment
        subject = msg.get('subject', '').strip()
        if not subject:
            raise NotFoundError(_("Subject is blank"))

        subject = decodeheader(subject)
        pat = re.compile('[^[]*\[([^#]+)(#([^\]]+))?\]')
        m = pat.match(subject)
        if m is None:
            raise NotFoundError(_("Unable to match a question"))
        
        questionid = m.groups()[0]
        commentid = m.groups()[2] # Might be None

        question = uuidToObject(questionid)
        if question is None:
            raise NotFoundError(_("Unable to match a question"))

        can_reply = getSecurityManager().checkPermission(
            'Reply to item', question)

        if not can_reply:
            raise PermissionError(_("Insufficient privileges"))

        comment = createObject('plone.Comment')

        member = pm.getAuthenticatedMember()
        address = member.getProperty('email')
        fullname = member.getProperty('fullname')
        if not fullname or fullname == '':
            fullname = member.getUserName()
        elif isinstance(fullname, str):
            fullname = unicode(fullname, 'utf-8')

        if address and isinstance(address, str):
            address = unicode(address, 'utf-8')

        comment.creator = member.getUserName()
        comment.author_username = comment.creator
        comment.author_name = fullname
        comment.author_email = address
        comment.creation_date = datetime.utcnow()
        comment.modification_date = comment.creation_date

        # Walk the message and get the inline component
        for part in msg.walk():
            if part.is_multipart():
                # Ignore multipart envelope if it exists
                continue
            if part.get_filename() is not None:
                # Ignore attachments
                continue

            content_type = part.get_content_type()

            if content_type not in ('text/plain', 'text/html'):
                # Skip non text/html parts
                continue

            payload = part.get_payload(decode=1)
            if content_type == 'text/html':
                transforms = getToolByName(site, 'portal_transforms')
                transform = transforms.convertTo('text/plain',
                    payload, context=site, mimetype='text/html')
                if transform:
                    payload = transform.getData().strip()
                else:
                    raise PermanentError(_(u"Could not convert html email"))

            comment.text = payload.strip()
            break # Get out of the for loop

        # Add comment to conversation
        conversation = IConversation(question)
        if commentid is not None:
            # Add a reply to an existing comment
            conversation_to_reply_to = conversation.get(commentid)
            replies = IReplies(conversation_to_reply_to)
            comment_id = replies.addComment(comment)
        else:
            # Add a comment to the conversation
            comment_id = conversation.addComment(comment)

        return True
Example #35
0
        def migrate_replies(context, in_reply_to, replies,
                            depth=0, just_delete=0):
            # Recursive function to migrate all direct replies
            # of a comment. Returns True if there are no replies to
            # this comment left, and therefore the comment can be removed.
            if len(replies) == 0:
                return True

            workflow = context.portal_workflow
            oldchain = workflow.getChainForPortalType('Discussion Item')
            new_workflow = workflow.comment_review_workflow
            mt = getToolByName(self.context, 'portal_membership')

            if type(oldchain) == TupleType and len(oldchain) > 0:
                oldchain = oldchain[0]

            for reply in replies:
                # log
                indent = "  "
                for i in range(depth):
                    indent += "  "
                log("%smigrate_reply: '%s'." % (indent, reply.title))

                should_migrate = True
                if filter_callback and not filter_callback(reply):
                    should_migrate = False
                if just_delete:
                    should_migrate = False

                new_in_reply_to = None
                if should_migrate:
                    # create a reply object
                    comment = CommentFactory()
                    comment.title = reply.Title()
                    comment.text = reply.cooked_text
                    comment.mime_type = 'text/html'
                    comment.creator = reply.Creator()

                    try:
                        comment.author_username = reply.author_username
                    except AttributeError:
                        comment.author_username = reply.Creator()

                    member = mt.getMemberById(comment.author_username)
                    if member:
                        comment.author_name = member.fullname

                    if not comment.author_name:
                        # In migrated site member.fullname = ''
                        # while member.getProperty('fullname') has the
                        # correct value
                        if member:
                            comment.author_name = member.getProperty(
                                'fullname'
                            )
                        else:
                            comment.author_name = comment.author_username

                    try:
                        comment.author_email = reply.email
                    except AttributeError:
                        comment.author_email = None

                    comment.creation_date = DT2dt(reply.creation_date)
                    comment.modification_date = DT2dt(reply.modification_date)

                    comment.reply_to = in_reply_to

                    if in_reply_to == 0:
                        # Direct reply to a content object
                        new_in_reply_to = conversation.addComment(comment)
                    else:
                        # Reply to another comment
                        comment_to_reply_to = conversation.get(in_reply_to)
                        replies = IReplies(comment_to_reply_to)
                        new_in_reply_to = replies.addComment(comment)

                    # migrate the review state
                    old_status = workflow.getStatusOf(oldchain, reply)
                    new_status = {
                        'action': None,
                        'actor': None,
                        'comment': 'Migrated workflow state',
                        'review_state': old_status and old_status.get(
                            'review_state',
                            new_workflow.initial_state
                        ) or 'published',
                        'time': DateTime()
                    }
                    workflow.setStatusOf('comment_review_workflow',
                                         comment,
                                         new_status)

                    auto_transition = new_workflow._findAutomaticTransition(
                        comment,
                        new_workflow._getWorkflowStateOf(comment))
                    if auto_transition is not None:
                        new_workflow._changeStateOf(comment, auto_transition)
                    else:
                        new_workflow.updateRoleMappingsFor(comment)
                    comment.reindexObject(idxs=['allowedRolesAndUsers',
                                                'review_state'])

                self.total_comments_migrated += 1

                # migrate all talkbacks of the reply
                talkback = getattr(reply, 'talkback', None)
                no_replies_left = migrate_replies(
                    context,
                    new_in_reply_to,
                    talkback.getReplies(),
                    depth=depth + 1,
                    just_delete=not should_migrate)

                if no_replies_left:
                    # remove reply and talkback
                    talkback.deleteReply(reply.id)
                    obj = aq_parent(talkback)
                    obj.talkback = None
                    log("%sremove %s" % (indent, reply.id))
                    self.total_comments_deleted += 1

            # Return True when all comments on a certain level have been
            # migrated.
            return True
 def can_delete(self, comment=None):
     comment = comment or self.context
     return (len(IReplies(aq_inner(comment))) == 0
             and self.could_delete(comment=comment))
Example #37
0
    def migrate_replies(context, in_reply_to, replies, depth=0, just_delete=0):
        # Recursive function to migrate all direct replies
        # of a comment. Returns True if there are no replies to
        # this comment left, and therefore the comment can be removed.
        if len(replies) == 0:
            return True

        for reply in replies:

            # log
            indent = "  "
            for i in range(depth):
                indent += "  "
            log("%smigrate_reply: '%s'." % (indent, reply.title))

            should_migrate = True
            if filter_callback and not filter_callback(reply):
                should_migrate = False
            if just_delete:
                should_migrate = False

            new_in_reply_to = None
            if should_migrate:
                # create a reply object
                comment = CommentFactory()
                comment.title = reply.Title()
                comment.text = reply.cooked_text
                comment.mime_type = 'text/html'
                comment.creator = reply.Creator()

                email = reply.getProperty('email', None)
                if email:
                    comment.author_email = email

                comment.creation_date = DT2dt(reply.creation_date)
                comment.modification_date = DT2dt(reply.modification_date)

                comment.reply_to = in_reply_to

                if in_reply_to == 0:
                    # Direct reply to a content object
                    new_in_reply_to = conversation.addComment(comment)
                else:
                    # Reply to another comment
                    comment_to_reply_to = conversation.get(in_reply_to)
                    replies = IReplies(comment_to_reply_to)
                    try:
                        comment.text.encode("utf-8")
                        new_in_reply_to = replies.addComment(comment)
                    except UnicodeDecodeError, e:
                        log("Fixing UnicodeDecodeError %s" % e)
                        comment.text = comment.text.decode("utf-8")
                    new_in_reply_to = replies.addComment(comment)

            total_comments_migrated[0] += 1

            # migrate all talkbacks of the reply
            talkback = getattr( reply, 'talkback', None)
            no_replies_left = migrate_replies(context,
                                              new_in_reply_to,
                                              talkback.getReplies(),
                                              depth=depth+1,
                                              just_delete=not should_migrate)

            if no_replies_left:
                # remove reply and talkback
                talkback.deleteReply(reply.id)
                obj = aq_parent(talkback)
                obj.talkback = None
                log("%sremove %s" % (indent, reply.id))
                total_comments_deleted[0] += 1
    def test_traversal(self):
        # Create a nested structure of comment replies and check the traversal

        # make sure comments are traversable, have an id, absolute_url and
        # physical path
        conversation = IConversation(self.portal.doc1)

        comment1 = createObject('plone.Comment')
        comment1.text = 'Comment text'

        conversation.addComment(comment1)

        comment = createObject('plone.Comment')
        comment.text = 'Comment text'
        new_id = conversation.addComment(comment)
        comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_id), )

        # Add a reply to the CommentReplies adapter of the first comment
        re_comment = createObject('plone.Comment')
        re_comment.text = 'Comment text'
        replies = IReplies(comment)
        new_re_id = replies.addComment(re_comment)
        re_comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_re_id), )

        # Add a reply to the reply
        re_re_comment = createObject('plone.Comment')
        re_re_comment.text = 'Comment text'
        replies = IReplies(re_comment)
        new_re_re_id = replies.addComment(re_re_comment)
        re_re_comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_re_re_id), )

        # Add a reply to the replies reply
        re_re_re_comment = createObject('plone.Comment')
        re_re_re_comment.text = 'Comment text'
        replies = IReplies(re_re_comment)
        new_re_re_re_id = replies.addComment(re_re_re_comment)
        re_re_re_comment = self.portal.doc1.restrictedTraverse(
            '++conversation++default/{0}'.format(new_re_re_re_id), )

        self.assertEqual(
            ('', 'plone', 'doc1', '++conversation++default', str(new_id)),
            comment.getPhysicalPath(),
        )
        self.assertEqual(
            'http://nohost/plone/doc1/++conversation++default/' + str(new_id),
            comment.absolute_url(),
        )
        self.assertEqual(
            ('', 'plone', 'doc1', '++conversation++default', str(new_re_id)),
            re_comment.getPhysicalPath(),
        )
        self.assertEqual(
            'http://nohost/plone/doc1/++conversation++default/' +
            str(new_re_id),
            re_comment.absolute_url(),
        )
        self.assertEqual(
            (
                '',
                'plone',
                'doc1',
                '++conversation++default',
                str(new_re_re_id),
            ),
            re_re_comment.getPhysicalPath(),
        )
        self.assertEqual(
            'http://nohost/plone/doc1/++conversation++default/' +
            str(new_re_re_id),
            re_re_comment.absolute_url(),
        )
        self.assertEqual(
            (
                '',
                'plone',
                'doc1',
                '++conversation++default',
                str(new_re_re_re_id),
            ),
            re_re_re_comment.getPhysicalPath(),
        )
        self.assertEqual(
            'http://nohost/plone/doc1/++conversation++default/' +
            str(new_re_re_re_id),
            re_re_re_comment.absolute_url(),
        )
Example #39
0
    def handleComment(self, action):
        context = aq_inner(self.context)

        # Check if conversation is enabled on this content object
        if not self.__parent__.restrictedTraverse(
                '@@conversation_view').enabled():
            raise Unauthorized("Discussion is not enabled for this content "
                               "object.")

        # Validation form
        data, errors = self.extractData()
        if errors:
            return

        # Validate Captcha
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        portal_membership = getToolByName(self.context, 'portal_membership')
        if settings.captcha != 'disabled' and \
        settings.anonymous_comments and \
        portal_membership.isAnonymousUser():
            if not 'captcha' in data:
                data['captcha'] = u""
            captcha = CaptchaValidator(self.context, self.request, None,
                                       ICaptcha['captcha'], None)
            captcha.validate(data['captcha'])

        # some attributes are not always set
        author_name = u""

        # Create comment
        comment = createObject('plone.Comment')

        # Set comment mime type to current setting in the discussion registry
        comment.mime_type = settings.text_transform

        # Set comment attributes (including extended comment form attributes)
        for attribute in self.fields.keys():
            setattr(comment, attribute, data[attribute])
        # Make sure author_name is properly encoded
        if 'author_name' in data:
            author_name = data['author_name']
            if isinstance(author_name, str):
                author_name = unicode(author_name, 'utf-8')

        # Set comment author properties for anonymous users or members
        can_reply = getSecurityManager().checkPermission(
            'Reply to item', context)
        portal_membership = getToolByName(self.context, 'portal_membership')
        if portal_membership.isAnonymousUser() and \
           settings.anonymous_comments:
            # Anonymous Users
            comment.author_name = author_name
            comment.author_email = u""
            comment.user_notification = None
            comment.creation_date = datetime.utcnow()
            comment.modification_date = datetime.utcnow()
        elif not portal_membership.isAnonymousUser() and can_reply:
            # Member
            member = portal_membership.getAuthenticatedMember()
            username = member.getUserName()
            email = member.getProperty('email')
            fullname = member.getProperty('fullname')
            if not fullname or fullname == '':
                fullname = member.getUserName()
            # memberdata is stored as utf-8 encoded strings
            elif isinstance(fullname, str):
                fullname = unicode(fullname, 'utf-8')
            if email and isinstance(email, str):
                email = unicode(email, 'utf-8')
            comment.creator = username
            comment.author_username = username
            comment.author_name = fullname
            comment.author_email = email
            comment.creation_date = datetime.utcnow()
            comment.modification_date = datetime.utcnow()
        else:  # pragma: no cover
            raise Unauthorized(
                "Anonymous user tries to post a comment, but "
                "anonymous commenting is disabled. Or user does not have the "
                "'reply to item' permission.")

        # Add comment to conversation
        conversation = IConversation(self.__parent__)
        if data['in_reply_to']:
            # Add a reply to an existing comment
            conversation_to_reply_to = conversation.get(data['in_reply_to'])
            replies = IReplies(conversation_to_reply_to)
            comment_id = replies.addComment(comment)
        else:
            # Add a comment to the conversation
            comment_id = conversation.addComment(comment)

        # Redirect after form submit:
        # If a user posts a comment and moderation is enabled, a message is
        # shown to the user that his/her comment awaits moderation. If the user
        # has 'review comments' permission, he/she is redirected directly
        # to the comment.
        can_review = getSecurityManager().checkPermission(
            'Review comments', context)
        workflowTool = getToolByName(context, 'portal_workflow')
        comment_review_state = workflowTool.getInfoFor(comment, 'review_state')
        if comment_review_state == 'pending' and not can_review:
            # Show info message when comment moderation is enabled
            IStatusMessage(self.context.REQUEST).addStatusMessage(
                _("Your comment awaits moderator approval."), type="info")
            self.request.response.redirect(self.action)
        else:
            # Redirect to comment (inside a content object page)
            self.request.response.redirect(self.action + '#' + str(comment_id))
    def handleComment(self, action):
        context = aq_inner(self.context)

        # Check if conversation is enabled on this content object
        if not self.__parent__.restrictedTraverse(
            '@@conversation_view').enabled():
            raise Unauthorized("Discussion is not enabled for this content "
                               "object.")

        # Validation form
        data, errors = self.extractData()
        if errors:
            return

        # Validate Captcha
        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        portal_membership = getToolByName(self.context, 'portal_membership')
        if settings.captcha != 'disabled' and \
        settings.anonymous_comments and \
        portal_membership.isAnonymousUser():
            if not 'captcha' in data:
                data['captcha'] = u""
            captcha = CaptchaValidator(self.context,
                                       self.request,
                                       None,
                                       ICaptcha['captcha'],
                                       None)
            captcha.validate(data['captcha'])

        # some attributes are not always set
        author_name = u""

        # Create comment
        comment = createObject('plone.Comment')

        # Set comment mime type to current setting in the discussion registry
        comment.mime_type = settings.text_transform

        # Set comment attributes (including extended comment form attributes)
        for attribute in self.fields.keys():
            setattr(comment, attribute, data[attribute])
        # Make sure author_name is properly encoded
        if 'author_name' in data:
            author_name = data['author_name']
            if isinstance(author_name, str):
                author_name = unicode(author_name, 'utf-8')

        # Set comment author properties for anonymous users or members
        can_reply = getSecurityManager().checkPermission('Reply to item',
                                                         context)
        portal_membership = getToolByName(self.context, 'portal_membership')
        if portal_membership.isAnonymousUser() and \
           settings.anonymous_comments:
            # Anonymous Users
            comment.author_name = author_name
            comment.author_email = u""
            comment.user_notification = None
            comment.creation_date = datetime.utcnow()
            comment.modification_date = datetime.utcnow()
        elif not portal_membership.isAnonymousUser() and can_reply:
            # Member
            member = portal_membership.getAuthenticatedMember()
            username = member.getUserName()
            email = member.getProperty('email')
            fullname = member.getProperty('fullname')
            if not fullname or fullname == '':
                fullname = member.getUserName()
            # memberdata is stored as utf-8 encoded strings
            elif isinstance(fullname, str):
                fullname = unicode(fullname, 'utf-8')
            if email and isinstance(email, str):
                email = unicode(email, 'utf-8')
            comment.creator = username
            comment.author_username = username
            comment.author_name = fullname
            comment.author_email = email
            comment.creation_date = datetime.utcnow()
            comment.modification_date = datetime.utcnow()
        else:  # pragma: no cover
            raise Unauthorized("Anonymous user tries to post a comment, but "
                "anonymous commenting is disabled. Or user does not have the "
                "'reply to item' permission.")

        # Add comment to conversation
        conversation = IConversation(self.__parent__)
        if data['in_reply_to']:
            # Add a reply to an existing comment
            conversation_to_reply_to = conversation.get(data['in_reply_to'])
            replies = IReplies(conversation_to_reply_to)
            comment_id = replies.addComment(comment)
        else:
            # Add a comment to the conversation
            comment_id = conversation.addComment(comment)

        # Redirect after form submit:
        # If a user posts a comment and moderation is enabled, a message is
        # shown to the user that his/her comment awaits moderation. If the user
        # has 'review comments' permission, he/she is redirected directly
        # to the comment.
        can_review = getSecurityManager().checkPermission('Review comments',
                                                          context)
        workflowTool = getToolByName(context, 'portal_workflow')
        comment_review_state = workflowTool.getInfoFor(
            comment, 
            'review_state', 
            None
        )
        if comment_review_state == 'pending' and not can_review:
            # Show info message when comment moderation is enabled
            IStatusMessage(self.context.REQUEST).addStatusMessage(
                _("Your comment awaits moderator approval."),
                type="info")
            self.request.response.redirect(self.action)
        else:
            # Redirect to comment (inside a content object page)
            self.request.response.redirect(self.action + '#' + str(comment_id))
Example #41
0
    def migrate_replies(context, in_reply_to, replies, depth=0, just_delete=0):
        """migrate_replies"""
        # Recursive function to migrate all direct replies
        # of a comment. Returns True if there are no replies to
        # this comment left, and therefore the comment can be removed.
        if not replies:
            return True

        for reply in replies:

            # log
            indent = "  " * depth + 1
            log("%smigrate_reply: '%s'." % (indent, reply.title))

            should_migrate = True
            if filter_callback and not filter_callback(reply):
                should_migrate = False
            if just_delete:
                should_migrate = False

            new_in_reply_to = None
            if should_migrate:
                # create a reply object
                comment = CommentFactory()
                comment.title = reply.Title()
                comment.text = reply.cooked_text
                comment.mime_type = 'text/html'
                comment.creator = reply.Creator()

                email = reply.getProperty('email', None)
                if email:
                    comment.author_email = email

                comment.creation_date = DT2dt(reply.creation_date)
                comment.modification_date = DT2dt(reply.modification_date)

                comment.in_reply_to = in_reply_to

                if in_reply_to == 0:
                    # Direct reply to a content object
                    new_in_reply_to = conversation.addComment(comment)
                else:
                    # Reply to another comment
                    comment_to_reply_to = conversation.get(in_reply_to)
                    replies = IReplies(comment_to_reply_to)
                    new_in_reply_to = replies.addComment(comment)

            self.total_comments_migrated += 1

            # migrate all talkbacks of the reply
            talkback = getattr(reply, 'talkback', None)
            no_replies_left = migrate_replies(context,
                                              new_in_reply_to,
                                              talkback.getReplies(),
                                              depth=depth+1,
                                              just_delete=not should_migrate)

            if no_replies_left:
                # remove reply and talkback
                talkback.deleteReply(reply.id)
                obj = aq_parent(talkback)
                obj.talkback = None
                log("%sremove %s" % (indent, reply.id))
                self.total_comments_deleted += 1

        # Return True when all comments on a certain level have been
        # migrated.
        return True