예제 #1
0
class EditCommentForm(CommentForm):
    """Form to edit an existing comment."""
    ignoreContext = True
    id = 'edit-comment-form'
    label = _(u'edit_comment_form_title', default=u'Edit comment')

    def updateWidgets(self):
        super(EditCommentForm, self).updateWidgets()
        self.widgets['text'].value = self.context.text
        # We have to rename the id, otherwise TinyMCE can't initialize
        # because there are two textareas with the same id.
        self.widgets['text'].id = 'overlay-comment-text'

    def _redirect(self, target=''):
        if not target:
            portal_state = getMultiAdapter((self.context, self.request),
                                           name=u'plone_portal_state')
            target = portal_state.portal_url()
        self.request.response.redirect(target)

    @button.buttonAndHandler(_(u'edit_comment_form_button',
                               default=u'Edit comment'), name='comment')
    def handleComment(self, action):

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

        # Check permissions
        can_edit = getSecurityManager().checkPermission(
            'Edit comments',
            self.context)
        mtool = getToolByName(self.context, 'portal_membership')
        if mtool.isAnonymousUser() or not can_edit:
            return

        # Update text
        self.context.text = data['text']
        # Notify that the object has been modified
        notify(ObjectModifiedEvent(self.context))

        # Redirect to comment
        IStatusMessage(self.request).add(_(u'comment_edit_notification',
                                           default='Comment was edited'),
                                         type='info')
        return self._redirect(
            target=self.action.replace('@@edit-comment', '@@view'))

    @button.buttonAndHandler(_(u'cancel_form_button',
                               default=u'Cancel'), name='cancel')
    def handle_cancel(self, action):
        IStatusMessage(self.request).add(
            _(u'comment_edit_cancel_notification',
              default=u'Edit comment cancelled'),
            type='info')
        return self._redirect(target=self.context.absolute_url())
예제 #2
0
class Id(CommentSubstitution):
    """ Comment id string substitution
    """
    category = _(u'Comments')
    description = _(u'Comment id')

    def safe_call(self):
        """ Safe call
        """
        return getattr(self.comment, 'comment_id', u'')
예제 #3
0
class AuthorUserName(CommentSubstitution):
    """ Comment author user name string substitution
    """
    category = _(u'Comments')
    description = _(u'Comment author user name')

    def safe_call(self):
        """ Safe call
        """
        return self.comment.get('author_username', u'')
예제 #4
0
class Text(CommentSubstitution):
    """ Comment text
    """
    category = _(u'Comments')
    description = _(u'Comment text')

    def safe_call(self):
        """ Safe call
        """
        return getattr(self.comment, 'text', u'')
예제 #5
0
class AuthorFullName(CommentSubstitution):
    """ Comment author full name string substitution
    """
    category = _(u'Comments')
    description = _(u'Comment author full name')

    def safe_call(self):
        """ Safe call
        """
        return getattr(self.comment, 'author_name', u'')
예제 #6
0
class AuthorEmail(CommentSubstitution):
    """ Comment author email string substitution
    """
    category = _(u'Comments')
    description = _(u'Comment author email')

    def safe_call(self):
        """ Safe call
        """
        return getattr(self.comment, 'author_email', u'')
예제 #7
0
    def Title(self):
        # The title of the comment.

        if self.title:
            return self.title

        if not self.author_name:
            author_name = translate(
                Message(_(
                    u'label_anonymous',
                    default=u'Anonymous',
                ), ), )
        else:
            author_name = self.author_name

        # Fetch the content object (the parent of the comment is the
        # conversation, the parent of the conversation is the content object).
        content = aq_base(self.__parent__.__parent__)
        title = translate(
            Message(COMMENT_TITLE,
                    mapping={
                        'author_name': safe_unicode(author_name),
                        'content': safe_unicode(content.Title())
                    }))
        return title
예제 #8
0
    def handleComment(self, action):

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

        # Check permissions
        can_edit = getSecurityManager().checkPermission(
            'Edit comments',
            self.context)
        mtool = getToolByName(self.context, 'portal_membership')
        if mtool.isAnonymousUser() or not can_edit:
            return

        # Update text
        self.context.text = data['text']
        # Notify that the object has been modified
        notify(ObjectModifiedEvent(self.context))

        # Redirect to comment
        IStatusMessage(self.request).add(_(u'comment_edit_notification',
                                           default='Comment was edited'),
                                         type='info')
        return self._redirect(
            target=self.action.replace('@@edit-comment', '@@view'))
예제 #9
0
    def handleComment(self, action):

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

        # Check permissions
        can_edit = getSecurityManager().checkPermission(
            'Edit comments', self.context)
        mtool = getToolByName(self.context, 'portal_membership')
        if mtool.isAnonymousUser() or not can_edit:
            return

        # Update text
        self.context.text = data['text']
        # Notify that the object has been modified
        notify(ObjectModifiedEvent(self.context))

        # Redirect to comment
        IStatusMessage(self.request).add(_(u'comment_edit_notification',
                                           default='Comment was edited'),
                                         type='info')
        return self._redirect(
            target=self.action.replace('@@edit-comment', '@@view'))
예제 #10
0
    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
        self.widgets['text'].addClass("autoresize")
        self.widgets['user_notification'].label = _(u"")

        # Rename the id of the text widgets because there can be css-id
        # clashes with the text field of documents when using and overlay
        # with TinyMCE.
        self.widgets['text'].id = "form-widgets-comment-text"

        # Anonymous / Logged-in
        mtool = getToolByName(self.context, 'portal_membership')
        if not mtool.isAnonymousUser():
            self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)

        if mtool.isAnonymousUser() and not settings.anonymous_email_enabled:
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty('email')

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid
        # email address
        if member_email == '' or \
           not settings.user_notification_enabled or \
           mtool.isAnonymousUser():
                self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
예제 #11
0
    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets["in_reply_to"].mode = interfaces.HIDDEN_MODE
        self.widgets["text"].addClass("autoresize")
        self.widgets["user_notification"].label = _(u"")

        # Anonymous / Logged-in
        mtool = getToolByName(self.context, "portal_membership")
        if not mtool.isAnonymousUser():
            self.widgets["author_name"].mode = interfaces.HIDDEN_MODE
            self.widgets["author_email"].mode = interfaces.HIDDEN_MODE

        # Todo: Since we are not using the author_email field in the
        # current state, we hide it by default. But we keep the field for
        # integrators or later use.
        self.widgets["author_email"].mode = interfaces.HIDDEN_MODE

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty("email")

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid
        # email address
        if member_email == "" or not settings.user_notification_enabled or mtool.isAnonymousUser():
            self.widgets["user_notification"].mode = interfaces.HIDDEN_MODE
예제 #12
0
    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
        self.widgets['text'].addClass("autoresize")
        self.widgets['user_notification'].label = _(u"")

        # Anonymous / Logged-in
        portal_membership = getToolByName(self.context, 'portal_membership')
        if not portal_membership.isAnonymousUser():
            self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        # Todo: Since we are not using the author_email field in the
        # current state, we hide it by default. But we keep the field for
        # integrators or later use.
        self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        mtool = getToolByName(self.context, 'portal_membership')
        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty('email')

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid email
        # address
        if member_email == '' or \
           not settings.user_notification_enabled or \
           mtool.isAnonymousUser():
            self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
예제 #13
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))
예제 #14
0
    def getText(self, targetMimetype=None):
        """The body text of a comment.
        """
        transforms = getToolByName(self, 'portal_transforms')

        if targetMimetype is None:
            targetMimetype = 'text/x-html-safe'

        sourceMimetype = getattr(self, 'mime_type', None)
        if sourceMimetype is None:
            registry = queryUtility(IRegistry)
            settings = registry.forInterface(IDiscussionSettings, check=False)
            sourceMimetype = settings.text_transform
        text = self.text
        if text is None:
            return ''
        if isinstance(text, unicode):
            text = text.encode('utf8')
        transform = transforms.convertTo(
            targetMimetype,
            text,
            context=self,
            mimetype=sourceMimetype)
        if transform:
            return transform.getData()
        else:
            logger = logging.getLogger("plone.app.discussion")
            logger.error(_(
                u"Transform '%s' => '%s' not available." % (
                    sourceMimetype,
                    targetMimetype
                ) +
                u"Failed to transform comment '%s'." % self.absolute_url()
            ))
            return text
예제 #15
0
    def Title(self):
        """The title of the comment.
        """

        if self.title:
            return self.title

        if not self.author_name:
            author_name = translate(
                Message(_(
                    u"label_anonymous",
                    default=u"Anonymous"
                ))
            )
        else:
            author_name = self.author_name

        # Fetch the content object (the parent of the comment is the
        # conversation, the parent of the conversation is the content object).
        content = aq_base(self.__parent__.__parent__)
        title = translate(
            Message(COMMENT_TITLE,
                    mapping={'author_name': safe_unicode(author_name),
                             'content': safe_unicode(content.Title())}))
        return title
예제 #16
0
    def getText(self, targetMimetype=None):
        """The body text of a comment.
        """
        transforms = getToolByName(self, 'portal_transforms')

        if targetMimetype is None:
            targetMimetype = 'text/x-html-safe'

        sourceMimetype = getattr(self, 'mime_type', None)
        if sourceMimetype is None:
            registry = queryUtility(IRegistry)
            settings = registry.forInterface(IDiscussionSettings, check=False)
            sourceMimetype = settings.text_transform
        text = self.text
        if text is None:
            return ''
        if isinstance(text, unicode):
            text = text.encode('utf8')
        transform = transforms.convertTo(targetMimetype,
                                         text,
                                         context=self,
                                         mimetype=sourceMimetype)
        if transform:
            return transform.getData()
        else:
            logger = logging.getLogger("plone.app.discussion")
            logger.error(
                _(u"Transform '%s' => '%s' not available." %
                  (sourceMimetype, targetMimetype) +
                  u"Failed to transform comment '%s'." % self.absolute_url()))
            return text
예제 #17
0
def notify_moderator(obj, event):
    """Tell the moderator when a comment needs attention.
       
       This method sends an email to the moderator if comment moderation a new 
       comment has been added that needs to be approved.
       
       The moderator_notification setting has to be enabled in the discussion
       control panel.
       
       Configure the moderator e-mail address in the discussion control panel.
       If no moderator is configured but moderator notifications are turned on,
       the site admin email (from the mail control panel) will be used.
    """
    # Check if moderator notification is enabled
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)
    if not settings.moderator_notification_enabled:
        return
    
    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, 'MailHost')
    portal_url = getToolByName(obj, 'portal_url')
    portal = portal_url.getPortalObject()
    sender = portal.getProperty('email_from_address')
    
    if settings.moderator_email:
        mto = settings.moderator_email
    else:
        mto = sender
    
    # Check if a sender address is available
    if not sender:
        return
    
    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)
    
    # Compose email
    subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
    message = translate(Message(MAIL_NOTIFICATION_MESSAGE_MODERATOR,
        mapping={
            'title': safe_unicode(content_object.title),
            'link': content_object.absolute_url() + '/view#' + obj.id,
            'text': obj.text,
            'link_approve': obj.absolute_url() + '/@@moderate-publish-comment',
            'link_delete': obj.absolute_url() + '/@@moderate-delete-comment',
            }),
        context=obj.REQUEST)
    
    # Send email
    try:
        mail_host.send(message, mto, sender, subject, charset='utf-8')
    except SMTPException, e:
        logger.error('SMTP exception (%s) while trying to send an ' +
                     'email notification to the comment moderator ' +
                     '(from %s to %s, message: %s)',
                     e,
                     sender,
                     mto,
                     message)
예제 #18
0
def notify_moderator(obj, event):
    """Tell the moderator when a comment needs attention.

       This method sends an email to the moderator if comment moderation a new
       comment has been added that needs to be approved.

       The moderator_notification setting has to be enabled in the discussion
       control panel.

       Configure the moderator e-mail address in the discussion control panel.
       If no moderator is configured but moderator notifications are turned on,
       the site admin email (from the mail control panel) will be used.
    """
    # Check if moderator notification is enabled
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)
    if not settings.moderator_notification_enabled:
        return

    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, 'MailHost')
    portal_url = getToolByName(obj, 'portal_url')
    portal = portal_url.getPortalObject()
    sender = portal.getProperty('email_from_address')

    if settings.moderator_email:
        mto = settings.moderator_email
    else:
        mto = sender

    # Check if a sender address is available
    if not sender:
        return

    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)

    # Compose email
    subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
    message = translate(Message(MAIL_NOTIFICATION_MESSAGE_MODERATOR,
        mapping={
            'title': safe_unicode(content_object.title),
            'link': content_object.absolute_url() + '/view#' + obj.id,
            'text': obj.text,
            'link_approve': obj.absolute_url() + '/@@moderate-publish-comment',
            'link_delete': obj.absolute_url() + '/@@moderate-delete-comment',
            }),
        context=obj.REQUEST)

    # Send email
    try:
        mail_host.send(message, mto, sender, subject, charset='utf-8')
    except SMTPException, e:
        logger.error('SMTP exception (%s) while trying to send an ' +
                     'email notification to the comment moderator ' +
                     '(from %s to %s, message: %s)',
                     e,
                     sender,
                     mto,
                     message)
예제 #19
0
def notify_user(obj, event):
    """Tell users when a comment has been added.
       
       This method composes and sends emails to all users that have added a
       comment to this conversation and enabled user notification.
       
       This requires the user_notification setting to be enabled in the
       discussion control panel.
    """

    # Check if user notification is enabled
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)
    if not settings.user_notification_enabled:
        return

    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, "MailHost")
    portal_url = getToolByName(obj, "portal_url")
    portal = portal_url.getPortalObject()
    sender = portal.getProperty("email_from_address")

    # Check if a sender address is available
    if not sender:
        return

    # Compose and send emails to all users that have add a comment to this
    # conversation and enabled user_notification.
    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)

    # Avoid sending multiple notification emails to the same person
    # when he has commented multiple times.
    emails = set()
    for comment in conversation.getComments():
        if obj != comment and comment.user_notification and comment.author_email:
            emails.add(comment.author_email)

    if not emails:
        return

    subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
    message = translate(
        Message(
            MAIL_NOTIFICATION_MESSAGE,
            mapping={
                "title": safe_unicode(content_object.title),
                "link": content_object.absolute_url() + "/view#" + obj.id,
                "text": obj.text,
            },
        ),
        context=obj.REQUEST,
    )
    for email in emails:
        # Send email
        try:
            mail_host.send(message, email, sender, subject, charset="utf-8")
        except SMTPException:
            logger.error("SMTP exception while trying to send an " + "email from %s to %s", sender, email)
예제 #20
0
    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
        self.widgets['text'].addClass('autoresize')
        self.widgets['user_notification'].label = _(u'')
        # Reset widget field settings to their defaults, which may be changed
        # further on.  Otherwise, the email field might get set to required
        # when an anonymous user visits, and then remain required when an
        # authenticated user visits, making it impossible for an authenticated
        # user to fill in the form without validation error.  Or when in the
        # control panel the field is set as not required anymore, that change
        # would have no effect until the instance was restarted.  Note that the
        # widget is new each time, but the field is the same item in memory as
        # the previous time.
        self.widgets['author_email'].field.required = False
        # The widget is new, but its 'required' setting is based on the
        # previous value on the field, so we need to reset it here.  Changing
        # the field in updateFields does not help.
        self.widgets['author_email'].required = False

        # Rename the id of the text widgets because there can be css-id
        # clashes with the text field of documents when using and overlay
        # with TinyMCE.
        self.widgets['text'].id = 'form-widgets-comment-text'

        # Anonymous / Logged-in
        mtool = getToolByName(self.context, 'portal_membership')
        anon = mtool.isAnonymousUser()

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)

        if anon:
            if settings.anonymous_email_enabled:
                # according to IDiscussionSettings.anonymous_email_enabled:
                # 'If selected, anonymous user will have to give their email.'
                self.widgets['author_email'].field.required = True
                self.widgets['author_email'].required = True
            else:
                self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
        else:
            self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty('email')

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid
        # email address
        member_email_is_empty = member_email == ''
        user_notification_disabled = not settings.user_notification_enabled
        if member_email_is_empty or user_notification_disabled or anon:
            self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
예제 #21
0
def notify_owner(obj, event):
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)

    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, 'MailHost')
    portal_url = getToolByName(obj, 'portal_url')
    portal = portal_url.getPortalObject()
    mtool = getToolByName(obj, 'portal_membership')
    sender = '"KSP Comment Notification" <*****@*****.**>'

    # Check if a sender address is available
    if not sender:
        return


    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)
    
    creator = content_object.Creator()
    member = mtool.getMemberById(creator)
    mto = member.getProperty('email', '')

    if not mto:
        return

    # Compose email
    subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
    message = translate(Message(MAIL_NOTIFICATION_MESSAGE,
        mapping={
            'title': safe_unicode(content_object.title),
            'link': content_object.absolute_url() + '/view#' + obj.id,
            'text': obj.text,
            'name': obj.author_name
            }),
        context=obj.REQUEST)

    # Send email
    try:
        mail_host.send(message, mto, sender, subject, charset='utf-8')
    except SMTPException, e:
        logger.error('SMTP exception (%s) while trying to send an ' +
                     'email notification to the comment moderator ' +
                     '(from %s to %s, message: %s)',
                     e,
                     sender,
                     mto,
                     message)
예제 #22
0
    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
        self.widgets['text'].addClass('autoresize')
        self.widgets['user_notification'].label = _(u'')

        # Rename the id of the text widgets because there can be css-id
        # clashes with the text field of documents when using and overlay
        # with TinyMCE.
        self.widgets['text'].id = 'form-widgets-comment-text'

        # Anonymous / Logged-in
        mtool = getToolByName(self.context, 'portal_membership')
        anon = mtool.isAnonymousUser()

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)

        if anon:
            if settings.anonymous_email_enabled:
                # according to IDiscussionSettings.anonymous_email_enabled:
                # 'If selected, anonymous user will have to give their email.'
                self.widgets['author_email'].field.required = True
                self.widgets['author_email'].required = True
            else:
                self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
        else:
            self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty('email')

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid
        # email address
        member_email_is_empty = member_email == ''
        user_notification_disabled = not settings.user_notification_enabled
        if member_email_is_empty or user_notification_disabled or anon:
            self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
예제 #23
0
    def Title(self):
        """The title of the comment.
        """

        if self.title:
            return self.title

        if not self.creator:
            creator = translate(Message(_(u"label_anonymous", default=u"Anonymous")))
        else:
            creator = self.creator
            creator = creator

        # Fetch the content object (the parent of the comment is the
        # conversation, the parent of the conversation is the content object).
        content = aq_base(self.__parent__.__parent__)
        title = translate(
            Message(COMMENT_TITLE, mapping={"creator": creator, "content": safe_unicode(content.Title())})
        )
        return title
예제 #24
0
 def Title(self):
     """The title of the comment.
     """
     
     if self.title:
         return self.title
     
     if not self.creator:
         creator = translate(Message(_(u"label_anonymous",
                                       default=u"Anonymous")))
     else:
         creator = self.creator
         creator = creator
     
     # Fetch the content object (the parent of the comment is the
     # conversation, the parent of the conversation is the content object).
     content = aq_base(self.__parent__.__parent__)
     title = translate(
         Message(COMMENT_TITLE,
                 mapping={'creator': creator,
                          'content': safe_unicode(content.Title())}))
     return title
예제 #25
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))
예제 #26
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', 
            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))
예제 #27
0
def notify_user(obj, event):
    """Tell users when a comment has been added.

       This method composes and sends emails to all users that have added a
       comment to this conversation and enabled user notification.

       This requires the user_notification setting to be enabled in the
       discussion control panel.
    """

    # Check if user notification is enabled
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)
    if not settings.user_notification_enabled:
        return

    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, 'MailHost')
    registry = getUtility(IRegistry)
    mail_settings = registry.forInterface(IMailSchema, prefix='plone')
    sender = mail_settings.email_from_address

    # Check if a sender address is available
    if not sender:
        return

    # Compose and send emails to all users that have add a comment to this
    # conversation and enabled user_notification.
    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)

    # Avoid sending multiple notification emails to the same person
    # when he has commented multiple times.
    emails = set()
    for comment in conversation.getComments():
        obj_is_not_the_comment = obj != comment
        valid_user_email = comment.user_notification and comment.author_email
        if obj_is_not_the_comment and valid_user_email:
            emails.add(comment.author_email)

    if not emails:
        return

    subject = translate(_(u"A comment has been posted."),
                        context=obj.REQUEST)
    message = translate(
        Message(
            MAIL_NOTIFICATION_MESSAGE,
            mapping={
                'title': safe_unicode(content_object.title),
                'link': content_object.absolute_url() + '/view#' + obj.id,
                'text': obj.text
            }
        ),
        context=obj.REQUEST
    )
    for email in emails:
        # Send email
        try:
            mail_host.send(message,
                           email,
                           sender,
                           subject,
                           charset='utf-8')
        except SMTPException:
            logger.error('SMTP exception while trying to send an ' +
                         'email from %s to %s',
                         sender,
                         email)
예제 #28
0
from plone.app.discussion import PloneAppDiscussionMessageFactory as _
from plone.app.discussion.interfaces import IComment
from plone.app.discussion.interfaces import IConversation
from plone.app.discussion.interfaces import IDiscussionSettings

from Products.CMFCore.CMFCatalogAware import CatalogAware
from Products.CMFCore.CMFCatalogAware import WorkflowAware

from OFS.role import RoleManager
from AccessControl import ClassSecurityInfo
from Products.CMFCore import permissions


COMMENT_TITLE = _(
    u"comment_title",
    default=u"${author_name} on ${content}")

MAIL_NOTIFICATION_MESSAGE = _(
    u"mail_notification_message",
    default=u"A comment on '${title}' "
            u"has been posted here: ${link}\n\n"
            u"---\n"
            u"${text}\n"
            u"---\n")

MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
    u"mail_notification_message_moderator",
    default=u"A comment on '${title}' "
            u"has been posted here: ${link}\n\n"
            u"---\n"
예제 #29
0
class ICaptcha(Interface):
    """Captcha/ReCaptcha text field to extend the existing comment form.
    """
    captcha = schema.TextLine(title=_(u"Captcha"), required=False)
예제 #30
0
class IConversation(IIterableMapping):
    """A conversation about a content object.

    This is a persistent object in its own right and manages all comments.

    The dict interface allows access to all comments. They are stored by
    long integer key, in the order they were added.

    Note that __setitem__() is not supported - use addComment() instead.
    However, comments can be deleted using __delitem__().

    To get replies at the top level, adapt the conversation to IReplies.

    The conversation can be traversed to via the ++comments++ namespace.
    For example, path/to/object/++comments++/123 retrieves comment 123.

    The __parent__ of the conversation (and the acquisition parent during
    traversal) is the content object. The conversation is the __parent__
    (and acquisition parent) for all comments, regardless of threading.
    """

    total_comments = schema.Int(
        title=_(u"Total number of public comments on this item"),
        min=0,
        readonly=True,
    )

    last_comment_date = schema.Date(
        title=_(u"Date of the most recent public comment"),
        readonly=True,
    )

    commentators = schema.Set(
        title=_(u"The set of unique commentators (usernames)"),
        readonly=True,
    )

    public_commentators = schema.Set(
        title=_(u"The set of unique commentators (usernames) of"
                u" published_comments"),
        readonly=True,
    )

    def addComment(comment):
        """Adds a new comment to the list of comments, and returns the
        comment id that was assigned. The comment_id property on the comment
        will be set accordingly.
        """

    def __delitem__(key):
        """Delete the comment with the given key. The key is a long id.
        """

    def getComments(start=0, size=None):
        """Return an iterator of comment objects for rendering.

        The 'start' parameter is the id of the comment from which to start the
        batch. If no such comment exists, the next higher id will be used.
        This means that you can use max key from a previous batch + 1 safely.

        The 'size' parameter is the number of comments to return in the
        batch.

        The comments are returned in creation date order, in the exact batch
        size specified.
        """

    def getThreads(start=0, size=None, root=0, depth=None):
        """Return a batch of comment objects for rendering.
예제 #31
0
from plone.registry.interfaces import IRegistry

from plone.app.discussion import PloneAppDiscussionMessageFactory as _
from plone.app.discussion.interfaces import IComment
from plone.app.discussion.interfaces import IConversation
from plone.app.discussion.interfaces import IDiscussionSettings

from Products.CMFCore.CMFCatalogAware import CatalogAware
from Products.CMFCore.CMFCatalogAware import WorkflowAware

from OFS.role import RoleManager


COMMENT_TITLE = _(
    u"comment_title",
    default=u"${creator} on ${content}")

MAIL_NOTIFICATION_MESSAGE = _(
    u"mail_notification_message",
    default=u"A comment on '${title}' "
             "has been posted here: ${link}\n\n"
             "---\n"
             "${text}\n"
             "---\n")

MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
    u"mail_notification_message_moderator",
    default=u"A comment on '${title}' "
             "has been posted here: ${link}\n\n"
             "---\n"
예제 #32
0
 def handle_cancel(self, action):
         IStatusMessage(self.request).add(
             _(u'comment_edit_cancel_notification',
               default=u'Edit comment cancelled'),
             type='info')
         return self._redirect(target=self.context.absolute_url())
예제 #33
0
def isEmail(value):
    portal = getUtility(ISiteRoot)
    reg_tool = getToolByName(portal, 'portal_registration')
    if not (value and reg_tool.isValidEmail(value)):
        raise Invalid(_('Invalid email address.'))
    return True
예제 #34
0
class CommentFormWithHoneyPot(CommentForm):

    fields = field.Fields(ICommentWithHoneyPot).omit(
        "portal_type",
        "__parent__",
        "__name__",
        "comment_id",
        "mime_type",
        "creator",
        "creation_date",
        "modification_date",
        "author_username",
        "title",
    )

    def updateFields(self):
        super(CommentFormWithHoneyPot, self).updateFields()
        self.fields["honeypot"].widgetFactory = HiddenHoneyPotFieldWidget

    def updateWidgets(self):
        super(CommentFormWithHoneyPot, self).updateWidgets()
        self.widgets["honeypot"].label = u""

    @button.buttonAndHandler(_(u"Cancel"))
    def handleCancel(self, action):
        # This method should never be called, it's only there to show
        # a cancel button that is handled by a jQuery method.
        pass  # pragma: no cover

    @button.buttonAndHandler(_(u"add_comment_button", default=u"Comment"),
                             name="comment")
    def handleComment(self, action):
        if self.request.form["form.widgets.honeypot"]:
            return
        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))
import logging
from zope.component import queryUtility
from Products.CMFCore.utils import getToolByName
from smtplib import SMTPException
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.registry.interfaces import IRegistry
from zope.i18n import translate
from plone.app.discussion import PloneAppDiscussionMessageFactory as _
from Acquisition import aq_parent, aq_base, Implicit
from zope.i18nmessageid import Message
from Products.CMFPlone.utils import safe_unicode

MAIL_NOTIFICATION_MESSAGE = _(
    u"mail_notification_message",
    default=u"A comment on '${title}' "
            u"has been posted here: ${link}\n\n"
            u"---\n"
            u"${text}\n"
            u"---\n")

logger = logging.getLogger("plone.app.discussion")

def notify_user(obj, event):
    """Tell users when a comment has been added.

       This method composes and sends emails to all users that have added a
       comment to this conversation and enabled user notification.

       This requires the user_notification setting to be enabled in the
       discussion control panel.
    """
예제 #36
0
from plone.app.discussion.interfaces import IComment
from plone.app.discussion.interfaces import IReplies
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.app.discussion.interfaces import ICaptcha

from plone.app.discussion.browser.validator import CaptchaValidator

from plone.z3cform import z2
from plone.z3cform.widget import SingleCheckBoxWidget
from plone.z3cform.fieldsets import extensible


from plone.z3cform.interfaces import IWrappedForm

COMMENT_DESCRIPTION_PLAIN_TEXT = _(
    u"comment_description_plain_text",
    default=u"You can add a comment by filling out the form below. " + "Plain text formatting.",
)

COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
    u"comment_description_intelligent_text",
    default=u"You can add a comment by filling out the form below. "
    + "Plain text formatting. Web and email addresses are transformed "
    + "into clickable links.",
)

COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
    u"comment_description_moderation_enabled", default=u"Comments are moderated."
)


class CommentForm(extensible.ExtensibleForm, form.Form):
예제 #37
0
class CommentForm(extensible.ExtensibleForm, form.Form):

    ignoreContext = True  # don't use context to get widget data
    id = None
    label = _(u'Add a comment')
    fields = field.Fields(IComment).omit('portal_type',
                                         '__parent__',
                                         '__name__',
                                         'comment_id',
                                         'mime_type',
                                         'creator',
                                         'creation_date',
                                         'modification_date',
                                         'author_username',
                                         'title')

    def updateFields(self):
        super(CommentForm, self).updateFields()
        self.fields['user_notification'].widgetFactory = \
            SingleCheckBoxFieldWidget

    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
        self.widgets['text'].addClass('autoresize')
        self.widgets['user_notification'].label = _(u'')
        # Reset widget field settings to their defaults, which may be changed
        # further on.  Otherwise, the email field might get set to required
        # when an anonymous user visits, and then remain required when an
        # authenticated user visits, making it impossible for an authenticated
        # user to fill in the form without validation error.  Or when in the
        # control panel the field is set as not required anymore, that change
        # would have no effect until the instance was restarted.  Note that the
        # widget is new each time, but the field is the same item in memory as
        # the previous time.
        self.widgets['author_email'].field.required = False
        # The widget is new, but its 'required' setting is based on the
        # previous value on the field, so we need to reset it here.  Changing
        # the field in updateFields does not help.
        self.widgets['author_email'].required = False

        # Rename the id of the text widgets because there can be css-id
        # clashes with the text field of documents when using and overlay
        # with TinyMCE.
        self.widgets['text'].id = 'form-widgets-comment-text'

        # Anonymous / Logged-in
        mtool = getToolByName(self.context, 'portal_membership')
        anon = mtool.isAnonymousUser()

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)

        if anon:
            if settings.anonymous_email_enabled:
                # according to IDiscussionSettings.anonymous_email_enabled:
                # 'If selected, anonymous user will have to give their email.'
                self.widgets['author_email'].field.required = True
                self.widgets['author_email'].required = True
            else:
                self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
        else:
            self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty('email')

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid
        # email address
        member_email_is_empty = member_email == ''
        user_notification_disabled = not settings.user_notification_enabled
        if member_email_is_empty or user_notification_disabled or anon:
            self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE

    def updateActions(self):
        super(CommentForm, self).updateActions()
        self.actions['cancel'].addClass('standalone')
        self.actions['cancel'].addClass('hide')
        self.actions['comment'].addClass('context')

    def get_author(self, data):
        context = aq_inner(self.context)
        # some attributes are not always set
        author_name = u''

        # Make sure author_name/ author_email is properly encoded
        if 'author_name' in data:
            author_name = safe_unicode(data['author_name'])
        if 'author_email' in data:
            author_email = safe_unicode(data['author_email'])

        # Set comment author properties for anonymous users or members
        portal_membership = getToolByName(context, 'portal_membership')
        anon = portal_membership.isAnonymousUser()
        if not anon and getSecurityManager().checkPermission(
                'Reply to item', context):
            # Member
            member = portal_membership.getAuthenticatedMember()
            email = safe_unicode(member.getProperty('email'))
            fullname = member.getProperty('fullname')
            if not fullname or fullname == '':
                fullname = member.getUserName()
            fullname = safe_unicode(fullname)
            author_name = fullname
            email = safe_unicode(email)
            # XXX: according to IComment interface author_email must not be  # noqa T000
            # set for logged in users, cite:
            # 'for anonymous comments only, set to None for logged in comments'
            author_email = email
            # /XXX  # noqa T000

        return author_name, author_email

    def create_comment(self, data):
        context = aq_inner(self.context)
        comment = createObject('plone.Comment')

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        anonymous_comments = settings.anonymous_comments

        # 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])

        # Set dates
        comment.creation_date = datetime.utcnow()
        comment.modification_date = datetime.utcnow()

        # Get author name and email
        comment.author_name, comment.author_email = self.get_author(data)

        # Set comment author properties for anonymous users or members
        portal_membership = getToolByName(context, 'portal_membership')
        anon = portal_membership.isAnonymousUser()
        if anon and anonymous_comments:
            # Anonymous Users
            comment.user_notification = None
        elif not anon and getSecurityManager().checkPermission(
                'Reply to item', context):
            # Member
            member = portal_membership.getAuthenticatedMember()
            memberid = member.getId()
            user = member.getUser()
            comment.changeOwnership(user, recursive=False)
            comment.manage_setLocalRoles(memberid, ['Owner'])
            comment.creator = memberid
            comment.author_username = memberid

        else:  # pragma: no cover
            raise Unauthorized(
                u'Anonymous user tries to post a comment, but anonymous '
                u'commenting is disabled. Or user does not have the '
                u"'reply to item' permission.",
            )

        return comment

    @button.buttonAndHandler(_(u'add_comment_button', default=u'Comment'),
                             name='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))

    @button.buttonAndHandler(_(u'Cancel'))
    def handleCancel(self, action):
        # This method should never be called, it's only there to show
        # a cancel button that is handled by a jQuery method.
        pass  # pragma: no cover
예제 #38
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))
예제 #39
0
class IDiscussionSettings(Interface):
    """Global discussion settings. This describes records stored in the
    configuration registry and obtainable via plone.registry.
    """

    # Todo: Write a short hint, that other discussion related options can
    # be found elsewhere in the Plone control panel:
    #
    # - Types control panel: Allow comments on content types
    # - Search control panel: Show comments in search results

    globally_enabled = schema.Bool(
        title=_(u'label_globally_enabled',
                default=u'Globally enable comments'),
        description=_(
            u'help_globally_enabled',
            default=u'If selected, users are able to post comments on the '
            u'site. However, you will still need to enable comments '
            u'for specific content types, folders or content '
            u'objects before users will be able to post comments.',
        ),
        required=False,
        default=False,
    )

    anonymous_comments = schema.Bool(
        title=_(u'label_anonymous_comments',
                default='Enable anonymous comments'),
        description=_(
            u'help_anonymous_comments',
            default=u'If selected, anonymous users are able to post '
            u'comments without logging in. It is highly '
            u'recommended to use a captcha solution to prevent '
            u'spam if this setting is enabled.',
        ),
        required=False,
        default=False,
    )

    anonymous_email_enabled = schema.Bool(
        title=_(u'label_anonymous_email_enabled',
                default=u'Enable anonymous email field'),
        description=_(
            u'help_anonymous_email_enabled',
            default=u'If selected, anonymous user will have to '
            u'give their email.',
        ),
        required=False,
        default=False,
    )

    moderation_enabled = schema.Bool(
        title=_(
            u'label_moderation_enabled',
            default='Enable comment moderation',
        ),
        description=_(
            u'help_moderation_enabled',
            default=u'If selected, comments will enter a "Pending" state '
            u'in which they are invisible to the public. A user '
            u'with the "Review comments" permission ("Reviewer" '
            u'or "Manager") can approve comments to make them '
            u'visible to the public. If you want to enable a '
            u'custom comment workflow, you have to go to the '
            u'types control panel.',
        ),
        required=False,
        default=False,
    )

    edit_comment_enabled = schema.Bool(
        title=_(u'label_edit_comment_enabled',
                default='Enable editing of comments'),
        description=_(u'help_edit_comment_enabled',
                      default=u'If selected, supports editing '
                      'of comments for users with the "Edit comments" '
                      'permission.'),
        required=False,
        default=False,
    )

    delete_own_comment_enabled = schema.Bool(
        title=_(u'label_delete_own_comment_enabled',
                default='Enable deleting own comments'),
        description=_(u'help_delete_own_comment_enabled',
                      default=u'If selected, supports deleting '
                      'of own comments for users with the '
                      '"Delete own comments" permission.'),
        required=False,
        default=False,
    )

    text_transform = schema.Choice(
        title=_(u'label_text_transform', default='Comment text transform'),
        description=_(
            u'help_text_transform',
            default=u'Use this setting to choose if the comment text '
            u'should be transformed in any way. You can choose '
            u'between "Plain text" and "Intelligent text". '
            u'"Intelligent text" converts plain text into HTML '
            u'where line breaks and indentation is preserved, '
            u'and web and email addresses are made into '
            u'clickable links.'),
        required=True,
        default='text/plain',
        vocabulary='plone.app.discussion.vocabularies.TextTransformVocabulary',
    )

    captcha = schema.Choice(
        title=_(u'label_captcha', default='Captcha'),
        description=_(u'help_captcha',
                      default=u'Use this setting to enable or disable Captcha '
                      u'validation for comments. Install '
                      u'plone.formwidget.captcha, '
                      u'plone.formwidget.recaptcha, collective.akismet, or '
                      u'collective.z3cform.norobots if there are no options '
                      u'available.'),
        required=True,
        default='disabled',
        vocabulary='plone.app.discussion.vocabularies.CaptchaVocabulary',
    )

    show_commenter_image = schema.Bool(
        title=_(u'label_show_commenter_image',
                default=u'Show commenter image'),
        description=_(
            u'help_show_commenter_image',
            default=u'If selected, an image of the user is shown next to '
            u'the comment.'),
        required=False,
        default=True,
    )

    moderator_notification_enabled = schema.Bool(
        title=_(u'label_moderator_notification_enabled',
                default=u'Enable moderator email notification'),
        description=_(
            u'help_moderator_notification_enabled',
            default=u'If selected, the moderator is notified if a comment '
            u'needs attention. The moderator email address can '
            u'be set below.'),
        required=False,
        default=False,
    )

    moderator_email = schema.ASCIILine(
        title=_(
            u'label_moderator_email',
            default=u'Moderator Email Address',
        ),
        description=_(u'help_moderator_email',
                      default=u'Address to which moderator notifications '
                      u'will be sent.'),
        required=False,
    )

    user_notification_enabled = schema.Bool(
        title=_(
            u'label_user_notification_enabled',
            default=u'Enable user email notification',
        ),
        description=_(u'help_user_notification_enabled',
                      default=u'If selected, users can choose to be notified '
                      u'of new comments by email.'),
        required=False,
        default=False,
    )
예제 #40
0
class IComment(Interface):
    """A comment.

    Comments are indexed in the catalog and subject to workflow and security.
    """

    portal_type = schema.ASCIILine(
        title=_(u"Portal type"),
        default="Discussion Item",
    )

    __parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)

    __name__ = schema.TextLine(title=_(u"Name"))

    comment_id = schema.Int(
        title=_(u"A comment id unique to this conversation"))

    in_reply_to = schema.Int(
        title=_(u"Id of comment this comment is in reply to"),
        required=False,
    )

    # for logged in comments - set to None for anonymous
    author_username = schema.TextLine(title=_(u"Name"), required=False)

    # for anonymous comments only, set to None for logged in comments
    author_name = schema.TextLine(title=_(u"Name"), required=False)
    author_email = schema.TextLine(title=_(u"Email"), required=False)

    title = schema.TextLine(title=_(u"label_subject", default=u"Subject"))

    mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
    text = schema.Text(title=_(u"label_comment", default=u"Comment"))

    user_notification = schema.Bool(
        title=_(u"Notify me of new comments via email."), required=False)

    creator = schema.TextLine(title=_(u"Username of the commenter"))
    creation_date = schema.Date(title=_(u"Creation date"))
    modification_date = schema.Date(title=_(u"Modification date"))
예제 #41
0
from Products.CMFPlone.utils import safe_unicode
from smtplib import SMTPException
from zope.annotation.interfaces import IAnnotatable
from zope.component import getUtility
from zope.component import queryUtility
from zope.component.factory import Factory
from zope.event import notify
from zope.i18n import translate
from zope.i18nmessageid import Message
from zope.interface import implementer

import logging


COMMENT_TITLE = _(
    u'comment_title',
    default=u'${author_name} on ${content}')

MAIL_NOTIFICATION_MESSAGE = _(
    u'mail_notification_message',
    default=u'A comment on "${title}" '
            u'has been posted here: ${link}\n\n'
            u'---\n'
            u'${text}\n'
            u'---\n')

MAIL_NOTIFICATION_MESSAGE_MODERATOR = _(
    u'mail_notification_message_moderator',
    default=u'A comment on "${title}" '
            u'has been posted here: ${link}\n\n'
            u'---\n'
예제 #42
0
class IDiscussionSettings(Interface):
    """Global discussion settings. This describes records stored in the
    configuration registry and obtainable via plone.registry.
    """

    # Todo: Write a short hint, that other discussion related options can
    # be found elsewhere in the Plone control panel:
    #
    # - Types control panel: Allow comments on content types
    # - Search control panel: Show comments in search results

    globally_enabled = schema.Bool(
        title=_(u"label_globally_enabled",
                default=u"Globally enable comments"),
        description=_(
            u"help_globally_enabled",
            default=u"If selected, users are able to post comments on the "
            u"site. Though, you have to enable comments for "
            u"specific content types, folders or content objects "
            u"before users will be able to post comments."),
        required=False,
        default=False,
    )

    anonymous_comments = schema.Bool(
        title=_(u"label_anonymous_comments",
                default="Enable anonymous comments"),
        description=_(u"help_anonymous_comments",
                      default=u"If selected, anonymous users are able to post "
                      u"comments without loggin in. It is highly "
                      u"recommended to use a captcha solution to prevent "
                      u"spam if this setting is enabled."),
        required=False,
        default=False,
    )

    anonymous_email_enabled = schema.Bool(
        title=_(u"label_anonymous_email_enabled",
                default=u"Enable anonymous email field"),
        description=_(u"help_anonymous_email_enabled",
                      default=u"If selected, anonymous user will have to "
                      u"give their email."),
        required=False,
        default=False)

    moderation_enabled = schema.Bool(
        title=_(u"label_moderation_enabled",
                default="Enable comment moderation"),
        description=_(
            u"help_moderation_enabled",
            default=u"If selected, comments will enter a 'Pending' state "
            u"in which they are invisible to the public. A user "
            u"with the 'Review comments' permission ('Reviewer' "
            u"or 'Manager') can approve comments to make them "
            u"visible to the public. If you want to enable a "
            u"custom comment workflow, you have to go to the "
            u"types control panel."),
        required=False,
        default=False,
    )

    edit_comment_enabled = schema.Bool(
        title=_(u"label_edit_comment_enabled",
                default="Enable editing of comments"),
        description=_(u"help_edit_comment_enabled",
                      default=u"If selected, supports editing "
                      "of comments for users with the 'Edit comments' "
                      "permission."),
        required=False,
        default=False,
    )

    delete_own_comment_enabled = schema.Bool(
        title=_(u"label_delete_own_comment_enabled",
                default="Enable deleting own comments"),
        description=_(u"help_delete_own_comment_enabled",
                      default=u"If selected, supports deleting "
                      "of own comments for users with the "
                      "'Delete own comments' permission."),
        required=False,
        default=False,
    )

    text_transform = schema.Choice(
        title=_(u"label_text_transform", default="Comment text transform"),
        description=_(
            u"help_text_transform",
            default=u"Use this setting to choose if the comment text " +
            u"should be transformed in any way. You can choose "
            u"between 'Plain text' and 'Intelligent text'. " +
            u"'Intelligent text' converts plain text into HTML " +
            u"where line breaks and indentation is preserved, " +
            u"and web and email addresses are made into " +
            u"clickable links."),
        required=True,
        default='text/plain',
        vocabulary='plone.app.discussion.vocabularies.TextTransformVocabulary',
    )

    captcha = schema.Choice(
        title=_(u"label_captcha", default="Captcha"),
        description=_(u"help_captcha",
                      default=u"Use this setting to enable or disable Captcha "
                      u"validation for comments. Install "
                      u"plone.formwidget.captcha, "
                      u"plone.formwidget.recaptcha, collective.akismet, or "
                      u"collective.z3cform.norobots if there are no options "
                      u"available."),
        required=True,
        default='disabled',
        vocabulary='plone.app.discussion.vocabularies.CaptchaVocabulary',
    )

    show_commenter_image = schema.Bool(
        title=_(u"label_show_commenter_image",
                default=u"Show commenter image"),
        description=_(
            u"help_show_commenter_image",
            default=u"If selected, an image of the user is shown next to "
            u"the comment."),
        required=False,
        default=True,
    )

    moderator_notification_enabled = schema.Bool(
        title=_(u"label_moderator_notification_enabled",
                default=u"Enable moderator email notification"),
        description=_(
            u"help_moderator_notification_enabled",
            default=u"If selected, the moderator is notified if a comment "
            u"needs attention. The moderator email address can " +
            u"be set below."),
        required=False,
        default=False,
    )

    moderator_email = schema.ASCIILine(
        title=_(u'label_moderator_email', default=u'Moderator Email Address'),
        description=_(u'help_moderator_email',
                      default=u"Address to which moderator notifications "
                      u"will be sent."),
        required=False,
    )

    user_notification_enabled = schema.Bool(
        title=_(u"label_user_notification_enabled",
                default=u"Enable user email notification"),
        description=_(u"help_user_notification_enabled",
                      default=u"If selected, users can choose to be notified "
                      u"of new comments by email."),
        required=False,
        default=False)
예제 #43
0
def notify_user(obj, event):
    # Check if user notification is enabled
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)
    if not settings.user_notification_enabled:
        return

    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, 'MailHost')
    portal_url = getToolByName(obj, 'portal_url')
    portal = portal_url.getPortalObject()


    # Compose and send emails to all users that have add a comment to this
    # conversation and enabled user_notification.
    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)

    sender = '"KSP Comment Notification" <*****@*****.**>'

    # Check if a sender address is available
    if not sender:
        return

    # Avoid sending multiple notification emails to the same person
    # when he has commented multiple times.
    emails = set()
    for comment in conversation.getComments():
        if (obj != comment and
            comment.user_notification and comment.author_email):
            emails.add(comment.author_email)

    if obj.author_email and obj.author_email in emails:
        emails.remove(obj.author_email)

    if not emails:
        return

    subject = translate(_(u"A comment has been posted."),
                        context=obj.REQUEST)
    message = translate(Message(
            MAIL_NOTIFICATION_MESSAGE,
            mapping={'title': safe_unicode(content_object.title),
                     'link': content_object.absolute_url() +
                             '/view#' + obj.id,
                     'name': obj.author_name,
                     'text': obj.text}),
            context=obj.REQUEST)
    for email in emails:
        # Send email
        try:
            mail_host.send(message,
                           email,
                           sender,
                           subject,
                           charset='utf-8')
        except SMTPException:
            padcomment.logger.error('SMTP exception while trying to send an ' +
                         'email from %s to %s',
                         sender,
                         email)
예제 #44
0
from plone.app.discussion import PloneAppDiscussionMessageFactory as _
from plone.app.discussion.interfaces import IConversation
from plone.app.discussion.interfaces import IComment
from plone.app.discussion.interfaces import IReplies
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.app.discussion.interfaces import ICaptcha

from plone.app.discussion.browser.validator import CaptchaValidator

from plone.z3cform import z2
from plone.z3cform.fieldsets import extensible

from plone.z3cform.interfaces import IWrappedForm

COMMENT_DESCRIPTION_PLAIN_TEXT = _(
    u"comment_description_plain_text",
    default=u"You can add a comment by filling out the form below. " +
    "Plain text formatting.")

COMMENT_DESCRIPTION_MARKDOWN = _(
    u"comment_description_markdown",
    default=u"You can add a comment by filling out the form below. " +
    "Plain text formatting. You can use the Markdown syntax for " +
    "links and images.")

COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
    u"comment_description_intelligent_text",
    default=u"You can add a comment by filling out the form below. " +
    "Plain text formatting. Web and email addresses are " +
    "transformed into clickable links.")

COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
예제 #45
0
class CommentForm(extensible.ExtensibleForm, form.Form):

    ignoreContext = True  # don't use context to get widget data
    id = None
    label = _(u"Add a comment")
    fields = field.Fields(IComment).omit('portal_type', '__parent__',
                                         '__name__', 'comment_id', 'mime_type',
                                         'creator', 'creation_date',
                                         'modification_date',
                                         'author_username', 'title')

    def updateFields(self):
        super(CommentForm, self).updateFields()
        self.fields['user_notification'].widgetFactory = \
            SingleCheckBoxFieldWidget

    def updateWidgets(self):
        super(CommentForm, self).updateWidgets()

        # Widgets
        self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
        self.widgets['text'].addClass("autoresize")
        self.widgets['user_notification'].label = _(u"")

        # Rename the id of the text widgets because there can be css-id
        # clashes with the text field of documents when using and overlay
        # with TinyMCE.
        self.widgets['text'].id = "form-widgets-comment-text"

        # Anonymous / Logged-in
        mtool = getToolByName(self.context, 'portal_membership')
        if not mtool.isAnonymousUser():
            self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
            self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        # Todo: Since we are not using the author_email field in the
        # current state, we hide it by default. But we keep the field for
        # integrators or later use.
        self.widgets['author_email'].mode = interfaces.HIDDEN_MODE

        registry = queryUtility(IRegistry)
        settings = registry.forInterface(IDiscussionSettings, check=False)
        member = mtool.getAuthenticatedMember()
        member_email = member.getProperty('email')

        # Hide the user_notification checkbox if user notification is disabled
        # or the user is not logged in. Also check if the user has a valid
        # email address
        if member_email == '' or \
           not settings.user_notification_enabled or \
           mtool.isAnonymousUser():
            self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE

    def updateActions(self):
        super(CommentForm, self).updateActions()
        self.actions['cancel'].addClass("standalone")
        self.actions['cancel'].addClass("hide")
        self.actions['comment'].addClass("context")

    @button.buttonAndHandler(_(u"add_comment_button", default=u"Comment"),
                             name='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')
        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))

    @button.buttonAndHandler(_(u"Cancel"))
    def handleCancel(self, action):
        # This method should never be called, it's only there to show
        # a cancel button that is handled by a jQuery method.
        pass  # pragma: no cover
예제 #46
0
from plone.app.discussion.interfaces import IConversation
from plone.app.discussion.interfaces import IComment
from plone.app.discussion.interfaces import IReplies
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.app.discussion.interfaces import ICaptcha

from plone.app.discussion.browser.validator import CaptchaValidator

from plone.z3cform import z2
from plone.z3cform.fieldsets import extensible


from plone.z3cform.interfaces import IWrappedForm

COMMENT_DESCRIPTION_PLAIN_TEXT = _(
    u"comment_description_plain_text",
    default=u"You can add a comment by filling out the form below. " +
             "Plain text formatting.")

COMMENT_DESCRIPTION_MARKDOWN = _(
    u"comment_description_markdown",
    default=u"You can add a comment by filling out the form below. " +
             "Plain text formatting. You can use the Markdown syntax for " +
             "links and images.")

COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
    u"comment_description_intelligent_text",
    default=u"You can add a comment by filling out the form below. " +
             "Plain text formatting. Web and email addresses are " +
             "transformed into clickable links.")

COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
예제 #47
0
from z3c.form import button
from z3c.form import field
from z3c.form import form
from z3c.form import interfaces
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
from z3c.form.interfaces import IFormLayer
from zope.component import createObject
from zope.component import queryUtility
from zope.i18n import translate
from zope.i18nmessageid import Message
from zope.interface import alsoProvides


COMMENT_DESCRIPTION_PLAIN_TEXT = _(
    u'comment_description_plain_text',
    default=u'You can add a comment by filling out the form below. '
            u'Plain text formatting.',
)

COMMENT_DESCRIPTION_MARKDOWN = _(
    u'comment_description_markdown',
    default=u'You can add a comment by filling out the form below. '
            u'Plain text formatting. You can use the Markdown syntax for '
            u'links and images.',
)

COMMENT_DESCRIPTION_INTELLIGENT_TEXT = _(
    u'comment_description_intelligent_text',
    default=u'You can add a comment by filling out the form below. '
            u'Plain text formatting. Web and email addresses are '
            u'transformed into clickable links.',
예제 #48
0
def isEmail(value):
    portal = getUtility(ISiteRoot)
    reg_tool = getToolByName(portal, 'portal_registration')
    if not (value and reg_tool.isValidEmail(value)):
        raise Invalid(_('Invalid email address.'))
    return True
예제 #49
0
def notify_user(obj, event):
    """Tell users when a comment has been added.
       
       This method composes and sends emails to all users that have added a
       comment to this conversation and enabled user notification.
       
       This requires the user_notification setting to be enabled in the
       discussion control panel.
    """
    
    # Check if user notification is enabled
    registry = queryUtility(IRegistry)
    settings = registry.forInterface(IDiscussionSettings, check=False)
    if not settings.user_notification_enabled:
        return
    
    # Get informations that are necessary to send an email
    mail_host = getToolByName(obj, 'MailHost')
    portal_url = getToolByName(obj, 'portal_url')
    portal = portal_url.getPortalObject()
    sender = portal.getProperty('email_from_address')

    # Check if a sender address is available
    if not sender:
        return

    # Compose and send emails to all users that have add a comment to this
    # conversation and enabled user_notification.
    conversation = aq_parent(obj)
    content_object = aq_parent(conversation)

    # Avoid sending multiple notification emails to the same person
    # when he has commented multiple times.
    emails = set()
    for comment in conversation.getComments():
        if (obj != comment and
            comment.user_notification and comment.author_email):
            emails.add(comment.author_email)
    
    if not emails:
        return
    
    subject = translate(_(u"A comment has been posted."),
                        context=obj.REQUEST)
    message = translate(Message(
            MAIL_NOTIFICATION_MESSAGE,
            mapping={'title': safe_unicode(content_object.title),
                     'link': content_object.absolute_url() + 
                             '/view#' + obj.id,
                     'text': obj.text}),
            context=obj.REQUEST)
    for email in emails:
        # Send email
        try:
            mail_host.send(message,
                           email,
                           sender,
                           subject,
                           charset='utf-8')
        except SMTPException:
            logger.error('SMTP exception while trying to send an ' +
                         'email from %s to %s',
                         sender,
                         email)