\n"
+"X-Rosetta-Export-Date: 2007-06-12 09:19+0000\n"
+
+#: ./skins/qplonecomments/discussion_publish_comment.py:38
+msgid "Comment published."
+msgstr "Коментар опубліковано."
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Comment(s) deleted."
+msgstr "Коментар(і) знищено."
+
+#: ./skins/qplonecomments/prefs_recent_comments_publish.cpy:26
+msgid "Comment(s) published."
+msgstr "Коментар(і) опубліковано."
+
+#: ./skins/qplonecomments/discussion_reply.cpy:96
+msgid "Currently, all comments require approval before being published. Please check back later."
+msgstr "Наразі коментарі очікують на опублікування. Завітайте сюди пізніше."
+
+#: ./skins/qplonecomments/validate_talkback.vpy:56
+msgid "Please correct the indicated errors."
+msgstr ""
+
+#: ./skins/qplonecomments/validate_talkback.vpy:45
+msgid "Please enter your name."
+msgstr "Введіть своє ім'я, будь-ласка."
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Please select items to be processed."
+msgstr "Будь-ласка, відзначте коментарі для подальшого опрацювання."
+
+#: ./skins/qplonecomments/validate_setup.vpy:23
+msgid "Please submit a valid e-mail address."
+msgstr "Будь-ласка, введіть дійсну електронну адресу."
+
+#: ./skins/qplonecomments/validate_talkback.vpy:32
+msgid "Please submit email."
+msgstr "Будь-ласка, введіть електронну адресу."
+
+#: ./skins/qplonecomments/validate_reply.vpy:15
+msgid "Reply not allowed because the response is to a comment waiting to be approved. Please wait for the comment to be approved before replying."
+msgstr "Відповіати на неопубліковані коментарі заборонено. Зачекайте на затвердження коментаря перед тим, як відповідати."
+
+#: ./skins/qplonecomments/validate_talkback.vpy:50
+msgid "You must correctly enter the word."
+msgstr ""
+
+#. Default: "Please review new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/approve_comment_template.pt:10
+msgid "approvemail_new_comment_link"
+msgstr "Будь-ласка, перегляньте новий коментар, доданий до сторінки \\\" ${title} \\\": ${link}"
+
+#. Default: "Enable Manager moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:129
+msgid "enable_manager_moderation"
+msgstr "Дозволити модерування уравляючому."
+
+#. Default: "Recent comments"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:39
+msgid "heading_comments"
+msgstr "Останні коментарі"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "Налаштування qPloneComments"
+
+#. Default: "The email address where comment notifications will be sent"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:142
+msgid "help_discussion-manager_email"
+msgstr "Електронна адреса, на яку буде надіслано повідомлення про нові коментарі."
+
+#. Default: "Enter your e-mail address."
+#: ./skins/qplonecomments/discussion_reply_form.cpt:133
+msgid "help_email"
+msgstr ""
+
+#. Default: "Tell us your name."
+#: ./skins/qplonecomments/discussion_reply_form.cpt:103
+msgid "help_name"
+msgstr "Буль-ласка, вкажіть своє ім'я."
+
+#. Default: "You can change notification message templates by customizing 'approve_comment_template' and 'published_comment_template' in the qplonecomments skin."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:165
+msgid "help_organization_name"
+msgstr "Ви можете поміняти шаблон повідомлення змінюючи 'approve_comment_template' та 'published_comment_template' з шару qplonecomments в шкірах."
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Дозволити анонімне коментування."
+
+#. Default: "Recent comments moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:26
+msgid "label_comments"
+msgstr "Останні коментарі"
+
+#. Default: "Configure qPloneComments"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:20
+msgid "label_configure"
+msgstr "Налаштування qPloneComments"
+
+#. Default: "Send comments notification emails to:"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:140
+msgid "label_discussion-manager_email"
+msgstr "Надсилати повідомлення по коментарях на електронну адресу:"
+
+#. Default: "Notify Discussion Manager of new comments."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:221
+msgid "label_enable_approve_notification"
+msgstr "Повідомляти управляючого дискусіями (Discussion Manager) про нові коментарі."
+
+#. Default: "Notify Commenter when comment is published."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:240
+msgid "label_enable_approve_user_notification"
+msgstr "Повідомляти автора про опублікування його коментаря."
+
+#. Default: "Enable comment moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:73
+msgid "label_enable_moderation"
+msgstr "Увімкнути модерування."
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Повідомляти автора документа про нові коментарі."
+
+#. Default: "Enable Commentator notification on comment was rejected."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:259
+msgid "label_enable_reject_notification"
+msgstr "Повідомляти автора про відхилення його коментаря."
+
+#. Default: "Notify Commenter when their comment receives a reply"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:279
+msgid "label_enable_reply_notification"
+msgstr "Повідомляти автора про відповідь на його коментар."
+
+#. Default: "Email subject prefix (organization name)"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:163
+msgid "label_organization_name"
+msgstr "Префікс заголовка електронних листів (назва організації)"
+
+#. Default: "Require email from Anonymous commenters"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:93
+msgid "label_require_anonym_email"
+msgstr "Вимагати електронну адресу від Анонімного користувача."
+
+#. Default: "Email Notification Options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:185
+msgid "legend_qpc_notification_legend"
+msgstr "Налаштування інформування"
+
+#. Default: "List of recent comments"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:62
+msgid "legend_recent_comments"
+msgstr "Перелік останніх коментарів"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "Опції налаштувань qPloneComments"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Коментар"
+
+#. Default: "Your comment on '${url}' has been published. To see your comment, go to ${url}"
+#: ./skins/qplonecomments/notify_comment_template.pt:12
+msgid "notifycomment_link"
+msgstr "Ваш коментар до \\\'${url}\\\' опубліковано. Щоб переглянути його - зайдіть на ${url}"
+
+#. Default: "Your comment on '${url}' did not receive approval. Please direct any questions or concerns to ${mfrom}."
+#: ./skins/qplonecomments/rejected_comment_template.pt:13
+msgid "notifyreject_link"
+msgstr "Ваш коментар до \\\'${url}\\\' відхилено. Усі питання стосовно цього надсилайте на ${mfrom}."
+
+#. Default: "View new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/published_comment_template.pt:8
+msgid "publishedmail_new_comment_link"
+msgstr "Перегляньте коментар доданий до сторінки \\\" ${title} \\\": ${link}"
+
+#. Default: "qPloneComments"
+#: ./profiles/default/controlpanel.xml
+#: ./profiles/default/actionicons.xml
+msgid "qPloneComments"
+msgstr ""
+
+#: ./skins/qplonecomments/prefs_comments_setup.cpy:54
+msgid "qPloneComments configuration changes saved."
+msgstr "Записано зміни конфігурації qPloneComments."
+
+#. Default: "Someone replied to your comment '${url}' on '${here_url}'. To read the response, go to '${url}'."
+#: ./skins/qplonecomments/reply_notify_template.pt:10
+msgid "replynotify_link"
+msgstr "Надійшла відповідь на ваш коментар \\\'${url}\\\' до \\\'${here_url}\\\'. Для перегляду - перейдіть на \\\'${url}\\\'."
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr "Команда підтримки ${organization_name}."
+
+#. Default: "Comment must be approved before replies to comment accepted."
+#: ./browser/comments.pt:106
+msgid "text_no_add_reply"
+msgstr "Незатверджений коментар коментувати неможна."
+
+#. Default: "No new comments."
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:49
+msgid "text_no_new_comments"
+msgstr "Жодних нових коментарів."
+
Index: /qPloneComments/tags/3.2.3/overrides.zcml
===================================================================
--- /qPloneComments/tags/3.2.3/overrides.zcml (revision 843)
+++ /qPloneComments/tags/3.2.3/overrides.zcml (revision 843)
@@ -0,0 +1,17 @@
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/patch.py
===================================================================
--- /qPloneComments/tags/3.2.3/patch.py (revision 843)
+++ /qPloneComments/tags/3.2.3/patch.py (revision 843)
@@ -0,0 +1,90 @@
+from DateTime import DateTime
+from Globals import InitializeClass
+from AccessControl import Unauthorized
+from AccessControl import getSecurityManager
+from Products.CMFCore.utils import getToolByName
+from Products.CMFDefault.DiscussionItem import DiscussionItem
+from Products.CMFDefault.DiscussionItem import DiscussionItemContainer
+
+from Products.qPloneComments.utils import getProp
+
+# Patching createReply method of
+# Products.CMFDefault.DiscussionItem.DiscussionItemContainer
+def createReply(self, title, text, Creator=None, email=''):
+ """Create a reply in the proper place.
+ """
+ container = self._container
+
+ id = int(DateTime().timeTime())
+ while self._container.get(str(id), None) is not None:
+ id += 1
+ id = str(id)
+
+ item = DiscussionItem(id, title=title, description=title)
+
+ if Creator:
+ if getattr(item, 'addCreator', None) is not None:
+ item.addCreator(Creator)
+ else:
+ item.creator = Creator
+
+ self._container[id] = item
+ item = item.__of__(self)
+
+ item.setFormat('structured-text')
+ item._edit(text)
+
+ pm = getToolByName(self, 'portal_membership')
+ if pm.isAnonymousUser():
+ item.manage_addProperty(id='email', value=email, type='string')
+
+ item.review_state = 'private'
+
+ # Control of performing moderation
+ if getProp(self, 'enable_moderation', marker=False):
+ roles = [role['name'] for role in self.acl_users.rolesOfPermission('Moderate Discussion')
+ if role['selected']== 'SELECTED']
+ roles.append('DiscussionManager')
+ item.manage_permission('Delete objects', roles, acquire=1)
+ item.manage_permission('View', roles, acquire=0)
+ else:
+ item.review_state = 'published'
+
+ item.setReplyTo(self._getDiscussable())
+ item.indexObject()
+
+ return id
+
+def getReplies( self ):
+ """Return a sequence of the DiscussionResponse objects which are
+ associated with this Discussable.
+ """
+ objects = []
+ validate = getSecurityManager().validate
+
+ result_ids = self._getReplyResults()
+ for id in result_ids:
+ comment = self._container.get(id).__of__(self)
+ try:
+ if validate(self, self, id, comment):
+ objects.append(comment)
+ except Unauthorized:
+ pass
+ return objects
+
+perms = DiscussionItemContainer.__ac_permissions__
+new_perms = []
+for item in perms:
+ perm_name = item[0]
+ funcs = item[1]
+ if 'deleteReply' in funcs:
+ new_perms.append((perm_name, [f for f in funcs if f != 'deleteReply']))
+ new_perms.append(('Moderate Discussion', ('deleteReply',)))
+ else:
+ new_perms.append(item)
+
+DiscussionItemContainer.__ac_permissions__ = new_perms
+InitializeClass(DiscussionItemContainer)
+
+DiscussionItemContainer.createReply = createReply
+DiscussionItemContainer.getReplies = getReplies
Index: /qPloneComments/tags/3.2.3/profiles/default/actionicons.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/actionicons.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/actionicons.xml (revision 843)
@@ -0,0 +1,8 @@
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/default/browserlayer.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/browserlayer.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/browserlayer.xml (revision 843)
@@ -0,0 +1,7 @@
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/default/controlpanel.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/controlpanel.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/controlpanel.xml (revision 843)
@@ -0,0 +1,11 @@
+
+
+
+ Manage portal
+
+
Index: /qPloneComments/tags/3.2.3/profiles/default/import_steps.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/import_steps.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/import_steps.xml (revision 843)
@@ -0,0 +1,11 @@
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/default/metadata.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/metadata.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/metadata.xml (revision 843)
@@ -0,0 +1,4 @@
+
+
+ 3.2.1
+
Index: /qPloneComments/tags/3.2.3/profiles/default/propertiestool.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/propertiestool.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/propertiestool.xml (revision 843)
@@ -0,0 +1,18 @@
+
+
+
+ qPloneComments properties
+ True
+ True
+ True
+ True
+ False
+ True
+ True
+ True
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/default/qPloneComments-default.txt
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/qPloneComments-default.txt (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/qPloneComments-default.txt (revision 843)
@@ -0,0 +1,1 @@
+Yes, I do want this import step to run.
Index: /qPloneComments/tags/3.2.3/profiles/default/rolemap.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/rolemap.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/rolemap.xml (revision 843)
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/default/skins.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/default/skins.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/default/skins.xml (revision 843)
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/uninstall/import_steps.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/uninstall/import_steps.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/uninstall/import_steps.xml (revision 843)
@@ -0,0 +1,7 @@
+
+
+
+
Index: /qPloneComments/tags/3.2.3/profiles/uninstall/qPloneComments-uninstall.txt
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/uninstall/qPloneComments-uninstall.txt (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/uninstall/qPloneComments-uninstall.txt (revision 843)
@@ -0,0 +1,1 @@
+This file is used as a marker in setuphandlers.py.
Index: /qPloneComments/tags/3.2.3/profiles/uninstall/skins.xml
===================================================================
--- /qPloneComments/tags/3.2.3/profiles/uninstall/skins.xml (revision 843)
+++ /qPloneComments/tags/3.2.3/profiles/uninstall/skins.xml (revision 843)
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/setuphandlers.py
===================================================================
--- /qPloneComments/tags/3.2.3/setuphandlers.py (revision 843)
+++ /qPloneComments/tags/3.2.3/setuphandlers.py (revision 843)
@@ -0,0 +1,28 @@
+from Products.CMFCore.utils import getToolByName
+
+def setupVarious(context):
+ """Run the various non-Generic Setup profile import steps.
+ """
+ if context.readDataFile('qPloneComments-default.txt') is None:
+ return
+
+ portal = context.getSite()
+ logger = context.getLogger('qPloneComments')
+
+ # Add 'DiscussionManagers' group
+ gtool = getToolByName(portal, 'portal_groups')
+ existing = gtool.listGroupIds()
+ if not 'DiscussionManager' in existing:
+ gtool.addGroup('DiscussionManager', roles=['DiscussionManager'])
+ logger.info('Added DiscussionManager group to portal_groups with DiscussionManager role.')
+
+ # Remove workflow-chain for Discussion Item
+ wf_tool = getToolByName(portal, 'portal_workflow')
+ wf_tool.setChainForPortalTypes(('Discussion Item',), [])
+ logger.info('Removed workflow chain for Discussion Item type.')
+
+def removeConfiglet(context):
+ if context.readDataFile('qPloneComments-uninstall.txt') is None:
+ return
+ portal_conf=getToolByName(context.getSite(),'portal_controlpanel')
+ portal_conf.unregisterConfiglet('prefs_comments_setup_form')
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/approve_comment_template.pt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/approve_comment_template.pt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/approve_comment_template.pt (revision 843)
@@ -0,0 +1,20 @@
+
+
+
+ Please review new comment added to the following page " ":
+
+
+
+--
+
+
+Support Team
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/deleteDiscussion.py
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/deleteDiscussion.py (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/deleteDiscussion.py (revision 843)
@@ -0,0 +1,33 @@
+## Script (Python) "deleteDiscussion"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=obj=None
+##title=Delete discussion item
+##
+
+from Products.qPloneComments.utils import manage_mails
+from Products.CMFPlone import PloneMessageFactory
+
+if obj is None:
+ obj=context
+
+parent = obj.inReplyTo()
+if parent is not None:
+ talkback = context.portal_discussion.getDiscussionFor(parent)
+else:
+ talkback = parent = obj.aq_parent
+
+# remove the discussion item
+talkback.deleteReply( obj.getId() )
+manage_mails(obj, context, 'deleting')
+
+# redirect to the object that is being discussed
+redirect_target = context.plone_utils.getDiscussionThread(talkback)[0]
+view = redirect_target.getTypeInfo().immediate_view
+
+context.plone_utils.addPortalMessage(PloneMessageFactory(u'Reply deleted.'))
+
+context.REQUEST['RESPONSE'].redirect( redirect_target.absolute_url() + '/%s' % view )
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_publish_comment.py
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_publish_comment.py (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_publish_comment.py (revision 843)
@@ -0,0 +1,41 @@
+## Script (Python) "discussion_publish_comment"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=obj=None
+##title=
+##
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFCore.utils import getToolByName
+from Products.qPloneComments.utils import publishDiscussion
+from Products.qPloneComments.utils import manage_mails
+from Products.CMFPlone import MessageFactory
+_ = MessageFactory('plonecomments')
+
+if obj is None:
+ obj = context
+
+parent = obj.inReplyTo()
+if parent is not None:
+ dtool = getToolByName(context, 'portal_discussion')
+ talkback = dtool.getDiscussionFor(parent)
+else:
+ talkback = parent = obj.aq_parent
+
+reply = talkback.getReply(obj.getId())
+publishDiscussion(reply)
+manage_mails(reply, context, action='publishing')
+
+putils = getToolByName(context, 'plone_utils')
+redirect_target = putils.getDiscussionThread(talkback)[0]
+view = redirect_target.getTypeInfo().getActionInfo('object/view')['url']
+anchor = reply.getId()
+
+transaction_note('Published discussion item')
+
+context.plone_utils.addPortalMessage(_(u'Comment published.'))
+target = '%s/%s#%s' % (redirect_target.absolute_url(), view, anchor)
+
+return context.REQUEST.RESPONSE.redirect(target)
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply.cpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply.cpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply.cpy (revision 843)
@@ -0,0 +1,106 @@
+## Script (Python) "discussion_reply"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=subject,body_text,text_format='plain',username=None,password=None
+##title=Reply to content
+
+from Products.PythonScripts.standard import url_quote_plus
+from Products.CMFCore.utils import getToolByName
+from Products.qPloneComments.utils import manage_mails
+from Products.CMFPlone import MessageFactory
+from Products.CMFPlone import PloneMessageFactory
+_ = MessageFactory('plonecomments')
+
+mtool = getToolByName(context, 'portal_membership')
+dtool = getToolByName(context, 'portal_discussion')
+
+req = context.REQUEST
+pp = getToolByName(context,'portal_properties')
+# Get properties
+isForAnonymous = pp['qPloneComments'].getProperty('enable_anonymous_commenting', False)
+ifModerate = pp['qPloneComments'].getProperty('enable_moderation', False)
+requireEmail = pp['qPloneComments'].getProperty('require_email', False)
+if username or password:
+ # The user username/password inputs on on the comment form were used,
+ # which might happen when anonymous commenting is enabled. If they typed
+ # something in to either of the inputs, we send them to 'logged_in'.
+ # 'logged_in' will redirect them back to this script if authentication
+ # succeeds with a query string which will post the message appropriately
+ # and show them the result. if 'logged_in' fails, the user will be
+ # presented with the stock login failure page. This all depends
+ # heavily on cookiecrumbler, but I believe that is a Plone requirement.
+ came_from = '%s?subject=%s&body_text=%s' % (req['URL'], subject, body_text)
+ came_from = url_quote_plus(came_from)
+ portal_url = context.portal_url()
+
+ return req.RESPONSE.redirect(
+ '%s/logged_in?__ac_name=%s'
+ '&__ac_password=%s'
+ '&came_from=%s' % (portal_url,
+ url_quote_plus(username),
+ url_quote_plus(password),
+ came_from,
+ )
+ )
+
+# if (the user is already logged in) or (if anonymous commenting is enabled and
+# they posted without typing a username or password into the form), we do
+# the following
+
+#########################################################
+# Get discussion item (reply) author and creating reply #
+comment_creator = req.get('Creator', None)
+if isForAnonymous and comment_creator:
+ # Get entered anonymous name
+ comment_creator = comment_creator
+else:
+ member = mtool.getAuthenticatedMember()
+ comment_creator = member.getUserName()
+tb = dtool.getDiscussionFor(context)
+if requireEmail:
+ if mtool.isAnonymousUser():
+ email = req.get('email', '')
+ else:
+ email = mtool.getAuthenticatedMember().getProperty('email')
+
+ id = tb.createReply(title=subject, text=body_text, Creator=comment_creator, email=email)
+else:
+ id = tb.createReply(title=subject, text=body_text, Creator=comment_creator)
+
+reply = tb.getReply(id)
+
+# TODO THIS NEEDS TO GO AWAY!
+if hasattr(dtool.aq_explicit, 'cookReply'):
+ dtool.cookReply(reply, text_format='plain')
+
+parent = tb.aq_parent
+# Send notification e-mail
+
+manage_mails(reply, context, 'aproving')
+if not ifModerate:
+ manage_mails(reply, context, 'publishing')
+
+# return to the discussable object.
+redirect_target = context.plone_utils.getDiscussionThread(tb)[0]
+view = redirect_target.getTypeInfo().getActionInfo('object/view',
+ redirect_target)['url']
+anchor = reply.getId()
+
+# Inform user about awaiting moderation
+portal_status_message=_(u'Comment published.')
+if ifModerate and reply:
+ portal_status_message=_(u'Currently, all comments require approval before being published. Please check back later.')
+
+from Products.CMFPlone.utils import transaction_note
+transaction_note('Added comment to %s at %s' % (parent.title_or_id(),
+ reply.absolute_url()))
+
+context.plone_utils.addPortalMessage(portal_status_message)
+context.plone_utils.addPortalMessage(PloneMessageFactory(u'Comment added.'))
+
+target = '%s#%s' % (view, anchor)
+return req.RESPONSE.redirect(target)
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply.cpy.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply.cpy.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply.cpy.metadata (revision 843)
@@ -0,0 +1,6 @@
+[validators]
+validators=validate_reply
+
+[actions]
+action.success=redirect_to_action:string:view
+action.failure=redirect_to_action:string:view
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply_form.cpt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply_form.cpt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply_form.cpt (revision 843)
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply_form.cpt.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply_form.cpt.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/discussion_reply_form.cpt.metadata (revision 843)
@@ -0,0 +1,9 @@
+[default]
+title=Reply to Comment
+
+[validators]
+validators = validate_talkback
+
+[actions]
+action.success = traverse_to:string:discussion_reply
+action.failure = traverse_to:string:discussion_reply_form
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/notify_comment_template.pt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/notify_comment_template.pt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/notify_comment_template.pt (revision 843)
@@ -0,0 +1,22 @@
+
+
+ ,
+
+ Your comment on ' ' has been
+published. To see your comment, go to
+
+
+--
+
+
+Support Team
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup.cpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup.cpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup.cpy (revision 843)
@@ -0,0 +1,55 @@
+## Script (Python) "prefs_comments_setup"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=
+##title=
+##
+from Products.CMFCore.utils import getToolByName
+from Products.qPloneComments.utils import setAnonymCommenting
+from Products.qPloneComments.utils import setStatusMsg
+from Products.CMFPlone import MessageFactory
+_ = MessageFactory('plonecomments')
+
+form = context.REQUEST.form
+pp = getToolByName(context, 'portal_properties')
+props_sheet = getattr(pp, 'qPloneComments')
+property_maps=[(m['id'], m['type']) for m in props_sheet.propertyMap() if not m['id']=='title']
+request_ids = form.keys()
+
+kw={}
+for id,type in property_maps:
+ if type == 'boolean':
+ if id in request_ids:
+ kw[id] = True
+ else:
+ kw[id] = False
+
+ # Switch anonymouse commenting
+ if id == 'enable_anonymous_commenting':
+ allow = False
+ if id in request_ids:
+ allow = True
+ setAnonymCommenting(context, allow)
+ else:
+ if id in request_ids:
+ kw[id] = form[id]
+
+props_sheet.manage_changeProperties(**kw)
+
+moderate_discussion = 'Moderate Discussion'
+if not 'EnableManagerModeration' in request_ids:
+ roles = [item['name'] for item in context.rolesOfPermission(moderate_discussion)
+ if (item['name'] != 'Manager') and (item['selected'] == 'SELECTED')]
+ context.manage_permission(moderate_discussion, roles, acquire=0)
+
+else:
+ roles = [item['name'] for item in context.rolesOfPermission(moderate_discussion)
+ if item['selected'] == 'SELECTED']
+ roles.append('Manager')
+ context.manage_permission(moderate_discussion, roles, acquire=0)
+
+setStatusMsg(state, context, _(u'qPloneComments configuration changes saved.'))
+return state
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup.cpy.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup.cpy.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup.cpy.metadata (revision 843)
@@ -0,0 +1,6 @@
+[validators]
+validators=
+
+[actions]
+action.success=redirect_to:string:prefs_comments_setup_form
+action.failure=traverse_to:string:prefs_comments_setup_form
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup_form.cpt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup_form.cpt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup_form.cpt (revision 843)
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup_form.cpt.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup_form.cpt.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_comments_setup_form.cpt.metadata (revision 843)
@@ -0,0 +1,13 @@
+[default]
+title=qPloneComments setup
+
+[security]
+View=1:Authenticated
+
+[validators]
+validators=validate_setup
+
+[actions]
+action.success..form_submit=traverse_to:string:prefs_comments_setup
+action.success=traverse_to:string:prefs_comments_setup_form
+action.failure=traverse_to:string:prefs_comments_setup_form
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_delete.cpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_delete.cpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_delete.cpy (revision 843)
@@ -0,0 +1,38 @@
+## Script (Python) "prefs_recent_comments_delete"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=
+##title=
+##
+from Products.CMFCore.utils import getToolByName
+from Products.qPloneComments.utils import manage_mails
+from Products.qPloneComments.utils import setStatusMsg
+from Products.CMFPlone import MessageFactory
+_ = MessageFactory('plonecomments')
+
+portal_discussion = getToolByName(context, "portal_discussion")
+portal_catalog = getToolByName(context, "portal_catalog")
+
+request = context.REQUEST
+comment_ids = request.get('ids', [])
+
+for comment_id in comment_ids:
+ comment = portal_catalog(id=comment_id,portal_type='Discussion Item')[0].getObject()
+
+ parent = comment.inReplyTo()
+ if parent is not None:
+ talkback = portal_discussion.getDiscussionFor(parent)
+ else:
+ talkback = parent = comment.aq_parent
+
+ comment = portal_catalog(id=comment_id,portal_type='Discussion Item')[0].getObject()
+ talkback.deleteReply(comment_id)
+ manage_mails(comment, context, 'deleting')
+
+msg = comment_ids and _(u'Comment(s) deleted.') or _(u'Please select items to be processed.')
+setStatusMsg(state, context, msg)
+
+return state
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_delete.cpy.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_delete.cpy.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_delete.cpy.metadata (revision 843)
@@ -0,0 +1,6 @@
+[validators]
+validators=
+
+[actions]
+action.success=redirect_to:string:prefs_recent_comments_form
+action.failure=traverse_to:string:prefs_recent_comments_form
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_form.cpt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_form.cpt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_form.cpt (revision 843)
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_form.cpt.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_form.cpt.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_form.cpt.metadata (revision 843)
@@ -0,0 +1,11 @@
+[default]
+title=Recent comments form
+
+[security]
+View=1:Authenticated
+
+[actions]
+action.success..form_delete=traverse_to:string:prefs_recent_comments_delete
+action.success..form_publish=traverse_to:string:prefs_recent_comments_publish
+action.success=traverse_to:string:prefs_recent_comments_form
+action.failure=traverse_to:string:prefs_recent_comments_form
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_publish.cpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_publish.cpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_publish.cpy (revision 843)
@@ -0,0 +1,29 @@
+## Script (Python) "prefs_recent_comments_publish"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=
+##title=
+##
+from Products.CMFCore.utils import getToolByName
+from Products.qPloneComments.utils import publishDiscussion, manage_mails
+from Products.qPloneComments.utils import setStatusMsg
+from Products.CMFPlone import MessageFactory
+_ = MessageFactory('plonecomments')
+
+request = context.REQUEST
+
+comment_ids = request.get('ids', [])
+portal_catalog = getToolByName(context, "portal_catalog")
+
+for comment_id in comment_ids:
+ comment = portal_catalog(id=comment_id,portal_type='Discussion Item')[0].getObject()
+ publishDiscussion(comment)
+ manage_mails(comment, container, action='publishing')
+
+msg = comment_ids and _(u'Comment(s) published.') or _(u'Please select items to be processed.')
+setStatusMsg(state, context, msg)
+
+return state
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_publish.cpy.metadata
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_publish.cpy.metadata (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/prefs_recent_comments_publish.cpy.metadata (revision 843)
@@ -0,0 +1,6 @@
+[validators]
+validators=
+
+[actions]
+action.success=redirect_to:string:prefs_recent_comments_form
+action.failure=traverse_to:string:prefs_recent_comments_form
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/published_comment_template.pt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/published_comment_template.pt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/published_comment_template.pt (revision 843)
@@ -0,0 +1,18 @@
+
+
+
+View new comment added to the following page " ":
+
+
+
+--
+
+
+ Support Team
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/rejected_comment_template.pt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/rejected_comment_template.pt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/rejected_comment_template.pt (revision 843)
@@ -0,0 +1,24 @@
+
+
+ ,
+
+
+ Your comment on ' ' did not receive
+approval. Please direct any questions or concerns to .
+
+
+--
+
+
+ Support Team
+
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/reply_notify_template.pt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/reply_notify_template.pt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/reply_notify_template.pt (revision 843)
@@ -0,0 +1,21 @@
+
+ ,
+
+ Someone replied to your comment ' ' on ' '. To read the response, go to ' '.
+
+
+--
+
+
+ Support Team
+
+
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_reply.vpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_reply.vpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_reply.vpy (revision 843)
@@ -0,0 +1,29 @@
+## Controller Script Python "validate_reply"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=validates discussion_reply
+from Products.CMFPlone import MessageFactory
+_ = MessageFactory('plonecomments')
+
+if hasattr(context, 'review_state') and not context.review_state=="published":
+ #from Products.qPloneComments.utils import setStatusMsg
+ msg = _(u'Reply not allowed because the response is to a comment waiting to be\
+ approved. Please wait for the comment to be approved before replying.')
+
+ state.set(status='failure', portal_status_message=msg)
+ return state.set(status='failure')
+
+ # Resolve needless redirection, which leed to loosing portal status message.
+ #try:
+ #redirect_target = context.plone_utils.getDiscussionThread(context)[0]
+ #except:
+ #redirect_target = context
+ #setStatusMsg(state, context, msg)
+ #return state.set(status='failure', context=redirect_target)
+
+return state
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_setup.vpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_setup.vpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_setup.vpy (revision 843)
@@ -0,0 +1,29 @@
+## Controller Script Python "validate_setup"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=validates qpc setup
+##
+from Products.CMFCore.utils import getToolByName
+from Products.CMFPlone import MessageFactory
+from Products.CMFPlone import PloneMessageFactory
+_ = MessageFactory('plonecomments')
+
+form = context.REQUEST.form
+email_key = 'email_discussion_manager'
+
+if email_key in form:
+ email = form[email_key]
+ pu = getToolByName(context, 'plone_utils')
+ if not pu.validateEmailAddresses(email):
+ state.setError(email_key, _(u'Please submit a valid e-mail address.'))
+
+if state.getErrors():
+ return state.set(status='failure', portal_status_message=PloneMessageFactory(u'Please correct the indicated errors.'))
+else:
+ return state
+
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_talkback.vpy
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_talkback.vpy (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/validate_talkback.vpy (revision 843)
@@ -0,0 +1,59 @@
+## Controller Script Python "validate_talkback"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=subject='',body_text='', email='', require_email
+##title=validates a discussion entry
+from Products.CMFCore.utils import getToolByName
+from Products.CMFPlone import MessageFactory
+from Products.CMFPlone import PloneMessageFactory
+_ = MessageFactory('plonecomments')
+
+dtool = context.portal_discussion
+try:
+ content = context.parentsInThread()[0]
+except AttributeError:
+ content = context
+
+if not dtool.isDiscussionAllowedFor(content):
+ raise Exception, "Discussion not allowed."
+
+if not subject:
+ state.setError('subject', PloneMessageFactory(u'Please submit a subject.'), 'subject_required')
+
+if not body_text:
+ state.setError('body_text', PloneMessageFactory(u'Please submit a body.'), 'body_required')
+
+if require_email:
+ if not (email and context.portal_registration.isValidEmail(email)):
+ state.setError('email', _(u'Please submit email.'), 'email_required')
+
+if hasattr(context, 'captcha_validator'):
+ context.captcha_validator()
+
+pp = getToolByName(context, 'portal_properties')
+isForAnonymous = pp['qPloneComments'].getProperty('enable_anonymous_commenting', None)
+if isForAnonymous:
+ pm = getToolByName(context, 'portal_membership')
+ isAnonym = pm.isAnonymousUser()
+ if isAnonym:
+ req = context.REQUEST
+ if not (req.form.has_key('Creator') and not req.form['Creator'] == ""):
+ state.setError('Creator', _(u'Please enter your name.'), 'name_required')
+
+ try:
+ captcha_view = context.restrictedTraverse('@@captcha')
+ if not captcha_view.verify(captcha):
+ state.setError('captcha', _(u'You must correctly enter the word.'), 'captcha')
+ except:
+ # no collective.captcha installed
+ pass
+
+if state.getErrors():
+ context.plone_utils.addPortalMessage(_(u'Please correct the indicated errors.'))
+ return state.set(status='failure')
+else:
+ return state
Index: /qPloneComments/tags/3.2.3/skins/qplonecomments/viewThreadsAtBottom.pt
===================================================================
--- /qPloneComments/tags/3.2.3/skins/qplonecomments/viewThreadsAtBottom.pt (revision 843)
+++ /qPloneComments/tags/3.2.3/skins/qplonecomments/viewThreadsAtBottom.pt (revision 843)
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
Index: /qPloneComments/tags/3.2.3/tests/__init__.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/__init__.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/__init__.py (revision 843)
@@ -0,0 +1,1 @@
+# This file intentionally left blank
Index: /qPloneComments/tags/3.2.3/tests/common.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/common.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/common.py (revision 843)
@@ -0,0 +1,34 @@
+#
+# Common constants and methods
+#
+
+from Products.CMFCore.utils import getToolByName
+
+PRODUCT = 'qPloneComments'
+USERS = {# Common Members
+ 'admin':{'passw': 'secret_admin', 'roles': ['Manager']},
+ 'owner':{'passw': 'secret_owner', 'roles': ['Owner']},
+ 'member':{'passw': 'secret_member', 'roles': ['Member']},
+ 'reviewer':{'passw': 'secret_reviewer', 'roles': ['Reviewer']},
+ # Members for discussion manager group
+ 'dm_admin':{'passw': 'secret_dm_admin', 'roles': ['Manager']},
+ 'dm_owner':{'passw': 'secret_dm_owner', 'roles': ['Owner']},
+ 'dm_member':{'passw': 'secret_dm_member', 'roles': ['Member']},
+ 'dm_reviewer':{'passw': 'secret_dm_reviewer', 'roles': ['Reviewer']},
+ }
+COMMON_USERS_IDS = [u for u in USERS.keys() if not u.startswith('dm_')]
+COMMON_USERS_IDS.append('anonym')
+DM_USERS_IDS = [u for u in USERS.keys() if u.startswith('dm_')]
+
+def addMembers(portal, users_map):
+ """ Add all members """
+ membership = getToolByName(portal, 'portal_membership', None)
+ for user_id in users_map.keys():
+ membership.addMember(user_id, users_map[user_id]['passw'] , users_map[user_id]['roles'], [],
+ {'email': '%s@test.com'%user_id,})
+
+def add2Group(portal, group, group_members):
+ """ Add users to Discussion Manager group """
+ pg = getToolByName(portal, 'portal_groups')
+ group = pg.getGroupById(group)
+ [group.addMember(u) for u in group_members]
Index: /qPloneComments/tags/3.2.3/tests/helperNotify.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/helperNotify.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/helperNotify.py (revision 843)
@@ -0,0 +1,79 @@
+#########################################################
+## Helper methods for testing Mail sending ##
+#########################################################
+
+import sys
+import os, os.path
+from Products.SecureMailHost.SecureMailHost import SecureMailBase
+
+PREFIX = os.path.abspath(os.path.dirname(__file__))
+
+ALL_PROPS = ['enable_approve_user_notification', 'enable_reply_user_notification',
+ 'enable_rejected_user_notification','enable_moderation',
+ 'require_email', 'enable_anonymous_commenting',
+ 'enable_published_notification', 'enable_approve_notification']
+
+def sample_file_path(file):
+ return os.path.join(PREFIX, 'sample', file)
+
+def output_file_path(file):
+ return os.path.join(PREFIX, 'output', file)
+
+def getFileContent(f_path):
+ result_f = open(f_path,"r")
+ result = result_f.read()
+ result_f.close()
+ return result
+
+def writeToFile(f_path, text):
+ result_f = open(f_path,'w')
+ result_f.write(text.as_string()+'\n')
+ result_f.close()
+
+def clearFile(f_path):
+ result_f = open(f_path,"w")
+ result_f.write('')
+ result_f.close()
+
+def _send_MH( self, mfrom, mto, messageText ):
+ files = [f for f in os.listdir(output_file_path("")) if f.startswith('mail')]
+ files.sort()
+ fn = files and (files[-1]+ '1') or 'mail'
+ writeToFile(output_file_path(fn), messageText)
+
+def _send_SMH(self, mfrom, mto, messageText, debug=False):
+ files = [f for f in os.listdir(output_file_path("")) if f.startswith('mail')]
+ files.sort()
+ fn = files and (files[-1]+ '1') or 'mail'
+ writeToFile(output_file_path(fn), messageText)
+
+def send_SMH(self, message, mto=None, mfrom=None, subject=None, encode=None):
+ files = [f for f in os.listdir(output_file_path("")) if f.startswith('mail')]
+ files.sort()
+ fn = files and (files[-1]+ '1') or 'mail'
+ writeToFile(output_file_path(fn), message)
+
+def prepareMailSendTest():
+ # patch SecureMailHost
+ SecureMailBase.send = send_SMH
+ SecureMailBase._send = _send_SMH
+
+def setProperties(prop_sheet, *props):
+ for p in ALL_PROPS:
+ prop_sheet._updateProperty(p, p in props)
+
+def testMailExistance():
+ for f in os.listdir(output_file_path("")):
+ if f.startswith('mail'):
+ return True
+ return False
+
+def getMails():
+ return [file(output_file_path(f),'r').read()
+ for f in os.listdir(output_file_path(""))
+ if f.startswith('mail')]
+
+def cleanOutputDir():
+ for f in os.listdir(output_file_path("")):
+ if f.startswith('mail'):
+ os.remove(output_file_path(f))
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsCommenting.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsCommenting.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsCommenting.py (revision 843)
@@ -0,0 +1,398 @@
+#
+# Test adding comments possibility on switching on/off moderation
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+from zExceptions import Unauthorized
+
+PRODUCT = 'qPloneComments'
+USERS = {# Common Members
+ 'admin':{'passw': 'secret_admin', 'roles': ['Manager']},
+ 'owner':{'passw': 'secret_owner', 'roles': ['Owner']},
+ 'member':{'passw': 'secret_member', 'roles': ['Member']},
+ 'reviewer':{'passw': 'secret_reviewer', 'roles': ['Reviewer']},
+ # Members for discussion manager group
+ 'dm_admin':{'passw': 'secret_dm_admin', 'roles': ['Manager']},
+ 'dm_owner':{'passw': 'secret_dm_owner', 'roles': ['Owner']},
+ 'dm_member':{'passw': 'secret_dm_member', 'roles': ['Member']},
+ 'dm_reviewer':{'passw': 'secret_dm_reviewer', 'roles': ['Reviewer']},
+ }
+COMMON_USERS_IDS = [u for u in USERS.keys() if not u.startswith('dm_')]
+COMMON_USERS_IDS.append('anonym')
+DM_USERS_IDS = [u for u in USERS.keys() if u.startswith('dm_')]
+
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+
+class TestCommBase(PloneTestCase.FunctionalTestCase):
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+ self.request = self.app.REQUEST
+
+ self.qi = getToolByName(self.portal, 'portal_quickinstaller', None)
+ self.qi.installProduct(PRODUCT)
+ # VERY IMPORTANT to guarantee product skin's content visibility
+ self._refreshSkinData()
+
+ # Add all users
+ self.membership = getToolByName(self.portal, 'portal_membership', None)
+ for user_id in USERS.keys():
+ self.membership.addMember(user_id, USERS[user_id]['passw'] , USERS[user_id]['roles'], [])
+
+ # Add users to Discussion Manager group
+ portal_groups = getToolByName(self.portal, 'portal_groups')
+ dm_group = portal_groups.getGroupById('DiscussionManager')
+ dm_users = [dm_group.addMember(u) for u in DM_USERS_IDS]
+
+ # Allow discussion for Document
+ portal_types = getToolByName(self.portal, 'portal_types', None)
+ doc_fti = portal_types.getTypeInfo('Document')
+ doc_fti._updateProperty('allow_discussion', 1)
+
+ # Make sure Documents are visible by default
+ # XXX only do this for plone 3
+ self.portal.portal_workflow.setChainForPortalTypes(('Document',), 'plone_workflow')
+
+ # Add testing documents to portal. Add one document for avery user.
+ # For testing behaviors, where made some changes to document state it's more usefull.
+ self.discussion = getToolByName(self.portal, 'portal_discussion', None)
+ self.all_users_id = DM_USERS_IDS + COMMON_USERS_IDS
+ for user_id in self.all_users_id:
+ doc_id = 'doc_%s' % user_id
+ self.portal.invokeFactory('Document', id=doc_id)
+ doc_obj = getattr(self.portal, doc_id)
+ doc_obj.edit(text_format='plain', text='hello world from %s' % doc_id)
+ # Create talkback for document and Add comment to doc_obj
+ self.discussion.getDiscussionFor(doc_obj)
+ doc_obj.discussion_reply('A Reply for %s' % doc_id,'text of reply for %s' % doc_id)
+
+
+class TestMixinAnonymOn:
+
+ def afterSetUp(self):
+ pass
+
+ def testAddCommentToDocAnonymUsers(self):
+ # ADDING COMMENTS MUST ALLOWED for anonymous users
+ self.login('dm_admin')
+ doc_obj = getattr(self.portal, "doc_anonym")
+ replies_before = len(self.discussion.getDiscussionFor(doc_obj).getReplies())
+ # Create talkback for document and Add comment
+ self.logout()
+ doc_obj.discussion_reply("Anonym reply", "text of 'anonym' reply")
+ self.login('dm_admin')
+ replies_after = len(self.discussion.getDiscussionFor(doc_obj).getReplies())
+ self.assert_(replies_after-replies_before, "Anonymous user CAN'T really add comment in terned ON *Anonymous commenting mode*.")
+
+ def testAddCommentToDocNotAnonymUsers(self):
+ # All users CAN ADD COMMENTS
+ not_anonym_users = [u for u in self.all_users_id if not u=='anonym']
+ failed_users = []
+ for u in not_anonym_users:
+ self.login('dm_admin')
+ doc_id = "doc_%s" % u
+ doc_obj = getattr(self.portal, doc_id)
+ replies_before = self.discussion.getDiscussionFor(doc_obj).getReplies()
+ self.login(u)
+ # Create talkback for document and Add comment
+ doc_obj.discussion_reply("%s's reply" % u, "text of '%s' reply" % u)
+ # Check is comment added
+ self.login('dm_admin')
+ replies_after = self.discussion.getDiscussionFor(doc_obj).getReplies()
+ disparity = len(replies_after) - len(replies_before)
+ if not disparity:
+ failed_users.append(u)
+ self.assert_(not failed_users, "%s - user(s) can not really add comment" % failed_users)
+
+
+class TestMixinAnonymOff:
+
+ def afterSetUp(self):
+ all_users_id = DM_USERS_IDS + COMMON_USERS_IDS
+ self.not_like_anonym = ['admin', 'member', 'dm_admin', 'dm_member']
+ self.like_anonym = [u for u in all_users_id if u not in self.not_like_anonym]
+
+
+ def testAddCommentToDocLikeAnonymUsers(self):
+ # ADDING COMMENTS MUST REFUSED for anonymous users
+ failed_users = []
+ for u in self.like_anonym:
+ self.login('dm_admin')
+ doc_obj = getattr(self.portal, "doc_%s" % u)
+ replies_before = self.discussion.getDiscussionFor(doc_obj).getReplies()
+ # Create talkback for document and Add comment
+ if u=='anonym':
+ self.logout()
+ else:
+ self.login(u)
+ self.assertRaises(Unauthorized, doc_obj.discussion_reply, "%s's reply" % u, "text of '%s' reply" % u)
+ self.login('dm_admin')
+ replies_after = self.discussion.getDiscussionFor(doc_obj).getReplies()
+ disparity = len(replies_after) - len(replies_before)
+ if disparity:
+ failed_users.append(u)
+ self.assert_(not failed_users, "%s user(s) CAN really add comment in terned OFF *Anonymous commenting mode*." % failed_users)
+
+
+ def testAddCommentToDocNotLikeAnonymUsers(self):
+ # All users CAN ADD COMMENTS
+ failed_users = []
+ for u in self.not_like_anonym:
+ self.login('dm_admin')
+ doc_id = "doc_%s" % u
+ doc_obj = getattr(self.portal, doc_id)
+ replies_before = self.discussion.getDiscussionFor(doc_obj).getReplies()
+ self.login(u)
+ # Create talkback for document and Add comment
+ doc_obj.discussion_reply("%s's reply" % u, "text of '%s' reply" % u)
+ # Check is comment added
+ self.login('dm_admin')
+ replies_after = self.discussion.getDiscussionFor(doc_obj).getReplies()
+ disparity = len(replies_after) - len(replies_before)
+ if not disparity:
+ failed_users.append(u)
+ self.assert_(not failed_users, "%s - user(s) can not really add commentin terned OFF *Anonymous commenting mode*." % failed_users)
+
+
+class TestMixinModerationOn:
+
+ def afterSetUp(self):
+ # Get Moderation state
+ pp = getToolByName(self.portal, 'portal_properties')
+ config_ps = getattr(pp, 'qPloneComments', None)
+ EnableAnonymComm = getattr(config_ps, "enable_anonymous_commenting")
+ # Group users depending on Anonymous commenting enabling/disabling
+ if EnableAnonymComm:
+ self.allowable_dm_users = DM_USERS_IDS
+ self.allowable_common_users = COMMON_USERS_IDS
+ self.illegal_dm_users = []
+ self.illegal_common_users = []
+ else:
+ self.allowable_dm_users = ['dm_admin', 'dm_member']
+ self.allowable_common_users = ['admin', 'member']
+ self.illegal_dm_users = [u for u in DM_USERS_IDS if not u in self.allowable_dm_users]
+ self.illegal_common_users = [u for u in COMMON_USERS_IDS if not u in self.allowable_common_users]
+
+ """
+ def testAddCommentToNotPublishedReplyDMUsers(self):
+ # DiscussionManager's group's members with Manager or Member roles CAN ADD COMMENTS
+ # to reply IN ANY STATE (published/not published)
+ failed_users = []
+ for u in self.allowable_dm_users:
+ self.login(u)
+ doc_obj = getattr(self.portal, "doc_%s" % u)
+ # Get reply to this document
+ reply = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ # Create talkback for reply and Add comment
+ self.discussion.getDiscussionFor(reply)
+ reply.discussion_reply("%s's reply" % u, "text of '%s' reply" % u)
+ replies_to_reply = self.discussion.getDiscussionFor(reply).getReplies()
+ if not replies_to_reply:
+ failed_users.append(u)
+ self.assert_(not failed_users, "%s - member(s) of DiscussionManager group CAN'T really ADD comment" % failed_users)
+ # This is actual only in terned OFF *Anonymous commenting mode*
+ failed_users = []
+ for u in self.illegal_dm_users:
+ self.login(u)
+ doc_obj = getattr(self.portal, "doc_%s" % u)
+ # Get reply to this document
+ reply = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ # Create talkback for reply and Add comment
+ self.discussion.getDiscussionFor(reply)
+ self.assertRaises(Unauthorized, reply.discussion_reply, "%s's reply" % u, "text of '%s' reply" % u)
+ replies_to_reply = self.discussion.getDiscussionFor(reply).getReplies()
+ if replies_to_reply:
+ failed_users.append(u)
+ self.assert_(not failed_users, "%s user(s) CAN really add comment in terned OFF *Anonymous commenting mode*." % failed_users)
+
+
+ def testAddCommentToNotPublishedReplyNotDMUsers(self):
+ # Users without DiscussionManager role CAN'T ACCESS an so ADD COMMENTS
+ # TO NOT PUBLISHED reply.
+ manager = 'dm_admin'
+ for u in COMMON_USERS_IDS:
+ self.login(manager)
+ doc_obj = getattr(self.portal, "doc_%s" % u)
+ reply = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ reply_to_reply = self.discussion.getDiscussionFor(reply).getReplies()
+ reply_to_reply_before = len(reply_to_reply)
+ if u=='anonym':
+ self.logout()
+ else:
+ self.logout()
+ self.login(u)
+ # On adding reply to not published reply MUST generte Unauthorized exception
+ self.assertRaises(Unauthorized, reply.discussion_reply, "Reply %s" % u, "text of %s reply" % u)
+ """
+
+ def testAddCommentToPublishedReplyALLUsers(self):
+ # All users CAN ADD COMMENTS to published reply
+ manager = 'dm_admin'
+ allowable_users = self.allowable_dm_users + self.allowable_common_users
+ illegal_users = self.illegal_dm_users + self.illegal_common_users
+ all_users = allowable_users + illegal_users
+ # 1. Publish comments
+ self.login(manager)
+ for u in all_users:
+ doc_obj = getattr(self.portal, "doc_%s" % u)
+ reply = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ reply.discussion_publish_comment()
+ # 2.Check adding reply to reply for allowable users
+ failed_users = []
+ for u in allowable_users:
+ if u=='anonym':
+ self.logout()
+ else:
+ self.logout()
+ self.login(u)
+ # Create talkback for document and Add comment
+ self.discussion.getDiscussionFor(reply)
+ reply.discussion_reply("Reply %s" % u, "text of %s reply" % u)
+ # Check is comment added
+ self.login(manager)
+ reply_to_reply = self.discussion.getDiscussionFor(reply).getReplies()
+ if not reply_to_reply:
+ failed_users.append(u)
+ self.assert_(not failed_users, "%s - user(s) can not really add comment to PUBLISHED reply" % failed_users)
+ # 3.Check adding reply to reply for illegal users
+ for u in illegal_users:
+ if u=='anonym':
+ self.logout()
+ else:
+ self.login(u)
+ # On adding reply to not published reply MUST generte Unauthorized exception
+ self.discussion.getDiscussionFor(reply)
+ self.assertRaises(Unauthorized, reply.discussion_reply, "Reply %s" % u, "text of %s reply" % u)
+
+
+class TestMixinModerationOff:
+
+ def afterSetUp(self):
+ # Get Moderation state
+ pp = getToolByName(self.portal, 'portal_properties')
+ config_ps = getattr(pp, 'qPloneComments', None)
+ EnableAnonymComm = getattr(config_ps, "enable_anonymous_commenting")
+ # Group users depending on Anonymous commenting enabling/disabling
+ if EnableAnonymComm:
+ self.allowable_users = DM_USERS_IDS + COMMON_USERS_IDS
+ self.illegal_users = []
+ else:
+ self.allowable_users = ['dm_admin', 'dm_member', 'admin', 'member']
+ self.illegal_users = [u for u in self.all_users_id if not u in self.allowable_users]
+
+ # Add testing document to portal in Moderation OFF mode.
+ self.discussion = getToolByName(self.portal, 'portal_discussion', None)
+ self.doc_moder_off_id = 'doc_moderation_off'
+ self.portal.invokeFactory('Document', id=self.doc_moder_off_id)
+ doc_obj = getattr(self.portal, self.doc_moder_off_id)
+ doc_obj.edit(text_format='plain', text='hello world from in moderation off mode')
+ # Create talkback for document and Add comment to 'doc_moderatio_off'
+ self.discussion.getDiscussionFor(doc_obj)
+ doc_obj.discussion_reply("A Reply to '%s'" % self.doc_moder_off_id,"text of reply to '%s'" % self.doc_moder_off_id)
+
+
+ def testAddCommentToReplyAllowableUsers(self):
+ # Users CAN ADD COMMENTS
+ failed_users = []
+ for u in self.allowable_users:
+ if u=='anonym':
+ self.logout()
+ else:
+ self.login(u)
+ doc_obj = getattr(self.portal, self.doc_moder_off_id)
+ # Get reply to this document
+ reply_to_doc = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ # Create talkback for reply and Add comment
+ replies_before = self.discussion.getDiscussionFor(reply_to_doc).getReplies()
+ if not replies_before:
+ self.discussion.getDiscussionFor(reply_to_doc)
+ reply_to_doc.discussion_reply("%s's reply" % u, "text of '%s' reply" % u)
+ replies_after = self.discussion.getDiscussionFor(reply_to_doc).getReplies()
+ disparity = len(replies_after) - len(replies_before)
+ if not disparity:
+ failed_users.append(u)
+ self.assert_(not failed_users , "%s - member(s) CAN'T really ADD comment in terned off comments Moderation mode." % failed_users)
+
+
+ def testAddCommentToReplyIllegalUsers(self):
+ # This users CAN'T ADD COMMENTS
+ # This is actual only in terned OFF *Anonymous commenting mode*
+ for u in self.illegal_users:
+ self.login('admin')
+ if u=='anonym':
+ self.logout()
+ else:
+ self.logout()
+ self.login(u)
+ doc_obj = getattr(self.portal, self.doc_moder_off_id)
+ # Get reply to this document
+ reply_to_doc = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ # Create talkback for reply and Add comment
+ self.discussion.getDiscussionFor(reply_to_doc)
+ self.assertRaises(Unauthorized, reply_to_doc.discussion_reply, "%s's reply" % u, "text of '%s' reply" % u)
+
+
+class TestModerationAnonymComm(TestCommBase, TestMixinAnonymOn, TestMixinModerationOn):
+
+ def afterSetUp(self):
+ TestCommBase.afterSetUp(self)
+ # Preparation for functional testing
+ # Tern On Moderation and tern on Anonymous commenting
+ self.request.form['enable_anonymous_commenting'] = 'True'
+ self.request.form['enable_moderation'] = 'True'
+ self.portal.prefs_comments_setup()
+ # Initialize base classes
+ TestMixinAnonymOn.afterSetUp(self)
+ TestMixinModerationOn.afterSetUp(self)
+
+
+class TestModerationOFFAnonymComm(TestCommBase, TestMixinAnonymOff, TestMixinModerationOn):
+
+ def afterSetUp(self):
+ TestCommBase.afterSetUp(self)
+ # Preparation for functional testing
+ # Tern On Moderation and tern off Anonymous commenting
+ self.request.form['enable_moderation'] = 'True'
+ self.portal.prefs_comments_setup()
+ # Initialize base classes
+ TestMixinAnonymOff.afterSetUp(self)
+ TestMixinModerationOn.afterSetUp(self)
+
+
+class TestAnonymCommOFFModeration(TestCommBase, TestMixinAnonymOn, TestMixinModerationOff):
+
+ def afterSetUp(self):
+ TestCommBase.afterSetUp(self)
+ # Preparation for functional testing
+ # Tern On Anonymous commenting and tern off Moderation
+ self.request.form['enable_anonymous_commenting'] = 'True'
+ self.portal.prefs_comments_setup()
+ # Initialize base classes
+ TestMixinAnonymOn.afterSetUp(self)
+ TestMixinModerationOff.afterSetUp(self)
+
+
+class TestOFFModerationOFFAnonymComm(TestCommBase, TestMixinAnonymOff, TestMixinModerationOff):
+
+ def afterSetUp(self):
+ TestCommBase.afterSetUp(self)
+ # Preparation for functional testing
+ # Tern Off Moderation and tern off Anonymous commenting
+ self.portal.prefs_comments_setup()
+ # Initialize base classes
+ TestMixinAnonymOff.afterSetUp(self)
+ TestMixinModerationOff.afterSetUp(self)
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestModerationAnonymComm))
+ suite.addTest(makeSuite(TestModerationOFFAnonymComm))
+ suite.addTest(makeSuite(TestAnonymCommOFFModeration))
+ suite.addTest(makeSuite(TestOFFModerationOFFAnonymComm))
+
+ return suite
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsConfiglet.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsConfiglet.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsConfiglet.py (revision 843)
@@ -0,0 +1,178 @@
+#
+# Test configuration form working
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.permissions import ReplyToItem
+from AccessControl.SecurityManagement import noSecurityManager
+
+PRODUCT = 'qPloneComments'
+PROPERTY_SHEET = "qPloneComments"
+
+USERS = {# Common Members
+ 'admin':{'passw': 'secret_admin', 'roles': ['Manager']},
+ 'owner':{'passw': 'secret_owner', 'roles': ['Owner']},
+ 'member':{'passw': 'secret_member', 'roles': ['Member']},
+ 'reviewer':{'passw': 'secret_reviewer', 'roles': ['Reviewer']},
+ # Members for discussion manager group
+ 'dm_admin':{'passw': 'secret_dm_admin', 'roles': ['Manager']},
+ 'dm_owner':{'passw': 'secret_dm_owner', 'roles': ['Owner']},
+ 'dm_member':{'passw': 'secret_dm_member', 'roles': ['Member']},
+ 'dm_reviewer':{'passw': 'secret_dm_reviewer', 'roles': ['Reviewer']},
+ }
+COMMON_USERS_IDS = [u for u in USERS.keys() if not u.startswith('dm_')]
+COMMON_USERS_IDS.append('anonym')
+DM_USERS_IDS = [u for u in USERS.keys() if u.startswith('dm_')]
+
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+def addUsers(self):
+ self.loginAsPortalOwner()
+ # Add all users
+ self.membership = getToolByName(self.portal, 'portal_membership', None)
+ for user_id in USERS.keys():
+ self.membership.addMember(user_id, USERS[user_id]['passw'] , USERS[user_id]['roles'], [])
+
+ # Add users to Discussion Manager group
+ portal_groups = getToolByName(self.portal, 'portal_groups')
+ dm_group = portal_groups.getGroupById('DiscussionManager')
+ dm_users = [dm_group.addMember(u) for u in DM_USERS_IDS]
+
+
+class TestConfiglet(PloneTestCase.FunctionalTestCase):
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+
+ self.qi = self.portal.portal_quickinstaller
+ self.qi.installProduct(PRODUCT)
+ # VERY IMPORTANT to guarantee product skin's content visibility
+ self._refreshSkinData()
+
+ '''Preparation for functional testing'''
+ # Allow discussion for Document
+ portal_types = getToolByName(self.portal, 'portal_types', None)
+ doc_fti = portal_types.getTypeInfo('Document')
+ doc_fti._updateProperty('allow_discussion', 1)
+
+ # Make sure Documents are visible by default
+ # XXX only do this for plone 3
+ self.portal.portal_workflow.setChainForPortalTypes(('Document',), 'plone_workflow')
+
+ portal_properties = getToolByName(self.portal, 'portal_properties', None)
+ self.prefs = portal_properties[PROPERTY_SHEET]
+ self.request = self.app.REQUEST
+
+ # Add Manager user - 'dm' and add him to Discussion Manager group
+ self.portal.portal_membership.addMember('dm', 'secret' , ['Manager'], [])
+ portal_groups = getToolByName(self.portal, 'portal_groups')
+ dm_group = portal_groups.getGroupById('DiscussionManager')
+ dm_group.addMember('dm')
+ #self.logout()
+ self.login('dm')
+ # For prepare mail sending - enter an e-mail adress
+ self.prefs._updateProperty('email_discussion_manager', 'discussion.manager@test.com')
+ member = self.portal.portal_membership.getAuthenticatedMember()
+ member.setMemberProperties({'email':'creator@test.com'})
+ #self.fail(member.getMemberId()+' :: '+member.getUserName()+' :: '+str(member.getRoles())+' :: '+member.getProperty('email'))
+
+ # Add testing document to portal
+ my_doc = self.portal.invokeFactory('Document', id='my_doc')
+ self.my_doc = self.portal['my_doc']
+ self.my_doc.edit(text_format='plain', text='hello world')
+
+ def testAnonymousCommenting(self):
+ getPortalReplyPerm = self.portal.rolesOfPermission
+ def getReplyRoles():
+ return [r['name'] for r in getPortalReplyPerm(ReplyToItem) if r['selected']=='SELECTED']
+ # Simulate switching ON Anonymous Commenting
+ self.request.form['enable_anonymous_commenting'] = 'True'
+ self.portal.prefs_comments_setup()
+ actual_reply_permission = getReplyRoles()
+ self.assert_('Anonymous' in actual_reply_permission, \
+ "'Reply to Item' permission set for %s. 'Anonymous' role NOT added" % actual_reply_permission)
+ # Simulate switching OFF Anonymous Commenting
+ if self.request.form.has_key('enable_anonymous_commenting'):
+ del self.request.form['enable_anonymous_commenting']
+ self.portal.prefs_comments_setup()
+ actual_reply_permission = getReplyRoles()
+ self.assert_(not 'Anonymous' in actual_reply_permission, \
+ "'Reply to Item' permission set for %s. 'Anonymous' role NOT erased" % actual_reply_permission)
+
+ def testSwitchONModeration(self):
+ addUsers(self)
+ self.discussion = self.portal.portal_discussion
+ self.request.form['enable_anonymous_commenting'] = 'True'
+ self.request.form['enable_moderation'] = 'True'
+ self.portal.prefs_comments_setup()
+ # Create talkback for document and Add comment to my_doc
+ self.discussion.getDiscussionFor(self.my_doc)
+ self.my_doc.discussion_reply('Reply 1','text of reply')
+ # Check moderating discussion
+ # MUST ALLOW for: members of 'DiscussionMnagers' group
+ # MUST REFUSE for: NOT members of 'DiscussionMnagers' group
+ getReplies = self.discussion.getDiscussionFor(self.my_doc).getReplies
+ for u in DM_USERS_IDS:
+ self.logout()
+ self.login(u)
+ self.assert_(getReplies(), "None discussion item added or discussion forbiden for %s user" % u)
+ for u in COMMON_USERS_IDS:
+ self.logout()
+ if not u=='anonym':
+ self.login(u)
+ noSecurityManager()
+ self.assert_(not getReplies(), "Viewing discussion item allow for Anonymous user")
+
+ def testSwitchOFFModeration(self):
+ addUsers(self)
+ self.discussion = self.portal.portal_discussion
+ self.request.form['enable_anonymous_commenting'] = 'True'
+ self.portal.prefs_comments_setup()
+ # Create talkback for document and Add comment to my_doc
+ self.discussion.getDiscussionFor(self.my_doc)
+ self.request.form['Creator'] = self.portal.portal_membership.getAuthenticatedMember().getUserName()
+ self.request.form['subject'] = "Reply 1"
+ self.request.form['body_text'] = "text of reply"
+ self.my_doc.discussion_reply('Reply 1','text of reply')
+ # Check moderating discussion
+ # MUST ALLOW for: user with any role or Anonym
+ all_users_ids = DM_USERS_IDS + COMMON_USERS_IDS
+ for u in all_users_ids:
+ self.logout()
+ if not u=='anonym':
+ self.login(u)
+ replies = self.discussion.getDiscussionFor(self.my_doc).getReplies()
+ self.assert_(replies, "No discussion item added or discussion forbidden for %s user" % u)
+
+ def testApproveNotification(self):
+ # Check ON Notification Anonymous Commenting
+ self.request.form['enable_approve_notification'] = 'True'
+ self.portal.prefs_comments_setup()
+ self.assert_(self.prefs.getProperty('enable_approve_notification')==1,"Approve Notification not terned ON")
+
+ # Check OFF Notification Anonymous Commenting
+ if self.request.form.has_key('enable_approve_notification'):
+ del self.request.form['enable_approve_notification']
+ self.portal.prefs_comments_setup()
+ self.assert_(self.prefs.getProperty('enable_approve_notification')==0,"Approve Notification not terned OFF")
+
+ def testPublishedNotification(self):
+ # Check ON Notification Anonymous Commenting
+ self.request.form['enable_published_notification'] = 'True'
+ self.portal.prefs_comments_setup()
+ self.assert_(self.prefs.getProperty('enable_published_notification')==1,"Published Notification not terned ON")
+
+ # Check OFF Notification Anonymous Commenting
+ if self.request.form.has_key('enable_published_notification'):
+ del self.request.form['enable_published_notification']
+ self.portal.prefs_comments_setup()
+ self.assert_(self.prefs.getProperty('enable_published_notification')==0,"Published Notification not terned OFF")
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestConfiglet))
+ return suite
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsInstall.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsInstall.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsInstall.py (revision 843)
@@ -0,0 +1,103 @@
+#
+# Test product's installation/uninstallation
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+
+PRODUCT = 'qPloneComments'
+
+PRODUCT_SKIN_NAME = "qplonecomments"
+PROPERTY_SHEET = "qPloneComments"
+CONFIGLET_ID = "prefs_comments_setup_form"
+
+EMAIL_PID = "email_discussion_manager"
+APPROVE_NOTIFICATION_PID = "enable_approve_notification"
+PUBLISHED_NOTIFICATION_PID = "enable_published_notification"
+MODERATION_PID = "enable_moderation"
+ANONYMOUS_COMMENTING_PID = "enable_anonymous_commenting"
+
+PERM_NAME = 'Moderate Discussion'
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+
+class TestInstallation(PloneTestCase.PloneTestCase):
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+ self.qi = self.portal.portal_quickinstaller
+ self.qi.installProduct(PRODUCT)
+
+ def test_configlet_install(self):
+ configTool = getToolByName(self.portal, 'portal_controlpanel', None)
+ self.assert_(CONFIGLET_ID in [a.getId() for a in configTool.listActions()], 'Configlet not found')
+
+ def test_skins_install(self):
+ skinstool=getToolByName(self.portal, 'portal_skins')
+ for skin in skinstool.getSkinSelections():
+ path = skinstool.getSkinPath(skin)
+ path = map(str.strip, path.split(','))
+ self.assert_(PRODUCT_SKIN_NAME in path, 'qPloneComments layer not found in %s' % skin)
+
+ def test_layer_install(self):
+ from plone.browserlayer.utils import registered_layers
+ from Products.qPloneComments.interfaces import IPloneCommentsLayer
+ self.failUnless(IPloneCommentsLayer in registered_layers())
+
+ def test_propertysheet_install(self):
+ portal_properties = getToolByName(self.portal, 'portal_properties', None)
+
+ self.assert_(PROPERTY_SHEET in portal_properties.objectIds(), 'qPloneComments properies not found in portal_properties')
+
+ property_ids = portal_properties[PROPERTY_SHEET].propertyIds()
+ self.assert_(EMAIL_PID in property_ids, '%s propery not found in %s property' % (EMAIL_PID, PROPERTY_SHEET))
+ self.assert_(APPROVE_NOTIFICATION_PID in property_ids, '%s propery not found in %s property' % (APPROVE_NOTIFICATION_PID, PROPERTY_SHEET))
+ self.assert_(PUBLISHED_NOTIFICATION_PID in property_ids, '%s propery not found in %s property' % (PUBLISHED_NOTIFICATION_PID, PROPERTY_SHEET))
+ self.assert_(MODERATION_PID in property_ids, '%s propery not found in %s property' % (MODERATION_PID, PROPERTY_SHEET))
+ self.assert_(ANONYMOUS_COMMENTING_PID in property_ids, '%s propery not found in %s property' % (ANONYMOUS_COMMENTING_PID, PROPERTY_SHEET))
+
+ def test_skins_uninstall(self):
+ self.qi.uninstallProducts([PRODUCT])
+ self.assertNotEqual(self.qi.isProductInstalled(PRODUCT), True,'qPloneComments is already installed')
+ skinstool=getToolByName(self.portal, 'portal_skins')
+
+ self.assert_(not PRODUCT_SKIN_NAME in skinstool.objectIds(), '%s directory view found in portal_skins after uninstallation' % PRODUCT_SKIN_NAME)
+ for skin in skinstool.getSkinSelections():
+ path = skinstool.getSkinPath(skin)
+ path = map(str.strip, path.split(','))
+ self.assert_(not PRODUCT_SKIN_NAME in path, '%s layer found in %s after uninstallation' % (PRODUCT_SKIN_NAME, skin))
+
+ def test_layer_uninstall(self):
+ self.qi.uninstallProducts([PRODUCT])
+ self.assertNotEqual(self.qi.isProductInstalled(PRODUCT), True,'qPloneComments is already installed')
+
+ from plone.browserlayer.utils import registered_layers
+ from Products.qPloneComments.interfaces import IPloneCommentsLayer
+ self.failIf(IPloneCommentsLayer in registered_layers())
+
+ def test_configlet_uninstall(self):
+ self.qi.uninstallProducts([PRODUCT])
+ self.assertNotEqual(self.qi.isProductInstalled(PRODUCT), True,'qPloneComments is already installed')
+
+ configTool = getToolByName(self.portal, 'portal_controlpanel', None)
+ self.assert_(not CONFIGLET_ID in [a.getId() for a in configTool.listActions()], 'Configlet found after uninstallation')
+
+ def test_propertysheet_uninstall(self):
+ self.qi.uninstallProducts([PRODUCT])
+ self.assertNotEqual(self.qi.isProductInstalled(PRODUCT), True,'qPloneComments is already installed')
+
+ portal_properties = getToolByName(self.portal, 'portal_properties')
+ self.assert_(PROPERTY_SHEET in portal_properties.objectIds(), \
+ 'qPloneComments property_sheet not found in portal_properties after uninstallation')
+
+ def test_permission_added(self):
+ roles = [item['name'] for item in self.portal.rolesOfPermission(PERM_NAME)]
+ self.assert_( roles != [], '%s not installed'%PERM_NAME)
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestInstallation))
+ return suite
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsModeration.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsModeration.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsModeration.py (revision 843)
@@ -0,0 +1,205 @@
+#
+# Test moderation behavior
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+import re
+
+from common import *
+
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+
+class TestModeration(PloneTestCase.FunctionalTestCase):
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+ self.portal.portal_quickinstaller.installProduct(PRODUCT)
+
+ # Add all users
+ addMembers(self.portal, USERS)
+
+ # Add users to Discussion Manager group
+ add2Group(self.portal, 'DiscussionManager', DM_USERS_IDS)
+
+ # Allow discussion for Document
+ portal_types = getToolByName(self.portal, 'portal_types')
+ doc_fti = portal_types.getTypeInfo('Document')
+ doc_fti._updateProperty('allow_discussion', 1)
+
+ # Make sure Documents are visible by default
+ self.portal.portal_workflow.setChainForPortalTypes(('Document',), 'plone_workflow')
+
+ # Add testing documents to portal. Add one document for avery user.
+ # For testing behaviors, where made some changes to document state it's more usefull.
+ self.discussion = getToolByName(self.portal, 'portal_discussion', None)
+ all_users_id = DM_USERS_IDS + COMMON_USERS_IDS
+ for user_id in all_users_id:
+ doc_id = 'doc_%s' % user_id
+ self.portal.invokeFactory('Document', id=doc_id)
+ doc_obj = getattr(self.portal, doc_id)
+ doc_obj.edit(text_format='plain', text='hello world from %s' % doc_id)
+ # Create talkback for document and Add comment to doc_obj
+ self.discussion.getDiscussionFor(doc_obj)
+ doc_obj.discussion_reply('A Reply for %s' % doc_id,'text of reply for %s' % doc_id)
+
+ ## TEST VIEWING
+
+ def testViewRepliesNotPublishedDMUsers(self):
+ # All members of DiscussionManager group MUST VIEW comments
+ doc = getattr(self.portal, 'doc_%s' % DM_USERS_IDS[0])
+ for u in DM_USERS_IDS:
+ self.login(u)
+ replies = self.discussion.getDiscussionFor(doc).getReplies()
+ self.assert_(replies, "Viewing discussion item forbiden for %s - member of DiscussionManager group" % u)
+
+ def testViewRepliesNotPublishedNotDMUsers(self):
+ # All common users SHOULD NOT VIEW NOT PUBLISHED comments
+ doc = getattr(self.portal, 'doc_%s' % DM_USERS_IDS[0])
+ roles = [r['name'] for r in self.portal.rolesOfPermission('Moderate Discussion') if r['selected'] == 'SELECTED']
+ authorized_users = [user for user in COMMON_USERS_IDS if user !='anonym']
+ users_without_md_perm = [u for u in authorized_users if filter(lambda x: x not in roles, USERS[u]['roles'])]
+ for u in users_without_md_perm:
+ self.logout()
+ if not u=='anonym':
+ self.login(u)
+ replies = self.discussion.getDiscussionFor(doc).getReplies()
+ self.assert_(not replies, "Viewing of NOT published discussion item allow %s - user without 'Moderate Discussion' permission" % u)
+
+ def testViewRepliesPublishedAllUsers(self):
+ # All users MUST VIEW PUBLISHED comments
+ # Get any document and publish it's comment
+ doc = getattr(self.portal, 'doc_%s' % 'dm_admin')
+ self.login('dm_admin')
+ di = self.discussion.getDiscussionFor(doc).getReplies()[0]
+ di.discussion_publish_comment()
+
+ all_users_id = USERS.keys() + ['anonym']
+ for u in all_users_id:
+ self.logout()
+ if not u=='anonym':
+ self.login(u)
+ replies = self.discussion.getDiscussionFor(doc).getReplies()
+ self.assert_(replies, "Viewing PUBLISHED discussion item forbiden for %s user" % u)
+
+ ## TEST PUBLISHING
+
+ def testViewPublishButtonNonDMUsers(self):
+ # Publish button MUST BE ABSENT in document view form
+ # Pattern for publish button presence checking
+ pattern = re.compile('.*', re.S|re.M)
+ for u in DM_USERS_IDS:
+ self.login(u)
+ auth = '%s:%s' % (u,USERS[u]['passw'])
+ doc_id = "doc_%s" % u
+ html = str(self.publish(self.portal.id+'/%s' % doc_id, auth))
+ m = pattern.match(html)
+ self.assert_(m, "Publish button NOT PRESENT for %s - member of DiscussionManager group" % u)
+
+
+ def testPublishing(self):
+ # Check whether perform real publishing
+ # Pattern for publish button presence checking
+ pattern = re.compile('.*', re.S|re.M)
+ for u in COMMON_USERS_IDS:
+ self.logout()
+ auth = "%s:" % u
+ if not u=='anonym':
+ self.login(u)
+ auth = '%s:%s' % (u,USERS[u]['passw'])
+ doc_id = "doc_%s" % u
+ html = str(self.publish(self.portal.id+'/%s' % doc_id, auth))
+ m = pattern.match(html)
+ if not u=='anonym' and 'Manager' in USERS[u]['roles']:
+ self.assert_(m, "%s - user with Manager role NOT VIEW Delete reply button for published reply on document view form" % u)
+ else:
+ self.assert_(not m, "%s - user without Manager role CAN VIEW Delete reply button for published reply on document view form" % u)
+
+ def testDeleting(self):
+ # Manager with DiscussionManager role CAN delete ANY REPLY.
+ # Manager without DiscussionManager role [common manager] CAN delete ONLY PUBLISHED REPLY.
+ # Get Managers
+ managers = [u for u in USERS.keys() if 'Manager' in USERS[u]['roles']]
+ dm_man = [u for u in managers if u.startswith('dm_')][0]
+ common_man = [u for u in managers if not u.startswith('dm_')][0]
+ # Publish document for common manager
+ self.login(dm_man)
+ doc_obj = getattr(self.portal, "doc_%s" % common_man)
+ reply = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+ reply.discussion_publish_comment()
+ # Check for really deleting
+ for u in managers:
+ self.login(u)
+ auth = '%s:%s' % (u,USERS[u]['passw'])
+ doc_id = "doc_%s" % u
+ doc_obj = getattr(self.portal, doc_id)
+ getReplies = self.discussion.getDiscussionFor(doc_obj).getReplies
+ self.assert_(getReplies(), "%s - user with Manager role not view discussion reply" % u)
+ getReplies()[0].deleteDiscussion()
+ self.assert_(not getReplies(), "%s - user with Manager role not really delete discussion" % u)
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestModeration))
+ return suite
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsNotification.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsNotification.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsNotification.py (revision 843)
@@ -0,0 +1,195 @@
+#
+# Test configuration form working
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.permissions import ManagePortal, ReplyToItem
+
+import re
+from helperNotify import *
+from email.Header import Header
+from Products.qPloneComments.utils import getMsg
+
+PRODUCT = 'qPloneComments'
+PROPERTY_SHEET = "qPloneComments"
+
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+
+class TestNotification(PloneTestCase.FunctionalTestCase):
+
+ def setApprovePublished(self, swithA=1,swithP=1):
+ self.prefs._updateProperty('enable_approve_notification', swithA)
+ self.prefs._updateProperty('enable_published_notification', swithP)
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+
+ self.qi = self.portal.portal_quickinstaller
+ self.qi.installProduct(PRODUCT)
+ # VERY IMPORTANT to guarantee product skin's content visibility
+ self._refreshSkinData()
+
+ '''Preparation for functional testing'''
+ self.discussion = getToolByName(self.portal, 'portal_discussion', None)
+ # Allow discussion for Document
+ portal_types = getToolByName(self.portal, 'portal_types', None)
+ doc_fti = portal_types.getTypeInfo('Document')
+ doc_fti._updateProperty('allow_discussion', 1)
+
+ # Make sure Documents are visible by default
+ # XXX only do this for plone 3
+ self.portal.portal_workflow.setChainForPortalTypes(('Document',), 'plone_workflow')
+
+ portal_properties = getToolByName(self.portal, 'portal_properties', None)
+ self.prefs = portal_properties[PROPERTY_SHEET]
+
+ # Add Manager user - 'dm' and add him to Discussion Manager group
+ self.portal.portal_membership.addMember('dm', 'secret' , ['Manager'], [])
+ portal_groups = getToolByName(self.portal, 'portal_groups')
+ dm_group = portal_groups.getGroupById('DiscussionManager')
+ dm_group.addMember('dm')
+ self.logout()
+ self.login('dm')
+ # For prepare mail sending - enter an e-mail adress
+ self.portal.email_from_address = 'mail@plone.test'
+ self.prefs._updateProperty('email_discussion_manager', 'discussion.manager@test.com')
+ member = self.portal.portal_membership.getAuthenticatedMember()
+ member.setMemberProperties({'email':'creator@test.com'})
+
+ # Add testing document to portal
+ my_doc = self.portal.invokeFactory('Document', id='my_doc', title='Doc')
+ self.my_doc = self.portal['my_doc']
+ self.my_doc.edit(text_format='plain', text='hello world')
+ # Create talkback for document and Prepare REQUEST
+ self.discussion.getDiscussionFor(self.my_doc)
+ self.request = self.app.REQUEST
+ self.request.form['Creator'] = self.portal.portal_membership.getAuthenticatedMember().getUserName()
+ self.request.form['subject'] = "Reply 1"
+ self.request.form['body_text'] = "text of reply"
+
+ prepareMailSendTest()
+
+ def test_bug_parent_reply(self):
+ setProperties(self.prefs, 'enable_reply_user_notification')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ parent_reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ parent_reply.discussion_reply('reply', 'text')
+
+ def test_bug_mistakable_names(self):
+ setProperties(self.prefs, 'enable_reply_user_notification')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ parent_reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+
+ args={'mto': 'user_email@gmail.com',
+ 'mfrom': 'admin_email@gmail.com',
+ 'obj': parent_reply,
+ 'organization_name': 'org_name',
+ 'name': parent_reply.getOwnerTuple()[1]}
+
+ msg = getMsg(self.portal, 'reply_notify_template', args)
+ patt = re.compile('\\n([^,]*?),\\n\\n')
+ m = patt.search(msg)
+ if m:
+ name = m.group(1)
+ self.assertEqual(parent_reply.getOwnerTuple()[1], name)
+ else:
+ raise "No name"
+
+ def test_notificafion_disabled(self):
+ cleanOutputDir()
+ setProperties(self.prefs)
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.assert_(not testMailExistance(), 'Mail was sended when all notification was disabled')
+
+ def test_published_comment_notification(self):
+ cleanOutputDir()
+ setProperties(self.prefs, 'enable_published_notification')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.assert_(testMailExistance(), 'Mail was not sended when enable_published_notification')
+
+ def test_approve_comment_notification(self):
+ cleanOutputDir()
+ setProperties(self.prefs, 'enable_approve_notification')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.assert_(testMailExistance(), 'Mail was not sended when enable_approve_notification')
+
+ def test_reply_comment_user_notification(self):
+ cleanOutputDir()
+ setProperties(self.prefs, 'enable_reply_user_notification')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.assert_(not testMailExistance(), 'Mail was sended for simple reply when enable_reply_user_notification')
+
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ reply.discussion_reply('A Reply for comment' ,'text of reply for comment')
+ reply_for_comment = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ self.assert_(testMailExistance(), 'Mail was not sended when enable_reply_user_notification')
+
+ def test_rejected_comment_notification(self):
+ cleanOutputDir()
+ setProperties(self.prefs, 'enable_rejected_user_notification', 'enable_moderation')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.assert_(not testMailExistance(), 'Mail was sended when enable_rejected_user_notification was enabled')
+
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ self.portal.REQUEST.set('ids', [reply.getId()])
+ self.portal.prefs_recent_comments_delete()
+ self.assert_(testMailExistance(), 'Mail was not sended when enable_rejected_user_notification')
+
+ def test_approve_comment_user__notification(self):
+ cleanOutputDir()
+ setProperties(self.prefs, 'enable_approve_user_notification')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.assert_(testMailExistance(), 'Mail was not sended when enable_approve_user_notification')
+
+ def test_bug_notification_on_single_reply_publish(self):
+ """ Bug: no notification sent on publishing single comment.
+ Must be 3 mails: for replier about replying on his commen;
+ for replier about publishig his comment;
+ for document creator about adding new comment.
+ """
+ properties = ['enable_approve_user_notification', 'enable_reply_user_notification',
+ 'enable_published_notification']
+ setProperties(self.prefs, *properties)
+ #setProperties(self.prefs, 'enable_published_notification', )
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ reply.discussion_reply('A Reply for reply for my_doc' ,'text of reply on reply for my_doc')
+ reply2 = self.discussion.getDiscussionFor(reply).getReplies()[0]
+
+ cleanOutputDir()
+ reply2.discussion_publish_comment()
+ mails = getMails()
+ self.assert_([1 for m in mails if re.search('^Subject:.*(replied).*$', m, re.I|re.M)] \
+ ,'No notification for reply' % properties)
+ self.assert_([1 for m in mails if re.search('^Subject:.*(added).*$', m, re.I|re.M)] \
+ , 'No notification for adding comment' % properties)
+ self.assert_([1 for m in mails if re.search('^Subject:.*(published).*$', m, re.I|re.M)] \
+ , 'No notification for publishing comment' % properties)
+
+ def test_bug_notification_on_single_reply_delete(self):
+ """ Bug: no notification sent on deleting single comment.
+ Mail about rejecing comment should be sent to comentator.
+ """
+ properties = ['enable_rejected_user_notification',]
+ setProperties(self.prefs, *properties)
+ #setProperties(self.prefs, 'enable_published_notification', )
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+
+ cleanOutputDir()
+ reply.deleteDiscussion()
+ mails = getMails()
+ regexp = re.compile("Subject:\s*(.*?)$",re.M)
+ subject = str(Header('Your comment on "Doc" was not approved', 'utf-8'))
+ self.assert_([1 for m in mails if regexp.search(m).group(1) == subject] \
+ ,'No notification for rejecting comment' % properties)
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestNotification))
+ return suite
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsNotificationRecipients.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsNotificationRecipients.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsNotificationRecipients.py (revision 843)
@@ -0,0 +1,161 @@
+#
+# Test configuration form working
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.permissions import ManagePortal, ReplyToItem
+
+from Products.qPloneComments.utils import getMsg
+
+import re
+from common import *
+from helperNotify import *
+from email.Header import Header
+from testQPloneCommentsModeration import USERS, COMMON_USERS_IDS, DM_USERS_IDS
+
+
+PRODUCT = 'qPloneComments'
+PROPERTY_SHEET = "qPloneComments"
+
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+USERS = {# Common Members
+ 'admin':{'passw': 'secret_admin', 'roles': ['Manager']},
+ 'owner':{'passw': 'secret_creator', 'roles': ['Member']},
+ 'replier1':{'passw': 'secret_member', 'roles': ['Member']},
+ 'replier2':{'passw': 'secret_member', 'roles': ['Member']},
+ # Members for discussion manager group
+ 'dm_admin':{'passw': 'secret_dm_admin', 'roles': ['Manager']},
+ }
+DM_USERS_IDS = [u for u in USERS.keys() if u.startswith('dm_')]
+
+REXP_TO = re.compile("To:\s*(.*?)$",re.M)
+REXP_SUBJ = re.compile("Subject:\s*(.*?)$",re.M)
+
+
+class TestNotificationRecipients(PloneTestCase.FunctionalTestCase):
+ """ Test is notifications sends to right recipients. """
+
+ def prepareRequest4Reply(self, member_id):
+ self.login(member_id)
+ self.request = self.app.REQUEST
+ self.request.form['Creator'] = self.membership.getAuthenticatedMember().getUserName()
+ self.request.form['subject'] = "Reply of '%s'" % self.request.form['Creator']
+ self.request.form['body_text'] = "text of reply"
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+
+ self.qi = self.portal.portal_quickinstaller
+ self.qi.installProduct(PRODUCT)
+ # VERY IMPORTANT to guarantee product skin's content visibility
+ self._refreshSkinData()
+
+ '''Preparation for functional testing'''
+ self.membership = getToolByName(self.portal, 'portal_membership', None)
+ self.discussion = getToolByName(self.portal, 'portal_discussion', None)
+ # Allow discussion for Document
+ portal_types = getToolByName(self.portal, 'portal_types', None)
+ doc_fti = portal_types.getTypeInfo('Document')
+ doc_fti._updateProperty('allow_discussion', 1)
+
+ # Make sure Documents are visible by default
+ # XXX only do this for plone 3
+ self.portal.portal_workflow.setChainForPortalTypes(('Document',), 'plone_workflow')
+
+ portal_properties = getToolByName(self.portal, 'portal_properties', None)
+ self.prefs = portal_properties[PROPERTY_SHEET]
+
+ # Add users and add members to DiscussionManager group
+ addMembers(self.portal, USERS)
+ add2Group(self.portal, 'DiscussionManager', DM_USERS_IDS)
+ self.createMemberarea('owner')
+
+ # Prepare mail sending - enter an e-mail adress, and allow all possible notifications
+ self.portal.email_from_address = 'mail@plone.test'
+ setProperties(self.prefs, 'enable_moderation', 'enable_approve_notification',
+ 'enable_approve_user_notification','enable_reply_user_notification',
+ 'enable_published_notification', 'enable_rejected_user_notification')
+ self.prefs._updateProperty('email_discussion_manager', 'discussion.manager@test.com')
+ self.prefs._updateProperty('email_subject_prefix', 'PREFIX')
+
+ # Add testing document to portal
+ self.login('owner')
+ self.portal.Members['owner'].invokeFactory('Document', id='my_doc', title="Test document")
+ self.my_doc = self.portal.Members['owner']['my_doc']
+ self.my_doc.edit(text_format='plain', text='hello world')
+
+ # Create talkback for document and Prepare REQUEST
+ self.discussion.getDiscussionFor(self.my_doc)
+
+ prepareMailSendTest()
+
+ def checkToANDSubj(self, mails, to, subj):
+ messages = [m for m in mails if REXP_TO.search(m) and REXP_TO.search(m).group(1)==to]
+ self.assert_(len(messages) > 0, "No message sent to '%s' recipient" % to)
+ mangled = str(Header(subj, 'utf-8'))
+ self.assert_([1 for m in messages if REXP_SUBJ.search(m) and REXP_SUBJ.search(m).group(1)==mangled],\
+ "There is no message for '%s' recipient with '%s' subject" % (to,subj))
+
+ def test_Reply(self):
+ cleanOutputDir()
+ self.prepareRequest4Reply('replier1')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+
+ mails = getMails()
+ self.assertEqual(len(mails), 1)
+ self.checkToANDSubj(mails, to="discussion.manager@test.com",
+ subj="[PREFIX] New comment awaits moderation")
+
+ def test_PublishReply(self):
+ self.prepareRequest4Reply('replier1')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.login('dm_admin')
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ cleanOutputDir()
+
+ reply.discussion_publish_comment()
+ mails = getMails()
+ self.assertEqual(len(mails), 2)
+ self.checkToANDSubj(mails, to="owner@test.com", subj="[PREFIX] New comment added")
+ self.checkToANDSubj(mails, to="replier1@test.com", subj='Your comment on "Test document" is now published')
+
+ def test_Publish2ndReply(self):
+ self.prepareRequest4Reply('replier1')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.login('dm_admin')
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ reply.discussion_publish_comment()
+ self.prepareRequest4Reply('replier2')
+ reply.discussion_reply('A Reply for reply for my_doc' ,'text of reply on reply for my_doc')
+ self.login('dm_admin')
+ reply2 = self.discussion.getDiscussionFor(reply).getReplies()[0]
+ cleanOutputDir()
+
+ reply2.discussion_publish_comment()
+ mails = getMails()
+ self.assertEqual(len(mails), 3)
+ self.checkToANDSubj(mails, to="owner@test.com", subj="[PREFIX] New comment added")
+ self.checkToANDSubj(mails, to="replier1@test.com", subj='Someone replied to your comment on "Test document"')
+ self.checkToANDSubj(mails, to="replier2@test.com", subj='Your comment on "Test document" is now published')
+
+ def test_DeleteReply(self):
+ self.prepareRequest4Reply('replier1')
+ self.my_doc.discussion_reply('A Reply for my_doc' ,'text of reply for my_doc')
+ self.login('dm_admin')
+ reply = self.discussion.getDiscussionFor(self.my_doc).getReplies()[0]
+ cleanOutputDir()
+
+ reply.deleteDiscussion()
+ mails = getMails()
+ self.assertEqual(len(mails), 1)
+ self.checkToANDSubj(mails, to="replier1@test.com", subj='Your comment on "Test document" was not approved')
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestNotificationRecipients))
+ return suite
Index: /qPloneComments/tags/3.2.3/tests/testQPloneCommentsPermission.py
===================================================================
--- /qPloneComments/tags/3.2.3/tests/testQPloneCommentsPermission.py (revision 843)
+++ /qPloneComments/tags/3.2.3/tests/testQPloneCommentsPermission.py (revision 843)
@@ -0,0 +1,39 @@
+#
+# Test 'Moderate Discussion' permission
+#
+
+from Products.PloneTestCase import PloneTestCase
+from Products.CMFCore.utils import getToolByName
+
+from Products.CMFDefault.DiscussionItem import DiscussionItemContainer
+
+PRODUCT = 'qPloneComments'
+PERM_NAME = 'Moderate Discussion'
+PloneTestCase.installProduct(PRODUCT)
+PloneTestCase.setupPloneSite()
+
+
+class TestPermission(PloneTestCase.PloneTestCase):
+
+ def afterSetUp(self):
+ self.loginAsPortalOwner()
+ self.portal.portal_quickinstaller.installProduct(PRODUCT)
+
+ def test_install_moderate_discussion_permission(self):
+ roles = [item['name'] for item in self.portal.rolesOfPermission(PERM_NAME) if item['selected'] == 'SELECTED']
+ self.assert_( roles != [], '%s not installed'%PERM_NAME)
+
+ def test_deleteReply_permission(self):
+ #dic = DiscussionItemContainer()
+ #dic.createReply('Title', 'Text')
+ pass
+
+ def test_manager_moderation(self):
+ pass
+
+
+def test_suite():
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestPermission))
+ return suite
Index: /qPloneComments/tags/3.2.3/utils.py
===================================================================
--- /qPloneComments/tags/3.2.3/utils.py (revision 843)
+++ /qPloneComments/tags/3.2.3/utils.py (revision 843)
@@ -0,0 +1,178 @@
+from Products.CMFPlone import MessageFactory
+from Products.CMFCore.utils import getToolByName
+
+# Get apropriate property from (propery_sheeet) configlet
+def getProp(self, prop_name, marker=None):
+ result = marker
+ pp = getToolByName(self, 'portal_properties')
+ config_ps = getattr(pp, 'qPloneComments', None)
+ if config_ps:
+ result = getattr(config_ps, prop_name, marker)
+ return result
+
+def publishDiscussion(self):
+ roles = ['Anonymous']
+ self.review_state = "published"
+ self.manage_permission('View', roles, acquire=1)
+ self._p_changed = 1
+ self.reindexObject()
+
+def setAnonymCommenting(context, allow=False):
+ portal = getToolByName(context, 'portal_url').getPortalObject()
+ if allow:
+ portal.manage_permission('Reply to item', ['Anonymous','Manager','Member'], 1)
+ else:
+ portal.manage_permission('Reply to item', ['Manager','Member'], 1)
+
+def manage_mails(reply, context, action):
+ def sendMails(props, actions, key):
+ for p in props:
+ if p in actions[key]:
+ send_email(reply, context, p)
+
+ prop_sheet = context.portal_properties['qPloneComments']
+ props = filter(lambda x: prop_sheet.getProperty(x), prop_sheet.propertyIds())
+
+ actions = { 'onPublish': ('enable_approve_user_notification',
+ 'enable_reply_user_notification',
+ 'enable_published_notification'),
+ 'onDelete' : ('enable_rejected_user_notification',),
+ 'onApprove': ('enable_approve_notification',)}
+
+ if action == 'publishing':
+ sendMails(props, actions, 'onPublish')
+
+ elif action == 'deleting':
+ sendMails(props, actions, 'onDelete')
+
+ elif action == 'aproving':
+ sendMails(props, actions, 'onApprove')
+
+def getMsg(context, template, args):
+ return getattr(context, template)(**args)
+
+def allowEmail(context, reply, state, creator):
+ condition = getattr(context, 'emailCommentNotification', True)
+ if callable(condition):
+ condition = condition(reply=reply, state=state, creator=creator)
+ return condition
+
+def send_email(reply, context, state):
+ def getEmail(obj, context):
+ email = obj.getProperty('email', None)
+ if email is None:
+ creators = hasattr(obj, 'listCreators') and obj.listCreators() or [obj.Creator(),]
+ userid = creators and creators[0] or ""
+ creator = getToolByName(context, 'portal_membership').getMemberById(userid)
+ if creator and allowEmail(context, reply, state, creator):
+ return creator.getProperty('email', '')
+ else:
+ return email
+ return ''
+
+ def getParent(reply):
+ if reply.meta_type == 'Discussion Item':
+ reply = reply.inReplyTo()
+ return getParent(reply)
+ return reply
+
+ def getDIParent(reply):
+ r = reply.inReplyTo()
+ return r.meta_type == 'Discussion Item' and r or None
+
+ def getParentOwnerEmail(reply, context):
+ creator_id = getParent(reply).getOwnerTuple()[1]
+ creator = getToolByName(context, 'portal_membership').getMemberById(creator_id)
+ if creator and allowEmail(context, reply, state, creator):
+ return creator.getProperty('email', '')
+ return ''
+
+ args = {}
+ if reply:
+ user_email = getEmail(reply, context)
+ reply_parent = getParent(reply)
+
+ organization_name = getProp(context, 'email_subject_prefix', '')
+ creator_name = reply.getOwnerTuple()[1]
+ admin_email = context.portal_url.getPortalObject().getProperty('email_from_address')
+
+ subject = ''
+ if state == 'enable_approve_user_notification':
+ subject = 'Your comment on "%s" is now published' % getParent(context).Title()
+ if user_email:
+ template = 'notify_comment_template'
+ args={'mto': user_email,
+ 'mfrom': admin_email,
+ 'obj': reply_parent,
+ 'organization_name': organization_name,
+ 'name': creator_name}
+ else:
+ args = {}
+
+ elif state == 'enable_rejected_user_notification':
+ subject = 'Your comment on "%s" was not approved' % getParent(context).Title()
+ if user_email:
+ template = 'rejected_comment_template'
+ args={'mto': user_email,
+ 'mfrom': admin_email,
+ 'obj': reply_parent,
+ 'organization_name': organization_name,
+ 'name': creator_name}
+ else:
+ args = {}
+
+ elif state == 'enable_reply_user_notification':
+ template = 'reply_notify_template'
+ subject = 'Someone replied to your comment on "%s"' % getParent(context).Title()
+ di_parrent = getDIParent(reply)
+ if di_parrent:
+ user_email = getEmail(di_parrent, context)
+ if user_email:
+ args={'mto': user_email,
+ 'mfrom': admin_email,
+ 'obj': reply_parent,
+ 'organization_name': organization_name,
+ 'name': di_parrent.getOwnerTuple()[1]}
+ else:
+ args = {}
+ else:
+ args = {}
+
+ elif state == 'enable_published_notification':
+ template = 'published_comment_template'
+ user_email = getParentOwnerEmail(reply, context)
+ if user_email:
+ args={'mto':user_email,
+ 'mfrom':admin_email,
+ 'obj':reply_parent,
+ 'organization_name':organization_name}
+ subject = '[%s] New comment added' % organization_name
+ else:
+ args = {}
+
+ elif state == 'enable_approve_notification':
+ template = 'approve_comment_template'
+ user_email = getProp(context, "email_discussion_manager", None)
+ if user_email:
+ args={'mto':user_email,
+ 'mfrom':admin_email,
+ 'obj':reply_parent,
+ 'organization_name':organization_name}
+ subject = '[%s] New comment awaits moderation' % organization_name
+ else:
+ args = {}
+
+ if args:
+ msg = getMsg(context, template, args)
+ p_utils = context.plone_utils
+ site_props = context.portal_properties.site_properties
+ host = p_utils.getMailHost()
+ host.secureSend(msg, user_email, admin_email,
+ subject = subject,
+ subtype = 'plain',
+ debug = False,
+ charset = site_props.getProperty('default_charset', 'utf-8'),
+ From = admin_email)
+
+def setStatusMsg(state, context, msg):
+ context.plone_utils.addPortalMessage(msg)
Index: /qPloneComments/tags/3.2.3/version.txt
===================================================================
--- /qPloneComments/tags/3.2.3/version.txt (revision 843)
+++ /qPloneComments/tags/3.2.3/version.txt (revision 843)
@@ -0,0 +1,1 @@
+3.2.3
+ + Comment title + +
++