Index: /qPloneComments/branches/jcbrand-abuseFeature/Extensions/Install.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/Extensions/Install.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/Extensions/Install.py	(revision 892)
@@ -0,0 +1,9 @@
+from Products.CMFCore.utils import getToolByName
+from StringIO import StringIO
+
+def uninstall(self):
+    out = StringIO()
+    setup_tool = getToolByName(self, 'portal_setup')
+    setup_tool.runAllImportStepsFromProfile('profile-Products.qPloneComments:uninstall')
+    print >> out, "Imported uninstall profile."
+    return out.getvalue()
Index: /qPloneComments/branches/jcbrand-abuseFeature/HISTORY.txt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/HISTORY.txt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/HISTORY.txt	(revision 892)
@@ -0,0 +1,66 @@
+Changelog
+=========
+
+3.2.4 (unreleased)
+------------------
+
+- Nothing changed yet.
+
+
+3.2.3 (2009-03-24)
+------------------
+
+- Product general code for enabling localization modified.
+  [liebster]
+- Add comment about Gravatar support to the comment form.
+  [mylan]
+
+
+3.2.2 - March 3, 2009
+---------------------
+
+- Added gravatar support
+
+
+3.2.1 - October 23, 2008 ( Unreleased )
+---------------------------------------
+
+- Added uninstall configlet and skins
+
+- Converted a i18n layout to a locales layout
+
+
+3.1.2 - Unreleased
+------------------
+
+- Fixed to use the username as the comment creator. That's necessary whenever
+  the user is authenticated. This fixes the problems where commenters weren't
+  receiving any notifications.
+  [deo]
+
+3.1.1 - September 19, 2008
+--------------------------
+
+- Fixed bug in registration of product also as a package.
+  [liebster]
+
+3.1.0 - August 28, 2008
+-----------------------
+
+- Added pt-br translation.
+  [erico_andrei]
+
+- Converted `Extension/Install.py` into a complete GenericSetup profile.
+  [deo]
+
+- Added note about the `browser.layer` dependency for Plone 3.0.x.
+  [deo]
+
+- Updated tests to run with Plone 3.x.
+  [deo]
+
+- General code cleanup and modernization.
+  [deo]
+
+- Synchronized the discussion templates to match Plone 3.x versions.
+  [deo]
Index: /qPloneComments/branches/jcbrand-abuseFeature/LICENSE.GPL
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/LICENSE.GPL	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/LICENSE.GPL	(revision 892)
@@ -0,0 +1,225 @@
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
Index: /qPloneComments/branches/jcbrand-abuseFeature/README.txt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/README.txt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/README.txt	(revision 892)
@@ -0,0 +1,113 @@
+Plone Comments
+
+  Plone Comments (qPloneComments) is a Plone product developed developed
+  to improve the site managers and editors expirience with standard
+  commenting mechanism in Plone.
+
+Features
+
+  * Notify admin about comment posted
+
+  * Notify commentator about his comment aproval
+
+  * Notify author of parent comment about new follow up added
+
+  * Moderation of comments, approval of comments
+
+  * Anonymous commenting
+
+  * Added Name field to comment form, it is required for anonymous comments
+
+  * Article author can be notified about new comment after the approval by reviewer
+
+  * List of recent comments for more comfortable moderation
+
+  * Configlet that allow:
+
+    o Turning on/off Moderation
+
+    o Turning on/off Manager notification
+
+    o Turning on/off Editor notification
+
+    o Turning on/off Anonymous Commenting
+
+    o Configure admin e-mail for notifications
+
+    o Configure notification subject
+
+  * qPloneCaptcha integrated (needs the qPloneCaptcha to be installed)
+
+Notes
+
+  Comments moderation is implemented with involvement of two stage workflow.
+  Comments are created in "private" state and visible only to DiscussionManager
+  group of users.
+
+  To differentiate between logged-in (registered) commentors and Anonymous
+  commentors that pretend to be one person or other one, we use Boldness of
+  name. The Comment author is in bold when posted by logged in member. The
+  names provided when posting Anonymously are in plain text.
+
+  Notification subject control allows to enter custom prefix to disctinct
+  notifications comming from different sites.
+
+Usage
+
+  One of possible UseCases:
+
+  Moderation is enabled and authors notification is turned on.
+
+    * New comment posted in private state.
+
+    * Notification is sent to the emails entered in Plone Comments configlet.
+
+    * Moderator User with DiscussionManager role see the comment.
+
+    * The comment can be deleted or published on modaration stage.
+
+    * When comment is published notification is sent to Article Editor.
+
+Links
+
+  * Download releases from Sourceforge.net "Plone Comments project area":http://sf.net/projects/quintagroup
+
+  * Get latest development version from "SVN":http://svn.quintagroup.com/products/qPloneComments/trunk
+
+Requirements
+
+  * Plone 3.0.x (with plone.browserlayer) or Plone 3.1.x.
+
+Installation
+
+   1. Unpack into the **Products** folder of your Zope instance.
+
+   2. Install *qPloneComments* with Quick Installer.
+
+  **Atention**: If you are using a Plone version **before** 3.1 you need to install
+  "plone.browserlayer":http://pypi.python.org/pypi/plone.browserlayer (which also
+  requires a "GenericSetup":http://pypi.python.org/pypi/Products.GenericSetup version
+  greater than 1.4) in your Plone site. It shows up as **Local browser layer support**
+  in the Plone Add-on Products Control Panel.
+
+License
+
+  Please find license in *LICENSE.GPL*.
+
+Author
+
+  The product is developed and maintained by http://quintagroup.com team.
+
+  Authors:
+
+    * Volodymyr Cherepanyak
+
+    * Andriy Mylenkyy
+
+    * Mykola Kharechko
+
+  Contributors
+
+    * Gerry Kirk: product translations improvement and proofreading
+
+    * Dorneles Tremea: code cleanup and generic setup porting
Index: /qPloneComments/branches/jcbrand-abuseFeature/__init__.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/__init__.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/__init__.py	(revision 892)
@@ -0,0 +1,6 @@
+from AccessControl import allow_module
+
+# Feed our monkeys :-)
+from Products.qPloneComments import patch
+
+allow_module('Products.qPloneComments.utils')
Index: /qPloneComments/branches/jcbrand-abuseFeature/browser/__init__.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/browser/__init__.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/browser/__init__.py	(revision 892)
@@ -0,0 +1,1 @@
+#
Index: /qPloneComments/branches/jcbrand-abuseFeature/browser/comments.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/browser/comments.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/browser/comments.pt	(revision 892)
@@ -0,0 +1,114 @@
+<tal:block define="userHasReplyPermission view/can_reply;
+                   isDiscussionAllowed view/is_discussion_allowed;
+                   replies view/get_replies;
+                   isAnon view/is_anonymous;
+                   ifModerate view/is_moderation_enabled;
+                   isModerationAllowed view/can_moderate"
+                   i18n:domain="plone">
+    <div class="discussion"
+         tal:condition="python:replies or (userHasReplyPermission and isDiscussionAllowed) or (isAnon and not userHasReplyPermission and isDiscussionAllowed)">
+        <form name="reply"
+              action=""
+              method="post"
+              tal:condition="python:userHasReplyPermission and isDiscussionAllowed"
+              tal:attributes="action string:${context/absolute_url}/discussion_reply_form#discussion">
+
+              <input class="standalone"
+                     style="margin-bottom: 1.25em;"
+                     type="submit"
+                     value="Add Comment"
+                     i18n:attributes="value label_add_comment;"
+                     />
+        </form>
+        <form tal:condition="python:isAnon and not userHasReplyPermission and isDiscussionAllowed"
+              tal:attributes="action view/login_action">
+            <input class="standalone"
+                   style="margin-bottom: 1.25em;"
+                   type="submit"
+                   value="Log in to add comments"
+                   i18n:attributes="value label_login_to_add_comments;"
+                   />
+        </form>
+
+        <tal:getreplies repeat="reply_dict replies">
+            <div class="comment" style=""
+                tal:condition="python:isModerationAllowed or isPublished"
+                tal:define="indent python:reply_dict['depth']*2;
+                            reply python:reply_dict['object'];
+                            isPublished python:reply.review_state=='published';"
+                tal:attributes="style string:margin-left:${indent}em;">
+
+                <h3>
+                    <a name="comments" tal:attributes="name reply/id">
+                    <span tal:replace="reply/pretty_title_or_id">Comment title</span>
+                    </a>
+                </h3>
+                <div class="documentByLine"
+                     tal:define="creator reply/Creator;
+                                 anonymous_creator python:creator=='Anonymous User';
+                                 mi python:not anonymous_creator and view.member_info(creator);
+                                 fullname python: mi and mi['fullname'] or creator;
+                                 gavatar_src python:view.getGravatar(reply)" >
+                    <img alt="Avatar" class="avatar" height="40" width="40"
+                         src="http://default.gavatar.gif"
+                         tal:attributes="src gavatar_src">
+                    <span i18n:translate="label_comment_by">Posted by</span>
+                    <span tal:content="fullname"
+                          tal:condition="not:anonymous_creator">Poster Name</span>
+                    <span i18n:translate="label_anonymous_user"
+                          tal:condition="anonymous_creator">Anonymous User</span>
+                    <span i18n:translate="label_commented_at">at</span>
+                    <span tal:replace="python:view.format_time(reply.ModificationDate())">8/23/2001 12:40:44 PM</span>
+                    <span tal:condition="python:ifModerate and not isPublished">[pending]</span>
+                </div>
+                <div class="commentBody"
+                     tal:content="structure reply/CookedBody">
+                     This is the body text of the comment.
+                </div>
+                <form name="reply"
+                      action="discussion_reply_form"
+                      method="post"
+                      style="display: inline;"
+                      tal:attributes="action string:${reply/absolute_url}/discussion_reply_form"
+                      tal:condition="python:userHasReplyPermission and isPublished">
+                    <input class="standalone"
+                           type="submit"
+                           value="Reply"
+                           i18n:attributes="value label_reply;"
+                           />
+                </form>
+                <form name="delete"
+                      action=""
+                      method="post"
+                      style="display: inline;"
+                      tal:condition="isModerationAllowed"
+                      tal:attributes="action string:${reply/absolute_url}/deleteDiscussion">
+                    <input class="destructive"
+                           type="submit"
+                           value="Remove"
+                           i18n:attributes="value label_remove;"
+                           />
+                </form>
+                <form name="publish_discussion"
+                      action=""
+                      method="post"
+                      style="display: inline;"
+                      tal:condition="not:isPublished"
+                      tal:attributes="action string:${reply/absolute_url}/discussion_publish_comment">
+
+                    <input class="standalone"
+                           type="submit"
+                           value="Publish"
+                           i18n:attributes="value"
+                           />
+                </form>
+                <br/>
+                <div class="documentByLine"
+                     i18n:translate="text_no_add_reply"
+                     i18n:domain="plonecomments"
+                     tal:condition="not:isPublished">Comment must be approved before replies to comment accepted.</div>
+
+            </div>
+        </tal:getreplies>
+    </div>
+</tal:block>
Index: /qPloneComments/branches/jcbrand-abuseFeature/browser/comments.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/browser/comments.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/browser/comments.py	(revision 892)
@@ -0,0 +1,53 @@
+import urllib, md5 #hashlib
+
+from Acquisition import aq_inner
+from AccessControl import getSecurityManager
+from Products.CMFPlone.utils import getToolByName
+
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+from plone.app.layout.viewlets import comments
+
+class CommentsViewlet(comments.CommentsViewlet):
+    """A custom version of the comments viewlet
+    """
+
+    render = ViewPageTemplateFile('comments.pt')
+
+    def is_moderation_enabled(self):
+        """ Returns a boolean indicating whether the user has enabled moderation
+            in the qPloneComments configlet
+        """
+        portal_properties = getToolByName(self.context, 'portal_properties')
+        try:
+            return portal_properties.qPloneComments.getProperty('enable_moderation', None)
+        except AttributeError:
+            return False
+
+    def can_moderate(self):
+        """ Returns a boolean indicating whether the user has the 'Moderate Discussion'
+            permission
+        """
+        return getSecurityManager().checkPermission('Moderate Discussion', aq_inner(self.context))
+
+    def getGravatar(self, reply):
+        purl = getToolByName(self.context, 'portal_url')
+        default = purl() + '/defaultUser.gif' 
+        email = ''
+
+        creator = reply.Creator()
+        if creator and not creator=='Anonymous User':
+            mtool = getToolByName(self.context, "portal_membership")
+            member = mtool.getMemberById(creator)
+            email = member and member.getProperty('email','') or ''
+        else:
+            email = reply.getProperty('email',d='')
+        if not email:
+            return default
+
+        size = 40
+        gravatar_url = "http://www.gravatar.com/avatar.php?"
+        # construct the url
+        gravatar_url += urllib.urlencode({'gravatar_id':md5.md5(email).hexdigest(), 
+            'default':default, 'size':str(size)})
+
+        return gravatar_url
Index: /qPloneComments/branches/jcbrand-abuseFeature/configure.zcml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/configure.zcml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/configure.zcml	(revision 892)
@@ -0,0 +1,35 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:cmf="http://namespaces.zope.org/cmf"
+    xmlns:gs="http://namespaces.zope.org/genericsetup"
+    xmlns:five="http://namespaces.zope.org/five"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    i18n_domain="plonecomments">
+
+  <include package="plone.browserlayer" />
+  <i18n:registerTranslations directory="locales" />
+
+  <cmf:registerDirectory name="skins" directory="skins" recursive="True" />
+
+  <gs:registerProfile
+      name="default"
+      title="qPloneComments"
+      directory="profiles/default"
+      provides="Products.GenericSetup.interfaces.EXTENSION"
+      description="Extension profile for the Plone Comments product."
+      />
+
+  <gs:registerProfile
+      name="uninstall"
+      title="qPloneComments uninstall"
+      directory="profiles/uninstall"
+      provides="Products.GenericSetup.interfaces.EXTENSION"
+      description="Extension profile for the Plone Comments product."
+      />
+
+  <permission
+      id="moderate.discussion"
+      title="Moderate Discussion"
+      />
+
+</configure>
Index: /qPloneComments/branches/jcbrand-abuseFeature/interfaces.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/interfaces.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/interfaces.py	(revision 892)
@@ -0,0 +1,6 @@
+from plone.theme.interfaces import IDefaultPloneLayer
+
+class IPloneCommentsLayer(IDefaultPloneLayer):
+    """Marker interface that defines a Zope 3 skin layer bound to a Skin
+       Selection in portal_skins.
+    """
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/de/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/de/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/de/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,229 @@
+# --- PLEASE EDIT THE LINES BELOW CORRECTLY ---
+# SOME DESCRIPTIVE TITLE.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2006-12-29 11:01+0100\n"
+"Last-Translator: \n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: de\n"
+"Language-Name: Deutsch\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\n"
+"X-Is-Fallback-For: de-at de-li de-lu de-ch de-de\n"
+
+#: ./skins/qplonecomments/discussion_publish_comment.py:38
+msgid "Comment published."
+msgstr "Kommentar veröffentlicht"
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Comment(s) deleted."
+msgstr "Kommentare gelöscht"
+
+#: ./skins/qplonecomments/prefs_recent_comments_publish.cpy:26
+msgid "Comment(s) published."
+msgstr "Kommentar(e) veröffentlicht."
+
+#: ./skins/qplonecomments/discussion_reply.cpy:96
+msgid "Currently, all comments require approval before being published. Please check back later."
+msgstr "Zur Zeit warten alle Kommentare auf ihre Veröffentlichung. Bitte kommen Sie später wieder."
+
+#: ./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 "Geben Sie Ihren Namen ein"
+
+#: ./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 "Eine Antwort ist nicht möglich, da der Kommentar noch nicht veröffentlicht wurde."
+
+#: ./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 "Bitte schalten Sie den neuen Kommentar auf der Seite \" ${title} \" (${link} frei "
+
+#. 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 "Neuste Kommentare"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "qPloneComments Konfiguration"
+
+#. 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 "Bitte geben Sie Ihren Namen ein."
+
+#. 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 ""
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Anonyme Kommentare aktivieren"
+
+#. 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 konfigurieren"
+
+#. 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 "Benachrichtungs-Emails an Manager aktivieren"
+
+#. 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 "Moderation aktivieren"
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Benachrichtigung für den Autor des Dokuments einschalten"
+
+#. 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 "Email Betreff"
+
+#. 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 "Liste der neusten Kommentare"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "qPloneComments Optionen"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Kommentar"
+
+#. 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 ""
+
+#. 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 ""
+
+#. Default: "View new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/published_comment_template.pt:8
+msgid "publishedmail_new_comment_link"
+msgstr ""
+
+#. 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 "qPloneComment Konfiguration gespeichert."
+
+#. 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 ""
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr "${organization_name} Support Team"
+
+#. 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 "Keine neuen Kommentare"
+
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/es/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/es/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/es/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,229 @@
+# 
+# Héctor Velarde
+# hvelarde@jornada.com.mx
+msgid ""
+msgstr ""
+"Project-Id-Version: qPloneComments\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2007-04-19 20:21-0600\n"
+"Last-Translator: Héctor Velarde <hvelarde@jornada.com.mx>\n"
+"Language-Team: La Jornada\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"Language-Code: es\n"
+"Language-Name: Español\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\n"
+"X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n"
+
+#: ./skins/qplonecomments/discussion_publish_comment.py:38
+msgid "Comment published."
+msgstr "Comentario publicado."
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Comment(s) deleted."
+msgstr "Comentario(s) borrados."
+
+#: ./skins/qplonecomments/prefs_recent_comments_publish.cpy:26
+msgid "Comment(s) published."
+msgstr "Comentario(s) publicados."
+
+#: ./skins/qplonecomments/discussion_reply.cpy:96
+msgid "Currently, all comments require approval before being published. Please check back later."
+msgstr "Todos los comentarios requieren aprobación antes de ser publicados. Por favor revise más tarde."
+
+#: ./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 "Por favor escriba su nombre."
+
+#: ./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 "No es posible responder a un comentario que no ha sido aprobado. Por favor espere a que sea aprobado antes de responderlo."
+
+#: ./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 "Por favor revise el nuevo comentario agregado a la siguiente página \" ${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 "Comentarios recientes"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "Configuración de 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 "Por favor escriba su nombre."
+
+#. 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 "Puede cambiar los mensajes de notificación personalizando las plantillas 'approve_comment_template' y 'published_comment_template' en el skin qplonecomments."
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Permitir comentarios anónimos."
+
+#. Default: "Recent comments moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:26
+msgid "label_comments"
+msgstr "Moderación de los comentarios recientes"
+
+#. Default: "Configure qPloneComments"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:20
+msgid "label_configure"
+msgstr "Configurar 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 "Notificar al administrador de discusiones cuando se reciban nuevos comentarios."
+
+#. 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 "Habilitar moderación."
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Notificar al dueño del documento cuando se reciban nuevos comentarios."
+
+#. 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 "Prefijo del asunto del mensaje (nombre de la organización)"
+
+#. 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 "Listado de comentarios recientes"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "Opciones de configuración de qPloneComments"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Comentario"
+
+#. 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 ""
+
+#. 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 ""
+
+#. Default: "View new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/published_comment_template.pt:8
+msgid "publishedmail_new_comment_link"
+msgstr "Ver nuevo comentario agregado a la siguiente página \" ${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 "Los cambios en la configuración de qPloneComments fueron guardados."
+
+#. 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 ""
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr "Equipo de soporte de ${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 "Sin comentarios nuevos."
+
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/fr/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/fr/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/fr/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,229 @@
+# --- PLEASE EDIT THE LINES BELOW CORRECTLY ---
+# SOME DESCRIPTIVE TITLE.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2006-11-10 08:59-0500\n"
+"Last-Translator: Nicole Turgeon-Courchesne <nicole@motspourmaux.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: fr\n"
+"Language-Name: French\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\n"
+"X-Is-Fallback-For: fr-be fr-ca fr-lu fr-mc fr-ch fr-fr\n"
+
+#: ./skins/qplonecomments/discussion_publish_comment.py:38
+msgid "Comment published."
+msgstr "Commentaire publié"
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Comment(s) deleted."
+msgstr "Commentaires supprimés"
+
+#: ./skins/qplonecomments/prefs_recent_comments_publish.cpy:26
+msgid "Comment(s) published."
+msgstr "Commentaires publiés"
+
+#: ./skins/qplonecomments/discussion_reply.cpy:96
+msgid "Currently, all comments require approval before being published. Please check back later."
+msgstr "Présentement, tous les commentaires doivent être approuvés avant d’être publiés. Vérifiez plus tard."
+
+#: ./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 "Veuillez indiquer votre nom."
+
+#: ./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 "Réponse non permise puisqu’elle se rapporte à un commentaire qui n’a pas été approuvé. Veuillez attendre l’approbation du commentaire avant de répondre."
+
+#: ./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 "Réviser le nouveau commentaire ajouté à la page suivante  \" ${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 "Commentaires récents"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "Configuration de 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 "Veuillez indiquer votre nom."
+
+#. 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 "Vous pouvez personnaliser les modèles d’avis 'approve_comment_template' et 'published_comment_template' au moyen de l’habillage qplonecomments."
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Permettre les commentaires anonymes"
+
+#. Default: "Recent comments moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:26
+msgid "label_comments"
+msgstr "Modération récente de commentaires"
+
+#. Default: "Configure qPloneComments"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:20
+msgid "label_configure"
+msgstr "Configurer 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 "Activer les avis du gestionnaire de discussions lorsqu’il y a de nouveaux commentaires."
+
+#. 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 "Activer la modération"
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Activer les avis du propriétaire du document lorsqu’il y a de nouveaux commentaires."
+
+#. 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 "Préfixe de l’objet du courriel (nom de l’organisme)"
+
+#. 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 "Liste des commentaires récents"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "Options de configuration qPloneComments"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Commentaire"
+
+#. 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 ""
+
+#. 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 ""
+
+#. Default: "View new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/published_comment_template.pt:8
+msgid "publishedmail_new_comment_link"
+msgstr "Voir le nouveau commentaire ajouté à la page suivante \" ${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 "Changements à la configuration qPloneComments sauvegardés"
+
+#. 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 ""
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr "Équipe de soutien informatique de ${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 "Aucun nouveau commentaire"
+
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/it/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/it/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/it/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,229 @@
+# 
+# Yuri Carrer
+# yurj@alfa.it
+msgid ""
+msgstr ""
+"Project-Id-Version: qPloneComments\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2007-04-19 20:21-0600\n"
+"Last-Translator: Yuri Carrer <yurj@alfa.it>\n"
+"Language-Team: Al ritmo del tam tam\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"Language-Code: it\n"
+"Language-Name: Italianol\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\n"
+"X-Is-Fallback-For: it-ch it-it\n"
+
+#: ./skins/qplonecomments/discussion_publish_comment.py:38
+msgid "Comment published."
+msgstr "Commento pubblicato."
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Comment(s) deleted."
+msgstr "Commento(i) cancellati"
+
+#: ./skins/qplonecomments/prefs_recent_comments_publish.cpy:26
+msgid "Comment(s) published."
+msgstr "Commento(i) pubblicati"
+
+#: ./skins/qplonecomments/discussion_reply.cpy:96
+msgid "Currently, all comments require approval before being published. Please check back later."
+msgstr "Tutti i commenti richiedono un'approvazione prima di essere pubblicati. Per favore controlla più tardi."
+
+#: ./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 "Per favore inserisci il tuo nome."
+
+#: ./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 "Non è possibile risponderere a un commento che non . stato approvato. Per favore aspetta che venga approvato prima di rispondere."
+
+#: ./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 "Per favore modera il commento inserito nella pagina \" ${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 "Commenti recenti"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "Configurazione di 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 "Per favore inserisci il tuo nome."
+
+#. 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 "Puoi cambiare i messaggi di notifica personalizzando i template 'approve_comment_template' e 'published_comment_template' nello skin qplonecomments."
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Permetti commenti anonimi."
+
+#. Default: "Recent comments moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:26
+msgid "label_comments"
+msgstr "Modera i commenti recenti"
+
+#. Default: "Configure qPloneComments"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:20
+msgid "label_configure"
+msgstr "Configura 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 "Notifica all' administratore delle discusioni quando viene inserito un nuovo commento."
+
+#. 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 "Abilita la moderazione."
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Notificare al proprietario del documento quando si ricevono nuovi commenti."
+
+#. 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 "Prefisso del messaggio (nome dell' organizzazione)"
+
+#. 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 "Lista dei commenti recenti"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "Opzioni di configurazione di qPloneComments"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Commento"
+
+#. 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 ""
+
+#. 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 ""
+
+#. Default: "View new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/published_comment_template.pt:8
+msgid "publishedmail_new_comment_link"
+msgstr "Visualizza il nuovo commento inserito nella pagina \" ${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 "Cambiamenti della configurazione salvati."
+
+#. 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 ""
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr "Team di supporto ${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 "Nessun nuovo commento."
+
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/nl/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/nl/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/nl/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,228 @@
+# Dutch translation for qPloneComments
+# Kees Hink <hink@gw20e.com>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2006-12-29 11:01+0100\n"
+"Last-Translator: Kees Hink\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: nl\n"
+"Language-Name: Nederlands\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\n"
+"X-Is-Fallback-For: nl-nl nl-be\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 "Verbeter de aangegeven fouten."
+
+#: ./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 "Bekijk alstublieft het commentaar op de volgende pagina: \" ${title} \" ${link} "
+
+#. Default: "Enable Manager moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:129
+msgid "enable_manager_moderation"
+msgstr "Beoordelen door beheerder aanzetten"
+
+#. Default: "Recent comments"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:39
+msgid "heading_comments"
+msgstr "Recente opmerkingen"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "Plone Opmerkingen instellen"
+
+#. Default: "The email address where comment notifications will be sent"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:142
+msgid "help_discussion-manager_email"
+msgstr "Het e-mailadres waarheen kennisgevingen van opmerkingen worden verstuurd"
+
+#. Default: "Enter your e-mail address."
+#: ./skins/qplonecomments/discussion_reply_form.cpt:133
+msgid "help_email"
+msgstr "Vul uw e-mailadres in."
+
+#. Default: "Tell us your name."
+#: ./skins/qplonecomments/discussion_reply_form.cpt:103
+msgid "help_name"
+msgstr "Vertel ons hoe u heet."
+
+#. 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 "U kunt de e-mails die als kennisgeving worden verstuurd aanpassen door de templates 'approve_comment_template' en 'published_comment_template' te wijzigen in de qplonecomments skin."
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Anonieme opmerkingen toestaan."
+
+#. Default: "Recent comments moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:26
+msgid "label_comments"
+msgstr "Beoordelen nieuwste opmerkingen"
+
+#. Default: "Configure qPloneComments"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:20
+msgid "label_configure"
+msgstr "qPloneComments instellen"
+
+#. Default: "Send comments notification emails to:"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:140
+msgid "label_discussion-manager_email"
+msgstr "Kennisgevingen over opmerkingen sturen aan:"
+
+#. Default: "Notify Discussion Manager of new comments."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:221
+msgid "label_enable_approve_notification"
+msgstr "Discussiebeheerder inlichten over nieuwe opmerkingen."
+
+#. Default: "Notify Commenter when comment is published."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:240
+msgid "label_enable_approve_user_notification"
+msgstr "Inzender berichten wanneer opmerking is gepubliceerd."
+
+#. Default: "Enable comment moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:73
+msgid "label_enable_moderation"
+msgstr "Beoordelen van opmerkingen aanzetten."
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Kennisgeving van nieuwe opmerking versturen aan eigenaar van document."
+
+#. Default: "Enable Commentator notification on comment was rejected."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:259
+msgid "label_enable_reject_notification"
+msgstr "Inzender berichten van geweigerde opmerking."
+
+#. Default: "Notify Commenter when their comment receives a reply"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:279
+msgid "label_enable_reply_notification"
+msgstr "Inzenders berichten dat er een reactie is op hun opmerking."
+
+#. Default: "Email subject prefix (organization name)"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:163
+msgid "label_organization_name"
+msgstr "Voorvoegsel in onderwerp van email (naam van organisatie)"
+
+#. Default: "Require email from Anonymous commenters"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:93
+msgid "label_require_anonym_email"
+msgstr "Anonieme inzenders moeten hun e-mailadres opgeven."
+
+#. Default: "Email Notification Options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:185
+msgid "legend_qpc_notification_legend"
+msgstr "Instellingen voor kennisgevingen per e-mail."
+
+#. Default: "List of recent comments"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:62
+msgid "legend_recent_comments"
+msgstr "Lijst van de nieuwste opmerkingen"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "Opties voor qPloneComments"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Opmerking"
+
+#. 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 "Uw opmerking op '${url}' is gepubliceerd. Ga naar ${url} om de opmerking te bekijken."
+
+#. 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 "Uw opmerking op '${url}' is niet goedgekeurd. Stuur vragen of opmerkingen hierover aan ${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 "Bekijk de nieuwe opmerking bij \"${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 ""
+
+#. 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 "Iemand heeft gereageerd op uw opmerking '${url}' bij '${here_url}'. Ga naar '${url}' om de reactie te lezen."
+
+#. 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 "Opmerkingen moeten worden goedgekeurd voordat reacties geaccepteerd worden."
+
+#. Default: "No new comments."
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:49
+msgid "text_no_new_comments"
+msgstr "Geen nieuwe opmerkingen."
+
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/plonecomments.pot
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/plonecomments.pot	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/plonecomments.pot	(revision 892)
@@ -0,0 +1,228 @@
+# --- PLEASE EDIT THE LINES BELOW CORRECTLY ---
+# SOME DESCRIPTIVE TITLE.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: en\n"
+"Language-Name: English\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. Default: "View new comment added to the following page \"${title}\": ${link}"
+#: ./skins/qplonecomments/published_comment_template.pt:8
+msgid "publishedmail_new_comment_link"
+msgstr ""
+
+#. 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 ""
+
+#. 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 ""
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr ""
+
+#. 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/branches/jcbrand-abuseFeature/locales/pt-br/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/pt-br/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/pt-br/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,226 @@
+# Érico Andrei <erico@simplesconsultoria.com.br>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: qPloneComments\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2008-07-18 03:51-0300\n"
+"Last-Translator: Érico Andrei <erico@simplesconsultoria.com.br>\n"
+"Language-Team: Simples Consultoria\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: pt-br\n"
+"Language-Name: Português do Brasil\n"
+"Preferred-Encodings: utf-8 latin1\n"
+"Domain: plonecomments\n"
+
+#: ./skins/qplonecomments/discussion_publish_comment.py:38
+msgid "Comment published."
+msgstr "Comentário publicado"
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Comment(s) deleted."
+msgstr "Comentário(s) apagado(s)"
+
+#: ./skins/qplonecomments/prefs_recent_comments_publish.cpy:26
+msgid "Comment(s) published."
+msgstr "Comentário(s) publicado(s)"
+
+#: ./skins/qplonecomments/discussion_reply.cpy:96
+msgid "Currently, all comments require approval before being published. Please check back later."
+msgstr "Os comentários necessitam de aprovação pr�via a sua publicação. Por favor, acesse novamente mais tarde para verificar sua publicação."
+
+#: ./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 "Por favor, informe seu nome."
+
+#: ./skins/qplonecomments/prefs_recent_comments_delete.cpy:35
+msgid "Please select items to be processed."
+msgstr "Por favor, selecione os itens a serem processados."
+
+#: ./skins/qplonecomments/validate_setup.vpy:23
+msgid "Please submit a valid e-mail address."
+msgstr "Por favor, informe um endereço de e-mail válido."
+
+#: ./skins/qplonecomments/validate_talkback.vpy:32
+msgid "Please submit email."
+msgstr "Por favor, preencha seu email"
+
+#: ./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 "Responder ainda não é permitido porque a resposta é para um comentário ainda sem aprovação. Aguarde o comentário original ser aprovado antes de responder a ele."
+
+#: ./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 "Por favor avalie o novo comentário adicionado à página \" ${title} \": ${link}"
+
+#. Default: "Enable Manager moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:129
+msgid "enable_manager_moderation"
+msgstr "Habilitar a moderação por um editor."
+
+#. Default: "Recent comments"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:39
+msgid "heading_comments"
+msgstr "Comentários recentes"
+
+#. Default: "Plone Comments Setup"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:37
+msgid "heading_comments_setup"
+msgstr "Configurações do 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 "Endereço de e-mail que receberá as notificações sobre novos comentários."
+
+#. 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 "Por favor, informe-nos seu nome."
+
+#. 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 "Você pode modificar os templates de e-mail de notificação editando os arquivos 'approve_comment_template' e 'published_comment_template' do layer qplonecomments (na pasta de skins)."
+
+#. Default: "Allow comments from Anonymous users"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:113
+msgid "label_anonymous_commenting"
+msgstr "Permitir comentários anônimos"
+
+#. Default: "Recent comments moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:26
+msgid "label_comments"
+msgstr "Moderação de comentários recentes"
+
+#. Default: "Configure qPloneComments"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:20
+msgid "label_configure"
+msgstr "Configurar o qPloneComments"
+
+#. Default: "Send comments notification emails to:"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:140
+msgid "label_discussion-manager_email"
+msgstr "Enviar a notificação de novos comentários para:"
+
+#. Default: "Notify Discussion Manager of new comments."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:221
+msgid "label_enable_approve_notification"
+msgstr "Habilitar a notificação de novos comentários ao Editor de Participações."
+
+#. Default: "Notify Commenter when comment is published."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:240
+msgid "label_enable_approve_user_notification"
+msgstr "Avisar ao usuário que seu comentário foi aprovado."
+
+#. Default: "Enable comment moderation"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:73
+msgid "label_enable_moderation"
+msgstr "Habilitar a moderação."
+
+#. Default: "Notify Document Owner of new comments on their documents."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:202
+msgid "label_enable_published_notification"
+msgstr "Permitir o envio de notificações ao dono do documento na inclusão de novos comentários."
+
+#. Default: "Enable Commentator notification on comment was rejected."
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:259
+msgid "label_enable_reject_notification"
+msgstr "Avisar ao usuário que seu comentário foi rejeitado."
+
+#. Default: "Notify Commenter when their comment receives a reply"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:279
+msgid "label_enable_reply_notification"
+msgstr "Avisar ao usuário que seu comentário recebeu uma resposta."
+
+#. Default: "Email subject prefix (organization name)"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:163
+msgid "label_organization_name"
+msgstr "Prefixo do assunto do e-mail (nome do site)"
+
+#. Default: "Require email from Anonymous commenters"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:93
+msgid "label_require_anonym_email"
+msgstr "Requerer e-mail de usuários anônimos."
+
+#. Default: "Email Notification Options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:185
+msgid "legend_qpc_notification_legend"
+msgstr "Configurar notificações"
+
+#. Default: "List of recent comments"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:62
+msgid "legend_recent_comments"
+msgstr "Lista de comentários recentes"
+
+#. Default: "qPloneComments setup options"
+#: ./skins/qplonecomments/prefs_comments_setup_form.cpt:56
+msgid "legend_setup_options"
+msgstr "Configuração das opções do qPloneComments"
+
+#. Default: "Comment"
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:80
+msgid "listingheader_comment"
+msgstr "Comentário"
+
+#. 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 "Seu comentário em \'${url}\' foi publicado. Para acessá-lo, visite: ${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 "Seu comentário em \'${url}\' não foi aceito. Por favor direcione suas dúvidas e questionamentos para ${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 "Visualizar comentários adicionados à página \" ${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 "Configurações do qPloneComments foram salvas."
+
+#. 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 "Alguém respondeu ao seu comentário \'${url}\' em \'${here_url}\'. Para ler esta resposta, visite: \'${url}\'."
+
+#. Default: "${organization_name} Support Team"
+#: ./skins/qplonecomments/reply_notify_template.pt:15
+msgid "signature"
+msgstr "Equipe de suporte ${organization_name}"
+
+#. Default: "Comment must be approved before replies to comment accepted."
+#: ./browser/comments.pt:106
+msgid "text_no_add_reply"
+msgstr "O comentário deve ser aprovado antes de poder receber respostas."
+
+#. Default: "No new comments."
+#: ./skins/qplonecomments/prefs_recent_comments_form.cpt:49
+msgid "text_no_new_comments"
+msgstr "Não há novos comentários."
+
Index: /qPloneComments/branches/jcbrand-abuseFeature/locales/uk/LC_MESSAGES/plonecomments.po
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/locales/uk/LC_MESSAGES/plonecomments.po	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/locales/uk/LC_MESSAGES/plonecomments.po	(revision 892)
@@ -0,0 +1,232 @@
+# Ukrainian translation for qPloneComments
+# Copyright (c) 2006 Quintagroup
+# This file is distributed under the same license as the qPloneComments product.
+# Myroslav Opyr <myroslav@quintagroup.com>, 2006.
+# 
+msgid ""
+msgstr ""
+"Project-Id-Version: qPloneComments\n"
+"POT-Creation-Date: 2009-03-16 13:48+0000\n"
+"PO-Revision-Date: 2008-09-22 10:55+0300\n"
+"Last-Translator: Olha <olha.pelishok@gmail.com>\n"
+"Language-Team: Ukrainian <uk@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"Language-Code: uk\n"
+"Language-Name: Українська\n"
+"Preferred-Encodings: utf-8 koi8-u windows-1251\n"
+"Domain: plonecomments\n"
+"Report-Msgid-Bugs-To: Quintagroup Support <support@quintagroup.com>\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/branches/jcbrand-abuseFeature/overrides.zcml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/overrides.zcml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/overrides.zcml	(revision 892)
@@ -0,0 +1,17 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    i18n_domain="plonecomments">
+
+  <browser:viewlet
+      name="plone.comments"
+      for="Products.CMFCore.interfaces.IContentish"
+      manager="plone.app.layout.viewlets.interfaces.IBelowContent"
+      view="plone.app.layout.globals.interfaces.IViewView"
+      class=".browser.comments.CommentsViewlet"
+      layer=".interfaces.IPloneCommentsLayer"
+      permission="zope2.View"
+      />
+
+</configure>
Index: /qPloneComments/branches/jcbrand-abuseFeature/patch.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/patch.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/patch.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/profiles/default/actionicons.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/actionicons.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/actionicons.xml	(revision 892)
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<action-icons  xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+   i18n:domain="plonecomments">
+   <action-icon category="controlpanel"
+                action_id="prefs_comments_setup_form"
+                title="qPloneComments" priority="0"
+                icon_expr="discussionitem_icon.gif" i18n:attributes="title" />
+</action-icons>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/browserlayer.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/browserlayer.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/browserlayer.xml	(revision 892)
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<layers
+   xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+   i18n:domain="plonecomments">
+   <layer name="qPloneComments"
+          interface="Products.qPloneComments.interfaces.IPloneCommentsLayer" />
+</layers>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/controlpanel.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/controlpanel.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/controlpanel.xml	(revision 892)
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<object name="portal_controlpanel"
+   xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+   i18n:domain="plonecomments">
+   <configlet title="qPloneComments" action_id="prefs_comments_setup_form"
+      appId="qPloneComments" category="Products" condition_expr=""
+      url_expr="string:${portal_url}/prefs_comments_setup_form"
+      visible="True" i18n:attributes="title">
+      <permission>Manage portal</permission>
+   </configlet>
+</object>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/import_steps.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/import_steps.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/import_steps.xml	(revision 892)
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<import-steps  xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+   i18n:domain="plonecomments">
+   <import-step
+      id="qPloneComments.various"
+      version="20080626-01"
+      handler="Products.qPloneComments.setuphandlers.setupVarious"
+      title="qPloneComments">
+    Run the extra steps to install the qPloneComments product.
+   </import-step>
+</import-steps>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/metadata.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/metadata.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/metadata.xml	(revision 892)
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<metadata>
+  <version>3.2.1</version>
+</metadata>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/propertiestool.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/propertiestool.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/propertiestool.xml	(revision 892)
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<object name="portal_properties"
+    xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+    i18n:domain="plonecomments">
+ <object name="qPloneComments" meta_type="Plone Property Sheet">
+  <property name="title">qPloneComments properties</property>
+  <property name="enable_approve_user_notification" type="boolean">True</property>
+  <property name="enable_reply_user_notification" type="boolean">True</property>
+  <property name="enable_rejected_user_notification" type="boolean">True</property>
+  <property name="enable_moderation" type="boolean">True</property>
+  <property name="require_email" type="boolean">False</property>
+  <property name="enable_anonymous_commenting" type="boolean">True</property>
+  <property name="enable_published_notification" type="boolean">True</property>
+  <property name="enable_approve_notification" type="boolean">True</property>
+  <property name="email_discussion_manager" type="string"></property>
+  <property name="email_subject_prefix" type="string"></property>
+ </object>
+</object>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/qPloneComments-default.txt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/qPloneComments-default.txt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/qPloneComments-default.txt	(revision 892)
@@ -0,0 +1,1 @@
+Yes, I do want this import step to run.
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/rolemap.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/rolemap.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/rolemap.xml	(revision 892)
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<rolemap>
+  <roles>
+    <role name="DiscussionManager"/>
+  </roles>
+  <permissions>
+    <permission name="Reply to item" acquire="True">
+      <role name="Anonymous"/>
+      <role name="Manager"/>
+      <role name="Member"/>
+    </permission>
+    <permission name="Moderate Discussion" acquire="True">
+      <role name="Manager"/>
+      <role name="DiscussionManager"/>
+    </permission>
+  </permissions>
+</rolemap>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/skins.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/skins.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/default/skins.xml	(revision 892)
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+ <object name="qplonecomments" meta_type="Filesystem Directory View"
+    directory="Products.qPloneComments:skins/qplonecomments" />
+ <skin-path name="*">
+  <layer name="qplonecomments" insert-after="custom" />
+ </skin-path>
+</object>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/import_steps.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/import_steps.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/import_steps.xml	(revision 892)
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<import-steps xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+   i18n:domain="plonecomments">
+ <import-step id="qPloneComments-uninstall-configlet" version="20090219-01"
+              handler="Products.qPloneComments.setuphandlers.removeConfiglet"
+              title="Remove qPloneComments's configlet">Removes the qPloneComments configlet</import-step>
+</import-steps>
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/qPloneComments-uninstall.txt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/qPloneComments-uninstall.txt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/qPloneComments-uninstall.txt	(revision 892)
@@ -0,0 +1,1 @@
+This file is used as a marker in setuphandlers.py.
Index: /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/skins.xml
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/skins.xml	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/profiles/uninstall/skins.xml	(revision 892)
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<object name="portal_skins"  xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+   i18n:domain="plonecomments">
+ <object name="qplonecomments" meta_type="Filesystem Directory View"
+   directory="Products.qPloneComments:skins/qplonecomments" remove="True" />
+ <skin-path name="*">
+   <layer name="qplonecomments" remove="True" />
+ </skin-path>
+</object>
Index: /qPloneComments/branches/jcbrand-abuseFeature/setuphandlers.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/setuphandlers.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/setuphandlers.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/approve_comment_template.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/approve_comment_template.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/approve_comment_template.pt	(revision 892)
@@ -0,0 +1,20 @@
+<div i18n:domain="plonecomments"
+     tal:omit-tag=""
+     tal:define="charset here/portal_properties/site_properties/default_charset|string:utf-8;
+                 dummy python:request.RESPONSE.setHeader('Content-Type', 'text/html;;charset=%s' % charset);
+                 organization_name options/organization_name">
+
+<tal:new_comment define="obj nocall:options/obj"
+       i18n:translate="approvemail_new_comment_link">
+       Please review new comment added to the following page "<tal:x replace="obj/Title" i18n:name="title"/>":
+       <tal:link replace="obj/absolute_url" i18n:name="link"/>
+</tal:new_comment>
+
+--
+<tal:signature i18n:translate="signature">
+    <tal:organization_name condition="organization_name"
+                           replace="string:$organization_name"
+                           i18n:name="organization_name"/>
+Support Team
+</tal:signature>
+</div>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/deleteDiscussion.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/deleteDiscussion.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/deleteDiscussion.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_publish_comment.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_publish_comment.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_publish_comment.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply.cpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply.cpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply.cpy	(revision 892)
@@ -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&amp;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'
+        '&amp;__ac_password=%s'
+        '&amp;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/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply.cpy.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply.cpy.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply.cpy.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply_form.cpt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply_form.cpt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply_form.cpt	(revision 892)
@@ -0,0 +1,259 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      metal:use-macro="here/main_template/macros/master"
+      i18n:domain="plone">
+<body>
+
+<metal:block fill-slot="top_slot"
+             tal:define="dummy python:request.set('disable_border',1)" />
+
+<metal:block fill-slot="sub" />
+
+<div metal:fill-slot="main"
+     tal:define="errors options/state/getErrors;
+                 thread python:putils.getDiscussionThread(here);
+                 discussable python:thread[0];
+                 replies python:thread[1:];
+                 props_sheet here/portal_properties/qPloneComments;
+                 require_email python:props_sheet.getProperty('require_email')">
+
+    <tal:discussable define="template python:getattr(here, discussable.getLayout(), None);
+                             macros template/macros | nothing;
+                             main_macro macros/main | nothing;
+                             showDiscussable python:main_macro;">
+
+        <div class="comment"
+             tal:condition="showDiscussable"
+             tal:define="here python:discussable">
+            <div metal:use-macro="main_macro"/>
+        </div>
+    </tal:discussable>
+
+    <tal:thread tal:repeat="reply replies">
+        <tal:block tal:define="level repeat/reply/index">
+            <div class="comment"
+                 tal:attributes="style python:'margin-left:'+str((int(level)+1)*2)+'em'">
+               <h3>
+                   <tal:block replace="structure portal/discussionitem_icon.gif"/>
+                   <span tal:replace="reply/pretty_title_or_id">Comment title</span>
+               </h3>
+               <div class="documentByLine"
+                    tal:define="anonymous_creator python:reply.Creator()=='Anonymous User'">
+                   <span i18n:translate="label_comment_by">Posted by</span>
+                   <span tal:content="reply/Creator"
+                         tal:condition="not:anonymous_creator">Poster Name</span>
+                   <span i18n:translate="label_anonymous_user"
+                         tal:condition="anonymous_creator">Anonymous User</span>
+                   <span i18n:translate="label_commented_at">at</span>
+                   <span tal:replace="python:toLocalizedTime(reply.ModificationDate(),
+                                      long_format=1)">8/23/2001 12:40:44 PM</span>
+               </div>
+               <div class="commentBody"
+                    tal:content="structure reply/CookedBody">
+                    This is the body text of the comment.
+               </div>
+           </div>
+
+        </tal:block>
+    </tal:thread>
+
+    <a name="discussion"></a>
+
+    <form name="edit_form"
+          class="enableAutoFocus"
+          method="post"
+          action="talkback"
+          tal:attributes="action string:$here_url/$template_id"
+          tal:define="isForAnonymous python:here.portal_properties.qPloneComments.getProperty('enable_anonymous_commenting', 0)">
+
+        <fieldset>
+
+            <legend i18n:translate="legend_add_comment">Add comment</legend>
+
+            <p i18n:translate="description_add_comment">
+                You can add a comment by filling out the form below. Plain text
+                formatting.
+            </p>
+
+            <input type="hidden" name="Creator" value=""
+                   tal:attributes="value member/getUserName"
+                   tal:condition="not:isForAnonymous"/>
+
+            <div tal:condition="isForAnonymous">
+
+                <input type="hidden"
+                       name="Creator"
+                       tal:condition="not:isAnon"
+                       tal:attributes="value member/getUserName" />
+
+                 <div class="field"
+                     tal:condition="isAnon"
+                     tal:define="error errors/Creator|nothing;"
+                     tal:attributes="class python:test(error, 'field error', 'field');">
+
+                    <label for="username" i18n:translate="label_name">Name</label>
+
+                    <span class="fieldRequired" title="Required"
+                          tal:condition="isAnon"
+                          i18n:attributes="title"
+                          i18n:translate="label_required">(Required)</span>
+
+                    <div class="formHelp" i18n:translate="help_name"
+                         i18n:domain="plonecomments">
+                        Tell us your name.
+                    </div>
+
+                    <div tal:content="error">Validation error output</div>
+
+                    <div>
+                        <input name="Creator"
+                               id="username"
+                               value="" alt="Submitter" title="Name"
+                               size="40"
+                               i18n:attributes="alt; title"
+                               tal:attributes="value request/Creator | nothing" />
+                    </div>
+
+                </div>
+
+                <div class="field"
+                     tal:define="error errors/email|nothing;"
+                     tal:attributes="class python:test(error, 'field error', 'field')"
+                     tal:condition="python:require_email and isAnon">
+
+                    <label for="email" i18n:translate="label_email">E-Mail</label>
+
+                    <span class="fieldRequired" title="Required"
+                          i18n:attributes="title title_required;"
+                          i18n:translate="label_required">(Required)
+                    </span>
+
+                    <div class="formHelp" i18n:translate="help_email"
+                         i18n:domain="plonecomments">
+                        Enter your e-mail address.
+                    </div>
+
+                    <div tal:content="error">Validation error output</div>
+
+                    <input name="email" value="" size="40" />
+
+                </div>
+
+            </div>
+
+            <div tal:condition="python:(not isForAnonymous) and isAnon">
+                <dl class="portalMessage warning">
+                    <dt i18n:translate="">
+                        Info
+                    </dt>
+                    <dd i18n:translate="legend_note_reply_anonymous">
+                        You are not logged in. You may optionally enter your
+                        username and password below. If you don't enter anything,
+                        this comment will be posted as 'Anonymous User'.
+                    </dd>
+                </dl>
+
+                <div class="field">
+
+                    <label for="username" i18n:translate="label_name">Name</label>
+
+                    <input name="username"
+                           id="username"
+                           value="" alt="Username" title="Name"
+                           size="40"
+                           i18n:attributes="title label_name; alt label_username;" />
+
+                </div>
+
+                <div class="field">
+
+                    <label for="password" i18n:translate="label_password">Password</label>
+
+                    <input type="password"
+                           id="password"
+                           name="password"
+                           value="" alt="Password" title="Password"
+                           size="40"
+                           i18n:attributes="title label_password; alt label_password;" />
+
+                </div>
+        </div>
+
+        <div class="field"
+        tal:define="error errors/subject|nothing;"
+        tal:attributes="class python:test(error, 'field error', 'field')">
+
+                <label for="subject" i18n:translate="label_subject">Subject</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+            <div tal:content="error">Validation error output</div>
+
+                <input name="subject"
+                       id="subject"
+                       value=""
+                       size="40"
+                       tal:attributes="value request/subject|request/title_override|nothing;" />
+
+            </div>
+
+        <div class="field"
+        tal:define="error errors/body_text|nothing;"
+        tal:attributes="class python:test(error, 'field error', 'field')">
+
+          <label for="body_text" i18n:translate="label_comment">Comment</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+            <div tal:content="error">Validation error output</div>
+
+                <textarea name="body_text"
+                          id="body_text"
+                          cols="40"
+                          rows="8"
+                          tal:content="request/body_text|request/text_override | nothing"
+                        ></textarea>
+
+            </div>
+
+             <p class="formHelp" tal:condition="python:require_email and isAnon">
+                Want to add a picture to your comments here on 
+                <tal:site content="portal_title|default">the Site</tal:site>? <br />
+                Upload a picture at <a href="http://en.gravatar.com">Gravatar</a> to make it happen.
+             </p>
+
+             <div tal:condition="nocall:here/captcha_widget|nothing"
+                  tal:omit-tag="">
+                <div metal:use-macro="here/captcha_widget/macros/captcha" />
+             </div>
+
+            <div class="formControls">
+
+                <input type="hidden" name="form.submitted" value="1" />
+
+                <input type="hidden" name="require_email:boolean" value=""
+                       tal:attributes="value python:require_email and isAnon" />
+
+                <input class="context"
+                       type="submit"
+                       value="Save"
+                       name="form.button.Save"
+                       i18n:attributes="value label_save;"
+                       tal:attributes="name string:discussion_reply:method;" />
+
+            </div>
+
+        </fieldset>
+
+    </form>
+
+</div>
+
+</body>
+</html>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply_form.cpt.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply_form.cpt.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/discussion_reply_form.cpt.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/notify_comment_template.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/notify_comment_template.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/notify_comment_template.pt	(revision 892)
@@ -0,0 +1,22 @@
+<div i18n:domain="plonecomments" 
+     tal:omit-tag=""
+     tal:define="charset here/portal_properties/site_properties/default_charset|string:utf-8;
+                 dummy python:request.RESPONSE.setHeader('Content-Type', 'text/html;;charset=%s' % charset);
+                 organization_name options/organization_name;
+                 obj nocall:options/obj">
+
+<tal:x replace="options/name"/>,
+<tal:notify define="obj nocall:options/obj" 
+       i18n:translate="notifycomment_link">
+       Your comment on '<tal:x replace="obj/absolute_url" i18n:name="url"/>' has been
+published. To see your comment, go to <tal:x replace="obj/absolute_url" i18n:name="url"/>
+</tal:notify>
+
+--
+<tal:signature i18n:translate="signature">
+    <tal:organization_name condition="organization_name"
+                           replace="string:$organization_name"
+                           i18n:name="organization_name"/>
+Support Team
+</tal:signature>
+</div>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup.cpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup.cpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup.cpy	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup.cpy.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup.cpy.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup.cpy.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup_form.cpt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup_form.cpt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup_form.cpt	(revision 892)
@@ -0,0 +1,302 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xml:lang="en"
+      lang="en"
+      i18n:domain="plonecomments"
+      metal:use-macro="here/prefs_main_template/macros/master">
+        <!--      metal:use-macro="here/main_template/macros/master" -->
+<body>
+
+<div metal:fill-slot="prefs_configlet_main"
+     tal:define="errors python:request.get('controller_state', None).getErrors();">
+
+    <div metal:define-macro="main">
+    <div class="configlet">
+    <div class="documentEditable"
+         tal:condition="python:checkPermission('Moderate Discussion', here)">
+
+    <!-- simulating views on the configure/comments pages until we have real objects. -->
+    <ul class="contentViews">
+        <li class="selected">
+        <a href=""
+        tal:attributes="href string:${template/id}"
+        i18n:translate="label_configure">Configure qPloneComments</a>
+        </li>
+
+        <li class="">
+        <a href=""
+        tal:attributes="href string:prefs_recent_comments_form"
+        i18n:translate="label_comments">Recent comments moderation</a>
+        </li>
+    </ul>
+
+    <div class="contentActions">
+    &nbsp;
+    </div>
+    <!-- End of tabs -->
+    <div class="documentContent" metal:define-slot="prefs_content">
+        <h1 i18n:translate="heading_comments_setup">Plone Comments Setup</h1>
+
+        <a href=""
+           class="link-parent"
+           tal:attributes="href string: $portal_url/plone_control_panel"
+           i18n:translate="label_up_to_plone_setup"
+           i18n:domain="plone">
+           Up to Plone Setup
+        </a>
+
+        <form name="edit_form"
+              action="href"
+              method="post"
+              enctype="multipart/form-data"
+              tal:define ="pp here/portal_properties;
+                           props_sheet python:getattr(pp, 'qPloneComments');"
+              tal:attributes="action string:${here/getId}/${template/getId}">
+
+            <fieldset>
+                <legend i18n:translate="legend_setup_options">qPloneComments setup options</legend>
+
+                <div class="field"
+                     tal:define="property_id string:enable_moderation;
+                                 isEnabledModeration python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isEnabledModeration"
+                           id="isEnabledModeration"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isEnabledModeration;
+                                           checked python:test(isEnabledModeration, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_enable_moderation" 
+                           for="isEnabledModeration">Enable comment moderation</label>
+
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:require_email;
+                                 requireEmail python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="requireEmail"
+                           id="requireEmail"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value requireEmail;
+                                           checked python:test(requireEmail, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_require_anonym_email" 
+                           for="requireEmail">Require email from Anonymous commenters</label>
+
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:enable_anonymous_commenting;
+                                 isEnabledAnonymousCommenting python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isEnabledAnonymousCommenting"
+                           id="isEnabledAnonymousCommenting"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isEnabledAnonymousCommenting;
+                                           checked python:test(isEnabledAnonymousCommenting, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_anonymous_commenting" 
+                           for="isEnabledAnonymousCommenting">Allow comments from Anonymous users</label>
+                </div>
+
+		<div class="field"
+		     tal:define="isEnabledManagerModeration python:checkPermission('Moderate Discussion', here);">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="EnableManagerModeration"
+                           id="EnableManagerModeration"
+                           tabindex =""
+                           tal:attributes="value isEnabledManagerModeration;
+                                           checked python:test(isEnabledManagerModeration, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="enable_manager_moderation"
+                           for="isEnableApproveNotification">Enable Manager moderation</label>
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:email_discussion_manager;
+                                 email python:props_sheet.getProperty(property_id, '');
+                                 error python:test(errors.has_key(property_id), errors.get(property_id), None);"
+                     tal:attributes="class python:test(error, 'field error', 'field')"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <label i18n:translate="label_discussion-manager_email">Send comments notification emails to:</label>
+
+                    <div class="formHelp" id="smtp_server_help"
+                         i18n:translate="help_discussion-manager_email">
+                        The email address where comment notifications will be sent
+                    </div>
+
+                    <div tal:content="error">Validation error output</div>
+
+                    <input name="email_discussion-manager"
+                           value=""
+                           size="40"
+                           tabindex=""
+                           tal:attributes="name property_id;
+                                           value email;
+                                           tabindex tabindex/next;" />
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:email_subject_prefix;
+                                 organization_name python:props_sheet.getProperty(property_id, '');"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <label i18n:translate="label_organization_name">Email subject prefix (organization name)</label>
+
+                    <div class="formHelp" id="organization_name_help"
+                         i18n:translate="help_organization_name">
+                        You can change notification message templates by customizing 'approve_comment_template' and 'published_comment_template' in the qplonecomments skin.
+                    </div>
+
+                    <input name="organization_name"
+                           value=""
+                           size="40"
+                           tabindex=""
+                           tal:attributes="name property_id;
+                                           value organization_name;
+                                           tabindex tabindex/next;" />
+                </div>
+
+            </fieldset>
+
+
+
+
+            <fieldset>
+                <legend i18n:translate="legend_qpc_notification_legend">Email Notification Options</legend>
+
+                <div class="field"
+                     tal:define="property_id string:enable_published_notification;
+                                 isEnabledPublishedNotification python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isEnabledPublishedNotification"
+                           id="isEnabledPublishedNotification"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isEnabledPublishedNotification;
+                                           checked python:test(isEnabledPublishedNotification, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_enable_published_notification" 
+                           for="isEnabledPublishedNotification">Notify Document Owner of new comments on their documents.</label>
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:enable_approve_notification;
+                                 isEnableApproveNotification python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isEnableApproveNotification"
+                           id="isEnableApproveNotification"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isEnableApproveNotification;
+                                           checked python:test(isEnableApproveNotification, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_enable_approve_notification"
+                           for="isEnableApproveNotification">Notify Discussion Manager of new comments.</label>
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:enable_approve_user_notification;
+                                 isEnableApproveUserNotification python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isEnableApproveUserNotification"
+                           id="isEnableApproveUserNotification"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isEnableApproveUserNotification;
+                                           checked python:test(isEnableApproveUserNotification, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_enable_approve_user_notification"
+                           for="isEnableApproveUserNotification">Notify Commenter when comment is published.</label>
+                </div>
+
+                <div class="field"
+                     tal:define="property_id string:enable_rejected_user_notification;
+                                 isRejectionNotificationEnabled python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isRejectionNotificationEnabled"
+                           id="isRejectionNotificationEnabled"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isRejectionNotificationEnabled;
+                                           checked python:test(isRejectionNotificationEnabled, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_enable_reject_notification"
+                           for="isRejectionNotificationEnabled">Enable Commentator notification on comment was rejected.</label>
+                </div>
+
+
+                <div class="field"
+                     tal:define="property_id string:enable_reply_user_notification;
+                                 isReplyNotificationEnabled python:props_sheet.getProperty(property_id, False);"
+                     tal:condition="python:props_sheet.hasProperty(property_id)">
+
+                    <input type="checkbox" 
+                           class="noborder"
+                           name="isReplyNotificationEnabled"
+                           id="isReplyNotificationEnabled"
+                           tabindex =""
+                           tal:attributes="name property_id;
+                                           value isReplyNotificationEnabled;
+                                           checked python:test(isReplyNotificationEnabled, 'True', '');
+                                           tabindex tabindex/next;"/>
+
+                    <label i18n:translate="label_enable_reply_notification"
+                           for="isReplyNotificationEnabled">Notify Commenter when their comment receives a reply</label>
+                </div>
+            </fieldset>
+
+            <div class="formControls">
+                <input type="hidden" name="form.submitted" value="1" />
+                <input class="context"
+                       tabindex=""
+                       type="submit"
+                       name="form.button.form_submit"
+                       value="Save"
+                       i18n:domain="plone"
+                       i18n:attributes="value"
+                       tal:attributes="tabindex tabindex/next;"/>
+            </div>
+        </form>
+    </div>
+    </div>
+    </div>
+    </div>
+</div>
+</body>
+</html>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup_form.cpt.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup_form.cpt.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_comments_setup_form.cpt.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_delete.cpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_delete.cpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_delete.cpy	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_delete.cpy.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_delete.cpy.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_delete.cpy.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_form.cpt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_form.cpt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_form.cpt	(revision 892)
@@ -0,0 +1,144 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xml:lang="en"
+      lang="en"
+      i18n:domain="plonecomments"
+      metal:use-macro="here/prefs_main_template/macros/master">
+        <!--      metal:use-macro="here/main_template/macros/master" -->
+<body>
+
+<div metal:fill-slot="prefs_configlet_main"
+     tal:define="errors python:request.get('controller_state', None).getErrors();">
+
+    <div metal:define-macro="main">
+    <div class="configlet">
+    <div class="documentEditable"
+         tal:condition="python:checkPermission('Moderate Discussion', here)">
+    <!-- simulating views on the configure/comments pages until we have real objects. -->
+    <ul class="contentViews">
+        <li class="">
+        <a href=""
+        tal:attributes="href string:prefs_comments_setup_form"
+        i18n:translate="label_configure">Configure qPloneComments</a>
+        </li>
+
+        <li class="selected">
+        <a href=""
+        tal:attributes="href string:${template/id}"
+        i18n:translate="label_comments">Recent comments moderation</a>
+        </li>
+    </ul>
+     
+    <div class="contentActions">
+    &nbsp;
+    </div>        
+    <!-- End of tabs -->
+
+    <div class="documentContent" metal:define-slot="prefs_content"
+         tal:define ="results python:here.portal_catalog.searchResults(portal_type='Discussion Item',review_state='private',sort_on='created',sort_order='reverse');">
+
+        <h1 i18n:translate="heading_comments">Recent comments</h1>
+
+        <a href=""
+           class="link-parent"
+           tal:attributes="href string: $portal_url/plone_control_panel"
+           i18n:translate="label_up_to_plone_setup"
+           i18n:domain="plone">
+           Up to Plone Setup
+        </a>
+        
+        <p tal:condition="not:results" i18n:translate="text_no_new_comments">No new comments.</p>
+
+        <div tal:condition="results"
+             tal:define="dummy python:results and results[0].getObject()"
+             tal:on-error="string:You must have DiscussionManager role to access this page">
+            
+            <form name="edit_form"
+                  action="href"
+                  method="post"
+                  enctype="multipart/form-data"
+                  tal:condition="results"
+                  tal:attributes="action string:${here/getId}/${template/getId}">
+                <fieldset>
+                    <legend i18n:translate="legend_recent_comments">List of recent comments</legend>
+
+                    <table><tbody>
+
+                      <tr>
+                      <th class="nosort">
+                          <input class="noborder"
+                                 type="checkbox"
+                                 src="select_all_icon.gif"
+                                 name="selectButton"
+                                 title="Select all items"
+                                 onClick="toggleSelect(this,'ids:list');"
+                                 tal:attributes="src string:$portal_url/select_all_icon.gif"
+                                 alt="Select all items"
+                                 i18n:attributes="title label_select_all_items; alt label_select_all_items;"
+                                 i18n:domain="plone"
+                      />
+                      </th>
+                      <th class="nosort">&nbsp;<tal:title i18n:translate="listingheader_comment"
+                          >Comment</tal:title>&nbsp;</th>
+                      </tr>
+
+                      <tal:comment tal:repeat="comment results">
+                         <tal:object tal:define="comm_url comment/getURL;
+                                                 oddrow repeat/comment/odd;">
+                             <tr class="" tal:attributes="class python:test(oddrow, 'even', 'odd')">
+                                <td><input value="0" type="checkbox"
+                                           name="ids:list" 
+                                           tal:attributes="value python:comment.getObject().getId()" /></td>
+                                <td> 
+                                   <dl>
+                                       <dt>
+                                         <a href="#" 
+                                            tal:attributes="href comment/getURL;" 
+                                            tal:content="comment/Title"/>
+                                       </dt>
+                                       <dd>
+                                          <span><em tal:content="string: ${comment/Creator}"> Creator</em></span>
+                                          <span tal:content="python: comment.getObject().text[:400]+'...'"> Description</span>
+                                       </dd>
+                                   </dl>
+                                </td>
+                             </tr>
+                         </tal:object>
+                      </tal:comment>
+
+                    </tbody></table>
+
+                    <div class="formControls"
+                               i18n:domain="plone">
+
+                        <input type="hidden" name="form.submitted" value="1" />
+
+                        <input class="context"
+                               tabindex=""
+                               type="submit"
+                               name="form.button.form_delete"
+                               value="Delete"
+                               i18n:attributes="value"
+                               tal:attributes="tabindex tabindex/next;"
+                               />
+
+                        <input class="context"
+                               tabindex=""
+                               type="submit"
+                               name="form.button.form_publish"
+                               value="Publish"
+                               i18n:attributes="value"
+                               tal:attributes="tabindex tabindex/next;"
+                               />
+
+
+                    </div>
+                </fieldset>
+            </form>
+        </div>
+    </div>
+    </div>
+    </div>
+    </div>
+    </div>
+</body>
+</html>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_form.cpt.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_form.cpt.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_form.cpt.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_publish.cpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_publish.cpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_publish.cpy	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_publish.cpy.metadata
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_publish.cpy.metadata	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/prefs_recent_comments_publish.cpy.metadata	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/published_comment_template.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/published_comment_template.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/published_comment_template.pt	(revision 892)
@@ -0,0 +1,18 @@
+<tal:mail i18n:domain="plonecomments"
+     tal:define="charset here/portal_properties/site_properties/default_charset|string:utf-8;
+                 dummy python:request.RESPONSE.setHeader('Content-Type', 'text/html;;charset=%s' % charset);
+                 organization_name options/organization_name">
+
+<tal:new_comment define="obj nocall:options/obj" i18n:translate="publishedmail_new_comment_link">
+View new comment added to the following page "<tal:x replace="obj/Title" i18n:name="title"/>":
+<tal:x replace="obj/absolute_url" i18n:name="link"/>
+</tal:new_comment>
+
+--
+<tal:signature i18n:translate="signature">
+    <tal:organization_name condition="organization_name"
+                           replace="string:$organization_name"
+                           i18n:name="organization_name"/>
+    Support Team
+</tal:signature>
+</tal:mail>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/rejected_comment_template.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/rejected_comment_template.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/rejected_comment_template.pt	(revision 892)
@@ -0,0 +1,24 @@
+<div i18n:domain="plonecomments"
+     tal:omit-tag=""
+     tal:define="charset here/portal_properties/site_properties/default_charset|string:utf-8;
+                 dummy python:request.RESPONSE.setHeader('Content-Type', 'text/html;;charset=%s' % charset);
+                 organization_name options/organization_name;
+                 obj nocall:options/obj">
+
+<tal:x replace="options/name"/>,
+
+<tal:notify define="obj nocall:options/obj" 
+       i18n:translate="notifyreject_link">
+       Your comment on '<tal:x replace="obj/absolute_url" i18n:name="url"/>' did not receive
+approval. Please direct any questions or concerns to <tal:x replace="options/mfrom" i18n:name="mfrom"/>.
+</tal:notify>
+
+--
+<tal:signature i18n:translate="signature"> 
+    <tal:organization_name condition="organization_name" 
+                           replace="string:$organization_name" 
+                           i18n:name="organization_name"/>
+    Support Team
+</tal:signature>
+
+</div>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/reply_notify_template.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/reply_notify_template.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/reply_notify_template.pt	(revision 892)
@@ -0,0 +1,21 @@
+<div i18n:domain="plonecomments"
+     tal:omit-tag=""
+     tal:define="charset here/portal_properties/site_properties/default_charset|string:utf-8;
+                 dummy python:request.RESPONSE.setHeader('Content-Type', 'text/html;;charset=%s' % charset);
+                 organization_name options/organization_name;
+                 obj nocall:options/obj">
+<tal:x replace="options/name"/>,
+<tal:notify define="obj nocall:options/obj" 
+       i18n:translate="replynotify_link">
+       Someone replied to your comment '<tal:x replace="obj/absolute_url" i18n:name="url"/>' on '<tal:x replace="here/absolute_url" i18n:name="here_url"/>'. To read the response, go to '<tal:x replace="obj/absolute_url" i18n:name="url"/>'.
+</tal:notify>
+
+--
+<tal:signature i18n:translate="signature"> 
+    <tal:organization_name condition="organization_name" 
+                           replace="string:$organization_name" 
+                           i18n:name="organization_name"/>
+    Support Team
+</tal:signature>
+
+</div>
Index: /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_reply.vpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_reply.vpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_reply.vpy	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_setup.vpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_setup.vpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_setup.vpy	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_talkback.vpy
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_talkback.vpy	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/validate_talkback.vpy	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/skins/qplonecomments/viewThreadsAtBottom.pt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/viewThreadsAtBottom.pt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/skins/qplonecomments/viewThreadsAtBottom.pt	(revision 892)
@@ -0,0 +1,124 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
+      i18n:domain="plone">
+
+<body>
+
+<div metal:define-macro="discussionView"
+     tal:omit-tag=""
+     tal:define="userHasReplyPermission python:checkPermission('Reply to item', here);
+                 portal_discussion portal/portal_discussion;
+                 isDiscussionAllowed python:portal_discussion.isDiscussionAllowedFor(here);
+                 ">
+                 
+    <tal:allowed condition="isViewTemplate" tal:define="replies python:here.getReplyReplies(here)">
+        <div class="discussion"
+             tal:condition="python:replies or (userHasReplyPermission and isDiscussionAllowed) or (isAnon and not userHasReplyPermission and isDiscussionAllowed)">
+            <form name="reply"
+                  action=""
+                  method="post"
+                  tal:condition="python:userHasReplyPermission and isDiscussionAllowed"
+                  tal:attributes="action string:$here_url/discussion_reply_form#discussion">
+    
+                  <input class="standalone"
+                         style="margin-bottom: 1.25em;"
+                         type="submit"
+                         value="Add Comment"
+                         i18n:attributes="value label_add_comment;"
+                         />
+            </form>
+            <form tal:condition="python:isAnon and not userHasReplyPermission and isDiscussionAllowed"
+                  tal:define="pss modules/Products/PythonScripts/standard"
+                  tal:attributes="action python:'%s/login_form?came_from=%s' %
+                                                (here.portal_url(),
+                                                pss.url_quote(request['URL']))">
+                <input class="standalone"
+                       style="margin-bottom: 1.25em;"
+                       type="submit"
+                       value="Log in to add comments"
+                       i18n:attributes="value label_login_to_add_comments;"
+                       />
+            </form>
+
+            <tal:getreplies repeat="reply_dict replies"
+                            define="ifModerate python:here.portal_properties.qPloneComments.getProperty('enable_moderation', None);">
+                <div class="comment" style=""
+            	    tal:condition="python:checkPermission('Moderate Discussion', here) or isPublished"
+                    tal:define="indent python:reply_dict['depth']*2;
+                                reply python:reply_dict['object'];
+                                isPublished python:reply.review_state=='published';"
+                    tal:attributes="style string:margin-left:${indent}em;">
+
+                    <h3>
+                        <a name="comments" tal:attributes="name reply/id">
+                        <span tal:replace="reply/pretty_title_or_id">Comment title</span>
+                        </a>
+                    </h3>
+                    <div class="documentByLine"
+                         tal:define="creator reply/Creator;
+                                     anonymous_creator python:creator=='Anonymous User';
+                                     mi python:not anonymous_creator and mtool.getMemberInfo(creator);
+                                     fullname python: mi and mi['fullname'] or creator;" >
+                        <span i18n:translate="label_comment_by">Posted by</span>
+                        <span tal:content="fullname"
+                              tal:condition="not:anonymous_creator">Poster Name</span>
+                        <span i18n:translate="label_anonymous_user"
+                              tal:condition="anonymous_creator">Anonymous User</span>
+                        <span i18n:translate="label_commented_at">at</span> 
+                        <span tal:replace="python:toLocalizedTime(reply.ModificationDate(),
+                                           long_format=1)">8/23/2001 12:40:44 PM</span>
+                        <span tal:condition="python:ifModerate and not isPublished">[pending]</span>
+                    </div>
+                    <div class="commentBody"
+                         tal:content="structure reply/CookedBody">
+                         This is the body text of the comment.
+                    </div>
+                    <form name="reply"
+                          action="discussion_reply_form"
+                          method="post"
+                          style="display: inline;"
+                          tal:attributes="action string:${reply/absolute_url}/discussion_reply_form"
+                          tal:condition="python:userHasReplyPermission and isPublished">
+                        <input class="standalone"
+                               type="submit"
+                               value="Reply"
+                               i18n:attributes="value label_reply;"
+                               />
+                    </form>
+                    <form name="delete"
+                          action=""
+                          method="post"
+                          style="display: inline;"
+                          tal:condition="python:checkPermission('Moderate Discussion', here)"
+                          tal:attributes="action string:${reply/absolute_url}/deleteDiscussion">
+                        <input class="destructive"
+                               type="submit"
+                               value="Remove"
+                               i18n:attributes="value label_remove;"
+                               />
+                    </form>
+                    <form name="publish_discussion"
+                          action=""
+                          method="post"
+                          style="display: inline;"
+                          tal:condition="not:isPublished"
+                          tal:attributes="action string:${reply/absolute_url}/discussion_publish_comment">
+
+                        <input class="standalone"
+                               type="submit"
+                               value="Publish"
+                               i18n:attributes="value"
+                               />
+                    </form>
+                    <br/>
+                    <div class="documentByLine"
+                         i18n:translate="text_no_add_reply"
+                         i18n:domain="plonecomments"
+                         tal:condition="not:isPublished">Comment must be approved before replies to comment accepted.</div>
+
+                </div>
+            </tal:getreplies>
+        </div>
+    </tal:allowed>
+</div>
+</body>
+</html>
Index: /qPloneComments/branches/jcbrand-abuseFeature/tests/__init__.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/__init__.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/__init__.py	(revision 892)
@@ -0,0 +1,1 @@
+# This file intentionally left blank
Index: /qPloneComments/branches/jcbrand-abuseFeature/tests/common.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/common.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/common.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/helperNotify.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/helperNotify.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/helperNotify.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsCommenting.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsCommenting.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsCommenting.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsConfiglet.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsConfiglet.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsConfiglet.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsInstall.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsInstall.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsInstall.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsModeration.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsModeration.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsModeration.py	(revision 892)
@@ -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('.*<input.+?value="Publish"', re.S|re.M)
+        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()
+            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)
+            self.assert_(not m, "Publish button present for %s - user without Moderate Discussion permission" % u)
+
+    def testViewPublishButtonDMUsers(self):
+        # Publish button MUST PRESENT in document view form
+        # Pattern for publish button presence checking
+        pattern = re.compile('.*<input.+?value="Publish"',re.S|re.M)
+        # pattern = re.compile('.*<input\\s*class="standalone"\\s*type="submit"\\s*value="Publish"\\s*/>', 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('.*<input.+?value="Publish"',re.S|re.M)
+        for u in DM_USERS_IDS:
+            doc_id = "doc_%s" % u
+            doc_obj = getattr(self.portal, doc_id)
+            getReplies = self.discussion.getDiscussionFor(doc_obj).getReplies
+            # Check whether anonymous get no reply
+            self.logout()
+            self.assert_(not getReplies(), "View not published reply ALLOW for Anonymous")
+            # Login with actual (tested) user with DiscussionManager role and publish discussion
+            self.login(u)
+            self.assert_(getReplies(), "%s - member of DiscussionManager group NOT VIEW not published reply" % u)
+            getReplies()[0].discussion_publish_comment()
+            # Check if Publish button still present in document view page
+            auth = "%s:" % u
+            if not u=='anonym':
+                auth = '%s:%s' % (u,USERS[u]['passw'])
+            html = str(self.publish(self.portal.id+'/%s' % doc_id, auth))
+            m = pattern.match(html)
+            self.assert_(not m, "Publish button present for %s - DiscussionManager role user after publishing" % u)
+            # Check whether Anonym view published reply
+            self.logout()
+            self.assert_(getReplies(), "%s - member of DiscussionManager group NOT PUBLISH reply" % u)
+
+    ## TEST DELETING
+
+    def testViewDeleteButtonNonDMUsers(self):
+        # Check Delete reply button presense ONLY for PUBLISHED reply.
+        # Because of NOT PUBLUISHED replies is not visible at all for common users.
+        # Delete reply button in document view form MUST BE ABSENT for all EXCEPT manager.
+        # Publish replies
+        self.logout()
+        self.login('dm_admin')
+        for u in COMMON_USERS_IDS:
+            doc_id = "doc_%s" % u
+            doc_obj = getattr(self.portal, doc_id)
+            reply = self.discussion.getDiscussionFor(doc_obj).getReplies()[0]
+            reply.discussion_publish_comment()
+        # Prepare pattern for delete reply button presence checking
+        pattern = re.compile('.*<input\\s*class="destructive"\\s*type="submit"\\s*value="Remove"\\s*/>', 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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsNotification.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsNotification.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsNotification.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsNotificationRecipients.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsNotificationRecipients.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsNotificationRecipients.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/tests/testQPloneCommentsPermission.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsPermission.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/tests/testQPloneCommentsPermission.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/utils.py
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/utils.py	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/utils.py	(revision 892)
@@ -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/branches/jcbrand-abuseFeature/version.txt
===================================================================
--- /qPloneComments/branches/jcbrand-abuseFeature/version.txt	(revision 892)
+++ /qPloneComments/branches/jcbrand-abuseFeature/version.txt	(revision 892)
@@ -0,0 +1,1 @@
+3.2.4 dev
