Merge branch 'master' into plip10359-z3cform
Conflicts: CHANGES.rst
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user