Nuke trailing white space
svn path=/plone.app.discussion/trunk/; revision=46366
This commit is contained in:
@@ -39,7 +39,7 @@ Captcha = factory(Captcha)
|
||||
|
||||
class CaptchaExtender(extensible.FormExtender):
|
||||
"""Extends the comment form with a Captcha. This Captcha extender is only
|
||||
registered when a plugin is installed that provides the
|
||||
registered when a plugin is installed that provides the
|
||||
"plone.app.discussion-captcha" feature.
|
||||
"""
|
||||
adapts(Interface, IDefaultBrowserLayer, CommentForm) # context, request, form
|
||||
@@ -72,4 +72,4 @@ class CaptchaExtender(extensible.FormExtender):
|
||||
self.form.fields['captcha'].widgetFactory = NorobotsFieldWidget
|
||||
else:
|
||||
self.form.fields['captcha'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:meta="http://namespaces.zope.org/meta"
|
||||
xmlns:meta="http://namespaces.zope.org/meta"
|
||||
xmlns:zcml="http://namespaces.zope.org/zcml"
|
||||
i18n_domain="plone.app.discussion">
|
||||
|
||||
<!--
|
||||
<!--
|
||||
Plone 3 fixes
|
||||
|
||||
Plone 3 / Zope 2.10 does not recognize the meta:provides feature.
|
||||
|
||||
Plone 3 / Zope 2.10 does not recognize the meta:provides feature.
|
||||
Therefore we claim to provide this feature when a suitable package is
|
||||
installed.
|
||||
installed.
|
||||
-->
|
||||
<configure zcml:condition="installed plone.formwidget.captcha">
|
||||
<meta:provides feature="plone.app.discussion-captcha" />
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
<!-- Captcha comment form extender -->
|
||||
<configure zcml:condition="have plone.app.discussion-captcha">
|
||||
<!--
|
||||
<!--
|
||||
Register the Captcha form extender and validator only if there are
|
||||
plugins installed that declare to implement a Captcha solution for
|
||||
plone.app.discussion (e.g. plone.formwidget.captcha and
|
||||
plone.formwidget.recaptcha).
|
||||
plone.app.discussion (e.g. plone.formwidget.captcha and
|
||||
plone.formwidget.recaptcha).
|
||||
-->
|
||||
<adapter
|
||||
factory=".captcha.Captcha"
|
||||
@@ -44,7 +44,7 @@
|
||||
<adapter
|
||||
factory="collective.akismet.validator.AkismetValidator"
|
||||
provides="z3c.form.interfaces.IValidator"
|
||||
/>
|
||||
/>
|
||||
</configure>
|
||||
|
||||
</configure>
|
||||
|
||||
</configure>
|
||||
|
||||
@@ -5,15 +5,15 @@ from Products.Five.browser import BrowserView
|
||||
class View(BrowserView):
|
||||
"""Comment View.
|
||||
|
||||
When the view of a comment object is called directly, redirect to the
|
||||
When the view of a comment object is called directly, redirect to the
|
||||
the page (content object) and the location (HTML-anchor) where the comment
|
||||
has been posted.
|
||||
|
||||
|
||||
Redirect from the comment object URL
|
||||
"/path/to/object/++conversation++default/123456789" to the content object
|
||||
where the comment has been posted appended by an HTML anchor that points to
|
||||
where the comment has been posted appended by an HTML anchor that points to
|
||||
the comment "/path/to/object#comment-123456789".
|
||||
|
||||
|
||||
Context is the comment object. The parent of the comment object is the
|
||||
conversation. The parent of the conversation is the content object where
|
||||
the comment has been posted.
|
||||
@@ -24,4 +24,4 @@ class View(BrowserView):
|
||||
self.request.response.redirect(
|
||||
aq_parent(aq_parent(context)).absolute_url() +
|
||||
'#' + str(context.id)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
tal:attributes="src portrait_url;
|
||||
alt reply/Creator" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="documentByLine" i18n:domain="plone.app.discussion">
|
||||
<tal:name>
|
||||
<a href=""
|
||||
@@ -72,16 +72,16 @@
|
||||
<span tal:condition="not: reply/Creator">Anonymous</span>
|
||||
</tal:name>
|
||||
<tal:posted i18n:translate="label_says">says:</tal:posted>
|
||||
<div class="commentDate"
|
||||
<div class="commentDate"
|
||||
tal:content="python:view.format_time(reply.modification_date)">
|
||||
8/23/2001 12:40:44 PM
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="commentBody">
|
||||
|
||||
|
||||
<span tal:replace="structure python:view.cook(reply.getText())" />
|
||||
|
||||
|
||||
<div class="commentActions">
|
||||
<form name="delete"
|
||||
action=""
|
||||
@@ -96,7 +96,7 @@
|
||||
i18n:attributes="value label_delete;"
|
||||
/>
|
||||
</form>
|
||||
|
||||
|
||||
<!-- Workflow actions (e.g. 'publish') -->
|
||||
<form name=""
|
||||
action=""
|
||||
@@ -115,7 +115,7 @@
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<button class="context reply-to-comment-button hide allowMultiSubmit"
|
||||
|
||||
@@ -41,10 +41,10 @@ from plone.z3cform import z2
|
||||
from plone.z3cform.widget import SingleCheckBoxWidget
|
||||
from plone.z3cform.fieldsets import extensible
|
||||
|
||||
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface
|
||||
# starting from 0.6.0 version plone.z3cform has IWrappedForm interface
|
||||
try:
|
||||
from plone.z3cform.interfaces import IWrappedForm
|
||||
HAS_WRAPPED_FORM = True
|
||||
from plone.z3cform.interfaces import IWrappedForm
|
||||
HAS_WRAPPED_FORM = True
|
||||
except ImportError: # pragma: no cover
|
||||
HAS_WRAPPED_FORM = False
|
||||
|
||||
@@ -87,48 +87,48 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# Widgets
|
||||
self.widgets['in_reply_to'].mode = interfaces.HIDDEN_MODE
|
||||
self.widgets['text'].addClass("autoresize")
|
||||
self.widgets['user_notification'].label = _(u"")
|
||||
|
||||
self.widgets['user_notification'].label = _(u"")
|
||||
|
||||
# Anonymous / Logged-in
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
if not portal_membership.isAnonymousUser():
|
||||
self.widgets['author_name'].mode = interfaces.HIDDEN_MODE
|
||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
# Todo: Since we are not using the author_email field in the
|
||||
# current state, we hide it by default. But we keep the field for
|
||||
# integrators or later use.
|
||||
# Todo: Since we are not using the author_email field in the
|
||||
# current state, we hide it by default. But we keep the field for
|
||||
# integrators or later use.
|
||||
self.widgets['author_email'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
|
||||
|
||||
if not settings.user_notification_enabled or portal_membership.isAnonymousUser():
|
||||
self.widgets['user_notification'].mode = interfaces.HIDDEN_MODE
|
||||
|
||||
|
||||
def updateActions(self):
|
||||
super(CommentForm, self).updateActions()
|
||||
super(CommentForm, self).updateActions()
|
||||
self.actions['cancel'].addClass("standalone")
|
||||
self.actions['cancel'].addClass("hide")
|
||||
self.actions['comment'].addClass("context")
|
||||
|
||||
@button.buttonAndHandler(_(u"add_comment_button", default=u"Comment"),
|
||||
self.actions['cancel'].addClass("hide")
|
||||
self.actions['comment'].addClass("context")
|
||||
|
||||
@button.buttonAndHandler(_(u"add_comment_button", default=u"Comment"),
|
||||
name='comment')
|
||||
def handleComment(self, action):
|
||||
context = aq_inner(self.context)
|
||||
wf = getToolByName(context, 'portal_workflow')
|
||||
|
||||
|
||||
data, errors = self.extractData()
|
||||
if errors:
|
||||
return
|
||||
|
||||
|
||||
text = u""
|
||||
author_name = u""
|
||||
author_email = u""
|
||||
user_notification = None
|
||||
|
||||
# Captcha check for anonymous users (if Captcha is enabled and
|
||||
# Captcha check for anonymous users (if Captcha is enabled and
|
||||
# anonymous commenting is allowed)
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
@@ -138,10 +138,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
portal_membership.isAnonymousUser():
|
||||
if not 'captcha' in data:
|
||||
data['captcha'] = u""
|
||||
captcha = CaptchaValidator(self.context,
|
||||
self.request,
|
||||
None,
|
||||
ICaptcha['captcha'],
|
||||
captcha = CaptchaValidator(self.context,
|
||||
self.request,
|
||||
None,
|
||||
ICaptcha['captcha'],
|
||||
None)
|
||||
captcha.validate(data['captcha'])
|
||||
|
||||
@@ -156,10 +156,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
author_email = data['author_email']
|
||||
if 'user_notification' in data:
|
||||
user_notification = data['user_notification']
|
||||
|
||||
|
||||
# The add-comment view is called on the conversation object
|
||||
conversation = IConversation(self.__parent__)
|
||||
|
||||
|
||||
# Check if conversation is enabled on this content object
|
||||
if not conversation.enabled():
|
||||
raise Unauthorized, "Discussion is not enabled for this content\
|
||||
@@ -175,10 +175,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
comment.text = text
|
||||
|
||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||
|
||||
|
||||
can_reply = getSecurityManager().checkPermission('Reply to item',
|
||||
context)
|
||||
|
||||
|
||||
if portal_membership.isAnonymousUser() and \
|
||||
settings.anonymous_comments:
|
||||
# Anonymous Users
|
||||
@@ -211,7 +211,7 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
"""Anonymous user tries to post a comment, but
|
||||
anonymous commenting is disabled. Or user
|
||||
does not have the 'reply to item' permission.""" # pragma: no cover
|
||||
|
||||
|
||||
# Check if the added comment is a reply to an existing comment
|
||||
# or just a regular reply to the content object.
|
||||
if data['in_reply_to']:
|
||||
@@ -221,11 +221,11 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
||||
# Add a comment to the conversation
|
||||
comment_id = conversation.addComment(comment)
|
||||
|
||||
# If a user posts a comment and moderation is enabled, a message is
|
||||
# shown to the user that his/her comment awaits moderation. If the user
|
||||
# has 'review comments' permission, he/she is redirected directly
|
||||
# If a user posts a comment and moderation is enabled, a message is
|
||||
# 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',
|
||||
can_review = getSecurityManager().checkPermission('Review comments',
|
||||
context)
|
||||
comment_review_state = wf.getInfoFor(comment, 'review_state')
|
||||
if comment_review_state == 'pending' and not can_review:
|
||||
@@ -254,8 +254,8 @@ class CommentsViewlet(ViewletBase):
|
||||
super(CommentsViewlet, self).update()
|
||||
z2.switch_on(self, request_layer=IFormLayer)
|
||||
self.form = self.form(aq_inner(self.context), self.request)
|
||||
if HAS_WRAPPED_FORM:
|
||||
alsoProvides(self.form, IWrappedForm)
|
||||
if HAS_WRAPPED_FORM:
|
||||
alsoProvides(self.form, IWrappedForm)
|
||||
self.form.update()
|
||||
|
||||
# view methods
|
||||
@@ -265,16 +265,16 @@ class CommentsViewlet(ViewletBase):
|
||||
targetMimetype = 'text/html'
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
mimetype = settings.text_transform
|
||||
return transforms.convertTo(targetMimetype,
|
||||
text,
|
||||
context=self,
|
||||
mimetype = settings.text_transform
|
||||
return transforms.convertTo(targetMimetype,
|
||||
text,
|
||||
context=self,
|
||||
mimetype=mimetype).getData()
|
||||
|
||||
|
||||
def can_reply(self):
|
||||
"""Returns true if current user has the 'Reply to item' permission.
|
||||
"""
|
||||
return getSecurityManager().checkPermission('Reply to item',
|
||||
return getSecurityManager().checkPermission('Reply to item',
|
||||
aq_inner(self.context))
|
||||
|
||||
def can_manage(self):
|
||||
@@ -282,13 +282,13 @@ class CommentsViewlet(ViewletBase):
|
||||
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',
|
||||
return getSecurityManager().checkPermission('Review comments',
|
||||
aq_inner(self.context))
|
||||
|
||||
|
||||
def is_discussion_allowed(self):
|
||||
context = aq_inner(self.context)
|
||||
conversation = IConversation(context)
|
||||
@@ -301,7 +301,7 @@ class CommentsViewlet(ViewletBase):
|
||||
"""
|
||||
registry = queryUtility(IRegistry)
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
|
||||
if settings.text_transform == "text/x-web-intelligent":
|
||||
message = translate(Message(COMMENT_DESCRIPTION_INTELLIGENT_TEXT))
|
||||
else:
|
||||
@@ -356,7 +356,7 @@ class CommentsViewlet(ViewletBase):
|
||||
r = r.copy()
|
||||
r['workflow_status'] = workflow_status
|
||||
yield r
|
||||
|
||||
|
||||
# Return all direct replies
|
||||
if conversation.total_comments > 0:
|
||||
if workflow_actions:
|
||||
@@ -395,11 +395,11 @@ class CommentsViewlet(ViewletBase):
|
||||
return settings.show_commenter_image
|
||||
|
||||
def is_anonymous(self):
|
||||
portal_membership = getToolByName(self.context,
|
||||
'portal_membership',
|
||||
portal_membership = getToolByName(self.context,
|
||||
'portal_membership',
|
||||
None)
|
||||
return portal_membership.isAnonymousUser()
|
||||
|
||||
|
||||
def login_action(self):
|
||||
return '%s/login_form?came_from=%s' % \
|
||||
(self.navigation_root_url,
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
class=".moderation.ModerateCommentsEnabled"
|
||||
permission="zope2.View"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Delete comment view -->
|
||||
<browser:page
|
||||
for="plone.app.discussion.interfaces.IComment"
|
||||
@@ -131,4 +131,4 @@
|
||||
permission="cmf.ManagePortal"
|
||||
/>
|
||||
|
||||
</configure>
|
||||
</configure>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
lang="en"
|
||||
metal:use-macro="here/prefs_main_template/macros/master"
|
||||
i18n:domain="plone">
|
||||
|
||||
|
||||
<metal:block fill-slot="top_slot"
|
||||
tal:define="dummy python:request.set('disable_border',1)" />
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
|
||||
<body>
|
||||
<div id="content"
|
||||
<div id="content"
|
||||
tal:attributes="class view/settings"
|
||||
metal:fill-slot="prefs_configlet_content">
|
||||
|
||||
<script type="text/javascript"
|
||||
tal:attributes="src string:${context/portal_url}/++resource++plone.app.discussion.javascripts/controlpanel.js">
|
||||
</script>
|
||||
|
||||
|
||||
<dl class="portalMessage warning"
|
||||
tal:condition="view/mailhost_warning">
|
||||
<dt i18n:translate="">
|
||||
@@ -63,24 +63,24 @@
|
||||
to choose a workflow for the 'Discussion Item' type.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<div metal:use-macro="context/global_statusmessage/macros/portal_message">
|
||||
Portal status message
|
||||
</div>
|
||||
|
||||
|
||||
<a href=""
|
||||
id="setup-link"
|
||||
tal:attributes="href string:$portal_url/plone_control_panel"
|
||||
i18n:translate="">
|
||||
Site Setup
|
||||
</a> ›
|
||||
|
||||
|
||||
<h1 class="documentFirstHeading" tal:content="view/label">View Title</h1>
|
||||
|
||||
|
||||
<div id="layout-contents">
|
||||
<span tal:replace="structure view/contents" />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -21,7 +21,7 @@ from zope.component import getMultiAdapter, queryUtility
|
||||
|
||||
from z3c.form import button
|
||||
from z3c.form.browser.checkbox import SingleCheckBoxFieldWidget
|
||||
|
||||
|
||||
from plone.app.discussion.interfaces import IDiscussionSettings, _
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
description = _(u"help_discussion_settings_editform",
|
||||
default=u"Some discussion related settings are not located "
|
||||
"in the Discussion Control Panel.\n"
|
||||
"To enable comments for a specific content type, "
|
||||
"To enable comments for a specific content type, "
|
||||
"go to the Types Control Panel of this type and "
|
||||
"choose \"Allow comments\".\n"
|
||||
"To enable the moderation workflow for comments, "
|
||||
@@ -73,18 +73,18 @@ class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
|
||||
self.status = self.formErrorsMessage
|
||||
return
|
||||
changes = self.applyChanges(data)
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"),
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"),
|
||||
"info")
|
||||
self.context.REQUEST.RESPONSE.redirect("@@discussion-settings")
|
||||
|
||||
@button.buttonAndHandler(_('Cancel'), name='cancel')
|
||||
def handleCancel(self, action):
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"),
|
||||
IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"),
|
||||
"info")
|
||||
self.request.response.redirect("%s/%s" % (self.context.absolute_url(),
|
||||
self.request.response.redirect("%s/%s" % (self.context.absolute_url(),
|
||||
self.control_panel_view))
|
||||
|
||||
|
||||
|
||||
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
"""Discussion settings control panel.
|
||||
"""
|
||||
@@ -100,22 +100,22 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
wftool = getToolByName(self.context, "portal_workflow", None)
|
||||
wf = wftool.getChainForPortalType('Discussion Item')
|
||||
output = []
|
||||
|
||||
|
||||
# Globally enabled
|
||||
if settings.globally_enabled:
|
||||
output.append("globally_enabled")
|
||||
|
||||
|
||||
# Comment moderation
|
||||
if 'one_state_workflow' not in wf and \
|
||||
'comment_review_workflow' not in wf:
|
||||
output.append("moderation_custom")
|
||||
output.append("moderation_custom")
|
||||
elif settings.moderation_enabled:
|
||||
output.append("moderation_enabled")
|
||||
|
||||
|
||||
# Anonymous comments
|
||||
if settings.anonymous_comments:
|
||||
output.append("anonymous_comments")
|
||||
|
||||
|
||||
# Invalid mail setting
|
||||
ctrlOverview = getMultiAdapter((self.context, self.request),
|
||||
name='overview-controlpanel')
|
||||
@@ -127,7 +127,7 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
discussion_workflow = wftool.getChainForPortalType('Discussion Item')[0]
|
||||
if discussion_workflow:
|
||||
output.append(discussion_workflow)
|
||||
|
||||
|
||||
# Merge all settings into one string
|
||||
return ' '.join(output)
|
||||
|
||||
@@ -152,27 +152,27 @@ class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
|
||||
if 'one_state_workflow' in wf or 'comment_review_workflow' in wf:
|
||||
return
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
def notify_configuration_changed(event):
|
||||
"""Event subscriber that is called every time the configuration changed.
|
||||
"""
|
||||
portal = getSite()
|
||||
wftool = getToolByName(portal, 'portal_workflow', None)
|
||||
|
||||
|
||||
if IRecordModifiedEvent.providedBy(event):
|
||||
# Discussion control panel setting changed
|
||||
if event.record.fieldName == 'moderation_enabled':
|
||||
# Moderation enabled has changed
|
||||
if event.record.value == 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',),
|
||||
wftool.setChainForPortalTypes(('Discussion Item',),
|
||||
'one_state_workflow')
|
||||
|
||||
|
||||
if IConfigurationChangedEvent.providedBy(event):
|
||||
# Types control panel setting changed
|
||||
if 'workflow' in event.data:
|
||||
|
||||
@@ -25,22 +25,22 @@ class View(BrowserView):
|
||||
out = []
|
||||
self.total_comments_migrated = 0
|
||||
self.total_comments_deleted = 0
|
||||
|
||||
|
||||
dry_run = self.request.has_key("dry_run")
|
||||
|
||||
|
||||
# This is for testing only.
|
||||
# Do not use transactions during a test.
|
||||
test = self.request.has_key("test")
|
||||
|
||||
test = self.request.has_key("test")
|
||||
|
||||
if not test:
|
||||
transaction.begin() # pragma: no cover
|
||||
|
||||
|
||||
catalog = getToolByName(context, 'portal_catalog')
|
||||
|
||||
|
||||
def log(msg):
|
||||
# encode string before sending it to external world
|
||||
if isinstance(msg, unicode):
|
||||
msg = msg.encode('utf-8') # pragma: no cover
|
||||
# encode string before sending it to external world
|
||||
if isinstance(msg, unicode):
|
||||
msg = msg.encode('utf-8') # pragma: no cover
|
||||
context.plone_log(msg)
|
||||
out.append(msg)
|
||||
|
||||
@@ -85,9 +85,9 @@ class View(BrowserView):
|
||||
|
||||
# migrate all talkbacks of the reply
|
||||
talkback = getattr( reply, 'talkback', None )
|
||||
no_replies_left = migrate_replies(context,
|
||||
new_in_reply_to,
|
||||
talkback.getReplies(),
|
||||
no_replies_left = migrate_replies(context,
|
||||
new_in_reply_to,
|
||||
talkback.getReplies(),
|
||||
depth=depth+1)
|
||||
if no_replies_left:
|
||||
# remove reply and talkback
|
||||
@@ -97,7 +97,7 @@ class View(BrowserView):
|
||||
log("%sremove %s" % (indent, reply.id))
|
||||
self.total_comments_deleted += 1
|
||||
|
||||
# Return True when all comments on a certain level have been
|
||||
# Return True when all comments on a certain level have been
|
||||
# migrated.
|
||||
return True
|
||||
|
||||
@@ -113,14 +113,14 @@ class View(BrowserView):
|
||||
count_comments_old = len(catalog.searchResults(
|
||||
object_provides=IDiscussionResponse.\
|
||||
__identifier__))
|
||||
|
||||
|
||||
log("Found %s Discussion Item objects." % count_discussion_items)
|
||||
log("Found %s old discussion items." % count_comments_old)
|
||||
log("Found %s plone.app.discussion comments." % count_comments_pad)
|
||||
|
||||
log("\n")
|
||||
log("Start comment migration.")
|
||||
|
||||
|
||||
# This loop is necessary to get all contentish objects, but not
|
||||
# the Discussion Items. This wouldn't be necessary if the
|
||||
# zcatalog would support NOT expressions.
|
||||
@@ -138,7 +138,7 @@ class View(BrowserView):
|
||||
if replies:
|
||||
conversation = IConversation(obj)
|
||||
log("\n")
|
||||
log("Migrate '%s' (%s)" % (obj.Title(),
|
||||
log("Migrate '%s' (%s)" % (obj.Title(),
|
||||
obj.absolute_url(relative=1)))
|
||||
migrate_replies(context, 0, replies)
|
||||
obj = aq_parent(talkback)
|
||||
@@ -152,23 +152,23 @@ class View(BrowserView):
|
||||
if not test: # pragma: no cover
|
||||
transaction.abort() # pragma: no cover
|
||||
log("Abort transaction") # pragma: no cover
|
||||
|
||||
|
||||
log("\n")
|
||||
log("Comment migration finished.")
|
||||
log("\n")
|
||||
|
||||
|
||||
log("%s of %s comments migrated."
|
||||
% (self.total_comments_migrated, count_comments_old))
|
||||
|
||||
|
||||
if self.total_comments_migrated != count_comments_old:
|
||||
log("%s comments could not be migrated."
|
||||
log("%s comments could not be migrated."
|
||||
% (count_comments_old - self.total_comments_migrated)) # pragma: no cover
|
||||
log("Please make sure your portal catalog is up-to-date.") # pragma: no cover
|
||||
|
||||
|
||||
if dry_run and not test:
|
||||
transaction.abort() # pragma: no cover
|
||||
log("Dry run") # pragma: no cover
|
||||
log("Abort transaction") # pragma: no cover
|
||||
if not test:
|
||||
transaction.commit() # pragma: no cover
|
||||
transaction.commit() # pragma: no cover
|
||||
return '\n'.join(out)
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
<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
|
||||
enable the 'Comment Review Workflow' for the Comment content
|
||||
type</a> before you can moderate comments here.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
<form tal:condition="not:items">
|
||||
<fieldset id="fieldset-moderate-comments" class="formPanel">
|
||||
<p id="no-comments-message" i18n:translate="message_nothing_to_moderate">
|
||||
@@ -114,7 +114,7 @@
|
||||
<a href=""
|
||||
tal:attributes="href string:${item/getURL}/getText"
|
||||
tal:condition="python:item.Description.endswith('[...]')"
|
||||
i18n:translate="label_show_full_comment_text"
|
||||
i18n:translate="label_show_full_comment_text"
|
||||
class="show-full-comment-text">show full comment text</a>
|
||||
</td>
|
||||
<td class="actions">
|
||||
|
||||
@@ -11,14 +11,14 @@ from Products.statusmessages.interfaces import IStatusMessage
|
||||
from plone.app.discussion.interfaces import _
|
||||
from plone.app.discussion.interfaces import IComment
|
||||
|
||||
# Begin ugly hack. It works around a ContentProviderLookupError:
|
||||
# Begin ugly hack. It works around a ContentProviderLookupError:
|
||||
# plone.htmlhead error caused by Zope 2 permissions.
|
||||
# This error occured on Plone 3.3.x only!
|
||||
#
|
||||
# Source:
|
||||
# Source:
|
||||
# http://athenageek.wordpress.com/2008/01/08/
|
||||
# contentproviderlookuperror-plonehtmlhead/
|
||||
#
|
||||
#
|
||||
# Bug report: https://bugs.launchpad.net/zope2/+bug/176566
|
||||
#
|
||||
|
||||
@@ -27,7 +27,7 @@ def _getContext(self): # pragma: no cover
|
||||
while getattr(self, '_is_wrapperish', None):
|
||||
self = self.aq_parent
|
||||
return self
|
||||
|
||||
|
||||
ZopeTwoPageTemplateFile._getContext = _getContext # pragma: no cover
|
||||
# End ugly hack.
|
||||
|
||||
@@ -61,7 +61,7 @@ class View(BrowserView):
|
||||
return text
|
||||
|
||||
def moderation_enabled(self):
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
content type. A 'review workflow' is characterized by implementing
|
||||
a 'pending' workflow state.
|
||||
"""
|
||||
@@ -78,7 +78,7 @@ class View(BrowserView):
|
||||
class ModerateCommentsEnabled(BrowserView):
|
||||
|
||||
def __call__(self):
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
"""Returns true if a 'review workflow' is enabled on 'Discussion Item'
|
||||
content type. A 'review workflow' is characterized by implementing
|
||||
a 'pending' workflow state.
|
||||
"""
|
||||
@@ -91,26 +91,26 @@ class ModerateCommentsEnabled(BrowserView):
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
|
||||
class DeleteComment(BrowserView):
|
||||
"""Delete a comment from a conversation.
|
||||
|
||||
|
||||
This view is always called directly on the comment object:
|
||||
|
||||
|
||||
http://nohost/front-page/++conversation++default/1286289644723317/\
|
||||
@@moderate-delete-comment
|
||||
|
||||
Each table row (comment) in the moderation view contains a hidden input
|
||||
field with the absolute URL of the content object:
|
||||
|
||||
<input type="hidden"
|
||||
|
||||
<input type="hidden"
|
||||
value="http://nohost/front-page/++conversation++default/\
|
||||
1286289644723317"
|
||||
1286289644723317"
|
||||
name="selected_obj_paths:list">
|
||||
|
||||
|
||||
This absolute URL is called from a jQuery method that is bind to the
|
||||
'delete' button of the table row. See javascripts/moderation.js for more
|
||||
details.
|
||||
details.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
@@ -132,23 +132,23 @@ class DeleteComment(BrowserView):
|
||||
|
||||
class PublishComment(BrowserView):
|
||||
"""Publish a comment.
|
||||
|
||||
|
||||
This view is always called directly on the comment object:
|
||||
|
||||
|
||||
http://nohost/front-page/++conversation++default/1286289644723317/\
|
||||
@@moderate-publish-comment
|
||||
|
||||
Each table row (comment) in the moderation view contains a hidden input
|
||||
field with the absolute URL of the content object:
|
||||
|
||||
<input type="hidden"
|
||||
|
||||
<input type="hidden"
|
||||
value="http://nohost/front-page/++conversation++default/\
|
||||
1286289644723317"
|
||||
1286289644723317"
|
||||
name="selected_obj_paths:list">
|
||||
|
||||
|
||||
This absolute URL is called from a jQuery method that is bind to the
|
||||
'delete' button of the table row. See javascripts/moderation.js for more
|
||||
details.
|
||||
details.
|
||||
"""
|
||||
|
||||
def __call__(self):
|
||||
@@ -171,24 +171,24 @@ class PublishComment(BrowserView):
|
||||
|
||||
class BulkActionsView(BrowserView):
|
||||
"""Bulk actions (unapprove, approve, delete, mark as spam).
|
||||
|
||||
Each table row of the moderation view has a checkbox with the absolute
|
||||
|
||||
Each table row of the moderation view has a checkbox with the absolute
|
||||
path (without host and port) of the comment objects:
|
||||
|
||||
|
||||
<input type="checkbox"
|
||||
name="paths:list"
|
||||
value="/plone/front-page/++conversation++default/\
|
||||
1286289644723317"
|
||||
1286289644723317"
|
||||
id="cb_1286289644723317" />
|
||||
|
||||
If checked, the comment path will occur in the 'paths' variable of
|
||||
the request when the bulk actions view is called. The bulk action
|
||||
(delete, publish, etc.) will be applied to all comments that are
|
||||
|
||||
If checked, the comment path will occur in the 'paths' variable of
|
||||
the request when the bulk actions view is called. The bulk action
|
||||
(delete, publish, etc.) will be applied to all comments that are
|
||||
included.
|
||||
|
||||
|
||||
The paths have to be 'traversable':
|
||||
|
||||
/plone/front-page/++conversation++default/1286289644723317
|
||||
|
||||
/plone/front-page/++conversation++default/1286289644723317
|
||||
|
||||
"""
|
||||
|
||||
@@ -219,11 +219,11 @@ class BulkActionsView(BrowserView):
|
||||
|
||||
def publish(self):
|
||||
"""Publishes all comments in the paths variable.
|
||||
|
||||
|
||||
Expects a list of absolute paths (without host and port):
|
||||
|
||||
|
||||
/Plone/startseite/++conversation++default/1286200010610352
|
||||
|
||||
|
||||
"""
|
||||
context = aq_inner(self.context)
|
||||
for path in self.paths:
|
||||
@@ -240,12 +240,12 @@ class BulkActionsView(BrowserView):
|
||||
|
||||
def delete(self):
|
||||
"""Deletes all comments in the paths variable.
|
||||
|
||||
|
||||
Expects a list of absolute paths (without host and port):
|
||||
|
||||
|
||||
/Plone/startseite/++conversation++default/1286200010610352
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
context = aq_inner(self.context)
|
||||
for path in self.paths:
|
||||
comment = context.restrictedTraverse(path)
|
||||
|
||||
@@ -18,21 +18,21 @@ class ConversationNamespace(object):
|
||||
(unnamed) adapter. This is to work around a bug in OFS.Traversable which
|
||||
does not allow traversal to namespaces with an empty string name.
|
||||
"""
|
||||
|
||||
|
||||
implements(ITraversable)
|
||||
adapts(Interface, IBrowserRequest)
|
||||
|
||||
|
||||
def __init__(self, context, request=None):
|
||||
self.context = context
|
||||
self.request = request
|
||||
|
||||
|
||||
def traverse(self, name, ignore):
|
||||
|
||||
|
||||
if name == "default":
|
||||
name = u""
|
||||
|
||||
|
||||
conversation = queryAdapter(self.context, IConversation, name=name)
|
||||
if conversation is None:
|
||||
raise TraversalError(name) # pragma: no cover
|
||||
|
||||
|
||||
return conversation
|
||||
|
||||
@@ -49,7 +49,7 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
||||
settings = registry.forInterface(IDiscussionSettings, check=False)
|
||||
|
||||
if settings.captcha in ('captcha', 'recaptcha', 'norobots'):
|
||||
captcha = getMultiAdapter((aq_inner(self.context), self.request),
|
||||
captcha = getMultiAdapter((aq_inner(self.context), self.request),
|
||||
name=settings.captcha)
|
||||
if not captcha.verify(input=value):
|
||||
if settings.captcha == 'norobots':
|
||||
@@ -61,6 +61,6 @@ class CaptchaValidator(validator.SimpleFieldValidator):
|
||||
|
||||
|
||||
# Register Captcha validator for the Captcha field in the ICaptcha Form
|
||||
validator.WidgetValidatorDiscriminators(CaptchaValidator,
|
||||
validator.WidgetValidatorDiscriminators(CaptchaValidator,
|
||||
field=ICaptcha['captcha'])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user