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)
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)
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))
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)
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 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)
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"), )
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_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)
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"), )
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)
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)
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)
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"])
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_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))
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"]))
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 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
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"])
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
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))
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(), )
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))
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