2009-08-04 21:36:33 +02:00
|
|
|
from Acquisition import aq_inner, aq_parent, aq_base
|
|
|
|
|
|
|
|
from AccessControl import getSecurityManager
|
2009-08-01 23:47:50 +02:00
|
|
|
|
2009-05-26 09:25:14 +02:00
|
|
|
from datetime import datetime
|
2009-06-16 22:53:45 +02:00
|
|
|
from DateTime import DateTime
|
2009-05-26 09:25:14 +02:00
|
|
|
|
2009-05-28 13:39:36 +02:00
|
|
|
from urllib import quote as url_quote
|
|
|
|
|
2009-08-04 21:36:33 +02:00
|
|
|
from zope import interface, schema
|
|
|
|
|
|
|
|
from zope.annotation import IAttributeAnnotatable
|
2009-05-28 08:08:55 +02:00
|
|
|
|
2009-06-08 10:00:15 +02:00
|
|
|
from zope.component import createObject, getMultiAdapter, queryUtility
|
2009-05-28 08:08:55 +02:00
|
|
|
|
2009-08-04 21:36:33 +02:00
|
|
|
from zope.interface import Interface, implements
|
2009-05-22 19:30:52 +02:00
|
|
|
|
2009-08-04 21:36:33 +02:00
|
|
|
from zope.viewlet.interfaces import IViewlet
|
2009-05-28 08:08:55 +02:00
|
|
|
|
2009-08-17 22:31:37 +02:00
|
|
|
from z3c.form import form, field, button, interfaces, widget
|
|
|
|
from z3c.form.browser.textarea import TextAreaWidget
|
2009-05-28 08:35:47 +02:00
|
|
|
|
2009-05-25 20:59:25 +02:00
|
|
|
from Products.Five.browser import BrowserView
|
2009-05-22 19:30:52 +02:00
|
|
|
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
|
|
|
|
2009-05-28 08:08:55 +02:00
|
|
|
from Products.CMFCore.utils import getToolByName
|
|
|
|
|
2009-06-21 09:48:33 +02:00
|
|
|
from Products.CMFPlone import PloneMessageFactory as _
|
2009-07-03 14:06:19 +02:00
|
|
|
from Products.statusmessages.interfaces import IStatusMessage
|
2009-06-21 09:48:33 +02:00
|
|
|
|
2009-06-08 10:00:15 +02:00
|
|
|
from plone.registry.interfaces import IRegistry
|
|
|
|
|
2009-05-28 13:39:36 +02:00
|
|
|
from plone.app.layout.viewlets.common import ViewletBase
|
|
|
|
|
2009-08-04 14:55:46 +02:00
|
|
|
from plone.app.discussion.comment import Comment, CommentFactory
|
2009-08-13 21:21:52 +02:00
|
|
|
from plone.app.discussion.interfaces import IConversation, IComment, IReplies, IDiscussionSettings, ICaptcha
|
|
|
|
|
2009-08-15 12:21:26 +02:00
|
|
|
from plone.app.discussion.browser.validator import CaptchaValidator
|
2009-05-22 19:30:52 +02:00
|
|
|
|
2009-08-04 21:36:33 +02:00
|
|
|
from plone.z3cform import layout, z2
|
2009-08-01 23:47:50 +02:00
|
|
|
from plone.z3cform.fieldsets import extensible
|
|
|
|
|
2009-05-27 15:23:25 +02:00
|
|
|
class View(BrowserView):
|
2009-08-04 14:55:46 +02:00
|
|
|
"""Comment View.
|
|
|
|
|
|
|
|
Redirect from /path/to/object/++conversation++default/123456789
|
|
|
|
to /path/to/object#comment-123456789.
|
2009-05-27 15:23:25 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __call__(self):
|
|
|
|
comment_id = aq_parent(self).id
|
2009-08-04 21:24:47 +02:00
|
|
|
self.request.response.redirect(
|
|
|
|
aq_parent(aq_parent(aq_parent(self))).absolute_url() +
|
|
|
|
'#' + str(comment_id))
|
2009-05-27 15:23:25 +02:00
|
|
|
|
2009-08-17 22:31:37 +02:00
|
|
|
class AutoResizeTextArea(TextAreaWidget):
|
2009-08-17 23:46:44 +02:00
|
|
|
"""Textarea with autoresize CSS class.
|
|
|
|
"""
|
2009-08-17 22:31:37 +02:00
|
|
|
klass = u'autoresize'
|
|
|
|
|
|
|
|
def AutoResizeTextAreaFieldWidget(field, request):
|
2009-08-17 23:46:44 +02:00
|
|
|
"""IFieldWidget factory for AutoResizeTextAreaWidget.
|
|
|
|
"""
|
2009-08-17 22:31:37 +02:00
|
|
|
return widget.FieldWidget(field, AutoResizeTextArea(request))
|
|
|
|
|
2009-08-17 23:46:44 +02:00
|
|
|
class CommentButtonAction(button.ButtonAction):
|
|
|
|
"""Comment button with Plone CSS style.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def commentButtonActionFactory(request, field):
|
|
|
|
"""Comment button action factory.
|
|
|
|
"""
|
|
|
|
button = CommentButtonAction(request, field)
|
|
|
|
button.klass += " context"
|
|
|
|
return button
|
|
|
|
|
|
|
|
class ReplyButtonAction(button.ButtonAction):
|
|
|
|
"""Reply button with Plone CSS style.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def replyButtonActionFactory(request, field):
|
|
|
|
button = ReplyButtonAction(request, field)
|
|
|
|
button.klass += " context"
|
|
|
|
return button
|
|
|
|
|
|
|
|
class CancelButtonAction(button.ButtonAction):
|
|
|
|
"""Cancel button with Plone CSS style.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def cancelButtonActionFactory(request, field):
|
|
|
|
button = ReplyButtonAction(request, field)
|
|
|
|
button.klass += " standalone"
|
|
|
|
return button
|
|
|
|
|
2009-08-01 23:47:50 +02:00
|
|
|
class CommentForm(extensible.ExtensibleForm, form.Form):
|
2009-08-02 19:32:41 +02:00
|
|
|
|
2009-08-01 23:47:50 +02:00
|
|
|
ignoreContext = True # don't use context to get widget data
|
2009-08-04 21:24:47 +02:00
|
|
|
label = _(u"Add a comment")
|
2009-08-05 11:01:14 +02:00
|
|
|
fields = field.Fields(IComment).omit('portal_type',
|
|
|
|
'__parent__',
|
|
|
|
'__name__',
|
|
|
|
'comment_id',
|
|
|
|
'mime_type',
|
|
|
|
'creator',
|
|
|
|
'creation_date',
|
|
|
|
'modification_date',
|
|
|
|
'author_username')
|
2009-08-17 22:31:37 +02:00
|
|
|
fields['text'].widgetFactory = AutoResizeTextAreaFieldWidget
|
2009-08-04 13:35:05 +02:00
|
|
|
|
2009-08-04 17:12:15 +02:00
|
|
|
def updateWidgets(self):
|
|
|
|
super(CommentForm, self).updateWidgets()
|
|
|
|
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
|
2009-08-05 11:01:14 +02:00
|
|
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
|
|
|
if not portal_membership.isAnonymousUser():
|
|
|
|
# XXX: This is ugly. The fields should be omitted, not hidden.
|
|
|
|
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
|
|
|
|
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
2009-08-17 23:46:44 +02:00
|
|
|
self.buttons['comment'].actionFactory = commentButtonActionFactory
|
|
|
|
self.buttons['reply'].actionFactory = replyButtonActionFactory
|
|
|
|
self.buttons['cancel'].actionFactory = cancelButtonActionFactory
|
2009-08-04 17:12:15 +02:00
|
|
|
|
2009-08-04 21:24:47 +02:00
|
|
|
@button.buttonAndHandler(_(u"Comment"))
|
2009-08-04 22:47:25 +02:00
|
|
|
def handleComment(self, action):
|
2009-08-01 23:47:50 +02:00
|
|
|
data, errors = self.extractData()
|
2009-08-04 13:56:52 +02:00
|
|
|
|
2009-08-15 13:35:54 +02:00
|
|
|
# Captcha check (only when captcha is enabled and user is anonymous)
|
2009-08-13 21:21:52 +02:00
|
|
|
registry = queryUtility(IRegistry)
|
|
|
|
settings = registry.forInterface(IDiscussionSettings)
|
2009-08-15 13:35:54 +02:00
|
|
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
|
|
|
if settings.captcha != 'disabled' and portal_membership.isAnonymousUser():
|
2009-08-13 21:21:52 +02:00
|
|
|
# Check captcha only if it is not disabled
|
|
|
|
if data.has_key('captcha'):
|
|
|
|
# Check captcha only if there is a value, otherwise
|
|
|
|
# the default "required" validator is sufficient.
|
|
|
|
captcha = CaptchaValidator(self.context, self.request, None, ICaptcha['captcha'], None)
|
|
|
|
captcha.validate(data['captcha'])
|
|
|
|
else:
|
|
|
|
return
|
2009-08-12 22:45:47 +02:00
|
|
|
|
2009-08-04 16:51:34 +02:00
|
|
|
if data.has_key('title') and data.has_key('text'):
|
|
|
|
|
|
|
|
title = data['title']
|
2009-08-04 13:56:52 +02:00
|
|
|
text = data['text']
|
2009-08-04 16:51:34 +02:00
|
|
|
|
2009-08-04 22:47:25 +02:00
|
|
|
if data.has_key('author_name'):
|
|
|
|
author_name = data['author_name']
|
|
|
|
else:
|
|
|
|
author_name = u""
|
|
|
|
|
|
|
|
if data.has_key('author_username'):
|
|
|
|
author_name = data['author_username']
|
|
|
|
else:
|
|
|
|
author_username = u""
|
|
|
|
|
|
|
|
if data.has_key('author_email'):
|
|
|
|
author_email = data['author_email']
|
|
|
|
else:
|
|
|
|
author_email = u""
|
|
|
|
|
2009-08-04 16:51:34 +02:00
|
|
|
# The add-comment view is called on the conversation object
|
|
|
|
conversation = IConversation(self.__parent__)
|
|
|
|
|
|
|
|
# Create the comment
|
2009-08-04 20:42:17 +02:00
|
|
|
comment = createObject('plone.Comment')
|
2009-08-04 16:51:34 +02:00
|
|
|
comment.title = title
|
|
|
|
comment.text = text
|
|
|
|
|
|
|
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
|
|
|
|
|
|
|
if portal_membership.isAnonymousUser():
|
2009-08-04 22:47:25 +02:00
|
|
|
comment.creator = author_name
|
|
|
|
comment.author_name = author_name
|
2009-08-04 16:51:34 +02:00
|
|
|
comment.author_email = author_email
|
|
|
|
comment.creation_date = comment.modification_date = datetime.now()
|
2009-08-04 13:56:52 +02:00
|
|
|
else:
|
2009-08-04 16:51:34 +02:00
|
|
|
member = portal_membership.getAuthenticatedMember()
|
|
|
|
fullname = member.getProperty('fullname')
|
|
|
|
if fullname == '' or None:
|
|
|
|
comment.creator = member.id
|
|
|
|
else:
|
|
|
|
comment.creator = fullname
|
|
|
|
comment.author_username = member.getUserName()
|
|
|
|
comment.author_name = member.getProperty('fullname')
|
|
|
|
comment.author_email = member.getProperty('email')
|
|
|
|
comment.creation_date = comment.modification_date = datetime.now()
|
|
|
|
|
|
|
|
# Add comment to the conversation
|
|
|
|
comment_id = conversation.addComment(comment)
|
|
|
|
|
|
|
|
# Redirect to comment (inside a content object page)
|
2009-08-04 21:24:47 +02:00
|
|
|
self.request.response.redirect(
|
|
|
|
aq_parent(aq_inner(self.context)).absolute_url() +
|
|
|
|
'#' + str(comment_id))
|
2009-08-04 16:51:34 +02:00
|
|
|
|
2009-08-04 21:24:47 +02:00
|
|
|
@button.buttonAndHandler(_(u"Reply"))
|
2009-08-04 16:51:34 +02:00
|
|
|
def handleReply(self, action):
|
|
|
|
data, errors = self.extractData()
|
|
|
|
|
2009-08-15 18:36:35 +02:00
|
|
|
# Captcha check (only when captcha is enabled and user is anonymous)
|
|
|
|
registry = queryUtility(IRegistry)
|
|
|
|
settings = registry.forInterface(IDiscussionSettings)
|
|
|
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
|
|
|
if settings.captcha != 'disabled' and portal_membership.isAnonymousUser():
|
|
|
|
# Check captcha only if it is not disabled
|
|
|
|
if data.has_key('captcha'):
|
|
|
|
# Check captcha only if there is a value, otherwise
|
|
|
|
# the default "required" validator is sufficient.
|
|
|
|
captcha = CaptchaValidator(self.context, self.request, None, ICaptcha['captcha'], None)
|
|
|
|
captcha.validate(data['captcha'])
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
2009-08-04 16:51:34 +02:00
|
|
|
if data.has_key('title') and data.has_key('text') and data.has_key('in_reply_to'):
|
|
|
|
|
|
|
|
title = data['title']
|
|
|
|
text = data['text']
|
|
|
|
reply_to_comment_id = data['in_reply_to']
|
|
|
|
|
2009-08-04 22:47:25 +02:00
|
|
|
if data.has_key('author_name'):
|
|
|
|
author_name = data['author_name']
|
|
|
|
else:
|
|
|
|
author_name = u""
|
|
|
|
|
2009-08-04 16:51:34 +02:00
|
|
|
if data.has_key('author_username'):
|
2009-08-04 22:47:25 +02:00
|
|
|
author_name = data['author_username']
|
|
|
|
else:
|
|
|
|
author_username = u""
|
|
|
|
|
2009-08-04 16:51:34 +02:00
|
|
|
if data.has_key('author_email'):
|
|
|
|
author_email = data['author_email']
|
2009-08-04 22:47:25 +02:00
|
|
|
else:
|
|
|
|
author_email = u""
|
2009-08-04 16:51:34 +02:00
|
|
|
|
|
|
|
# The add-comment view is called on the conversation object
|
|
|
|
conversation = IConversation(self.__parent__)
|
|
|
|
|
|
|
|
# Fetch the comment we want to reply to
|
|
|
|
comment_to_reply_to = conversation.get(reply_to_comment_id)
|
|
|
|
|
|
|
|
replies = IReplies(comment_to_reply_to)
|
|
|
|
|
|
|
|
# Create the comment
|
2009-08-04 20:42:17 +02:00
|
|
|
comment = createObject('plone.Comment')
|
2009-08-04 16:51:34 +02:00
|
|
|
comment.title = title
|
|
|
|
comment.text = text
|
|
|
|
|
|
|
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
|
|
|
|
|
|
|
if portal_membership.isAnonymousUser():
|
2009-08-04 22:47:25 +02:00
|
|
|
comment.creator = author_name
|
|
|
|
comment.author_name = author_name
|
2009-08-04 16:51:34 +02:00
|
|
|
comment.author_email = author_email
|
|
|
|
comment.creation_date = comment.modification_date = datetime.now()
|
|
|
|
else:
|
|
|
|
member = portal_membership.getAuthenticatedMember()
|
|
|
|
fullname = member.getProperty('fullname')
|
|
|
|
if fullname == '' or None:
|
|
|
|
comment.creator = member.id
|
|
|
|
else:
|
|
|
|
comment.creator = fullname
|
|
|
|
comment.author_username = member.getUserName()
|
|
|
|
comment.author_name = member.getProperty('fullname')
|
|
|
|
comment.author_email = member.getProperty('email')
|
|
|
|
comment.creation_date = comment.modification_date = datetime.now()
|
2009-08-04 13:56:52 +02:00
|
|
|
|
2009-08-04 16:51:34 +02:00
|
|
|
# Add the reply to the comment
|
|
|
|
new_re_id = replies.addComment(comment)
|
|
|
|
|
|
|
|
# Redirect to comment (inside a content object page)
|
|
|
|
self.request.response.redirect(aq_parent(aq_inner(self.context)).absolute_url() + '#' + str(reply_to_comment_id))
|
2009-08-04 13:56:52 +02:00
|
|
|
|
2009-08-04 21:24:47 +02:00
|
|
|
@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
|
2009-08-01 23:47:50 +02:00
|
|
|
|
2009-08-04 21:30:12 +02:00
|
|
|
class CommentsViewlet(ViewletBase, layout.FormWrapper):
|
2009-05-25 20:59:25 +02:00
|
|
|
|
2009-08-01 23:47:50 +02:00
|
|
|
form = CommentForm
|
|
|
|
|
2009-08-20 04:09:53 +02:00
|
|
|
index = ViewPageTemplateFile('comments.pt')
|
2009-08-01 23:47:50 +02:00
|
|
|
|
|
|
|
def __init__(self, context, request, view, manager):
|
2009-08-04 21:30:12 +02:00
|
|
|
super(CommentsViewlet, self).__init__(context, request, view, manager)
|
2009-08-01 23:47:50 +02:00
|
|
|
if self.form is not None:
|
|
|
|
self.form_instance = self.form(self.context.aq_inner, self.request)
|
|
|
|
self.form_instance.__name__ = self.__name__
|
2009-05-25 20:59:25 +02:00
|
|
|
|
2009-05-28 08:35:47 +02:00
|
|
|
self.portal_discussion = getToolByName(self.context, 'portal_discussion', None)
|
|
|
|
self.portal_membership = getToolByName(self.context, 'portal_membership', None)
|
|
|
|
|
2009-08-01 23:47:50 +02:00
|
|
|
def render_form(self):
|
2009-08-04 13:35:05 +02:00
|
|
|
z2.switch_on(self, request_layer=self.request_layer)
|
2009-08-01 23:47:50 +02:00
|
|
|
self.form.update(self.form_instance)
|
|
|
|
return self.form.render(self.form_instance)
|
|
|
|
|
|
|
|
# view methods
|
|
|
|
|
2009-05-28 08:35:47 +02:00
|
|
|
def can_reply(self):
|
|
|
|
return getSecurityManager().checkPermission('Reply to item', aq_inner(self.context))
|
|
|
|
|
2009-06-11 15:51:33 +02:00
|
|
|
def can_manage(self):
|
|
|
|
return getSecurityManager().checkPermission('Manage portal', aq_inner(self.context))
|
|
|
|
|
2009-05-28 08:35:47 +02:00
|
|
|
def is_discussion_allowed(self):
|
2009-06-18 22:57:47 +02:00
|
|
|
context = aq_inner(self.context)
|
2009-06-19 12:00:31 +02:00
|
|
|
conversation = IConversation(context)
|
2009-06-11 12:20:44 +02:00
|
|
|
return conversation.enabled
|
2009-05-25 20:59:25 +02:00
|
|
|
|
2009-06-13 18:46:37 +02:00
|
|
|
def get_replies(self, workflow_actions=False):
|
|
|
|
context = aq_inner(self.context)
|
|
|
|
conversation = IConversation(context)
|
|
|
|
|
2009-06-13 20:02:59 +02:00
|
|
|
def replies_with_workflow_actions():
|
|
|
|
# Return dict with workflow actions
|
|
|
|
#context = aq_inner(self.context)
|
|
|
|
wf = getToolByName(context, 'portal_workflow')
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2009-06-13 18:46:37 +02:00
|
|
|
# Return all direct replies
|
2009-05-28 17:08:36 +02:00
|
|
|
if conversation.total_comments > 0:
|
2009-06-13 18:46:37 +02:00
|
|
|
if workflow_actions:
|
2009-06-13 20:02:59 +02:00
|
|
|
return replies_with_workflow_actions()
|
2009-06-13 18:46:37 +02:00
|
|
|
else:
|
2009-06-13 20:02:59 +02:00
|
|
|
return conversation.getThreads()
|
2009-05-28 17:08:36 +02:00
|
|
|
else:
|
2009-06-13 20:02:59 +02:00
|
|
|
return None
|
|
|
|
|
2009-05-25 20:59:25 +02:00
|
|
|
|
2009-06-07 22:58:41 +02:00
|
|
|
def get_commenter_home_url(self, username):
|
|
|
|
if username is None:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return "%s/author/%s" % (self.context.portal_url(), username)
|
|
|
|
|
|
|
|
def get_commenter_portrait(self, username):
|
|
|
|
|
|
|
|
if username is None:
|
2009-06-14 13:10:45 +02:00
|
|
|
# return the default user image if no username is given
|
|
|
|
return 'defaultUser.gif'
|
2009-06-07 22:58:41 +02:00
|
|
|
else:
|
2009-06-14 13:10:45 +02:00
|
|
|
portal_membership = getToolByName(self.context, 'portal_membership', None)
|
|
|
|
return portal_membership.getPersonalPortrait(username).absolute_url();
|
2009-06-07 22:58:41 +02:00
|
|
|
|
2009-06-08 10:00:15 +02:00
|
|
|
def anonymous_discussion_allowed(self):
|
|
|
|
# Check if anonymous comments are allowed in the registry
|
|
|
|
registry = queryUtility(IRegistry)
|
2009-07-12 21:13:42 +02:00
|
|
|
settings = registry.forInterface(IDiscussionSettings)
|
2009-06-08 10:00:15 +02:00
|
|
|
return settings.anonymous_comments
|
|
|
|
|
2009-06-08 10:23:18 +02:00
|
|
|
def show_commenter_image(self):
|
|
|
|
# Check if showing commenter image is enabled in the registry
|
|
|
|
registry = queryUtility(IRegistry)
|
2009-07-12 21:13:42 +02:00
|
|
|
settings = registry.forInterface(IDiscussionSettings)
|
2009-06-08 10:23:18 +02:00
|
|
|
return settings.show_commenter_image
|
|
|
|
|
2009-05-28 08:35:47 +02:00
|
|
|
def is_anonymous(self):
|
|
|
|
return self.portal_state.anonymous()
|
|
|
|
|
2009-05-28 13:39:36 +02:00
|
|
|
def login_action(self):
|
|
|
|
return '%s/login_form?came_from=%s' % (self.navigation_root_url, url_quote(self.request.get('URL', '')),)
|
2009-05-28 08:35:47 +02:00
|
|
|
|
2009-05-26 09:25:14 +02:00
|
|
|
def format_time(self, time):
|
2009-06-16 22:53:45 +02:00
|
|
|
# We have to transform Python datetime into Zope DateTime
|
2009-06-21 22:33:02 +02:00
|
|
|
# before we can call toLocalizedTime.
|
2009-06-16 22:53:45 +02:00
|
|
|
util = getToolByName(self.context, 'translation_service')
|
|
|
|
zope_time = DateTime(time.year, time.month, time.day, time.hour, time.minute, time.second)
|
2009-08-20 04:09:53 +02:00
|
|
|
return util.toLocalizedTime(zope_time, long_format=True)
|