Merge branch 'master' into plip10359-z3cform

Conflicts:
	CHANGES.rst
This commit is contained in:
Timo Stollenwerk
2015-01-29 21:31:45 +01:00
105 changed files with 4849 additions and 3559 deletions
+69
View File
@@ -1,8 +1,16 @@
from Acquisition import aq_inner, aq_parent
from AccessControl import getSecurityManager
from zope.component import getMultiAdapter
from Products.statusmessages.interfaces import IStatusMessage
from Products.Five.browser import BrowserView
from Products.CMFCore.utils import getToolByName
from plone.app.discussion import PloneAppDiscussionMessageFactory as _
from comments import CommentForm
from z3c.form import button
from plone.z3cform.layout import wrap_form
class View(BrowserView):
"""Comment View.
@@ -37,3 +45,64 @@ class View(BrowserView):
url = "%s/view" % url
self.request.response.redirect('%s#%s' % (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')
def updateWidgets(self):
super(EditCommentForm, self).updateWidgets()
self.widgets['text'].value = self.context.text
# We have to rename the id, otherwise TinyMCE can't initialize
# because there are two textareas with the same id.
self.widgets['text'].id = 'overlay-comment-text'
def _redirect(self, target=''):
if not target:
portal_state = getMultiAdapter((self.context, self.request),
name=u'plone_portal_state')
target = portal_state.portal_url()
self.request.response.redirect(target)
@button.buttonAndHandler(_(u"edit_comment_form_button",
default=u"Edit comment"), name='comment')
def handleComment(self, action):
# Validate form
data, errors = self.extractData()
if errors:
return
# Check permissions
can_edit = getSecurityManager().checkPermission(
'Edit comments',
self.context)
mtool = getToolByName(self.context, 'portal_membership')
if mtool.isAnonymousUser() or not can_edit:
return
# Update text
self.context.text = data['text']
# Redirect to comment
IStatusMessage(self.request).add(_(u'comment_edit_notification',
default="Comment was edited"),
type='info')
return self._redirect(
target=self.action.replace("@@edit-comment", "@@view"))
@button.buttonAndHandler(_(u'cancel_form_button',
default=u'Cancel'), name='cancel')
def handle_cancel(self, action):
IStatusMessage(self.request).add(
_(u'comment_edit_cancel_notification',
default=u'Edit comment cancelled'),
type='info')
return self._redirect(target=self.context.absolute_url())
EditComment = wrap_form(EditCommentForm)
#EOF
+37 -4
View File
@@ -1,6 +1,8 @@
<tal:block tal:define="userHasReplyPermission view/can_reply;
isDiscussionAllowed view/is_discussion_allowed;
isAnonymousDiscussionAllowed view/anonymous_discussion_allowed;
isEditCommentAllowed view/edit_comment_allowed;
isDeleteOwnCommentAllowed view/delete_own_comment_allowed;
isAnon view/is_anonymous;
canReview view/can_review;
replies python:view.get_replies(canReview);
@@ -34,7 +36,9 @@
author_home_url python:view.get_commenter_home_url(username=reply.author_username);
has_author_link python:author_home_url and not isAnon;
portrait_url python:view.get_commenter_portrait(reply.author_username);
review_state python:wtool.getInfoFor(reply, 'review_state', 'none');"
review_state python:wtool.getInfoFor(reply, 'review_state', 'none');
canEdit python:view.can_edit(reply);
canDelete python:view.can_delete(reply)"
tal:attributes="class python:'comment replyTreeLevel'+str(depth)+' state-'+str(review_state);
id string:${reply/getId}"
tal:condition="python:canReview or review_state == 'published'">
@@ -42,14 +46,14 @@
<div class="commentImage" tal:condition="showCommenterImage">
<a href="" tal:condition="has_author_link"
tal:attributes="href author_home_url">
<img src="defaultUser.gif"
<img src="defaultUser.png"
alt=""
class="defaultuserimg"
height="32"
tal:attributes="src portrait_url;
alt reply/author_name" />
</a>
<img src="defaultUser.gif"
<img src="defaultUser.png"
alt=""
class="defaultuserimg"
height="32"
@@ -87,7 +91,21 @@
action=""
method="post"
class="commentactionsform"
tal:condition="canReview"
tal:condition="python:not canDelete and isDeleteOwnCommentAllowed and view.could_delete_own(reply)"
tal:attributes="action string:${reply/absolute_url}/@@delete-own-comment;
style python:view.can_delete_own(reply) and 'display: inline' or 'display: none'">
<input name="form.button.DeleteComment"
class="destructive"
type="submit"
value="Delete"
i18n:attributes="value label_delete;"
/>
</form>
<form name="delete"
action=""
method="post"
class="commentactionsform"
tal:condition="python:canDelete"
tal:attributes="action string:${reply/absolute_url}/@@moderate-delete-comment">
<input name="form.button.DeleteComment"
class="destructive"
@@ -97,6 +115,21 @@
/>
</form>
<form name="edit"
action=""
method="get"
class="commentactionsform"
tal:condition="python:isEditCommentAllowed and canEdit"
tal:attributes="action string:${reply/absolute_url}/@@edit-comment">
<input name="form.button.EditComment"
class="context"
type="submit"
value="Edit"
i18n:attributes="value label_edit;"
/>
</form>
<!-- Workflow actions (e.g. 'publish') -->
<form name=""
action=""
+75 -13
View File
@@ -101,14 +101,21 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# Anonymous / Logged-in
mtool = getToolByName(self.context, 'portal_membership')
if not mtool.isAnonymousUser():
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
anon = mtool.isAnonymousUser()
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
if mtool.isAnonymousUser() and not settings.anonymous_email_enabled:
if anon:
if settings.anonymous_email_enabled:
# according to IDiscussionSettings.anonymous_email_enabled:
# "If selected, anonymous user will have to give their email."
self.widgets['author_email'].field.required = True
self.widgets['author_email'].required = True
else:
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
else:
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
member = mtool.getAuthenticatedMember()
@@ -119,7 +126,6 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# email address
member_email_is_empty = member_email == ''
user_notification_disabled = not settings.user_notification_enabled
anon = mtool.isAnonymousUser()
if member_email_is_empty or user_notification_disabled or anon:
self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
@@ -175,11 +181,15 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
# Set comment attributes (including extended comment form attributes)
for attribute in self.fields.keys():
setattr(comment, attribute, data[attribute])
# Make sure author_name is properly encoded
# Make sure author_name/ author_email is properly encoded
if 'author_name' in data:
author_name = data['author_name']
if isinstance(author_name, str):
author_name = unicode(author_name, 'utf-8')
if 'author_email' in data:
author_email = data['author_email']
if isinstance(author_email, str):
author_email = unicode(author_email, 'utf-8')
# Set comment author properties for anonymous users or members
can_reply = getSecurityManager().checkPermission('Reply to item',
@@ -188,14 +198,14 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
if anon and anonymous_comments:
# Anonymous Users
comment.author_name = author_name
comment.author_email = u""
comment.author_email = author_email
comment.user_notification = None
comment.creation_date = datetime.utcnow()
comment.modification_date = datetime.utcnow()
elif not portal_membership.isAnonymousUser() and can_reply:
# Member
member = portal_membership.getAuthenticatedMember()
username = member.getUserName()
memberid = member.getId()
user = member.getUser()
email = member.getProperty('email')
fullname = member.getProperty('fullname')
@@ -207,13 +217,20 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
if email and isinstance(email, str):
email = unicode(email, 'utf-8')
comment.changeOwnership(user, recursive=False)
comment.manage_setLocalRoles(username, ["Owner"])
comment.creator = username
comment.author_username = username
comment.manage_setLocalRoles(memberid, ["Owner"])
comment.creator = memberid
comment.author_username = memberid
comment.author_name = fullname
# XXX: according to IComment interface author_email must not be
# set for logged in users, cite:
# "for anonymous comments only, set to None for logged in comments"
comment.author_email = email
# /XXX
comment.creation_date = datetime.utcnow()
comment.modification_date = datetime.utcnow()
else: # pragma: no cover
raise Unauthorized(
u"Anonymous user tries to post a comment, but anonymous "
@@ -301,6 +318,40 @@ class CommentsViewlet(ViewletBase):
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()
except Unauthorized:
return False
def could_delete_own(self, comment):
"""Returns true if the current user could delete the comment if it had
no replies. This is used to prepare hidden form buttons for JS.
"""
try:
return comment.restrictedTraverse(
'@@delete-own-comment').could_delete()
except Unauthorized:
return False
def can_edit(self, reply):
"""Returns true if current user has the 'Edit comments'
permission.
"""
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))
def is_discussion_allowed(self):
context = aq_inner(self.context)
return context.restrictedTraverse('@@conversation_view').enabled()
@@ -367,7 +418,6 @@ class CommentsViewlet(ViewletBase):
return iter([])
wf = getToolByName(context, 'portal_workflow')
# workflow_actions is only true when user
# has 'Manage portal' permission
@@ -411,7 +461,7 @@ class CommentsViewlet(ViewletBase):
if username is None:
# return the default user image if no username is given
return 'defaultUser.gif'
return 'defaultUser.png'
else:
portal_membership = getToolByName(self.context,
'portal_membership',
@@ -426,6 +476,18 @@ class CommentsViewlet(ViewletBase):
settings = registry.forInterface(IDiscussionSettings, check=False)
return settings.anonymous_comments
def edit_comment_allowed(self):
# Check if editing comments is allowed in the registry
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
return settings.edit_comment_enabled
def delete_own_comment_allowed(self):
# Check if delete own comments is allowed in the registry
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
return settings.delete_own_comment_enabled
def show_commenter_image(self):
# Check if showing commenter image is enabled in the registry
registry = queryUtility(IRegistry)
+21 -2
View File
@@ -71,13 +71,32 @@
permission="zope2.View"
/>
<!-- Delete comment view -->
<!-- Edit comment view -->
<browser:page
for="plone.app.discussion.interfaces.IComment"
name="edit-comment"
layer="..interfaces.IDiscussionLayer"
class=".comment.EditComment"
permission="plone.app.discussion.EditComments"
/>
<!-- Delete comment views
has conditional security dependent on controlpanel settings.
-->
<browser:page
for="plone.app.discussion.interfaces.IComment"
name="moderate-delete-comment"
layer="..interfaces.IDiscussionLayer"
class=".moderation.DeleteComment"
permission="plone.app.discussion.ReviewComments"
permission="plone.app.discussion.DeleteComments"
/>
<browser:page
for="plone.app.discussion.interfaces.IComment"
name="delete-own-comment"
layer="..interfaces.IDiscussionLayer"
class=".moderation.DeleteOwnComment"
permission="plone.app.discussion.DeleteOwnComments"
/>
<!-- Publish comment view -->
+20 -20
View File
@@ -14,7 +14,7 @@
<body>
<div id="content"
<article id="content"
tal:attributes="class view/settings"
metal:fill-slot="prefs_configlet_content">
@@ -22,12 +22,12 @@
tal:attributes="src string:${context/portal_url}/++resource++plone.app.discussion.javascripts/controlpanel.js">
</script>
<dl class="portalMessage warning"
<div class="portalMessage warning"
tal:condition="view/mailhost_warning">
<dt i18n:translate="">
<strong i18n:translate="">
Warning
</dt>
<dd i18n:translate="text_no_mailhost_configured">
</strong>
<span tal:omit-tag="" i18n:translate="text_no_mailhost_configured">
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
@@ -38,15 +38,15 @@
>Mail control panel</a>
</tal:link>
to fix this.
</dd>
</dl>
</span>
</div>
<dl class="portalMessage warning"
<div class="portalMessage warning"
tal:condition="view/custom_comment_workflow_warning">
<dt i18n:translate="">
<strong i18n:translate="">
Warning
</dt>
<dd i18n:translate="text_custom_comment_workflow">
</strong>
<span tal:omit-tag="" i18n:translate="text_custom_comment_workflow">
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
@@ -58,15 +58,15 @@
>Types control panel</a>
</tal:link>
to choose a workflow for the 'Discussion Item' type.
</dd>
</dl>
</span>
</div>
<dl class="portalMessage warning"
<div class="portalMessage warning"
tal:condition="view/unmigrated_comments_warning">
<dt i18n:translate="">
<strong i18n:translate="">
Warning
</dt>
<dd i18n:translate="text_unmigrated_comments">
</strong>
<span tal:omit-tag="" i18n:translate="text_unmigrated_comments">
You have comments that have not been migrated to the new
commenting system that has been introduced in Plone 4.1.
Please
@@ -77,8 +77,8 @@
>migrate your comments</a>
</tal:link>
to fix this.
</dd>
</dl>
</span>
</div>
<div metal:use-macro="context/global_statusmessage/macros/portal_message">
Portal status message
@@ -99,6 +99,6 @@
</div>
</div>
</div>
</article>
</body>
</html>
+26 -12
View File
@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
from Acquisition import aq_base, aq_inner
from zope.component import getUtility
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.interfaces._content import IDiscussionResponse
from Products.CMFPlone.interfaces.controlpanel import IMailSchema
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
@@ -24,6 +22,7 @@ from z3c.form import button
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
from plone.app.discussion.interfaces import IDiscussionSettings, _
from plone.app.discussion.upgrades import update_registry
class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
@@ -51,6 +50,10 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
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 = \
@@ -61,7 +64,13 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
SingleCheckBoxFieldWidget
def updateWidgets(self):
super(DiscussionSettingsEditForm, self).updateWidgets()
try:
super(DiscussionSettingsEditForm, self).updateWidgets()
except KeyError:
# upgrade profile not visible in prefs_install_products_form
# 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")
@@ -118,6 +127,12 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
elif settings.moderation_enabled:
output.append("moderation_enabled")
if settings.edit_comment_enabled:
output.append("edit_comment_enabled")
if settings.delete_own_comment_enabled:
output.append("delte_own_comment_enabled")
# Anonymous comments
if settings.anonymous_comments:
output.append("anonymous_comments")
@@ -140,12 +155,11 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
def mailhost_warning(self):
"""Returns true if mailhost is not configured properly.
"""
# Copied from plone.app.controlpanel/plone/app/controlpanel/overview.py
mailhost = getToolByName(aq_inner(self.context), 'MailHost', None)
if mailhost is None:
return True
mailhost = getattr(aq_base(mailhost), 'smtp_host', None)
email = getattr(aq_inner(self.context), 'email_from_address', None)
# Copied from Products.CMFPlone/controlpanel/browser/overview.py
registry = getUtility(IRegistry)
mail_settings = registry.forInterface(IMailSchema, prefix='plone')
mailhost = mail_settings.smtp_host
email = mail_settings.email_from_address
if mailhost and email:
return False
return True
@@ -165,7 +179,7 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
def unmigrated_comments_warning(self):
"""Returns true if site contains unmigrated comments.
"""
catalog = getToolByName(aq_inner(self.context), 'portal_catalog', None)
catalog = getToolByName(self.context, 'portal_catalog', None)
count_comments_old = catalog.searchResults(
object_provides=IDiscussionResponse.__identifier__)
if count_comments_old:
@@ -35,8 +35,9 @@
*/
reply_div.appendTo(comment_div).css("display", "none");
/* Remove id="reply" attribute, since we use it to uniquely
/* Remove id="commenting" attribute, since we use it to uniquely define
the main reply form. */
// Still belongs to class="reply"
reply_div.removeAttr("id");
/* Hide the reply button (only hide, because we may want to show it
@@ -47,6 +48,13 @@
/* Fetch the reply form inside the reply div */
var reply_form = reply_div.find("form");
/* Change the id of the textarea of the reply form
* To avoid conflict later between textareas with same id 'form-widgets-comment-text' while implementing a seperate instance of TinyMCE
* */
reply_form.find('#formfield-form-widgets-comment-text').attr('id', 'formfield-form-widgets-new-textarea'+comment_id );
reply_form.find('#form-widgets-comment-text').attr('id', 'form-widgets-new-textarea'+comment_id );
/* Populate the hidden 'in_reply_to' field with the correct comment
id */
reply_form.find("input[name='form.widgets.in_reply_to']")
@@ -98,7 +106,7 @@
var post_comment_div = $("#commenting");
var in_reply_to_field =
post_comment_div.find("input[name='form.widgets.in_reply_to']");
if (in_reply_to_field.val() !== "") {
if (in_reply_to_field.length !== 0 && in_reply_to_field.val() !== "") {
var current_reply_id = "#" + in_reply_to_field.val();
var current_reply_to_div = $(".discussion").find(current_reply_id);
$.createReplyForm(current_reply_to_div);
@@ -143,7 +151,7 @@
/**********************************************************************
* Publish a single comment.
**********************************************************************/
$("input[name='form.button.PublishComment']").live('click', function () {
$("input[name='form.button.PublishComment']").on('click', function () {
var trigger = this;
var form = $(this).parents("form");
var data = $(form).serialize();
@@ -151,7 +159,7 @@
$.ajax({
type: "GET",
url: form_url,
data: "workflow_action=publish",
data: data,
context: trigger,
success: function (msg) {
// remove button (trigger object can't be directly removed)
@@ -165,11 +173,20 @@
return false;
});
/**********************************************************************
* Edit a comment
**********************************************************************/
$("form[name='edit']").prepOverlay({
cssclass: 'overlay-edit-comment',
width: '60%',
subtype: 'ajax',
filter: '#content>*'
})
/**********************************************************************
* Delete a comment and its answers.
**********************************************************************/
$("input[name='form.button.DeleteComment']").live('click', function () {
$("input[name='form.button.DeleteComment']").on('click', function () {
var trigger = this;
var form = $(this).parents("form");
var data = $(form).serialize();
@@ -194,6 +211,9 @@
$(this).remove();
});
});
// Add delete button to the parent
var parent = comment.prev('[class*="replyTreeLevel' + (treelevel - 1) + '"]');
parent.find('form[name="delete"]').css('display', 'inline');
// remove comment
$(this).fadeOut('fast', function () {
$(this).remove();
@@ -40,6 +40,8 @@
$.enableSettings([
$('#formfield-form-widgets-anonymous_comments'),
$('#formfield-form-widgets-moderation_enabled'),
$('#formfield-form-widgets-edit_comment_enabled'),
$('#formfield-form-widgets-delete_own_comment_enabled'),
$('#formfield-form-widgets-text_transform'),
$('#formfield-form-widgets-captcha'),
$('#formfield-form-widgets-show_commenter_image'),
@@ -52,6 +54,8 @@
$.disableSettings([
$('#formfield-form-widgets-anonymous_comments'),
$('#formfield-form-widgets-moderation_enabled'),
$('#formfield-form-widgets-edit_comment_enabled'),
$('#formfield-form-widgets-delete_own_comment_enabled'),
$('#formfield-form-widgets-text_transform'),
$('#formfield-form-widgets-captcha'),
$('#formfield-form-widgets-show_commenter_image'),
@@ -100,7 +104,7 @@
$.updateSettings();
// Set #content class and update settings afterwards
$("input,select").live("change", function (e) {
$("input,select").on("change", function (e) {
var id = $(this).attr("id");
if (id === "form-widgets-globally_enabled-0") {
if ($(this).attr("checked")) {
+5 -5
View File
@@ -25,18 +25,18 @@
Moderate comments
</h1>
<dl class="portalMessage warning"
<div class="portalMessage warning"
tal:condition="not: view/moderation_enabled">
<dt i18n:domain="plone" i18n:translate="">Warning</dt>
<dd i18n:translate="message_moderation_disabled">
<strong i18n:domain="plone" i18n:translate="">Warning</strong>
<span tal:omit-tag="" i18n:translate="message_moderation_disabled">
Moderation workflow is disabled. You have to
<a i18n:name="enable_comment_workflow"
i18n:translate="message_enable_comment_workflow" href=""
tal:attributes="href string:${context/portal_url}/@@types-controlpanel?type_id=Discussion Item">
enable the 'Comment Review Workflow' for the Comment content
type</a> before you can moderate comments here.
</dd>
</dl>
</span>
</div>
<form tal:condition="not:items">
<fieldset id="fieldset-moderate-comments" class="formPanel">
+57 -11
View File
@@ -1,5 +1,9 @@
# -*- coding: utf-8 -*-
from Acquisition import aq_inner, aq_parent
from AccessControl import getSecurityManager
from zope.component import queryUtility
from AccessControl import Unauthorized, getSecurityManager
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
@@ -8,8 +12,11 @@ from Products.CMFCore.utils import getToolByName
from Products.statusmessages.interfaces import IStatusMessage
from plone.registry.interfaces import IRegistry
from plone.app.discussion.interfaces import IDiscussionSettings
from plone.app.discussion.interfaces import _
from plone.app.discussion.interfaces import IComment
from plone.app.discussion.interfaces import IReplies
class View(BrowserView):
@@ -94,17 +101,57 @@ class DeleteComment(BrowserView):
comment = aq_inner(self.context)
conversation = aq_parent(comment)
content_object = aq_parent(conversation)
del conversation[comment.id]
content_object.reindexObject()
IStatusMessage(self.context.REQUEST).addStatusMessage(
_("Comment deleted."),
type="info")
# conditional security
# base ZCML condition zope2.deleteObject allows 'delete own object'
# modify this for 'delete_own_comment_allowed' controlpanel setting
if self.can_delete(comment):
del conversation[comment.id]
content_object.reindexObject()
IStatusMessage(self.context.REQUEST).addStatusMessage(
_("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:
came_from = content_object.absolute_url()
return self.context.REQUEST.RESPONSE.redirect(came_from)
def can_delete(self, reply):
"""Returns true if current user has the 'Delete comments'
permission.
"""
return getSecurityManager().checkPermission('Delete comments',
aq_inner(reply))
class DeleteOwnComment(DeleteComment):
"""Delete an own comment if it has no replies. Following conditions have to be true
for a user to be able to delete his comments:
* "Delete own comments" permission
* no replies to the comment
* Owner role directly assigned on the comment object
"""
def could_delete(self, comment=None):
"""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))
def can_delete(self, comment=None):
comment = comment or self.context
return (len(IReplies(aq_inner(comment))) == 0
and self.could_delete(comment=comment))
def __call__(self):
if self.can_delete():
super(DeleteOwnComment, self).__call__()
else:
raise Unauthorized("You're not allowed to delete this comment.")
class PublishComment(BrowserView):
"""Publish a comment.
@@ -131,11 +178,10 @@ class PublishComment(BrowserView):
comment = aq_inner(self.context)
content_object = aq_parent(aq_parent(comment))
workflowTool = getToolByName(comment, 'portal_workflow', None)
current_state = workflowTool.getInfoFor(comment, 'review_state')
if current_state != 'published':
workflowTool.doActionFor(comment, 'publish')
workflow_action = self.request.form.get('workflow_action', 'publish')
workflowTool.doActionFor(comment, workflow_action)
comment.reindexObject()
content_object.reindexObject()
content_object.reindexObject(idxs=['total_comments'])
IStatusMessage(self.context.REQUEST).addStatusMessage(
_("Comment approved."),
type="info")
@@ -208,7 +254,7 @@ class BulkActionsView(BrowserView):
if current_state != 'published':
workflowTool.doActionFor(comment, 'publish')
comment.reindexObject()
content_object.reindexObject()
content_object.reindexObject(idxs=['total_comments'])
def mark_as_spam(self):
raise NotImplementedError
@@ -227,4 +273,4 @@ class BulkActionsView(BrowserView):
conversation = aq_parent(comment)
content_object = aq_parent(conversation)
del conversation[comment.id]
content_object.reindexObject()
content_object.reindexObject(idxs=['total_comments'])
@@ -224,3 +224,9 @@
.row .discussion label {
font-weight:bold;
}
/* editing comments */
.overlay-edit-comment textarea {
height: 10em;
}