black
This commit is contained in:
@@ -21,9 +21,9 @@ from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
||||
@adapter(Comment)
|
||||
@interface.implementer(ICaptcha)
|
||||
class Captcha(Persistent):
|
||||
"""Captcha input field.
|
||||
"""
|
||||
captcha = u''
|
||||
"""Captcha input field."""
|
||||
|
||||
captcha = u""
|
||||
|
||||
|
||||
Captcha = factory(Captcha)
|
||||
@@ -47,22 +47,24 @@ class CaptchaExtender(extensible.FormExtender):
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
self.captcha = settings.captcha
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
portal_membership = getToolByName(self.context, "portal_membership")
|
||||
self.isAnon = portal_membership.isAnonymousUser()
|
||||
|
||||
def update(self):
|
||||
if self.captcha != 'disabled' and self.isAnon:
|
||||
if self.captcha != "disabled" and self.isAnon:
|
||||
# Add a captcha field if captcha is enabled in the registry
|
||||
self.add(ICaptcha, prefix='')
|
||||
if self.captcha == 'captcha':
|
||||
self.add(ICaptcha, prefix="")
|
||||
if self.captcha == "captcha":
|
||||
from plone.formwidget.captcha import CaptchaFieldWidget
|
||||
self.form.fields['captcha'].widgetFactory = CaptchaFieldWidget
|
||||
elif self.captcha == 'recaptcha':
|
||||
|
||||
self.form.fields["captcha"].widgetFactory = CaptchaFieldWidget
|
||||
elif self.captcha == "recaptcha":
|
||||
from plone.formwidget.recaptcha import ReCaptchaFieldWidget
|
||||
self.form.fields['captcha'].widgetFactory = \
|
||||
ReCaptchaFieldWidget
|
||||
elif self.captcha == 'norobots':
|
||||
|
||||
self.form.fields["captcha"].widgetFactory = ReCaptchaFieldWidget
|
||||
elif self.captcha == "norobots":
|
||||
from collective.z3cform.norobots import NorobotsFieldWidget
|
||||
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
|
||||
|
||||
self.form.fields["captcha"].widgetFactory = NorobotsFieldWidget
|
||||
else:
|
||||
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
|
||||
self.form.fields["captcha"].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
@@ -37,8 +37,7 @@ class View(BrowserView):
|
||||
context = aq_inner(self.context)
|
||||
|
||||
registry = getUtility(IRegistry)
|
||||
view_action_types = registry.get(
|
||||
'plone.types_use_view_action_in_listings', [])
|
||||
view_action_types = registry.get("plone.types_use_view_action_in_listings", [])
|
||||
|
||||
obj = aq_parent(aq_parent(context))
|
||||
url = obj.absolute_url()
|
||||
@@ -49,33 +48,34 @@ class View(BrowserView):
|
||||
will redirect right to the binary object, bypassing comments.
|
||||
"""
|
||||
if obj.portal_type in view_action_types:
|
||||
url = '{0}/view'.format(url)
|
||||
url = "{0}/view".format(url)
|
||||
|
||||
self.request.response.redirect('{0}#{1}'.format(url, context.id))
|
||||
self.request.response.redirect("{0}#{1}".format(url, context.id))
|
||||
|
||||
|
||||
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')
|
||||
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
|
||||
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'
|
||||
self.widgets["text"].id = "overlay-comment-text"
|
||||
|
||||
def _redirect(self, target=''):
|
||||
def _redirect(self, target=""):
|
||||
if not target:
|
||||
portal_state = getMultiAdapter((self.context, self.request),
|
||||
name=u'plone_portal_state')
|
||||
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'label_save',
|
||||
default=u'Save'), name='comment')
|
||||
@button.buttonAndHandler(_(u"label_save", default=u"Save"), name="comment")
|
||||
def handleComment(self, action):
|
||||
|
||||
# Validate form
|
||||
@@ -84,32 +84,28 @@ class EditCommentForm(CommentForm):
|
||||
return
|
||||
|
||||
# Check permissions
|
||||
can_edit = getSecurityManager().checkPermission(
|
||||
'Edit comments',
|
||||
self.context)
|
||||
mtool = getToolByName(self.context, 'portal_membership')
|
||||
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']
|
||||
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'))
|
||||
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')
|
||||
@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')
|
||||
_(u"comment_edit_cancel_notification", default=u"Edit comment cancelled"),
|
||||
type="info",
|
||||
)
|
||||
return self._redirect(target=self.context.absolute_url())
|
||||
|
||||
|
||||
|
||||
@@ -35,28 +35,28 @@ 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.',
|
||||
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.',
|
||||
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.',
|
||||
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.",
|
||||
)
|
||||
|
||||
COMMENT_DESCRIPTION_MODERATION_ENABLED = _(
|
||||
u'comment_description_moderation_enabled',
|
||||
default=u'Comments are moderated.',
|
||||
u"comment_description_moderation_enabled",
|
||||
default=u"Comments are moderated.",
|
||||
)
|
||||
|
||||
|
||||
@@ -64,30 +64,31 @@ 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')
|
||||
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
|
||||
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'')
|
||||
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
|
||||
@@ -97,19 +98,19 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# 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
|
||||
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
|
||||
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'
|
||||
self.widgets["text"].id = "form-widgets-comment-text"
|
||||
|
||||
# Anonymous / Logged-in
|
||||
mtool = getToolByName(self.context, 'portal_membership')
|
||||
mtool = getToolByName(self.context, "portal_membership")
|
||||
anon = mtool.isAnonymousUser()
|
||||
|
||||
registry = queryUtility(IRegistry)
|
||||
@@ -119,52 +120,51 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
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
|
||||
self.widgets["author_email"].field.required = True
|
||||
self.widgets["author_email"].required = True
|
||||
else:
|
||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
||||
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
|
||||
self.widgets["author_name"].mode = interfaces.HIDDEN_MODE
|
||||
self.widgets["author_email"].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
member = mtool.getAuthenticatedMember()
|
||||
member_email = member.getProperty('email')
|
||||
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 == ''
|
||||
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
|
||||
self.widgets["user_notification"].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
def updateActions(self):
|
||||
super(CommentForm, self).updateActions()
|
||||
self.actions['cancel'].addClass('btn btn-secondary')
|
||||
self.actions['cancel'].addClass('hide')
|
||||
self.actions['comment'].addClass('btn btn-primary')
|
||||
self.actions["cancel"].addClass("btn btn-secondary")
|
||||
self.actions["cancel"].addClass("hide")
|
||||
self.actions["comment"].addClass("btn btn-primary")
|
||||
|
||||
def get_author(self, data):
|
||||
context = aq_inner(self.context)
|
||||
# some attributes are not always set
|
||||
author_name = u''
|
||||
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'])
|
||||
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')
|
||||
portal_membership = getToolByName(context, "portal_membership")
|
||||
anon = portal_membership.isAnonymousUser()
|
||||
if not anon and getSecurityManager().checkPermission(
|
||||
'Reply to item', context):
|
||||
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 == '':
|
||||
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
|
||||
@@ -179,7 +179,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
|
||||
def create_comment(self, data):
|
||||
context = aq_inner(self.context)
|
||||
comment = createObject('plone.Comment')
|
||||
comment = createObject("plone.Comment")
|
||||
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
@@ -200,42 +200,44 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
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')
|
||||
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):
|
||||
"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.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"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')
|
||||
@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',
|
||||
"@@conversation_view",
|
||||
).enabled():
|
||||
raise Unauthorized(
|
||||
'Discussion is not enabled for this content object.',
|
||||
"Discussion is not enabled for this content object.",
|
||||
)
|
||||
|
||||
# Validation form
|
||||
@@ -246,28 +248,26 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# Validate Captcha
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
captcha_enabled = settings.captcha != 'disabled'
|
||||
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'])
|
||||
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']:
|
||||
if data["in_reply_to"]:
|
||||
# Add a reply to an existing comment
|
||||
conversation_to_reply_to = conversation.get(data['in_reply_to'])
|
||||
conversation_to_reply_to = conversation.get(data["in_reply_to"])
|
||||
replies = IReplies(conversation_to_reply_to)
|
||||
comment_id = replies.addComment(comment)
|
||||
else:
|
||||
@@ -279,25 +279,24 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# 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')
|
||||
can_review = getSecurityManager().checkPermission("Review comments", context)
|
||||
workflowTool = getToolByName(context, "portal_workflow")
|
||||
comment_review_state = workflowTool.getInfoFor(
|
||||
comment,
|
||||
'review_state',
|
||||
"review_state",
|
||||
None,
|
||||
)
|
||||
if comment_review_state == 'pending' and not can_review:
|
||||
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')
|
||||
_("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))
|
||||
self.request.response.redirect(self.action + "#" + str(comment_id))
|
||||
|
||||
@button.buttonAndHandler(_(u'Cancel'))
|
||||
@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.
|
||||
@@ -307,15 +306,15 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
class CommentsViewlet(ViewletBase):
|
||||
|
||||
form = CommentForm
|
||||
index = ViewPageTemplateFile('comments.pt')
|
||||
index = ViewPageTemplateFile("comments.pt")
|
||||
|
||||
def update(self):
|
||||
super(CommentsViewlet, self).update()
|
||||
discussion_allowed = self.is_discussion_allowed()
|
||||
anonymous_allowed_or_can_reply = (
|
||||
self.is_anonymous() and
|
||||
self.anonymous_discussion_allowed() or
|
||||
self.can_reply()
|
||||
self.is_anonymous()
|
||||
and self.anonymous_discussion_allowed()
|
||||
or self.can_reply()
|
||||
)
|
||||
if discussion_allowed and anonymous_allowed_or_can_reply:
|
||||
z2.switch_on(self, request_layer=IFormLayer)
|
||||
@@ -326,30 +325,29 @@ class CommentsViewlet(ViewletBase):
|
||||
# view methods
|
||||
|
||||
def can_reply(self):
|
||||
"""Returns true if current user has the 'Reply to item' permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Reply to item',
|
||||
aq_inner(self.context))
|
||||
"""Returns true if current user has the 'Reply to item' permission."""
|
||||
return getSecurityManager().checkPermission(
|
||||
"Reply to item", aq_inner(self.context)
|
||||
)
|
||||
|
||||
def can_manage(self):
|
||||
"""We keep this method for <= 1.0b9 backward compatibility. Since we do
|
||||
not want any API changes in beta releases.
|
||||
not want any API changes in beta releases.
|
||||
"""
|
||||
return self.can_review()
|
||||
|
||||
def can_review(self):
|
||||
"""Returns true if current user has the 'Review comments' permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Review comments',
|
||||
aq_inner(self.context))
|
||||
"""Returns true if current user has the 'Review comments' permission."""
|
||||
return getSecurityManager().checkPermission(
|
||||
"Review comments", aq_inner(self.context)
|
||||
)
|
||||
|
||||
def can_delete_own(self, comment):
|
||||
"""Returns true if the current user can delete the comment. Only
|
||||
comments without replies can be deleted.
|
||||
"""
|
||||
try:
|
||||
return comment.restrictedTraverse(
|
||||
'@@delete-own-comment').can_delete()
|
||||
return comment.restrictedTraverse("@@delete-own-comment").can_delete()
|
||||
except Unauthorized:
|
||||
return False
|
||||
|
||||
@@ -358,8 +356,7 @@ class CommentsViewlet(ViewletBase):
|
||||
no replies. This is used to prepare hidden form buttons for JS.
|
||||
"""
|
||||
try:
|
||||
return comment.restrictedTraverse(
|
||||
'@@delete-own-comment').could_delete()
|
||||
return comment.restrictedTraverse("@@delete-own-comment").could_delete()
|
||||
except Unauthorized:
|
||||
return False
|
||||
|
||||
@@ -367,58 +364,63 @@ class CommentsViewlet(ViewletBase):
|
||||
"""Returns true if current user has the 'Edit comments'
|
||||
permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Edit comments',
|
||||
aq_inner(reply))
|
||||
return getSecurityManager().checkPermission("Edit comments", aq_inner(reply))
|
||||
|
||||
def can_delete(self, reply):
|
||||
"""Returns true if current user has the 'Delete comments'
|
||||
permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Delete comments',
|
||||
aq_inner(reply))
|
||||
return getSecurityManager().checkPermission("Delete comments", aq_inner(reply))
|
||||
|
||||
def is_discussion_allowed(self):
|
||||
context = aq_inner(self.context)
|
||||
return context.restrictedTraverse('@@conversation_view').enabled()
|
||||
return context.restrictedTraverse("@@conversation_view").enabled()
|
||||
|
||||
def comment_transform_message(self):
|
||||
"""Returns the description that shows up above the comment text,
|
||||
dependent on the text_transform setting and the comment moderation
|
||||
workflow in the discussion control panel.
|
||||
dependent on the text_transform setting and the comment moderation
|
||||
workflow in the discussion control panel.
|
||||
"""
|
||||
context = aq_inner(self.context)
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
# text transform setting
|
||||
if settings.text_transform == 'text/x-web-intelligent':
|
||||
message = translate(Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT),
|
||||
context=self.request)
|
||||
elif settings.text_transform == 'text/x-web-markdown':
|
||||
message = translate(Message(COMMENT_DESCRIPTION_MARKDOWN),
|
||||
context=self.request)
|
||||
if settings.text_transform == "text/x-web-intelligent":
|
||||
message = translate(
|
||||
Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT), context=self.request
|
||||
)
|
||||
elif settings.text_transform == "text/x-web-markdown":
|
||||
message = translate(
|
||||
Message(COMMENT_DESCRIPTION_MARKDOWN), context=self.request
|
||||
)
|
||||
else:
|
||||
message = translate(Message(COMMENT_DESCRIPTION_PLAIN_TEXT),
|
||||
context=self.request)
|
||||
message = translate(
|
||||
Message(COMMENT_DESCRIPTION_PLAIN_TEXT), context=self.request
|
||||
)
|
||||
|
||||
# comment workflow
|
||||
wftool = getToolByName(context, 'portal_workflow', None)
|
||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
||||
wftool = getToolByName(context, "portal_workflow", None)
|
||||
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||
if workflow_chain:
|
||||
comment_workflow = workflow_chain[0]
|
||||
comment_workflow = wftool[comment_workflow]
|
||||
# check if the current workflow implements a pending state. If this
|
||||
# is true comments are moderated
|
||||
if 'pending' in comment_workflow.states:
|
||||
message = message + ' ' + \
|
||||
translate(Message(COMMENT_DESCRIPTION_MODERATION_ENABLED),
|
||||
context=self.request)
|
||||
if "pending" in comment_workflow.states:
|
||||
message = (
|
||||
message
|
||||
+ " "
|
||||
+ translate(
|
||||
Message(COMMENT_DESCRIPTION_MODERATION_ENABLED),
|
||||
context=self.request,
|
||||
)
|
||||
)
|
||||
|
||||
return message
|
||||
|
||||
def has_replies(self, workflow_actions=False):
|
||||
"""Returns true if there are replies.
|
||||
"""
|
||||
"""Returns true if there are replies."""
|
||||
if self.get_replies(workflow_actions) is not None:
|
||||
try:
|
||||
next(self.get_replies(workflow_actions))
|
||||
@@ -442,31 +444,32 @@ class CommentsViewlet(ViewletBase):
|
||||
if conversation is None:
|
||||
return iter([])
|
||||
|
||||
wf = getToolByName(context, 'portal_workflow')
|
||||
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']
|
||||
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']
|
||||
a
|
||||
for a in wf.listActionInfos(object=comment_obj)
|
||||
if a["category"] == "workflow" and a["allowed"]
|
||||
]
|
||||
r = r.copy()
|
||||
r['actions'] = actions
|
||||
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':
|
||||
comment_obj = r["comment"]
|
||||
workflow_status = wf.getInfoFor(comment_obj, "review_state")
|
||||
if workflow_status == "published":
|
||||
r = r.copy()
|
||||
r['workflow_status'] = workflow_status
|
||||
r["workflow_status"] = workflow_status
|
||||
yield r
|
||||
|
||||
# Return all direct replies
|
||||
@@ -480,20 +483,16 @@ class CommentsViewlet(ViewletBase):
|
||||
if username is None:
|
||||
return None
|
||||
else:
|
||||
return '{0}/author/{1}'.format(self.context.portal_url(), username)
|
||||
return "{0}/author/{1}".format(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.png'
|
||||
return "defaultUser.png"
|
||||
else:
|
||||
portal_membership = getToolByName(self.context,
|
||||
'portal_membership',
|
||||
None)
|
||||
return portal_membership\
|
||||
.getPersonalPortrait(username)\
|
||||
.absolute_url()
|
||||
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
|
||||
@@ -520,20 +519,18 @@ class CommentsViewlet(ViewletBase):
|
||||
return settings.show_commenter_image
|
||||
|
||||
def is_anonymous(self):
|
||||
portal_membership = getToolByName(self.context,
|
||||
'portal_membership',
|
||||
None)
|
||||
portal_membership = getToolByName(self.context, "portal_membership", None)
|
||||
return portal_membership.isAnonymousUser()
|
||||
|
||||
def login_action(self):
|
||||
return '{0}/login_form?came_from={1}'.format(
|
||||
return "{0}/login_form?came_from={1}".format(
|
||||
self.navigation_root_url,
|
||||
quote(self.request.get('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')
|
||||
util = getToolByName(self.context, "translation_service")
|
||||
zope_time = DateTime(time.isoformat())
|
||||
return util.toLocalizedTime(zope_time, long_format=True)
|
||||
|
||||
@@ -28,42 +28,40 @@ except ImportError:
|
||||
|
||||
|
||||
class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
"""Discussion settings form.
|
||||
"""
|
||||
"""Discussion settings form."""
|
||||
|
||||
schema = IDiscussionSettings
|
||||
id = 'DiscussionSettingsEditForm'
|
||||
label = _(u'Discussion settings')
|
||||
id = "DiscussionSettingsEditForm"
|
||||
label = _(u"Discussion settings")
|
||||
description = _(
|
||||
u'help_discussion_settings_editform',
|
||||
default=u'Some discussion related settings are not '
|
||||
u'located in the Discussion Control Panel.\n'
|
||||
u'To enable comments for a specific content type, '
|
||||
u'go to the Types Control Panel of this type and '
|
||||
u'choose "Allow comments".\n'
|
||||
u'To enable the moderation workflow for comments, '
|
||||
u'go to the Types Control Panel, choose '
|
||||
u'"Comment" and set workflow to '
|
||||
u'"Comment Review Workflow".',
|
||||
u"help_discussion_settings_editform",
|
||||
default=u"Some discussion related settings are not "
|
||||
u"located in the Discussion Control Panel.\n"
|
||||
u"To enable comments for a specific content type, "
|
||||
u"go to the Types Control Panel of this type and "
|
||||
u'choose "Allow comments".\n'
|
||||
u"To enable the moderation workflow for comments, "
|
||||
u"go to the Types Control Panel, choose "
|
||||
u'"Comment" and set workflow to '
|
||||
u'"Comment Review Workflow".',
|
||||
)
|
||||
|
||||
def updateFields(self):
|
||||
super(DiscussionSettingsEditForm, self).updateFields()
|
||||
self.fields['globally_enabled'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['moderation_enabled'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['edit_comment_enabled'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['delete_own_comment_enabled'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['anonymous_comments'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['show_commenter_image'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['moderator_notification_enabled'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields['user_notification_enabled'].widgetFactory = \
|
||||
SingleCheckBoxFieldWidget
|
||||
self.fields["globally_enabled"].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields["moderation_enabled"].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields["edit_comment_enabled"].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields[
|
||||
"delete_own_comment_enabled"
|
||||
].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields["anonymous_comments"].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields["show_commenter_image"].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields[
|
||||
"moderator_notification_enabled"
|
||||
].widgetFactory = SingleCheckBoxFieldWidget
|
||||
self.fields[
|
||||
"user_notification_enabled"
|
||||
].widgetFactory = SingleCheckBoxFieldWidget
|
||||
|
||||
def updateWidgets(self):
|
||||
try:
|
||||
@@ -73,33 +71,31 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
# provide auto-upgrade
|
||||
update_registry(self.context)
|
||||
super(DiscussionSettingsEditForm, self).updateWidgets()
|
||||
self.widgets['globally_enabled'].label = _(u'Enable Comments')
|
||||
self.widgets['anonymous_comments'].label = _(u'Anonymous Comments')
|
||||
self.widgets['show_commenter_image'].label = _(u'Commenter Image')
|
||||
self.widgets['moderator_notification_enabled'].label = _(
|
||||
u'Moderator Email Notification',
|
||||
self.widgets["globally_enabled"].label = _(u"Enable Comments")
|
||||
self.widgets["anonymous_comments"].label = _(u"Anonymous Comments")
|
||||
self.widgets["show_commenter_image"].label = _(u"Commenter Image")
|
||||
self.widgets["moderator_notification_enabled"].label = _(
|
||||
u"Moderator Email Notification",
|
||||
)
|
||||
self.widgets['user_notification_enabled'].label = _(
|
||||
u'User Email Notification',
|
||||
self.widgets["user_notification_enabled"].label = _(
|
||||
u"User Email Notification",
|
||||
)
|
||||
|
||||
@button.buttonAndHandler(_('Save'), name=None)
|
||||
@button.buttonAndHandler(_("Save"), name=None)
|
||||
def handleSave(self, action):
|
||||
data, errors = self.extractData()
|
||||
if errors:
|
||||
self.status = self.formErrorsMessage
|
||||
return
|
||||
self.applyChanges(data)
|
||||
IStatusMessage(self.request).addStatusMessage(_(u'Changes saved'),
|
||||
'info')
|
||||
self.context.REQUEST.RESPONSE.redirect('@@discussion-controlpanel')
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"), "info")
|
||||
self.context.REQUEST.RESPONSE.redirect("@@discussion-controlpanel")
|
||||
|
||||
@button.buttonAndHandler(_('Cancel'), name='cancel')
|
||||
@button.buttonAndHandler(_("Cancel"), name="cancel")
|
||||
def handleCancel(self, action):
|
||||
IStatusMessage(self.request).addStatusMessage(_(u'Edit cancelled'),
|
||||
'info')
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"), "info")
|
||||
self.request.response.redirect(
|
||||
'{0}/{1}'.format(
|
||||
"{0}/{1}".format(
|
||||
self.context.absolute_url(),
|
||||
self.control_panel_view,
|
||||
),
|
||||
@@ -107,10 +103,10 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
|
||||
|
||||
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
"""Discussion settings control panel.
|
||||
"""
|
||||
"""Discussion settings control panel."""
|
||||
|
||||
form = DiscussionSettingsEditForm
|
||||
index = ViewPageTemplateFile('controlpanel.pt')
|
||||
index = ViewPageTemplateFile("controlpanel.pt")
|
||||
|
||||
def __call__(self):
|
||||
self.mailhost_warning()
|
||||
@@ -126,43 +122,44 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
|
||||
def settings(self):
|
||||
"""Compose a string that contains all registry settings that are
|
||||
needed for the discussion control panel.
|
||||
needed for the discussion control panel.
|
||||
"""
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
wftool = getToolByName(self.context, 'portal_workflow', None)
|
||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
||||
wftool = getToolByName(self.context, "portal_workflow", None)
|
||||
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||
output = []
|
||||
|
||||
# Globally enabled
|
||||
if settings.globally_enabled:
|
||||
output.append('globally_enabled')
|
||||
output.append("globally_enabled")
|
||||
|
||||
# Comment moderation
|
||||
one_state_worklow_disabled = \
|
||||
'comment_one_state_workflow' not in workflow_chain
|
||||
comment_review_workflow_disabled = \
|
||||
'comment_review_workflow' not in workflow_chain
|
||||
one_state_worklow_disabled = "comment_one_state_workflow" not in workflow_chain
|
||||
comment_review_workflow_disabled = (
|
||||
"comment_review_workflow" not in workflow_chain
|
||||
)
|
||||
if one_state_worklow_disabled and comment_review_workflow_disabled:
|
||||
output.append('moderation_custom')
|
||||
output.append("moderation_custom")
|
||||
elif settings.moderation_enabled:
|
||||
output.append('moderation_enabled')
|
||||
output.append("moderation_enabled")
|
||||
|
||||
if settings.edit_comment_enabled:
|
||||
output.append('edit_comment_enabled')
|
||||
output.append("edit_comment_enabled")
|
||||
|
||||
if settings.delete_own_comment_enabled:
|
||||
output.append('delete_own_comment_enabled')
|
||||
output.append("delete_own_comment_enabled")
|
||||
|
||||
# Anonymous comments
|
||||
if settings.anonymous_comments:
|
||||
output.append('anonymous_comments')
|
||||
output.append("anonymous_comments")
|
||||
|
||||
# Invalid mail setting
|
||||
ctrlOverview = getMultiAdapter((self.context, self.request),
|
||||
name='overview-controlpanel')
|
||||
ctrlOverview = getMultiAdapter(
|
||||
(self.context, self.request), name="overview-controlpanel"
|
||||
)
|
||||
if ctrlOverview.mailhost_warning():
|
||||
output.append('invalid_mail_setup')
|
||||
output.append("invalid_mail_setup")
|
||||
|
||||
# Workflow
|
||||
if workflow_chain:
|
||||
@@ -170,69 +167,71 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
output.append(discussion_workflow)
|
||||
|
||||
# Merge all settings into one string
|
||||
return ' '.join(output)
|
||||
return " ".join(output)
|
||||
|
||||
def mailhost_warning(self):
|
||||
"""Returns true if mailhost is not configured properly.
|
||||
"""
|
||||
"""Returns true if mailhost is not configured properly."""
|
||||
# Copied from Products.CMFPlone/controlpanel/browser/overview.py
|
||||
registry = getUtility(IRegistry)
|
||||
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
|
||||
mail_settings = registry.forInterface(IMailSchema, prefix="plone")
|
||||
mailhost = mail_settings.smtp_host
|
||||
email = mail_settings.email_from_address
|
||||
if mailhost and email:
|
||||
pass
|
||||
else:
|
||||
message = _(u'discussion_text_no_mailhost_configured',
|
||||
default=u'You have not configured a mail host or a site \'From\' address, various features including contact forms, email notification and password reset will not work. Go to the E-Mail Settings to fix this.') # noqa: E501
|
||||
IStatusMessage(self.request).addStatusMessage(message, 'warning')
|
||||
message = _(
|
||||
u"discussion_text_no_mailhost_configured",
|
||||
default=u"You have not configured a mail host or a site 'From' address, various features including contact forms, email notification and password reset will not work. Go to the E-Mail Settings to fix this.",
|
||||
) # noqa: E501
|
||||
IStatusMessage(self.request).addStatusMessage(message, "warning")
|
||||
|
||||
def custom_comment_workflow_warning(self):
|
||||
"""Return True if a custom comment workflow is enabled."""
|
||||
wftool = getToolByName(self.context, 'portal_workflow', None)
|
||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
||||
one_state_workflow_enabled = \
|
||||
'comment_one_state_workflow' in workflow_chain
|
||||
comment_review_workflow_enabled = \
|
||||
'comment_review_workflow' in workflow_chain
|
||||
wftool = getToolByName(self.context, "portal_workflow", None)
|
||||
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||
one_state_workflow_enabled = "comment_one_state_workflow" in workflow_chain
|
||||
comment_review_workflow_enabled = "comment_review_workflow" in workflow_chain
|
||||
if one_state_workflow_enabled or comment_review_workflow_enabled:
|
||||
pass
|
||||
else:
|
||||
message = _(u'discussion_text_custom_comment_workflow',
|
||||
default=u'You have configured a custom workflow for the \'Discussion Item\' content type. You can enable/disable the comment moderation in this control panel only if you use one of the default \'Discussion Item\' workflows. Go to the Types control panel to choose a workflow for the \'Discussion Item\' type.') # noqa: E501
|
||||
IStatusMessage(self.request).addStatusMessage(message, 'warning')
|
||||
message = _(
|
||||
u"discussion_text_custom_comment_workflow",
|
||||
default=u"You have configured a custom workflow for the 'Discussion Item' content type. You can enable/disable the comment moderation in this control panel only if you use one of the default 'Discussion Item' workflows. Go to the Types control panel to choose a workflow for the 'Discussion Item' type.",
|
||||
) # noqa: E501
|
||||
IStatusMessage(self.request).addStatusMessage(message, "warning")
|
||||
|
||||
|
||||
def notify_configuration_changed(event):
|
||||
"""Event subscriber that is called every time the configuration changed.
|
||||
"""
|
||||
"""Event subscriber that is called every time the configuration changed."""
|
||||
portal = getSite()
|
||||
wftool = getToolByName(portal, 'portal_workflow', None)
|
||||
wftool = getToolByName(portal, "portal_workflow", None)
|
||||
|
||||
if IRecordModifiedEvent.providedBy(event):
|
||||
# Discussion control panel setting changed
|
||||
if event.record.fieldName == 'moderation_enabled':
|
||||
if event.record.fieldName == "moderation_enabled":
|
||||
# Moderation enabled has changed
|
||||
if event.record.value is True:
|
||||
# Enable moderation workflow
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
'comment_review_workflow')
|
||||
wftool.setChainForPortalTypes(
|
||||
("Discussion Item",), "comment_review_workflow"
|
||||
)
|
||||
else:
|
||||
# Disable moderation workflow
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
'comment_one_state_workflow')
|
||||
wftool.setChainForPortalTypes(
|
||||
("Discussion Item",), "comment_one_state_workflow"
|
||||
)
|
||||
|
||||
if IConfigurationChangedEvent.providedBy(event):
|
||||
# Types control panel setting changed
|
||||
if 'workflow' in event.data:
|
||||
if "workflow" in event.data:
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
workflow_chain = wftool.getChainForPortalType('Discussion Item')
|
||||
workflow_chain = wftool.getChainForPortalType("Discussion Item")
|
||||
if workflow_chain:
|
||||
workflow = workflow_chain[0]
|
||||
if workflow == 'comment_one_state_workflow':
|
||||
if workflow == "comment_one_state_workflow":
|
||||
settings.moderation_enabled = False
|
||||
elif workflow == 'comment_review_workflow':
|
||||
elif workflow == "comment_review_workflow":
|
||||
settings.moderation_enabled = True
|
||||
else:
|
||||
# Custom workflow
|
||||
|
||||
@@ -14,6 +14,7 @@ from zope.component import queryUtility
|
||||
|
||||
try:
|
||||
from plone.dexterity.interfaces import IDexterityContent
|
||||
|
||||
DEXTERITY_INSTALLED = True
|
||||
except ImportError:
|
||||
DEXTERITY_INSTALLED = False
|
||||
@@ -26,15 +27,14 @@ def traverse_parents(context):
|
||||
if not IPloneSiteRoot.providedBy(obj):
|
||||
obj_is_folderish = IFolderish.providedBy(obj)
|
||||
obj_is_stuctural = not INonStructuralFolder.providedBy(obj)
|
||||
if (obj_is_folderish and obj_is_stuctural):
|
||||
flag = getattr(obj, 'allow_discussion', None)
|
||||
if obj_is_folderish and obj_is_stuctural:
|
||||
flag = getattr(obj, "allow_discussion", None)
|
||||
if flag is not None:
|
||||
return flag
|
||||
return None
|
||||
|
||||
|
||||
class ConversationView(object):
|
||||
|
||||
def enabled(self):
|
||||
if DEXTERITY_INSTALLED and IDexterityContent.providedBy(self.context):
|
||||
return self._enabled_for_dexterity_types()
|
||||
@@ -42,7 +42,7 @@ class ConversationView(object):
|
||||
return self._enabled_for_archetypes()
|
||||
|
||||
def _enabled_for_archetypes(self):
|
||||
""" Returns True if discussion is enabled for this conversation.
|
||||
"""Returns True if discussion is enabled for this conversation.
|
||||
|
||||
This method checks five different settings in order to figure out if
|
||||
discussion is enabled on a specific content object:
|
||||
@@ -82,7 +82,7 @@ class ConversationView(object):
|
||||
return False
|
||||
|
||||
# If discussion is disabled for the object, bail out
|
||||
obj_flag = getattr(aq_base(context), 'allow_discussion', None)
|
||||
obj_flag = getattr(aq_base(context), "allow_discussion", None)
|
||||
if obj_flag is False:
|
||||
return False
|
||||
|
||||
@@ -91,16 +91,16 @@ class ConversationView(object):
|
||||
folder_allow_discussion = traverse_parents(context)
|
||||
|
||||
if folder_allow_discussion:
|
||||
if not getattr(self, 'allow_discussion', None):
|
||||
if not getattr(self, "allow_discussion", None):
|
||||
return True
|
||||
else:
|
||||
if obj_flag:
|
||||
return True
|
||||
|
||||
# Check if discussion is allowed on the content type
|
||||
portal_types = getToolByName(self, 'portal_types')
|
||||
portal_types = getToolByName(self, "portal_types")
|
||||
document_fti = getattr(portal_types, context.portal_type)
|
||||
if not document_fti.getProperty('allow_discussion'):
|
||||
if not document_fti.getProperty("allow_discussion"):
|
||||
# If discussion is not allowed on the content type,
|
||||
# check if 'allow discussion' is overridden on the content object.
|
||||
if not obj_flag:
|
||||
@@ -109,7 +109,7 @@ class ConversationView(object):
|
||||
return True
|
||||
|
||||
def _enabled_for_dexterity_types(self):
|
||||
""" Returns True if discussion is enabled for this conversation.
|
||||
"""Returns True if discussion is enabled for this conversation.
|
||||
|
||||
This method checks five different settings in order to figure out if
|
||||
discussion is enable on a specific content object:
|
||||
@@ -134,11 +134,11 @@ class ConversationView(object):
|
||||
return False
|
||||
|
||||
# Check if discussion is allowed on the content object
|
||||
if safe_hasattr(context, 'allow_discussion'):
|
||||
if safe_hasattr(context, "allow_discussion"):
|
||||
if context.allow_discussion is not None:
|
||||
return context.allow_discussion
|
||||
|
||||
# Check if discussion is allowed on the content type
|
||||
portal_types = getToolByName(self, 'portal_types')
|
||||
portal_types = getToolByName(self, "portal_types")
|
||||
document_fti = getattr(portal_types, context.portal_type)
|
||||
return document_fti.getProperty('allow_discussion')
|
||||
return document_fti.getProperty("allow_discussion")
|
||||
|
||||
@@ -18,21 +18,20 @@ from zope.event import notify
|
||||
|
||||
# Translations for generated values in buttons
|
||||
# States
|
||||
_('comment_pending', default='pending')
|
||||
_("comment_pending", default="pending")
|
||||
# _('comment_approved', default='published')
|
||||
_('comment_published', default='published')
|
||||
_('comment_rejected', default='rejected')
|
||||
_('comment_spam', default='marked as spam')
|
||||
_("comment_published", default="published")
|
||||
_("comment_rejected", default="rejected")
|
||||
_("comment_spam", default="marked as spam")
|
||||
# Transitions
|
||||
_('Recall')
|
||||
_('Approve')
|
||||
_('Reject')
|
||||
_('Spam')
|
||||
_("Recall")
|
||||
_("Approve")
|
||||
_("Reject")
|
||||
_("Spam")
|
||||
PMF = _
|
||||
|
||||
|
||||
class TranslationHelper(BrowserView):
|
||||
|
||||
def translate(self, text=""):
|
||||
return _(text)
|
||||
|
||||
@@ -44,22 +43,21 @@ class TranslationHelper(BrowserView):
|
||||
class View(BrowserView):
|
||||
"""Show comment moderation view."""
|
||||
|
||||
template = ViewPageTemplateFile('moderation.pt')
|
||||
template = ViewPageTemplateFile("moderation.pt")
|
||||
try:
|
||||
template.id = '@@moderate-comments'
|
||||
template.id = "@@moderate-comments"
|
||||
except AttributeError:
|
||||
# id is not writeable in Zope 2.12
|
||||
pass
|
||||
|
||||
def __init__(self, context, request):
|
||||
super(View, self).__init__(context, request)
|
||||
self.workflowTool = getToolByName(self.context, 'portal_workflow')
|
||||
self.workflowTool = getToolByName(self.context, "portal_workflow")
|
||||
self.transitions = []
|
||||
|
||||
def __call__(self):
|
||||
self.request.set('disable_border', True)
|
||||
self.request.set('review_state',
|
||||
self.request.get('review_state', 'pending'))
|
||||
self.request.set("disable_border", True)
|
||||
self.request.set("review_state", self.request.get("review_state", "pending"))
|
||||
return self.template()
|
||||
|
||||
def comments(self):
|
||||
@@ -67,15 +65,19 @@ class View(BrowserView):
|
||||
|
||||
review_state is string or list of strings.
|
||||
"""
|
||||
catalog = getToolByName(self.context, 'portal_catalog')
|
||||
if self.request.review_state == 'all':
|
||||
return catalog(object_provides=IComment.__identifier__,
|
||||
sort_on='created',
|
||||
sort_order='reverse')
|
||||
return catalog(object_provides=IComment.__identifier__,
|
||||
review_state=self.request.review_state,
|
||||
sort_on='created',
|
||||
sort_order='reverse')
|
||||
catalog = getToolByName(self.context, "portal_catalog")
|
||||
if self.request.review_state == "all":
|
||||
return catalog(
|
||||
object_provides=IComment.__identifier__,
|
||||
sort_on="created",
|
||||
sort_order="reverse",
|
||||
)
|
||||
return catalog(
|
||||
object_provides=IComment.__identifier__,
|
||||
review_state=self.request.review_state,
|
||||
sort_on="created",
|
||||
sort_order="reverse",
|
||||
)
|
||||
|
||||
def moderation_enabled(self):
|
||||
"""Return true if a review workflow is enabled on 'Discussion Item'
|
||||
@@ -84,11 +86,10 @@ class View(BrowserView):
|
||||
A 'review workflow' is characterized by implementing a 'pending'
|
||||
workflow state.
|
||||
"""
|
||||
workflows = self.workflowTool.getChainForPortalType(
|
||||
'Discussion Item')
|
||||
workflows = self.workflowTool.getChainForPortalType("Discussion Item")
|
||||
if workflows:
|
||||
comment_workflow = self.workflowTool[workflows[0]]
|
||||
if 'pending' in comment_workflow.states:
|
||||
if "pending" in comment_workflow.states:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -100,11 +101,10 @@ class View(BrowserView):
|
||||
A 'review multipe state workflow' is characterized by implementing
|
||||
a 'rejected' workflow state and a 'spam' workflow state.
|
||||
"""
|
||||
workflows = self.workflowTool.getChainForPortalType(
|
||||
'Discussion Item')
|
||||
workflows = self.workflowTool.getChainForPortalType("Discussion Item")
|
||||
if workflows:
|
||||
comment_workflow = self.workflowTool[workflows[0]]
|
||||
if 'spam' in comment_workflow.states:
|
||||
if "spam" in comment_workflow.states:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -125,27 +125,26 @@ class View(BrowserView):
|
||||
"""
|
||||
if obj:
|
||||
transitions = [
|
||||
a for a in self.workflowTool.listActionInfos(object=obj)
|
||||
if a['category'] == 'workflow' and a['allowed']
|
||||
]
|
||||
a
|
||||
for a in self.workflowTool.listActionInfos(object=obj)
|
||||
if a["category"] == "workflow" and a["allowed"]
|
||||
]
|
||||
return transitions
|
||||
|
||||
|
||||
class ModerateCommentsEnabled(BrowserView):
|
||||
|
||||
def __call__(self):
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
content type. A 'review workflow' is characterized by implementing
|
||||
a 'pending' workflow state.
|
||||
content type. A 'review workflow' is characterized by implementing
|
||||
a 'pending' workflow state.
|
||||
"""
|
||||
context = aq_inner(self.context)
|
||||
workflowTool = getToolByName(context, 'portal_workflow', None)
|
||||
comment_workflow = workflowTool.getChainForPortalType(
|
||||
'Discussion Item')
|
||||
workflowTool = getToolByName(context, "portal_workflow", None)
|
||||
comment_workflow = workflowTool.getChainForPortalType("Discussion Item")
|
||||
if comment_workflow:
|
||||
comment_workflow = comment_workflow[0]
|
||||
comment_workflow = workflowTool[comment_workflow]
|
||||
if 'pending' in comment_workflow.states:
|
||||
if "pending" in comment_workflow.states:
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -184,13 +183,15 @@ class DeleteComment(BrowserView):
|
||||
content_object.reindexObject()
|
||||
notify(CommentDeletedEvent(self.context, comment))
|
||||
IStatusMessage(self.context.REQUEST).addStatusMessage(
|
||||
_('Comment deleted.'),
|
||||
type='info')
|
||||
_("Comment deleted."), type="info"
|
||||
)
|
||||
came_from = self.context.REQUEST.HTTP_REFERER
|
||||
# if the referrer already has a came_from in it, don't redirect back
|
||||
if (len(came_from) == 0 or 'came_from=' in came_from or
|
||||
not getToolByName(
|
||||
content_object, 'portal_url').isURLInPortal(came_from)):
|
||||
if (
|
||||
len(came_from) == 0
|
||||
or "came_from=" in came_from
|
||||
or not getToolByName(content_object, "portal_url").isURLInPortal(came_from)
|
||||
):
|
||||
came_from = content_object.absolute_url()
|
||||
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||
|
||||
@@ -198,8 +199,7 @@ class DeleteComment(BrowserView):
|
||||
"""Returns true if current user has the 'Delete comments'
|
||||
permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Delete comments',
|
||||
aq_inner(reply))
|
||||
return getSecurityManager().checkPermission("Delete comments", aq_inner(reply))
|
||||
|
||||
|
||||
class DeleteOwnComment(DeleteComment):
|
||||
@@ -213,21 +213,18 @@ class DeleteOwnComment(DeleteComment):
|
||||
"""
|
||||
|
||||
def could_delete(self, comment=None):
|
||||
"""Returns true if the comment could be deleted if it had no replies.
|
||||
"""
|
||||
"""Returns true if the comment could be deleted if it had no replies."""
|
||||
sm = getSecurityManager()
|
||||
comment = comment or aq_inner(self.context)
|
||||
userid = sm.getUser().getId()
|
||||
return (
|
||||
sm.checkPermission('Delete own comments', comment) and
|
||||
'Owner' in comment.get_local_roles_for_userid(userid)
|
||||
)
|
||||
return sm.checkPermission(
|
||||
"Delete own comments", comment
|
||||
) and "Owner" in comment.get_local_roles_for_userid(userid)
|
||||
|
||||
def can_delete(self, comment=None):
|
||||
comment = comment or self.context
|
||||
return (
|
||||
len(IReplies(aq_inner(comment))) == 0 and
|
||||
self.could_delete(comment=comment)
|
||||
return len(IReplies(aq_inner(comment))) == 0 and self.could_delete(
|
||||
comment=comment
|
||||
)
|
||||
|
||||
def __call__(self):
|
||||
@@ -262,33 +259,37 @@ class CommentTransition(BrowserView):
|
||||
"""Call CommentTransition."""
|
||||
comment = aq_inner(self.context)
|
||||
content_object = aq_parent(aq_parent(comment))
|
||||
workflow_action = self.request.form.get('workflow_action', 'publish')
|
||||
workflowTool = getToolByName(self.context, 'portal_workflow')
|
||||
workflow_action = self.request.form.get("workflow_action", "publish")
|
||||
workflowTool = getToolByName(self.context, "portal_workflow")
|
||||
workflowTool.doActionFor(comment, workflow_action)
|
||||
comment.reindexObject()
|
||||
content_object.reindexObject(idxs=['total_comments'])
|
||||
content_object.reindexObject(idxs=["total_comments"])
|
||||
notify(CommentPublishedEvent(self.context, comment))
|
||||
# for complexer workflows:
|
||||
notify(CommentTransitionEvent(self.context, comment))
|
||||
comment_state_translated = ''
|
||||
comment_state_translated = ""
|
||||
if workflowTool.getWorkflowsFor(comment):
|
||||
review_state_new = workflowTool.getInfoFor(ob=comment, name='review_state')
|
||||
review_state_new = workflowTool.getInfoFor(ob=comment, name="review_state")
|
||||
helper = self.context.restrictedTraverse("translationhelper")
|
||||
comment_state_translated = helper.translate_comment_review_state(review_state_new)
|
||||
comment_state_translated = helper.translate_comment_review_state(
|
||||
review_state_new
|
||||
)
|
||||
|
||||
msgid = _(
|
||||
"comment_transmitted",
|
||||
default='Comment ${comment_state_translated}.',
|
||||
mapping={"comment_state_translated": comment_state_translated})
|
||||
default="Comment ${comment_state_translated}.",
|
||||
mapping={"comment_state_translated": comment_state_translated},
|
||||
)
|
||||
translated = self.context.translate(msgid)
|
||||
IStatusMessage(self.request).add(translated, type='info')
|
||||
IStatusMessage(self.request).add(translated, type="info")
|
||||
|
||||
came_from = self.context.REQUEST.HTTP_REFERER
|
||||
# if the referrer already has a came_from in it, don't redirect back
|
||||
if (len(came_from) == 0
|
||||
or 'came_from=' in came_from
|
||||
or not getToolByName(
|
||||
content_object, 'portal_url').isURLInPortal(came_from)):
|
||||
if (
|
||||
len(came_from) == 0
|
||||
or "came_from=" in came_from
|
||||
or not getToolByName(content_object, "portal_url").isURLInPortal(came_from)
|
||||
):
|
||||
came_from = content_object.absolute_url()
|
||||
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
||||
|
||||
@@ -318,18 +319,18 @@ class BulkActionsView(BrowserView):
|
||||
|
||||
def __init__(self, context, request):
|
||||
super(BulkActionsView, self).__init__(context, request)
|
||||
self.workflowTool = getToolByName(context, 'portal_workflow')
|
||||
self.workflowTool = getToolByName(context, "portal_workflow")
|
||||
|
||||
def __call__(self):
|
||||
"""Call BulkActionsView."""
|
||||
if 'form.select.BulkAction' in self.request:
|
||||
bulkaction = self.request.get('form.select.BulkAction')
|
||||
self.paths = self.request.get('paths')
|
||||
if "form.select.BulkAction" in self.request:
|
||||
bulkaction = self.request.get("form.select.BulkAction")
|
||||
self.paths = self.request.get("paths")
|
||||
if self.paths:
|
||||
if bulkaction == '-1':
|
||||
if bulkaction == "-1":
|
||||
# no bulk action was selected
|
||||
pass
|
||||
elif bulkaction == 'delete':
|
||||
elif bulkaction == "delete":
|
||||
self.delete()
|
||||
else:
|
||||
self.transmit(bulkaction)
|
||||
@@ -346,13 +347,14 @@ class BulkActionsView(BrowserView):
|
||||
comment = context.restrictedTraverse(path)
|
||||
content_object = aq_parent(aq_parent(comment))
|
||||
allowed_transitions = [
|
||||
transition['id'] for transition in self.workflowTool.listActionInfos(object=comment)
|
||||
if transition['category'] == 'workflow' and transition['allowed']
|
||||
]
|
||||
transition["id"]
|
||||
for transition in self.workflowTool.listActionInfos(object=comment)
|
||||
if transition["category"] == "workflow" and transition["allowed"]
|
||||
]
|
||||
if action in allowed_transitions:
|
||||
self.workflowTool.doActionFor(comment, action)
|
||||
comment.reindexObject()
|
||||
content_object.reindexObject(idxs=['total_comments'])
|
||||
content_object.reindexObject(idxs=["total_comments"])
|
||||
notify(CommentPublishedEvent(content_object, comment))
|
||||
# for complexer workflows:
|
||||
notify(CommentTransitionEvent(self.context, comment))
|
||||
@@ -370,5 +372,5 @@ class BulkActionsView(BrowserView):
|
||||
conversation = aq_parent(comment)
|
||||
content_object = aq_parent(conversation)
|
||||
del conversation[comment.id]
|
||||
content_object.reindexObject(idxs=['total_comments'])
|
||||
content_object.reindexObject(idxs=["total_comments"])
|
||||
notify(CommentDeletedEvent(content_object, comment))
|
||||
|
||||
@@ -29,8 +29,8 @@ class ConversationNamespace(object):
|
||||
|
||||
def traverse(self, name, ignore):
|
||||
|
||||
if name == 'default':
|
||||
name = u''
|
||||
if name == "default":
|
||||
name = u""
|
||||
|
||||
conversation = queryAdapter(self.context, IConversation, name=name)
|
||||
if conversation is None:
|
||||
|
||||
@@ -44,11 +44,12 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
if settings.captcha in ('captcha', 'recaptcha', 'norobots'):
|
||||
captcha = getMultiAdapter((aq_inner(self.context), self.request),
|
||||
name=settings.captcha)
|
||||
if settings.captcha in ("captcha", "recaptcha", "norobots"):
|
||||
captcha = getMultiAdapter(
|
||||
(aq_inner(self.context), self.request), name=settings.captcha
|
||||
)
|
||||
if not captcha.verify(input=value):
|
||||
if settings.captcha == 'norobots':
|
||||
if settings.captcha == "norobots":
|
||||
raise WrongNorobotsAnswer
|
||||
else:
|
||||
raise WrongCaptchaCode
|
||||
@@ -57,5 +58,4 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
||||
|
||||
|
||||
# Register Captcha validator for the Captcha field in the ICaptcha Form
|
||||
validator.WidgetValidatorDiscriminators(CaptchaValidator,
|
||||
field=ICaptcha['captcha'])
|
||||
validator.WidgetValidatorDiscriminators(CaptchaValidator, field=ICaptcha["captcha"])
|
||||
|
||||
Reference in New Issue
Block a user