The way it happens now, the timezone gets corrected svn path=/plone.app.discussion/trunk/; revision=39728
		
			
				
	
	
		
			354 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding: utf-8 -*-
 | 
						|
from Acquisition import aq_inner
 | 
						|
 | 
						|
from AccessControl import Unauthorized
 | 
						|
from AccessControl import getSecurityManager
 | 
						|
 | 
						|
from datetime import datetime
 | 
						|
from DateTime import DateTime
 | 
						|
 | 
						|
from urllib import quote as url_quote
 | 
						|
 | 
						|
from zope.component import createObject, queryUtility
 | 
						|
 | 
						|
from zope.interface import alsoProvides
 | 
						|
 | 
						|
from z3c.form import form, field, button, interfaces
 | 
						|
from z3c.form.interfaces import IFormLayer
 | 
						|
 | 
						|
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
 | 
						|
from Products.CMFCore.utils import getToolByName
 | 
						|
from Products.statusmessages.interfaces import IStatusMessage
 | 
						|
 | 
						|
from plone.registry.interfaces import IRegistry
 | 
						|
 | 
						|
from plone.app.layout.viewlets.common import ViewletBase
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface 
 | 
						|
try:
 | 
						|
    from plone.z3cform.interfaces import IWrappedForm 
 | 
						|
    HAS_WRAPPED_FORM = True 
 | 
						|
except ImportError: 
 | 
						|
    HAS_WRAPPED_FORM = False
 | 
						|
 | 
						|
class CommentForm(extensible.ExtensibleForm, form.Form):
 | 
						|
 | 
						|
    ignoreContext = True # don't use context to get widget data
 | 
						|
    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')
 | 
						|
 | 
						|
    def updateFields(self):
 | 
						|
        super(CommentForm, self).updateFields()
 | 
						|
        #self.fields['author_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['author_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
 | 
						|
 | 
						|
        # XXX: 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
 | 
						|
         
 | 
						|
        # XXX: Author notification code
 | 
						|
        #registry = queryUtility(IRegistry)
 | 
						|
        #settings = registry.forInterface(IDiscussionSettings)
 | 
						|
        #if not settings.user_notification_enabled:
 | 
						|
        #    self.widgets['author_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)
 | 
						|
        wf = getToolByName(context, 'portal_workflow')
 | 
						|
        
 | 
						|
        data, errors = self.extractData()
 | 
						|
        if errors:
 | 
						|
            return
 | 
						|
 | 
						|
        title = u""
 | 
						|
        text = u""
 | 
						|
        author_name = u""
 | 
						|
        author_email = u""
 | 
						|
        #author_notification = None
 | 
						|
 | 
						|
        # Captcha check for anonymous users (if Captcha is enabled and 
 | 
						|
        # anonymous commenting is allowed)
 | 
						|
        registry = queryUtility(IRegistry)
 | 
						|
        settings = registry.forInterface(IDiscussionSettings)
 | 
						|
        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'])
 | 
						|
 | 
						|
        # Fetch data from request
 | 
						|
        if 'title' in data:
 | 
						|
            title = data['title']
 | 
						|
        if 'text' in data:
 | 
						|
            text = data['text']
 | 
						|
        if 'author_name' in data:
 | 
						|
            author_name = data['author_name']
 | 
						|
        if 'author_email' in data:
 | 
						|
            author_email = data['author_email']
 | 
						|
        #if 'author_notification' in data:
 | 
						|
        #    author_notification = data['author_notification']
 | 
						|
            
 | 
						|
        # The add-comment view is called on the conversation object
 | 
						|
        conversation = IConversation(self.__parent__)
 | 
						|
        
 | 
						|
        # Check if conversation is enabled on this content object
 | 
						|
        if not conversation.enabled():
 | 
						|
            raise Unauthorized, "Discussion is not enabled for this content\
 | 
						|
                                 object."
 | 
						|
 | 
						|
        if data['in_reply_to']:
 | 
						|
            # Fetch the comment we want to reply to
 | 
						|
            conversation_to_reply_to = conversation.get(data['in_reply_to'])
 | 
						|
            replies = IReplies(conversation_to_reply_to)
 | 
						|
 | 
						|
        # Create the comment
 | 
						|
        comment = createObject('plone.Comment')
 | 
						|
        comment.title = title
 | 
						|
        comment.text = text
 | 
						|
 | 
						|
        portal_membership = getToolByName(self.context, 'portal_membership')
 | 
						|
 | 
						|
        if portal_membership.isAnonymousUser() and \
 | 
						|
        settings.anonymous_comments:
 | 
						|
            comment.creator = None
 | 
						|
            comment.author_name = author_name
 | 
						|
            comment.author_email = author_email
 | 
						|
            #comment.author_notification = author_notification
 | 
						|
            comment.creation_date = comment.modification_date = datetime.utcnow()
 | 
						|
        elif not portal_membership.isAnonymousUser():
 | 
						|
            member = portal_membership.getAuthenticatedMember()
 | 
						|
            comment.creator = member.id
 | 
						|
            comment.author_username = member.getUserName()
 | 
						|
            comment.author_name = member.getProperty('fullname')
 | 
						|
            comment.author_email = member.getProperty('email')
 | 
						|
            #comment.author_notification = comment.author_notification
 | 
						|
            comment.creation_date = comment.modification_date = datetime.utcnow()
 | 
						|
        else:
 | 
						|
            raise Unauthorized, "Anonymous user tries to post a comment, but \
 | 
						|
                                 anonymous commenting is disabled."
 | 
						|
        
 | 
						|
        # Check if the added comment is a reply to an existing comment
 | 
						|
        # or just a regular reply to the content object.
 | 
						|
        if data['in_reply_to']:
 | 
						|
            # Add a reply to an existing comment
 | 
						|
            comment_id = replies.addComment(comment)
 | 
						|
        else:
 | 
						|
            # Add a comment to the conversation
 | 
						|
            comment_id = conversation.addComment(comment)
 | 
						|
 | 
						|
        # If a user post a comment and moderation is enabled, a message is shown
 | 
						|
        # to the user that his/her comment awaits moderation. If the user has 
 | 
						|
        # manage right, he/she is redirected directly to the comment.
 | 
						|
        can_manage = getSecurityManager().checkPermission('Manage portal', 
 | 
						|
                                                          context)
 | 
						|
        if wf.getChainForPortalType('Discussion Item') == \
 | 
						|
            ('comment_review_workflow',) and not can_manage:
 | 
						|
            # 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
 | 
						|
 | 
						|
 | 
						|
class CommentsViewlet(ViewletBase):
 | 
						|
 | 
						|
    form = CommentForm
 | 
						|
    index = ViewPageTemplateFile('comments.pt')
 | 
						|
 | 
						|
    def update(self):
 | 
						|
        super(CommentsViewlet, self).update()
 | 
						|
        z2.switch_on(self, request_layer=IFormLayer)
 | 
						|
        self.form = self.form(aq_inner(self.context), self.request)
 | 
						|
        if HAS_WRAPPED_FORM: 
 | 
						|
            alsoProvides(self.form, IWrappedForm) 
 | 
						|
        self.form.update()
 | 
						|
 | 
						|
    # view methods
 | 
						|
 | 
						|
    def cook(self, text):
 | 
						|
        transforms = getToolByName(self, 'portal_transforms')
 | 
						|
        targetMimetype = 'text/html'
 | 
						|
        registry = queryUtility(IRegistry)
 | 
						|
        settings = registry.forInterface(IDiscussionSettings)
 | 
						|
        mimetype = settings.text_transform        
 | 
						|
        return transforms.convertTo(targetMimetype, 
 | 
						|
                                    text, 
 | 
						|
                                    context=self, 
 | 
						|
                                    mimetype=mimetype).getData()
 | 
						|
    
 | 
						|
    def can_reply(self):
 | 
						|
        return getSecurityManager().checkPermission('Reply to item', 
 | 
						|
                                                    aq_inner(self.context))
 | 
						|
 | 
						|
    def can_manage(self):
 | 
						|
        return getSecurityManager().checkPermission('Manage portal', 
 | 
						|
                                                    aq_inner(self.context))
 | 
						|
 | 
						|
    def is_discussion_allowed(self):
 | 
						|
        context = aq_inner(self.context)
 | 
						|
        conversation = IConversation(context)
 | 
						|
        return conversation.enabled()
 | 
						|
 | 
						|
    def has_replies(self, workflow_actions=False):
 | 
						|
        """Returns true if there are replies.
 | 
						|
        """
 | 
						|
        if self.get_replies(workflow_actions) is not None:
 | 
						|
            try:
 | 
						|
                self.get_replies(workflow_actions).next()
 | 
						|
                return True
 | 
						|
            except StopIteration:
 | 
						|
                pass
 | 
						|
        return False
 | 
						|
 | 
						|
    def get_replies(self, workflow_actions=False):
 | 
						|
        """Returns all replies to a content object.
 | 
						|
 | 
						|
        If workflow_actions is false, only published
 | 
						|
        comments are returned.
 | 
						|
 | 
						|
        If workflow actions is true, comments are
 | 
						|
        returned with workflow actions.
 | 
						|
        """
 | 
						|
        context = aq_inner(self.context)
 | 
						|
        conversation = IConversation(context)
 | 
						|
 | 
						|
        wf = getToolByName(context, 'portal_workflow')
 | 
						|
 | 
						|
        # workflow_actions is only true when user
 | 
						|
        # has 'Manage portal' permission
 | 
						|
 | 
						|
        def replies_with_workflow_actions():
 | 
						|
            # Generator that returns replies dict with workflow actions
 | 
						|
            for r in conversation.getThreads():
 | 
						|
                comment_obj = r['comment']
 | 
						|
                # list all possible workflow actions
 | 
						|
                actions = [a for a in wf.listActionInfos(object=comment_obj)
 | 
						|
                               if a['category'] == 'workflow' and a['allowed']]
 | 
						|
                r = r.copy()
 | 
						|
                r['actions'] = actions
 | 
						|
                yield r
 | 
						|
 | 
						|
        def published_replies():
 | 
						|
            # Generator that returns replies dict with workflow status.
 | 
						|
            for r in conversation.getThreads():
 | 
						|
                comment_obj = r['comment']
 | 
						|
                workflow_status = wf.getInfoFor(comment_obj, 'review_state')
 | 
						|
                if workflow_status == 'published':
 | 
						|
                    r = r.copy()
 | 
						|
                    r['workflow_status'] = workflow_status
 | 
						|
                    yield r
 | 
						|
        
 | 
						|
        # Return all direct replies
 | 
						|
        if conversation.total_comments > 0:
 | 
						|
            if workflow_actions:
 | 
						|
                return replies_with_workflow_actions()
 | 
						|
            else:
 | 
						|
                return published_replies()
 | 
						|
 | 
						|
    def get_commenter_home_url(self, username=None):
 | 
						|
        if username is None:
 | 
						|
            return None
 | 
						|
        else:
 | 
						|
            return "%s/author/%s" % (self.context.portal_url(), username)
 | 
						|
 | 
						|
    def get_commenter_portrait(self, username=None):
 | 
						|
 | 
						|
        if username is None:
 | 
						|
            # return the default user image if no username is given
 | 
						|
            return 'defaultUser.gif'
 | 
						|
        else:
 | 
						|
            portal_membership = getToolByName(self.context,
 | 
						|
                                              'portal_membership',
 | 
						|
                                              None)
 | 
						|
            return portal_membership.getPersonalPortrait(username)\
 | 
						|
                   .absolute_url()
 | 
						|
 | 
						|
    def anonymous_discussion_allowed(self):
 | 
						|
        # Check if anonymous comments are allowed in the registry
 | 
						|
        registry = queryUtility(IRegistry)
 | 
						|
        settings = registry.forInterface(IDiscussionSettings)
 | 
						|
        return settings.anonymous_comments
 | 
						|
 | 
						|
    def show_commenter_image(self):
 | 
						|
        # Check if showing commenter image is enabled in the registry
 | 
						|
        registry = queryUtility(IRegistry)
 | 
						|
        settings = registry.forInterface(IDiscussionSettings)
 | 
						|
        return settings.show_commenter_image
 | 
						|
 | 
						|
    def is_anonymous(self):
 | 
						|
        portal_membership = getToolByName(self.context, 
 | 
						|
                                          'portal_membership', 
 | 
						|
                                          None)
 | 
						|
        return portal_membership.isAnonymousUser()
 | 
						|
    
 | 
						|
    def login_action(self):
 | 
						|
        return '%s/login_form?came_from=%s' % \
 | 
						|
            (self.navigation_root_url,
 | 
						|
             url_quote(self.request.get('URL', '')),)
 | 
						|
 | 
						|
    def format_time(self, time):
 | 
						|
        # We have to transform Python datetime into Zope DateTime
 | 
						|
        # before we can call toLocalizedTime.
 | 
						|
        util = getToolByName(self.context, 'translation_service')
 | 
						|
        zope_time = DateTime(time.isoformat())
 | 
						|
        return util.toLocalizedTime(zope_time, long_format=True)
 |