Index: /quintagroup.plonecaptchas/tags/3.0/MANIFEST.in
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/MANIFEST.in	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/MANIFEST.in	(revision 2065)
@@ -0,0 +1,3 @@
+recursive-include docs *
+recursive-include quintagroup *
+global-exclude *pyc
Index: /quintagroup.plonecaptchas/tags/3.0/README.txt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/README.txt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/README.txt	(revision 2065)
@@ -0,0 +1,72 @@
+Plone Captchas
+==============
+
+quintagroup.plonecaptchas is a simple captchas implementation for Plone, designed
+for validation of human input in insecure forms. This is a standalone 
+implementation which does not depend on captchas.net services.
+
+quintagroup.plonecaptchas adds captcha support to Plone, it works together with 
+quintagroup.captcha.core package. http://projects.quintagroup.com/products/wiki/quintagroup.captcha.core
+
+With these products installed captchas will be added to Plone's 'Send this', 
+'Contact Us' (/contact-info) forms, and Plone's default discussion mechanism: 
+'Add Comment' and 'Reply' forms.
+
+quintagroup.plonecaptchas does not automatically plug to Plone's default registration
+(/join_form). You can make captcha plug to Plone's Join form via Zope Management Interface.
+Instructions here: http://projects.quintagroup.com/products/wiki/quintagroup.plonecaptchas#JoinForm
+
+Requirements
+------------
+
+* Plone 3.0 and above 
+
+For earlier Plone versions - use 1.3.4 version of qPloneCaptchas product for use on forms
+created with PloneFormMailer product.
+
+Dependencies
+------------
+
+* quintagroup.captcha.core
+* PIL with Jpeg and FreeType support
+
+Plone Captchas on PloneFormGen forms 
+------------------------------------
+
+To make captchas work on forms created with PloneFormGen, please use 'quintagroup.pfg.captcha' product:
+http://projects.quintagroup.com/products/wiki/quintagroup.pfg.captcha
+
+Installation
+------------
+
+See docs/INSTALL.txt for instructions.
+
+Note: If Plone Captchas is expected to be used with Plone Comments 
+http://quintagroup.com/services/plone-development/products/plone-comments,
+for proper behavior you have to install Plone Captchas first, and then Plone Comments.
+
+Links
+-----
+
+* Plone Captchas home page - http://quintagroup.com/services/plone-development/products/plone-captchas
+* Plone Captchas Screencasts - http://quintagroup.com/cms/screencasts/qplonecaptchas
+* Documentation - http://projects.quintagroup.com/products/wiki/quintagroup.plonecaptchas
+* SVN Repository - http://svn.quintagroup.com/products/quintagroup.plonecaptchas
+
+Authors
+-------
+
+The product was developed by Quintagroup team:
+
+* Andriy Mylenkyi 
+* Volodymyr Cherepanyak
+* Mykola Kharechko
+* Vitaliy Stepanov
+* Bohdan Koval
+
+Contributors
+------------
+
+* Dorneles Tremea
+
+Copyright (c) "Quintagroup": http://quintagroup.com, 2004-2010
Index: /quintagroup.plonecaptchas/tags/3.0/docs/HISTORY.txt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/docs/HISTORY.txt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/docs/HISTORY.txt	(revision 2065)
@@ -0,0 +1,25 @@
+Changelog
+=========
+
+3.0 - Apr 7, 2010
+-----------------
+
+* product was refactored: captcha support for the standard Plone forms remained
+  in quintagroup.plonecaptchas; the captchas core rendering and validation 
+  functionality were factored into quintagroup.captcha.core package
+
+2.0.0 - July 15, 2009
+---------------------
+
+* product was transformed to meet new standards: eggified package
+
+* compatibility with Plone 3.x added
+
+* support for earlier Plone versions 2.0.x -2.5.x removed
+
+* test install/uninstall skins & configlet added
+
+1.3.4 - March 2, 2009
+---------------------
+
+* Fixed bug in method parseKey [Maik Röder]
Index: /quintagroup.plonecaptchas/tags/3.0/docs/INSTALL.txt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/docs/INSTALL.txt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/docs/INSTALL.txt	(revision 2065)
@@ -0,0 +1,55 @@
+Installation
+============
+
+Buildout
+--------
+
+To install quintagroup.plonecaptchas to your buildout-based project:
+
+* Add ``quintagroup.plonecaptchas`` to the list of eggs to install::
+
+    [buildout]
+    ...
+    eggs =
+        ...
+        quintagroup.plonecaptchas
+       
+* Tell the plone.recipe.zope2instance recipe to install a ZCML slug::
+
+    [instance]
+    recipe = plone.recipe.zope2instance
+    ...
+    zcml =
+        quintagroup.plonecaptchas
+      
+* Re-run buildout, e.g. with::
+
+    $ ./bin/buildout
+    
+* Restart the Zope server, e.g with the following command in the terminal::
+
+   $ ./bin/instance restart
+   
+* Install ``Plone Captchas`` with Quickinstaller in Plone (Site Setup -> Add/Remove Products). 
+``Quintagroup Captcha Core`` will automatically be installed as dependency.
+        
+	
+Traditional Zope 2 instance
+---------------------------
+
+To install quintagroup.plonecaptchas into the global Python environment (or a workingenv),
+using a traditional Zope 2 instance, you can do this:
+
+* Copy ``quintagroup`` folder to your instance's ``/lib/python`` directory.
+
+* Create a file called ``quintagroup.plonecaptchas-configure.zcml`` in your instance's ``/etc/package-includes`` directory. The file
+should only contain this::
+
+    <include package="quintagroup.plonecaptchas" />
+
+* Restart the Zope server, for example with the following command in the terminal::
+
+   $ ./bin/zopectl restart
+
+* Install ``Plone Captchas`` with Quickinstaller in Plone (Site Setup -> Add/Remove Products). 
+``Quintagroup Captcha Core`` will automatically be installed as dependency.
Index: /quintagroup.plonecaptchas/tags/3.0/docs/LICENSE.GPL
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/docs/LICENSE.GPL	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/docs/LICENSE.GPL	(revision 2065)
@@ -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: /quintagroup.plonecaptchas/tags/3.0/docs/LICENSE.txt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/docs/LICENSE.txt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/docs/LICENSE.txt	(revision 2065)
@@ -0,0 +1,16 @@
+  quintagroup.plonecaptchas is copyright Quintagroup
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
+  MA 02111-1307 USA.
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/__init__.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/__init__.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/__init__.py	(revision 2065)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/Extensions/Install.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/Extensions/Install.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/Extensions/Install.py	(revision 2065)
@@ -0,0 +1,21 @@
+import transaction
+from Products.CMFCore.utils import getToolByName
+REQUIRED = 'quintagroup.captcha.core'
+
+def install(self):
+    qi = getToolByName(self, 'portal_quickinstaller')
+    # install required quintagroup.captcha.core product
+    # BBB: Need to success installation in Plone<3.1
+    #      (with GenericSetup < v1.4.2, where dependency
+    #       support was not yet implemented)
+    if not REQUIRED in qi.listInstalledProducts():
+        qi.installProduct(REQUIRED)
+    # install plonecaptchas
+    gs = getToolByName(self, 'portal_setup')
+    gs.runAllImportStepsFromProfile('profile-quintagroup.plonecaptchas:default')
+    transaction.savepoint()
+
+def uninstall(self):
+    portal_setup = getToolByName(self, 'portal_setup')
+    portal_setup.runAllImportStepsFromProfile('profile-quintagroup.plonecaptchas:uninstall', purge_old=False)
+    transaction.savepoint()
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/Extensions/__init__.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/Extensions/__init__.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/Extensions/__init__.py	(revision 2065)
@@ -0,0 +1,1 @@
+#
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/__init__.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/__init__.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/__init__.py	(revision 2065)
@@ -0,0 +1,12 @@
+from zope.i18nmessageid import MessageFactory
+
+from AccessControl import allow_module, ModuleSecurityInfo
+
+ProductMessageFactory = MessageFactory('quintagroup.plonecaptchas')
+ModuleSecurityInfo('quintagroup.plonecaptchas').declarePublic("ProductMessageFactory")
+
+#from quintagroup.plonecaptchas import config
+#allow_module('quintagroup.plonecaptchas.config')
+
+def initialize(context):
+    pass
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/config.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/config.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/config.py	(revision 2065)
@@ -0,0 +1,4 @@
+GLOBALS = globals()
+PRODUCT_NAME = 'quintagroup.plonecaptchas'
+
+#TOOL_ICON = 'tool.gif'
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/configure.zcml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/configure.zcml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/configure.zcml	(revision 2065)
@@ -0,0 +1,67 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:five="http://namespaces.zope.org/five"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    xmlns:cmf="http://namespaces.zope.org/cmf"
+    xmlns:gs="http://namespaces.zope.org/genericsetup"
+    i18n_domain="quintagroup.plonecaptchas">
+
+    <!-- include dependent packages -->
+    <include package="quintagroup.captcha.core" />
+
+    <five:registerPackage package="." initialize=".initialize" />
+
+    <!-- File System Directory Views registration -->
+    <cmf:registerDirectory name="captchas_discussion" />
+    <cmf:registerDirectory name="captchas_sendto_form" />
+    <cmf:registerDirectory name="captchas_contact_info" />
+    <cmf:registerDirectory name="captchas_join_form_30" />
+    <cmf:registerDirectory name="captchas_join_form_31_32" />
+    <cmf:registerDirectory name="captchas_join_form_33" />
+
+    <gs:registerProfile
+        name="default"
+        title="Plone Captchas"
+        directory="profiles/default"
+        description="This product adds captcha support to Plone."
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        provides="Products.GenericSetup.interfaces.EXTENSION"
+        />
+
+    <gs:registerProfile
+        name="join_form_plone_30"
+        title="Plone Captchas Join Form for plone-3.0"
+        directory="profiles/join_form_plone_30"
+        description="This product adds captcha support to Plone."
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        provides="Products.GenericSetup.interfaces.EXTENSION"
+        />
+
+    <gs:registerProfile
+        name="join_form_plone_31_32"
+        title="Plone Captchas Join Form for plone-3.1 - plone-3.2"
+        directory="profiles/join_form_plone_31_32"
+        description="This product adds captcha support to Plone."
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        provides="Products.GenericSetup.interfaces.EXTENSION"
+        />
+
+    <gs:registerProfile
+        name="join_form_plone_33"
+        title="Plone Captchas Join Form for plone-3.3"
+        directory="profiles/join_form_plone_33"
+        description="This product adds captcha support to Plone."
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        provides="Products.GenericSetup.interfaces.EXTENSION"
+        />
+
+    <gs:registerProfile
+        name="uninstall"
+        title="Uninstall Plone Captchas"
+        directory="profiles/uninstall"
+        description="Uninstall Plone Captchas product."
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        provides="Products.GenericSetup.interfaces.EXTENSION"
+        />
+
+</configure>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/metadata.xml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/metadata.xml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/metadata.xml	(revision 2065)
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<metadata>
+  <version>2.0</version>
+  <dependencies>
+    <dependency>profile-quintagroup.captcha.core:default</dependency>
+  </dependencies>
+</metadata>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/quintagroup.plonecaptchas_various.txt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/quintagroup.plonecaptchas_various.txt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/quintagroup.plonecaptchas_various.txt	(revision 2065)
@@ -0,0 +1,1 @@
+This file is used as a marker in setuphandlers.py.
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/skins.xml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/skins.xml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/default/skins.xml	(revision 2065)
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_discussion"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_discussion" />
+  <object name="captchas_sendto_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_sendto_form" />
+  <object name="captchas_contact_info"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_contact_info" />
+
+  <skin-path name="*">
+    <layer name="captchas_discussion"
+           insert-after="custom" />
+    <layer name="captchas_sendto_form"
+           insert-after="custom" />
+    <layer name="captchas_contact_info"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_30/skins.xml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_30/skins.xml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_30/skins.xml	(revision 2065)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_join_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_join_form_30" />
+
+  <skin-path name="*">
+    <layer name="captchas_join_form"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_30/skins.xml~
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_30/skins.xml~	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_30/skins.xml~	(revision 2065)
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_discussion"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_discussion" />
+  <object name="captchas_join_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_join_form" />
+  <object name="captchas_sendto_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_sendto_form" />
+
+  <skin-path name="*">
+    <layer name="captchas_discussion"
+           insert-after="custom" />
+    <layer name="captchas_sendto_form"
+           insert-after="custom" />
+    <layer name="captchas_join_form"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_31_32/skins.xml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_31_32/skins.xml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_31_32/skins.xml	(revision 2065)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_join_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_join_form_31_32" />
+
+  <skin-path name="*">
+    <layer name="captchas_join_form"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_31_32/skins.xml~
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_31_32/skins.xml~	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_31_32/skins.xml~	(revision 2065)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_join_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_join_form_30" />
+
+  <skin-path name="*">
+    <layer name="captchas_join_form"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_33/skins.xml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_33/skins.xml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_33/skins.xml	(revision 2065)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_join_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_join_form_33" />
+
+  <skin-path name="*">
+    <layer name="captchas_join_form"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_33/skins.xml~
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_33/skins.xml~	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/join_form_plone_33/skins.xml~	(revision 2065)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <object name="captchas_join_form"
+          meta_type="Filesystem Directory View"
+          directory="quintagroup.plonecaptchas:skins/captchas_join_form_30" />
+
+  <skin-path name="*">
+    <layer name="captchas_join_form"
+           insert-after="custom" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/uninstall/skins.xml
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/uninstall/skins.xml	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/profiles/uninstall/skins.xml	(revision 2065)
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<object name="portal_skins">
+
+  <skin-path name="*">
+    <layer name="captchas_discussion" remove="True" />
+    <layer name="captchas_sendto_form" remove="True" />
+    <layer name="captchas_join_form" remove="True" />
+  </skin-path>
+
+</object>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_contact_info/contact-info.cpt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_contact_info/contact-info.cpt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_contact_info/contact-info.cpt	(revision 2065)
@@ -0,0 +1,173 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml: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"
+      lang="en"
+      metal:use-macro="here/main_template/macros/master"
+      i18n:domain="plone">
+
+<head>
+    <metal:noborder fill-slot="top_slot"
+                 tal:define="dummy python:request.set('disable_border',1)" />
+</head>
+
+<body>
+<div metal:fill-slot="main"
+     tal:define="email_from_address portal/email_from_address">
+
+    <h1 class="documentFirstHeading"
+        i18n:translate="heading_contact_form">Contact form</h1>
+    
+    <p tal:content="portal/description">Site Description</p>
+
+    <div tal:condition="not:email_from_address">
+
+        <p class="documentDescription"
+           i18n:translate="text_no_email_setup">
+            This site doesn't have a valid email setup, so you cannot use
+            any contact forms. 
+        </p>
+
+    </div>
+    <div tal:condition="email_from_address">
+
+        <p class="documentDescription"
+           i18n:translate="description_contact_site_owner">
+            Fill in this form to contact the site owners.
+        </p>
+
+        <form name="feedback_form"
+            class="enableAutoFocus"
+            action=""
+            method="post"
+            tal:define="errors options/state/getErrors"
+            tal:attributes="action string:$here_url/$template_id">
+
+        <fieldset>
+            <legend i18n:translate="legend_feedback_for_contact_info">Feedback details</legend>
+
+        <tal:feedbackForm 
+            tal:define="fullname python:request.get('fullname', member.getProperty('fullname', ''));
+                        email python:request.get('email', member.getProperty('email',''))">
+            <div class="field"
+              tal:condition="not:fullname"
+              tal:define="error errors/sender_fullname|nothing"
+              tal:attributes="class python:test(error, 'field error', 'field')">
+              <label for="sender_fullname" i18n:translate="label_sender_fullname">
+                Name
+              </label>
+
+              <div class="formHelp" i18n:translate="help_sender_fullname">
+                Please enter your full name
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text" 
+                     id="sender_fullname" 
+                     name="sender_fullname" 
+                     size="40" 
+                     value="" 
+                     tal:attributes="value request/sender_fullname|nothing"
+                     />
+            </div>
+            <input type="hidden" name="sender_fullname" value="fullname" tal:attributes="value fullname"
+                tal:condition="fullname"/>
+            <div class="field"
+              tal:condition="not:email"
+              tal:define="error errors/sender_from_address|nothing"
+              tal:attributes="class python:test(error, 'field error', 'field')">
+              <label for="sender_from_address" i18n:translate="label_sender_from_address">
+                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_sender_from_address">
+                Please enter your e-mail address
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text" 
+                     id="sender_from_address" 
+                     name="sender_from_address" 
+                     size="40" 
+                     value="" 
+                     tal:attributes="value request/sender_from_address|nothing"
+                     />
+            </div>
+            <input type="hidden" name="sender_from_address" value="email" tal:attributes="value email"
+                tal:condition="email"/>
+        </tal:feedbackForm>
+        <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 class="formHelp" i18n:translate="help_subject">
+            Please enter the subject of the message you want to send.
+          </div>
+
+          <div tal:content="error">Validation error output</div>            
+
+          <input type="text" 
+                 id="subject" 
+                 name="subject" 
+                 size="25" 
+                 value="" 
+                 tal:attributes="value request/subject|nothing"
+                 />
+        </div>
+        <div class="field"
+          tal:define="error errors/message|nothing"
+          tal:attributes="class python:test(error, 'field error', 'field')">
+          <label for="message" i18n:translate="label_message">
+            Message
+          </label>
+          <span class="fieldRequired" title="Required"
+                  i18n:attributes="title title_required;"
+                  i18n:translate="label_required">(Required)</span>
+
+          <div class="formHelp" i18n:translate="help_message">
+            Please enter the message you want to send.
+          </div>
+
+          <div tal:content="error">Validation error output</div>
+    
+          <textarea name="message"
+                    id="message"
+                    rows="5"
+                    cols="25"
+                    tal:content="request/message|nothing"
+                    > </textarea>
+        </div>
+    
+        <div metal:use-macro="here/captcha_widget/macros/captcha"></div>
+
+        <div class="formControls">
+
+            <input class="context"
+                   type="submit"
+                   name="form.button.Send"
+                   value="Send"
+                   i18n:attributes="value label_send;"
+                   />
+
+        </div>
+
+        </fieldset>
+
+        <input type="hidden" name="form.submitted" value="1" />
+
+        </form>
+    </div>
+</div>
+</body>
+</html>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_contact_info/contact-info.cpt.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_contact_info/contact-info.cpt.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_contact_info/contact-info.cpt.metadata	(revision 2065)
@@ -0,0 +1,12 @@
+[default]
+title= Contact information
+
+[security]
+View=0:Anonymous
+
+[validators]
+validators=validate_site_feedback, captcha_validator
+
+[actions]
+action.success=traverse_to:string:send_feedback_site
+action.failure=traverse_to:string:contact-info
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply.cpy
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply.cpy	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply.cpy	(revision 2065)
@@ -0,0 +1,68 @@
+## 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.CMFPlone import PloneMessageFactory as _
+mtool = getToolByName(context, 'portal_membership')
+dtool = getToolByName(context, 'portal_discussion')
+req = context.REQUEST
+
+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
+
+creator = mtool.getAuthenticatedMember().getId()
+tb = dtool.getDiscussionFor(context)
+id = tb.createReply(title=subject, text=body_text, Creator=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
+
+# 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()
+
+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(_(u'Comment added.'))
+target = '%s#%s' % (view, anchor)
+return req.RESPONSE.redirect(target)
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply.cpy.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply.cpy.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply.cpy.metadata	(revision 2065)
@@ -0,0 +1,5 @@
+[validators]
+validators=captcha_validator
+
+[actions]
+action.failure=traverse_to:string:discussion_reply_form
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply_form.cpt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply_form.cpt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply_form.cpt	(revision 2065)
@@ -0,0 +1,178 @@
+<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:]">
+
+    <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;
+                         context 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>
+
+    <form name="edit_form"
+          class="enableAutoFocus"
+          method="post"
+          action="talkback"
+          tal:attributes="action string:$here_url/$template_id">
+
+        <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" />
+
+            <div tal:condition="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>
+            <div metal:use-macro="here/captcha_widget/macros/captcha"></div>
+            <div class="formControls">
+
+                <input class="context"
+                       type="submit"
+                       value="Save"
+                       name="form.button.Save"
+                       i18n:attributes="value label_save;"
+                       tal:attributes="name string:discussion_reply:method;" />
+
+            </div>
+          <input type="hidden" name="form.submitted" value="1" />
+
+        </fieldset>
+
+    </form>
+
+</div>
+
+</body>
+</html>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply_form.cpt.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply_form.cpt.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_discussion/discussion_reply_form.cpt.metadata	(revision 2065)
@@ -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: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/join_form.cpt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/join_form.cpt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/join_form.cpt	(revision 2065)
@@ -0,0 +1,242 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml: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"
+      lang="en"
+      metal:use-macro="here/main_template/macros/master"
+      i18n:domain="plone">
+
+<head>
+    <metal:block fill-slot="top_slot"
+                 tal:define="dummy python:request.set('disable_border',1)" />
+    <metal:block fill-slot="column_one_slot" />
+    <metal:block fill-slot="column_two_slot" />
+</head>
+
+<body>
+
+<div metal:fill-slot="main"
+     tal:define="errors options/state/getErrors;
+                 mailhost context/MailHost/smtp_host|nothing;
+                 mailfrom context/email_from_address|nothing;
+                 site_properties context/portal_properties/site_properties;
+                 allowEnterPassword not:utool/getPortalObject/validate_email|nothing;
+                 mailpw site_properties/validate_email;
+                 mailconfigured python:allowEnterPassword or (mailhost and mailfrom)">
+
+  <h1 class="documentFirstHeading"
+      i18n:translate="heading_registration_form">Registration Form</h1>
+
+
+  <div tal:condition="not:mailconfigured">
+    <p i18n:translate="no_join_without_configured_mail">
+      This site has not configured its email settings. Until email has
+      been configured it is not possible to create an account.
+    </p>
+  </div>
+  <form action=""
+        class="enableAutoFocus enableUnloadProtection"
+        method="post"
+        tal:condition="mailconfigured"
+        tal:define="allowEnterPassword site_properties/validate_email|nothing;"
+        tal:attributes="action template_id" >
+
+    <fieldset>
+
+      <legend i18n:translate="legend_personal_details">
+          Personal Details
+      </legend>
+
+      <input type="hidden" name="came_from" value=""
+             tal:condition="request/came_from|nothing"
+             tal:attributes="value request/came_from" />
+      <input type="hidden" name="last_visit:date" value="ZopeTime"
+             tal:attributes="value here/ZopeTime" />
+      <input type="hidden" name="prev_visit:date" value="ZopeTime"
+             tal:attributes="value here/ZopeTime" />
+      <input type="hidden" name="came_from_prefs" value=""
+             tal:attributes="value request/came_from_prefs|nothing" />
+
+      <div class="row"
+           tal:define="error errors/fullname| nothing;
+                       fullname request/fullname | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="fullname"
+               i18n:translate="label_full_name">Full Name</label>
+
+        <div class="formHelp" i18n:translate="help_full_name_creation">
+          Enter full name, eg. John Smith.
+        </div>
+
+        <div tal:content="error">Validation error output</div>
+
+        <input type="text"
+               name="fullname"
+               id="fullname"
+               size="30"
+               value="fullname"
+               tal:attributes="value fullname;"
+               />
+
+      </div>
+
+      <div class="field"
+           tal:define="error errors/username| nothing;
+                       username request/username | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="username"
+               i18n:translate="label_user_name">User Name</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div i18n:translate="help_user_name_creation_casesensitive"
+                   class="formHelp">
+                Enter a user name, usually something like
+                'jsmith'. No spaces or special characters.
+                Usernames and passwords are case sensitive,
+                make sure the caps lock key is not enabled.
+                This is the name used to log in.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text"
+                     name="username"
+                     id="username"
+                     size="30"
+                     value="username"
+                     tal:attributes="value username;"
+                     />
+      </div>
+
+      <div class="field"
+           tal:define="error errors/email | nothing;
+                       email request/email | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <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_creation">
+                Enter an email address. This is necessary in case the password
+                is lost. We respect your privacy, and will not give the address
+                away to any third parties or expose it anywhere.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text"
+                     name="email"
+                     id="email"
+                     size="30"
+                     tal:attributes="value email;"
+                     />
+      </div>
+
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="error errors/password | nothing;
+                       password request/password | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="password"
+               i18n:translate="label_password">Password</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div class="formHelp" i18n:translate="help_password_creation">
+                Minimum 5 characters.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="password"
+                     name="password"
+                     id="password"
+                     size="10"
+                     value=""
+                     />
+      </div>
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="error errors/password_confirm | nothing;
+                       password_confirm request/password_confirm | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+
+        <label for="password_confirm"
+               i18n:translate="label_confirm_password">Confirm password</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div class="formHelp" i18n:translate="help_confirm_password">
+                Re-enter the password. Make sure the passwords are identical.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="password"
+                     name="password_confirm"
+                     id="password_confirm"
+                     size="10"
+                     value=""
+                     />
+      </div>
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="mail_me request/mail_me|nothing">
+
+        <input type="checkbox"
+               class="noborder"
+               name="mail_me"
+               size="30"
+               id="cb_mailme"
+               tal:attributes="checked python:test(mail_me, 'checked', None);"
+               />
+        <label for="cb_mailme" i18n:translate="label_mail_password">Send a mail with the password</label>
+      </div>
+
+      <div class="field"
+           tal:condition="allowEnterPassword">
+
+        <div class="formHelp" i18n:translate="label_password_change_mail">
+          A URL will be generated and e-mailed to you; follow the link
+          to reach a page where you can change your password and
+          complete the registration process. 
+        </div>
+      </div>
+
+      <div metal:use-macro="here/captcha_widget/macros/captcha"></div>
+
+      <div class="formControls">
+        <input class="context"
+               type="submit"
+               name="form.button.Register"
+               value="Register"
+               i18n:attributes="value label_register;" />
+      </div>
+
+    </fieldset>
+
+    <input type="hidden" name="form.submitted" value="1" />
+
+  </form>
+</div>
+
+</body>
+</html>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/join_form.cpt.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/join_form.cpt.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/join_form.cpt.metadata	(revision 2065)
@@ -0,0 +1,10 @@
+[default]
+title=Please sign in
+border=None
+
+[validators]
+validators = validate_emailaddr,join_form_validate,captcha_validator
+
+[actions]
+action.success = traverse_to:string:register
+action.failure = traverse_to:string:join_form
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/register.cpy
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/register.cpy	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/register.cpy	(revision 2065)
@@ -0,0 +1,71 @@
+## Controller Python Script "register"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=password='password', password_confirm='password_confirm', came_from_prefs=None
+##title=Register a User
+##
+
+from Products.CMFPlone import PloneMessageFactory as _
+from ZODB.POSException import ConflictError
+
+REQUEST = context.REQUEST
+
+portal = context.portal_url.getPortalObject()
+portal_registration = context.portal_registration
+
+username = REQUEST['username']
+
+password=REQUEST.get('password') or portal_registration.generatePassword()
+
+# This is a temporary work-around for an issue with CMF not properly
+# reserving some existing ids (FSDV skin elements, for example). Until
+# this is fixed in the CMF we can at least fail nicely. See
+# http://dev.plone.org/plone/ticket/2982 and http://plone.org/collector/3028
+# for more info. (rohrer 2004-10-24)
+try:
+    portal_registration.addMember(username, password, properties=REQUEST, REQUEST=context.REQUEST)
+except AttributeError:
+    state.setError('username', _(u'The login name you selected is already in use or is not valid. Please choose another.'))
+    context.plone_utils.addPortalMessage(_(u'Please correct the indicated errors.'), 'error')
+    return state.set(status='failure')
+
+if portal.validate_email or REQUEST.get('mail_me', 0):
+    try:
+        portal_registration.registeredNotify(username)
+    except ConflictError:
+        raise
+    except Exception, err:
+
+        # TODO registerdNotify calls into various levels.  Lets catch all
+        # exceptions.  Should not fail.  They cant CHANGE their password ;-)
+        # We should notify them.
+        #
+        # (MSL 12/28/03) We also need to delete the just made member and return to the join_form.
+        state.set(came_from='login_success')
+        if portal.validate_email:
+            context.acl_users.userFolderDelUsers([username,], REQUEST=context.REQUEST)
+            msg = _(u'status_fatal_password_mail',
+                    default=u'Failed to create your account: we were unable to send your password to your email address: ${address}',
+                    mapping={u'address' : str(err)})
+            context.plone_utils.addPortalMessage(msg, 'error')
+            return state.set(status='failure')
+        else:
+            msg = _(u'status_nonfatal_password_mail',
+                    default=u'You account has been created, but we were unable to send your password to your email address: ${address}',
+                    mapping={u'address' : str(err)})
+            context.plone_utils.addPortalMessage(msg, 'error')
+
+state.set(came_from=REQUEST.get('came_from','login_success'))
+
+if came_from_prefs:
+    context.plone_utils.addPortalMessage(_(u'User added.'))
+    state.set(status='prefs')
+
+from Products.CMFPlone.utils import transaction_note
+transaction_note('%s registered' % username)
+
+return state
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/register.cpy.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/register.cpy.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_30/register.cpy.metadata	(revision 2065)
@@ -0,0 +1,11 @@
+[default]
+title=Register a User
+proxy=Manager,Anonymous
+
+[validators]
+validators = captcha_validator,join_form_validate
+ 
+[actions]
+action.failure=traverse_to:string:join_form
+action.success=traverse_to:string:registered
+action.prefs=traverse_to:string:prefs_users_overview
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/join_form.cpt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/join_form.cpt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/join_form.cpt	(revision 2065)
@@ -0,0 +1,244 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml: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"
+      lang="en"
+      metal:use-macro="here/main_template/macros/master"
+      i18n:domain="plone">
+
+<head>
+    <metal:block fill-slot="top_slot"
+                 tal:define="dummy python:request.set('disable_border',1)" />
+    <metal:block fill-slot="column_one_slot" />
+    <metal:block fill-slot="column_two_slot" />
+</head>
+
+<body>
+
+<div metal:fill-slot="main"
+     tal:define="errors options/state/getErrors;
+                 mailhost context/MailHost/smtp_host|nothing;
+                 mailfrom context/email_from_address|nothing;
+                 site_properties context/portal_properties/site_properties;
+                 allowEnterPassword not:utool/getPortalObject/validate_email|nothing;
+                 mailpw site_properties/validate_email;
+                 mailconfigured python:allowEnterPassword or (mailhost and mailfrom)">
+
+  <h1 class="documentFirstHeading"
+      i18n:translate="heading_registration_form">Registration Form</h1>
+
+
+  <div tal:condition="not:mailconfigured">
+    <p i18n:translate="no_join_without_configured_mail">
+      This site has not configured its email settings. Until email has
+      been configured it is not possible to create an account.
+    </p>
+  </div>
+  <form action=""
+        class="enableAutoFocus enableUnloadProtection"
+        method="post"
+        tal:condition="mailconfigured"
+        tal:define="allowEnterPassword site_properties/validate_email|nothing;"
+        tal:attributes="action template_id" >
+
+    <fieldset>
+
+      <legend i18n:translate="legend_personal_details">
+          Personal Details
+      </legend>
+
+      <input type="hidden" name="came_from" value=""
+             tal:condition="request/came_from|nothing"
+             tal:attributes="value request/came_from" />
+      <input type="hidden" name="last_visit:date" value="ZopeTime"
+             tal:attributes="value here/ZopeTime" />
+      <input type="hidden" name="prev_visit:date" value="ZopeTime"
+             tal:attributes="value here/ZopeTime" />
+      <input type="hidden" name="came_from_prefs" value=""
+             tal:attributes="value request/came_from_prefs|nothing" />
+
+      <div class="row"
+           tal:define="error errors/fullname| nothing;
+                       fullname request/fullname | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="fullname"
+               i18n:translate="label_full_name">Full Name</label>
+
+        <div class="formHelp" i18n:translate="help_full_name_creation">
+          Enter full name, eg. John Smith.
+        </div>
+
+        <div tal:content="error">Validation error output</div>
+
+        <input type="text"
+               name="fullname"
+               id="fullname"
+               size="30"
+               value="fullname"
+               tal:attributes="value fullname;"
+               />
+
+      </div>
+
+      <div class="field"
+           tal:define="error errors/username| nothing;
+                       username request/username | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="username"
+               i18n:translate="label_user_name">User Name</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div i18n:translate="help_user_name_creation_casesensitive"
+                   class="formHelp">
+                Enter a user name, usually something like
+                'jsmith'. No spaces or special characters.
+                Usernames and passwords are case sensitive,
+                make sure the caps lock key is not enabled.
+                This is the name used to log in.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text"
+                     name="username"
+                     id="username"
+                     size="30"
+                     value="username"
+                     tal:attributes="value username;"
+                     />
+      </div>
+
+      <div class="field"
+           tal:define="error errors/email | nothing;
+                       email request/email | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <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_creation">
+                Enter an email address. This is necessary in case the password
+                is lost. We respect your privacy, and will not give the address
+                away to any third parties or expose it anywhere.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text"
+                     name="email"
+                     id="email"
+                     size="30"
+                     tal:attributes="value email;"
+                     />
+      </div>
+
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="error errors/password | nothing;
+                       password request/password | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="password"
+               i18n:translate="label_password">Password</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div class="formHelp" i18n:translate="help_password_creation">
+                Minimum 5 characters.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="password"
+                     name="password"
+                     id="password"
+                     size="10"
+                     value=""
+                     />
+      </div>
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="error errors/password_confirm | nothing;
+                       password_confirm request/password_confirm | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+
+        <label for="password_confirm"
+               i18n:translate="label_confirm_password">Confirm password</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div class="formHelp" i18n:translate="help_confirm_password">
+                Re-enter the password. Make sure the passwords are identical.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="password"
+                     name="password_confirm"
+                     id="password_confirm"
+                     size="10"
+                     value=""
+                     />
+      </div>
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="mail_me request/mail_me|nothing">
+
+        <input type="checkbox"
+               class="noborder"
+               name="mail_me"
+               size="30"
+               id="cb_mailme"
+               tal:attributes="checked python:test(mail_me, 'checked', None);"
+               />
+        <label for="cb_mailme" i18n:translate="label_mail_password">Send a mail with the password</label>
+      </div>
+
+      <div class="field"
+           tal:condition="allowEnterPassword">
+
+        <div class="formHelp" i18n:translate="label_password_change_mail">
+          A URL will be generated and e-mailed to you; follow the link
+          to reach a page where you can change your password and
+          complete the registration process. 
+        </div>
+      </div>
+
+      <div metal:use-macro="here/captcha_widget/macros/captcha"></div>
+
+      <div class="formControls">
+        <input class="context"
+               type="submit"
+               name="form.button.Register"
+               value="Register"
+               i18n:attributes="value label_register;" />
+      </div>
+
+    </fieldset>
+
+    <input type="hidden" name="form.submitted" value="1" />
+
+    <input tal:replace="structure context/@@authenticator/authenticator" />
+
+  </form>
+</div>
+
+</body>
+</html>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/join_form.cpt.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/join_form.cpt.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/join_form.cpt.metadata	(revision 2065)
@@ -0,0 +1,10 @@
+[default]
+title=Please sign in
+border=None
+
+[validators]
+validators = validate_emailaddr,join_form_validate,captcha_validator
+
+[actions]
+action.success = traverse_to:string:register
+action.failure = traverse_to:string:join_form
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/register.cpy
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/register.cpy	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/register.cpy	(revision 2065)
@@ -0,0 +1,71 @@
+## Controller Python Script "register"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=password='password', password_confirm='password_confirm', came_from_prefs=None
+##title=Register a User
+##
+
+from Products.CMFPlone import PloneMessageFactory as _
+from ZODB.POSException import ConflictError
+
+REQUEST = context.REQUEST
+
+portal = context.portal_url.getPortalObject()
+portal_registration = context.portal_registration
+
+username = REQUEST['username']
+
+password=REQUEST.get('password') or portal_registration.generatePassword()
+
+# This is a temporary work-around for an issue with CMF not properly
+# reserving some existing ids (FSDV skin elements, for example). Until
+# this is fixed in the CMF we can at least fail nicely. See
+# http://dev.plone.org/plone/ticket/2982 and http://plone.org/collector/3028
+# for more info. (rohrer 2004-10-24)
+try:
+    portal_registration.addMember(username, password, properties=REQUEST, REQUEST=context.REQUEST)
+except AttributeError:
+    state.setError('username', _(u'The login name you selected is already in use or is not valid. Please choose another.'))
+    context.plone_utils.addPortalMessage(_(u'Please correct the indicated errors.'), 'error')
+    return state.set(status='failure')
+
+if portal.validate_email or REQUEST.get('mail_me', 0):
+    try:
+        portal_registration.registeredNotify(username)
+    except ConflictError:
+        raise
+    except Exception, err:
+
+        # TODO registerdNotify calls into various levels.  Lets catch all
+        # exceptions.  Should not fail.  They cant CHANGE their password ;-)
+        # We should notify them.
+        #
+        # (MSL 12/28/03) We also need to delete the just made member and return to the join_form.
+        state.set(came_from='login_success')
+        if portal.validate_email:
+            context.acl_users.userFolderDelUsers([username,], REQUEST=context.REQUEST)
+            msg = _(u'status_fatal_password_mail',
+                    default=u'Failed to create your account: we were unable to send your password to your email address: ${address}',
+                    mapping={u'address' : str(err)})
+            context.plone_utils.addPortalMessage(msg, 'error')
+            return state.set(status='failure')
+        else:
+            msg = _(u'status_nonfatal_password_mail',
+                    default=u'You account has been created, but we were unable to send your password to your email address: ${address}',
+                    mapping={u'address' : str(err)})
+            context.plone_utils.addPortalMessage(msg, 'error')
+
+state.set(came_from=REQUEST.get('came_from','login_success'))
+
+if came_from_prefs:
+    context.plone_utils.addPortalMessage(_(u'User added.'))
+    state.set(status='prefs')
+
+from Products.CMFPlone.utils import transaction_note
+transaction_note('%s registered' % username)
+
+return state
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/register.cpy.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/register.cpy.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_31_32/register.cpy.metadata	(revision 2065)
@@ -0,0 +1,11 @@
+[default]
+title=Register a User
+proxy=Manager,Anonymous
+
+[validators]
+validators = captcha_validator,join_form_validate
+ 
+[actions]
+action.failure=traverse_to:string:join_form
+action.success=traverse_to:string:registered
+action.prefs=traverse_to:string:prefs_users_overview
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/join_form.cpt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/join_form.cpt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/join_form.cpt	(revision 2065)
@@ -0,0 +1,222 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml: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"
+      lang="en"
+      metal:use-macro="here/main_template/macros/master"
+      i18n:domain="plone">
+
+<head>
+    <metal:block fill-slot="top_slot"
+                 tal:define="dummy python:request.set('disable_border',1)" />
+    <metal:block fill-slot="column_one_slot" />
+    <metal:block fill-slot="column_two_slot" />
+</head>
+
+<body>
+
+<div metal:fill-slot="main"
+     tal:define="errors options/state/getErrors;
+                 mailhost context/MailHost/smtp_host|nothing;
+                 mailfrom context/email_from_address|nothing;
+                 site_properties context/portal_properties/site_properties;
+                 allowEnterPassword not:utool/getPortalObject/validate_email|nothing;
+                 mailpw site_properties/validate_email;
+                 mailconfigured python:allowEnterPassword or (mailhost and mailfrom)">
+
+  <h1 class="documentFirstHeading"
+      i18n:translate="heading_registration_form">Registration Form</h1>
+
+
+  <div tal:condition="not:mailconfigured">
+    <p i18n:translate="no_join_without_configured_mail">
+      This site has not configured its email settings. Until email has
+      been configured it is not possible to create an account.
+    </p>
+  </div>
+  <form action=""
+        class="enableAutoFocus enableUnloadProtection"
+        method="post"
+        tal:condition="mailconfigured"
+        tal:define="allowEnterPassword site_properties/validate_email|nothing;"
+        tal:attributes="action template_id" >
+
+      <input type="hidden" name="came_from" value=""
+             tal:condition="request/came_from|nothing"
+             tal:attributes="value request/came_from" />
+      <input type="hidden" name="last_visit:date" value="ZopeTime"
+             tal:attributes="value here/ZopeTime" />
+      <input type="hidden" name="prev_visit:date" value="ZopeTime"
+             tal:attributes="value here/ZopeTime" />
+      <input type="hidden" name="came_from_prefs" value=""
+             tal:attributes="value request/came_from_prefs|nothing" />
+
+      <div class="row"
+           tal:define="error errors/fullname| nothing;
+                       fullname request/fullname | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="fullname"
+               i18n:translate="label_full_name">Full Name</label>
+
+        <div class="formHelp" i18n:translate="help_full_name_creation">
+          Enter full name, e.g. John Smith.
+        </div>
+
+        <div tal:content="error">Validation error output</div>
+
+        <input type="text"
+               name="fullname"
+               id="fullname"
+               size="30"
+               value="fullname"
+               tal:attributes="value fullname;"
+               />
+
+      </div>
+
+      <div class="field"
+           tal:define="error errors/username| nothing;
+                       username request/username | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="username"
+               i18n:translate="label_user_name">User Name</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div i18n:translate="help_user_name_creation_casesensitive"
+                   class="formHelp">
+                Enter a user name, usually something like
+                'jsmith'. No spaces or special characters.
+                Usernames and passwords are case sensitive,
+                make sure the caps lock key is not enabled.
+                This is the name used to log in.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text"
+                     name="username"
+                     id="username"
+                     size="30"
+                     value="username"
+                     tal:attributes="value username;"
+                     />
+      </div>
+
+      <div class="field"
+           tal:define="error errors/email | nothing;
+                       email request/email | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <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_creation">
+                Enter an email address. This is necessary in case the password
+                is lost. We respect your privacy, and will not give the address
+                away to any third parties or expose it anywhere.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="text"
+                     name="email"
+                     id="email"
+                     size="30"
+                     tal:attributes="value email;"
+                     />
+      </div>
+
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="error errors/password | nothing;
+                       password request/password | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+        <label for="password"
+               i18n:translate="label_password">Password</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div class="formHelp" i18n:translate="help_password_creation">
+                Minimum 5 characters.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="password"
+                     name="password"
+                     id="password"
+                     size="10"
+                     value=""
+                     />
+      </div>
+
+      <div class="field"
+           tal:condition="not: allowEnterPassword"
+           tal:define="error errors/password_confirm | nothing;
+                       password_confirm request/password_confirm | nothing;"
+           tal:attributes="class python:test(error, 'field error', 'field')">
+
+
+        <label for="password_confirm"
+               i18n:translate="label_confirm_password">Confirm password</label>
+
+        <span class="fieldRequired" title="Required"
+              i18n:attributes="title title_required;"
+              i18n:translate="label_required">(Required)</span>
+
+              <div class="formHelp" i18n:translate="help_confirm_password">
+                Re-enter the password. Make sure the passwords are identical.
+              </div>
+
+              <div tal:content="error">Validation error output</div>
+
+              <input type="password"
+                     name="password_confirm"
+                     id="password_confirm"
+                     size="10"
+                     value=""
+                     />
+      </div>
+
+      <div class="field"
+           tal:condition="allowEnterPassword">
+
+        <div class="formHelp" i18n:translate="label_password_change_mail">
+          A URL will be generated and e-mailed to you; follow the link
+          to reach a page where you can change your password and
+          complete the registration process. 
+        </div>
+      </div>
+
+      <div metal:use-macro="here/captcha_widget/macros/captcha"></div>
+
+      <div class="formControls">
+        <input class="context"
+               type="submit"
+               name="form.button.Register"
+               value="Register"
+               i18n:attributes="value label_register;" />
+      </div>
+
+    <input type="hidden" name="form.submitted" value="1" />
+
+    <input tal:replace="structure context/@@authenticator/authenticator" />
+
+  </form>
+</div>
+
+</body>
+</html>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/join_form.cpt.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/join_form.cpt.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/join_form.cpt.metadata	(revision 2065)
@@ -0,0 +1,10 @@
+[default]
+title=Please sign in
+border=None
+
+[validators]
+validators = validate_emailaddr,join_form_validate,captcha_validator
+
+[actions]
+action.success = traverse_to:string:register
+action.failure = traverse_to:string:join_form
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/register.cpy
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/register.cpy	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/register.cpy	(revision 2065)
@@ -0,0 +1,71 @@
+## Controller Python Script "register"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=password='password', password_confirm='password_confirm', came_from_prefs=None
+##title=Register a User
+##
+
+from Products.CMFPlone import PloneMessageFactory as _
+from ZODB.POSException import ConflictError
+
+REQUEST = context.REQUEST
+
+portal = context.portal_url.getPortalObject()
+portal_registration = context.portal_registration
+
+username = REQUEST['username']
+
+password=REQUEST.get('password') or portal_registration.generatePassword()
+
+# This is a temporary work-around for an issue with CMF not properly
+# reserving some existing ids (FSDV skin elements, for example). Until
+# this is fixed in the CMF we can at least fail nicely. See
+# http://dev.plone.org/plone/ticket/2982 and http://plone.org/collector/3028
+# for more info. (rohrer 2004-10-24)
+try:
+    portal_registration.addMember(username, password, properties=REQUEST, REQUEST=context.REQUEST)
+except AttributeError:
+    state.setError('username', _(u'The login name you selected is already in use or is not valid. Please choose another.'))
+    context.plone_utils.addPortalMessage(_(u'Please correct the indicated errors.'), 'error')
+    return state.set(status='failure')
+
+if portal.validate_email:
+    try:
+        portal_registration.registeredNotify(username)
+    except ConflictError:
+        raise
+    except Exception, err:
+
+        # TODO registerdNotify calls into various levels.  Lets catch all
+        # exceptions.  Should not fail.  They cant CHANGE their password ;-)
+        # We should notify them.
+        #
+        # (MSL 12/28/03) We also need to delete the just made member and return to the join_form.
+        state.set(came_from='login_success')
+        if portal.validate_email:
+            context.acl_users.userFolderDelUsers([username,], REQUEST=context.REQUEST)
+            msg = _(u'status_fatal_password_mail',
+                    default=u'Failed to create your account: we were unable to send your password to your email address: ${address}',
+                    mapping={u'address' : str(err)})
+            context.plone_utils.addPortalMessage(msg, 'error')
+            return state.set(status='failure')
+        else:
+            msg = _(u'status_nonfatal_password_mail',
+                    default=u'You account has been created, but we were unable to send your password to your email address: ${address}',
+                    mapping={u'address' : str(err)})
+            context.plone_utils.addPortalMessage(msg, 'error')
+
+state.set(came_from=REQUEST.get('came_from','login_success'))
+
+if came_from_prefs:
+    context.plone_utils.addPortalMessage(_(u'User added.'))
+    state.set(status='prefs')
+
+from Products.CMFPlone.utils import transaction_note
+transaction_note('%s registered' % username)
+
+return state
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/register.cpy.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/register.cpy.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_join_form_33/register.cpy.metadata	(revision 2065)
@@ -0,0 +1,11 @@
+[default]
+title=Register a User
+proxy=Manager,Anonymous
+
+[validators]
+validators = captcha_validator,join_form_validate
+ 
+[actions]
+action.failure=traverse_to:string:join_form
+action.success=traverse_to:string:registered
+action.prefs=traverse_to:string:prefs_users_overview
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto.cpy
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto.cpy	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto.cpy	(revision 2065)
@@ -0,0 +1,68 @@
+## Controller Python Script "sendto"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind state=state
+##bind subpath=traverse_subpath
+##parameters=
+##title=Send an URL to a friend
+##
+REQUEST=context.REQUEST
+
+from Products.CMFPlone.utils import transaction_note
+from Products.CMFPlone.PloneTool import AllowSendto
+from Products.CMFCore.utils import getToolByName
+from Products.CMFPlone import PloneMessageFactory as _
+from ZODB.POSException import ConflictError
+
+plone_utils = getToolByName(context, 'plone_utils')
+mtool = getToolByName(context, 'portal_membership')
+site_properties = getToolByName(context, 'portal_properties').site_properties
+pretty_title_or_id = plone_utils.pretty_title_or_id
+
+if not mtool.checkPermission(AllowSendto, context):
+    context.plone_utils.addPortalMessage(_(u'You are not allowed to send this link.'), 'error')
+    return state.set(status='failure')
+
+at = getToolByName(context, 'portal_actions')
+show = False
+actions = at.listActionInfos(object=context)
+# Check for visbility of sendto action
+for action in actions:
+    if action['id'] == 'sendto' and action['category'] == 'document_actions':
+        show = True
+if not show:
+    context.plone_utils.addPortalMessage(_(u'You are not allowed to send this link.'), 'error')
+    return state.set(status='failure')
+
+# Find the view action.
+context_state = context.restrictedTraverse("@@plone_context_state")
+url = context_state.view_url()
+
+variables = {'send_from_address' : REQUEST.send_from_address,
+             'send_to_address'   : REQUEST.send_to_address,
+             'subject'           : pretty_title_or_id(context),
+             'url'               : url,
+             'title'             : pretty_title_or_id(context),
+             'description'       : context.Description(),
+             'comment'           : REQUEST.get('comment', None),
+             'envelope_from'     : site_properties.email_from_address
+             }
+
+try:
+    plone_utils.sendto( **variables )
+except ConflictError:
+    raise
+except: # TODO To many things could possibly go wrong. So we catch all.
+    exception = context.plone_utils.exceptionString()
+    message = _(u'Unable to send mail: ${exception}',
+                mapping={u'exception' : exception})
+    context.plone_utils.addPortalMessage(message, 'error')
+    return state.set(status='failure')
+
+tmsg='Sent page %s to %s' % (url, REQUEST.send_to_address)
+transaction_note(tmsg)
+
+context.plone_utils.addPortalMessage(_(u'Mail sent.'))
+return state
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto.cpy.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto.cpy.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto.cpy.metadata	(revision 2065)
@@ -0,0 +1,6 @@
+[validators]
+validators=captcha_validator,validate_sendto
+
+[actions]
+action.success = redirect_to_action:string:view
+action.failure = redirect_to_action:string:view
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto_form.cpt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto_form.cpt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto_form.cpt	(revision 2065)
@@ -0,0 +1,125 @@
+<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">
+
+<metal:block fill-slot="top_slot"
+             tal:define="dummy python:request.set('disable_border',1)" />
+
+  <metal:head fill-slot="head_slot">
+    <meta name="robots" content="noindex,follow" />
+  </metal:head>
+
+  <body>
+
+    <div metal:fill-slot="main"
+         tal:define="errors options/state/getErrors;">
+
+      <h1 class="documentFirstHeading"
+          i18n:translate="heading_send_page_to">Send this page to someone</h1>
+
+      <p class="documentDescription"
+         i18n:translate="description_send_page_url_to">
+        Fill in the email address of your friend, and we will send an email
+        that contains a link to this page.
+      </p>
+
+
+      <form name="sendto_form"
+            class="enableAutoFocus"
+            action="sendto_form"
+            method="post"
+            enctype="multipart/form-data"
+            tal:attributes="action string:$here_url/$template_id">
+
+        <fieldset>
+
+          <legend i18n:translate="legend_address_info">Address info</legend>
+
+          <div class="field"
+               tal:define="error errors/send_to_address|nothing;"
+               tal:attributes="class python:test(error, 'field error', 'field')">
+
+            <label for="send_to_address" i18n:translate="label_send_to_mail">Send to</label>
+
+            <span class="fieldRequired" title="Required"
+                  i18n:attributes="title title_required;"
+                  i18n:translate="label_required">(Required)</span>
+
+                  <div class="formHelp" i18n:translate="help_send_to_mail">
+                    The e-mail address to send this link to.
+                  </div>
+
+                  <div tal:content="error">Validation error output</div>
+
+                  <input type="text"
+                         id="send_to_address"
+                         name="send_to_address"
+                         size="25"
+                         tal:attributes="value request/send_to_address | nothing;"
+                         />
+          </div>
+
+          <div class="field"
+               tal:define="error errors/send_from_address|nothing;"
+               tal:attributes="class python:test(error, 'field error', 'field')">
+
+            <label for="send_from_address" i18n:translate="label_send_from">From</label>
+
+            <span class="fieldRequired" title="Required"
+                  i18n:attributes="title title_required;"
+                  i18n:translate="label_required">(Required)</span>
+
+                  <div class="formHelp" i18n:translate="help_send_from">
+                    Your email address.
+                  </div>
+
+                  <div tal:content="error">Validation error output</div>
+
+                  <input type="text"
+                         id="send_from_address"
+                         name="send_from_address"
+                         size="25"
+                         tal:attributes="value python: request.get('send_from_address', member.getProperty('email',''));"
+                         />
+          </div>
+
+          <div class="field">
+            <label for="comment" i18n:translate="label_comment">Comment</label>
+
+            <div class="formHelp" i18n:translate="help_comment_to_link">
+              A comment about this link.
+            </div>
+
+            <textarea cols="80"
+                      rows="5"
+                      id="comment"
+                      name="comment"
+                      tal:content="request/comment | nothing"
+                      >
+              Comment
+            </textarea>
+          </div>
+          
+          <div metal:use-macro="here/captcha_widget/macros/captcha"></div>
+          <div class="formControls">
+            <input class="context"
+                   type="submit"
+                   name="form.button.Send"
+                   value="Send"
+                   i18n:attributes="value label_send;"
+                   />
+          </div>
+
+          <input type="hidden" name="form.submitted" value="1" />
+
+        </fieldset>
+
+      </form>
+
+    </div>
+
+  </body>
+</html>
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto_form.cpt.metadata
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto_form.cpt.metadata	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/skins/captchas_sendto_form/sendto_form.cpt.metadata	(revision 2065)
@@ -0,0 +1,9 @@
+[default]
+title=Send this page to somebody
+
+[validators]
+validators=validate_sendto, captcha_validator
+
+[actions]
+action.success=traverse_to:string:sendto
+action.failure=traverse_to:string:sendto_form
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/base.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/base.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/base.py	(revision 2065)
@@ -0,0 +1,103 @@
+import os, sys, re
+import unittest
+import transaction
+
+from Products.Five import zcml
+from Products.Five import fiveconfigure
+
+from Testing import ZopeTestCase as ztc
+
+from Products.PloneTestCase import setup as ptc_setup
+from Products.PloneTestCase import PloneTestCase as ptc
+from Products.PloneTestCase.layer import onsetup, PloneSite
+from Products.PloneTestCase.PloneTestCase import portal_owner
+from Products.PloneTestCase.PloneTestCase import default_password
+
+from Products.CMFCore.utils import getToolByName
+
+from quintagroup.captcha.core.tests.base import testPatch
+from quintagroup.captcha.core.tests.testWidget import NOT_VALID
+from quintagroup.captcha.core.tests.testWidget import IMAGE_PATT
+from quintagroup.captcha.core.tests.testWidget import addTestLayer
+from quintagroup.captcha.core.utils import getWord, decrypt, parseKey
+
+from quintagroup.plonecaptchas.config import *
+
+# TESTING CONSTANTS
+CAPTCHA_KEY = 'captcha_key'
+CAPTCHAS_COUNT = 165
+LAYERS = ['captchas_discussion', 'captchas_sendto_form', 'captchas_join_form']
+
+TOOL_ICON = 'skins/plone_captchas/tool.gif'
+TOOL_ID = 'portal_captchas'
+CONFIGLET_ID = "qpc_tool"
+PROPERTY_SHEET = 'qPloneCaptchas'
+
+# join_form profile prefix
+JF_PROFILE_PREFIX = 'profile-quintagroup.plonecaptchas:join_form_plone_'
+
+ptc.setupPloneSite()
+
+class NotInstalled(PloneSite):
+    """ Only package register, without installation into portal
+    """
+
+    @classmethod
+    def setUp(cls):
+        fiveconfigure.debug_mode = True
+        import quintagroup.captcha.core
+        import quintagroup.plonecaptchas
+        zcml.load_config('configure.zcml', quintagroup.captcha.core)
+        zcml.load_config('configure.zcml', quintagroup.plonecaptchas)
+        fiveconfigure.debug_mode = False
+        ztc.installPackage('quintagroup.plonecaptchas')
+        ztc.installPackage('quintagroup.captcha.core')
+
+
+class Installed(NotInstalled):
+    """ Install product into the portal
+    """
+    @classmethod
+    def setUp(cls):
+        app = ztc.app()
+        portal = app[ptc_setup.portal_name]
+
+        # Sets the local site/manager
+        ptc_setup._placefulSetUp(portal)
+        # Install PROJECT
+        qi = getattr(portal, 'portal_quickinstaller', None)
+        qi.installProduct(PRODUCT_NAME)
+
+        # Install Join Form layer, depends on Plone version
+        js_layer = None
+        if getattr(ptc_setup, 'PLONE33', 0):
+            js_layer = JF_PROFILE_PREFIX+'33'
+        elif getattr(ptc_setup, 'PLONE32', 0):
+            js_layer = JF_PROFILE_PREFIX+'31_32'
+        elif getattr(ptc_setup, 'PLONE31', 0):
+            js_layer = JF_PROFILE_PREFIX+'31_32'
+        elif getattr(ptc_setup, 'PLONE30', 0):
+            js_layer = JF_PROFILE_PREFIX+'30'
+        if js_layer is not None:
+            gs = getattr(portal, 'portal_setup', None)
+            gs.runAllImportStepsFromProfile(js_layer)
+
+        transaction.commit()
+
+    @classmethod
+    def tearDown(cls):
+        ptc_setup._placefulTearDown()
+        
+
+class TestCase(ptc.PloneTestCase):
+    layer = Installed
+
+class TestCaseNotInstalled(ptc.PloneTestCase):
+    layer = NotInstalled
+
+
+class FunctionalTestCase(ptc.FunctionalTestCase):
+    layer = Installed
+
+class FunctionalTestCaseNotInstalled(ptc.FunctionalTestCase):
+    layer = NotInstalled
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/testForms.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/testForms.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/testForms.py	(revision 2065)
@@ -0,0 +1,175 @@
+from base import *
+from urllib import urlencode
+from StringIO import StringIO
+from DateTime import DateTime
+
+from plone.app.controlpanel.security import ISecuritySchema
+
+# BBB for plone v<3.1, where plone.protect not used yet 
+PROTECT_SUPPORT = True
+try:
+    from plone import protect
+except ImportError:
+    PROTECT_SUPPORT = False
+
+# USE PATCH FROM quintagroup.captcha.core
+# patch to use test images and dictionary
+testPatch()
+
+class TestFormMixin(FunctionalTestCase):
+
+    def afterSetUp(self):
+        self.loginAsPortalOwner()
+        self.addProduct(PRODUCT_NAME)
+        # Add test_captcha layer from quintagroup.captcah.core
+        addTestLayer(self)
+        # Prepare form data
+        self.basic_auth = ':'.join((portal_owner,default_password))
+        self.form_url = ''
+        self.form_method = "POST"
+        self.hasAuthenticator = False
+        self.form_data = self.getFormData()
+        # Prepare captcha related test data
+        self.captcha_key = self.portal.captcha_key
+        self.hashkey = self.portal.getCaptcha()
+        self.form_data['hashkey'] = self.hashkey
+        self.form_data['key'] = ''
+
+    def getFormData(self):
+        raise NotImplementedError(
+            "getFormData not implemented")
+        
+    def publishForm(self):
+        stdin_data = None
+        form_url = self.portal.absolute_url(1) + self.form_url
+        # Prepare form data
+        if PROTECT_SUPPORT and self.hasAuthenticator:
+            self.form_data['_authenticator'] = self._getauth()
+        form_data = urlencode(self.form_data)
+        if self.form_method.upper() == 'GET':
+            form_url += "?%s" % form_data
+        else:
+            stdin_data = StringIO(form_data)
+        # Publish form and get response
+        response = self.publish(form_url, self.basic_auth,
+            request_method=self.form_method, stdin=stdin_data)
+        return response
+
+    def _getauth(self):
+        # Fix authenticator for the form
+        authenticator = self.portal.restrictedTraverse("@@authenticator")
+        html = authenticator.authenticator()
+        handle = re.search('value="(.*)"', html).groups()[0]
+        return handle
+
+    def testImage(self):
+        self.form_data = {}
+        self.form_method = "GET"
+        response = self.publishForm().getBody()
+        patt = re.compile(IMAGE_PATT  % self.portal.absolute_url())
+        match_obj = patt.search(response)
+        img_url = match_obj.group(1)
+
+        content_type = self.publish('/plone' + img_url).getHeader('content-type')
+        self.assertTrue(content_type.startswith('image'),
+            "Wrong captcha image content type")
+
+    def testSubmitRightCaptcha(self):
+        key = getWord(int(parseKey(decrypt(self.captcha_key, self.hashkey))['key'])-1)
+        self.form_data['key'] = key
+        
+        response = self.publishForm().getBody()
+        self.assertFalse(NOT_VALID.search(response))
+
+    def testSubmitWrongCaptcha(self):
+        self.form_data['key'] = 'wrong word'
+        response = self.publishForm().getBody()
+        self.assertTrue(NOT_VALID.search(response))
+
+    def testSubmitRightCaptchaTwice(self):
+        key = getWord(int(parseKey(decrypt(self.captcha_key, self.hashkey))['key'])-1)
+        self.form_data['key'] = key
+
+        self.publishForm()
+        response = self.publishForm().getBody()
+        self.assertTrue(NOT_VALID.search(response))
+
+
+class TestDiscussionForm(TestFormMixin):
+
+    def afterSetUp(self):
+        TestFormMixin.afterSetUp(self)
+        self.portal.invokeFactory('Document', 'index_html')
+        self.portal['index_html'].allowDiscussion(True)
+        self.form_url = '/index_html/discussion_reply_form'
+        
+    def getFormData(self):
+        return {'form.submitted' : '1',
+                'subject': 'testing',
+                'Creator': portal_owner,
+                'body_text': 'Text in Comment',
+                'discussion_reply:method': 'Save',
+                'form.button.form_submit' : 'Save'}
+
+
+class TestJoinForm(TestFormMixin):
+
+    def afterSetUp(self):
+        TestFormMixin.afterSetUp(self)
+        ISecuritySchema(self.portal).enable_self_reg = True
+        self.hasAuthenticator = True
+        self.form_url = '/join_form'
+        self.basic_auth = ":"
+        self.logout()
+
+    def getFormData(self):
+        return {"last_visit:date" : str(DateTime()),
+                "prev_visit:date" : str(DateTime()-1),
+                "came_from_prefs" : "",
+                "fullname" : "Tester",
+                "username" : "tester",
+                "email" : "tester@test.com",
+                'form.button.Register':'Register',
+                'form.submitted':'1'}
+
+
+class TestSendtoForm(TestFormMixin):
+
+    def afterSetUp(self):
+        TestFormMixin.afterSetUp(self)
+        self.portal.invokeFactory('Document', 'index_html')
+        self.portal['index_html'].allowDiscussion(True)
+        self.form_url = '/index_html/sendto_form'
+        
+    def getFormData(self):
+        return {'form.submitted' : '1',
+                "send_to_address" : "recipient@test.com",
+                "send_from_address" : "sender@test.com",
+                'comment': 'Text in Comment',
+                'form.button.Send' : 'Save'}
+
+class TestContactInfo(TestFormMixin):
+
+    def afterSetUp(self):
+        TestFormMixin.afterSetUp(self)
+        # preparation to form correct working
+        self.portal._updateProperty('email_from_address','manager@test.com')
+        self.logout()
+        self.form_url = '/contact-info'
+        
+    def getFormData(self):
+        return {'form.submitted' : '1',
+                "sender_fullname" : "tester",
+                "sender_from_address" : "sender@test.com",
+                'subject': 'Subject',
+                'message': 'Message',
+                'form.button.Send' : 'Save'}
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestDiscussionForm))
+    suite.addTest(unittest.makeSuite(TestJoinForm))
+    suite.addTest(unittest.makeSuite(TestSendtoForm))
+    suite.addTest(unittest.makeSuite(TestContactInfo))
+    return suite
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/testInstallation.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/testInstallation.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/tests/testInstallation.py	(revision 2065)
@@ -0,0 +1,36 @@
+from base import *
+
+class TestInstallation(TestCase):
+
+    def afterSetUp(self):
+        self.loginAsPortalOwner()
+        self.skins = getToolByName(self.portal, 'portal_skins', None)
+
+    def testSkinInstall(self):
+        for skin in self.skins.getSkinSelections():
+            path = self.skins.getSkinPath(skin)
+            path = map(str.strip, path.split(','))
+            for layer in LAYERS:
+                self.assert_(layer.split('/')[0] in self.skins.objectIds(),
+                    '%s directory view not found in portal_skins after installation' % layer)
+                self.assert_(layer in path,
+                    '%s layer not found in %s' % (PRODUCT_NAME, skin))
+
+    def testSkinUninstall(self):
+        qi = getToolByName(self.portal, 'portal_quickinstaller', None)
+        qi.uninstallProducts([PRODUCT_NAME])
+        assert not qi.isProductInstalled(PRODUCT_NAME)
+
+        for skin in self.skins.getSkinSelections():
+            path = self.skins.getSkinPath(skin)
+            path = map(str.strip, path.split(','))
+            for layer in LAYERS:
+                self.assertTrue(not layer.split('/')[0] in self.skins.objectIds(),
+                    '%s directory view found in portal_skins after uninstallation' % layer)
+                self.assert_(not layer in path,
+                    '%s layer found in %s skin after uninstallation' % (layer, skin))
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestInstallation))
+    return suite
Index: /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/version.txt
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/version.txt	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/quintagroup/plonecaptchas/version.txt	(revision 2065)
@@ -0,0 +1,1 @@
+2.0.1dev
Index: /quintagroup.plonecaptchas/tags/3.0/setup.cfg
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/setup.cfg	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/setup.cfg	(revision 2065)
@@ -0,0 +1,5 @@
+[zopeskel]
+template = plone
+
+[egg_info]
+
Index: /quintagroup.plonecaptchas/tags/3.0/setup.py
===================================================================
--- /quintagroup.plonecaptchas/tags/3.0/setup.py	(revision 2065)
+++ /quintagroup.plonecaptchas/tags/3.0/setup.py	(revision 2065)
@@ -0,0 +1,44 @@
+from setuptools import setup, find_packages
+import os
+
+version = '3.0'
+
+setup(name='quintagroup.plonecaptchas',
+      version=version,
+      description="quintagroup.plonecaptchas is simple captchas implementation for Plone, designed for validating human input in insecure forms.",
+      long_description=open("README.txt").read() + "\n" +
+                       open(os.path.join("docs", "HISTORY.txt")).read(),
+      # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+      classifiers=[
+        "Framework :: Plone",
+        "Programming Language :: Python",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        ],
+      keywords='plone captcha',
+      author='Quintagroup',
+      author_email='support@quintagroup.com',
+      url='http://svn.quintagroup.com/products/quintagroup.plonecaptchas',
+      license='GPL',
+      packages=find_packages(exclude=['ez_setup']),
+      namespace_packages=['quintagroup'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          'setuptools',
+          'quintagroup.captcha.core',
+          # -*- Extra requirements: -*-
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+
+      [distutils.setup_keywords]
+      paster_plugins = setuptools.dist:assert_string_list
+
+      [egg_info.writers]
+      paster_plugins.txt = setuptools.command.egg_info:write_arg
+
+      [z3c.autoinclude.plugin]
+      target = plone
+      """,
+      paster_plugins = ["ZopeSkel"],
+      )
