Index: qPloneCaptchas/trunk/Extensions/Install.py
===================================================================
--- qPloneCaptchas/trunk/Extensions/Install.py	(revision 240)
+++ qPloneCaptchas/trunk/Extensions/Install.py	(revision 340)
@@ -44,6 +44,8 @@
     elif plone_version.startswith('2.5'):
         plone_version = '2.5'
-    elif plone_version.startswith('3'):
+    elif plone_version.startswith('3.0'):
         plone_version = '3.0'
+    elif plone_version.startswith('3.1'):
+        plone_version = '3.1'
     else:
         raise Exception("Error - Unsupported version. Suported versions: Plone 2.0.5-3")
@@ -59,6 +61,5 @@
     join_form_layer = '/'.join([LAYER_JOIN_FORM, plone_version])
     Layers.append(join_form_layer)
-    
-    
+
     sendto_form_layer = '/'.join([LAYER_SENDTO_FORM, plone_version])
     Layers.append(sendto_form_layer)
@@ -95,5 +96,5 @@
                 out.write('  Layer %s was already installed into skin %s.\n' % (layer,skin))
         if modified:
-            path = string.join( path, ', ' )
+            path = string.join( path, ',' )
             skinstool.addSkinSelection( skin, path )
 
Index: qPloneCaptchas/trunk/HISTORY.txt
===================================================================
--- qPloneCaptchas/trunk/HISTORY.txt	(revision 340)
+++ qPloneCaptchas/trunk/HISTORY.txt	(revision 340)
@@ -0,0 +1,85 @@
+1.3.3 - Unreleased
+==================
+
+  * Added support for Plone 3.1.
+    [deo]
+
+
+1.3.2 - May 15, 2008
+====================
+
+   * Completed the Plone 3.0 support.
+
+
+1.3.1 - March 8, 2008
+=====================
+
+  * Fixed bug in configlet.
+
+  * Added initial Plone 3.0 support.
+
+
+1.3.0 - February 6, 2008
+========================
+
+  * Secure sendto_form and join_form with captcha.
+
+  * Fix secure bug in join_form captcha.
+
+  * Added Czech translation.
+
+
+1.2.3 - September 6, 2007
+=========================
+
+  * Added Spanish translation.
+
+
+1.2.2 - July 10, 2007
+=====================
+
+  * Fixed problem with redirect in PloneFormMailer.
+
+
+1.2.1 - June 21, 2007
+=====================
+
+  * Fixed translation problem in Plone 2.1.4.
+
+
+1.2.0 - April 24, 2007
+======================
+
+  * Fixed qPloneCaptchas PFM forms layout breakage.
+
+  * Fixed the polish translation file.
+
+  * Added captchas for join form.
+
+
+1.1.0 - February 27,2007
+========================
+
+  * Fixed the '0.jpg' issue.
+
+  * Added status messages translation.
+
+
+1.0 - November 28, 2006
+=======================
+
+  * qPloneComments related code moved to the comments product.
+
+  * Refactored skins for easier dynamic/static captchas switch.
+
+
+0.9 - October 9, 2006
+=====================
+
+  * Added implementation of dynamic generation of captcha images using PIL.
+
+  * Changed verification method of entered catchas word.
+
+  * Implemented locking for used captchas keys to prevent possibility the past keys reusage.
+
+  * Added support for Plone 2.5.
Index: qPloneCaptchas/trunk/README.txt
===================================================================
--- qPloneCaptchas/trunk/README.txt	(revision 240)
+++ qPloneCaptchas/trunk/README.txt	(revision 340)
@@ -1,11 +1,13 @@
 Plone Capchas
 
-    qPloneCaptchas is simple captchas implementation for Plone, designed for validation 
-    human input in insecure forms. This is standalone implementation with static captcha
-    images, which does not depend on captchas.net services.
+    qPloneCaptchas is simple captchas implementation for Plone, designed
+    for validating human input in insecure forms. This is a standalone
+    implementation with static captcha images, which does not depend on
+    captchas.net services.
 
-    Starting from version 1.0 dynamic captchas option implemented. You can switch 
-    captchas into dynamic mode in correspondent configlet. In this case captcha images 
-    will be generated on the fly.
+    Since version 1.0, the dynamic captchas option is implemented. You can
+    switch captchas into dynamic mode in correspondent configlet. In this
+    case, captcha images will be generated on the fly.
+
 
 Supported Plone versions:
@@ -14,9 +16,11 @@
     * 2.1.x
     * 2.5.x
-    * 3.0.x
+    * 3.x
+
 
 Dependency:
 
     PIL with Jpeg and FreeType support
+
 
 Plugs to:
@@ -28,12 +32,22 @@
 Install:
 
-    If qPloneCaptchas is expected to be used with PloneFormMailer please make sure 
-    that qPloneCaptchas installed only after the product. Tested with PloneFormMailer 0.3.
+    If qPloneCaptchas is expected to be used with PloneFormMailer please
+    make sure that qPloneCaptchas is installed only after the product.
+    Tested with PloneFormMailer 0.3.
 
 
-The product is developed by Quintagroup.com team.
+Authors:
 
-    Volodymyr Cherepanyak - chervol@quintagroup.com
-    Mykola Kharechko - crchemist@quintagroup.com
+    The product is developed by Quintagroup.com team:
+
+    * Volodymyr Cherepanyak - chervol@quintagroup.com
+
+    * Mykola Kharechko - crchemist@quintagroup.com
+
+
+Contributors:
+
+    * Dorneles Tremea - dorneles@tremea.com
+
 
 Future features:
Index: qPloneCaptchas/trunk/history.txt
===================================================================
--- qPloneCaptchas/trunk/history.txt	(revision 240)
+++ 	(revision )
@@ -1,74 +1,0 @@
-1.3.2 - May 15, 2008
-==============================
-
-   * Added plone-3 support
-
-1.3.1 - March 8, 2008
-==============================
-
-   * Fix bag in configlet
-   
-   * Added Plone-3 support
-   
-
-1.3.0 - February 6, 2008
-==============================
-
-   * Secure sendto_form and join_form with captcha
-   
-   * Fix secure bug in join_form captcha
-   
-   * Added Czech translation
-
-1.2.3 - September 6, 2007
-==============================
-
-   * added Spanish translation.
-
-
-1.2.2 - July 10, 2007
-==============================
-
-  * fix problem with redirect in PloneFormMailer
-
-
-1.2.1 - June 21, 2007
-==============================
-
- * fixed translation problem in plone-2.1.4.
-
-
-1.2.0 - April 24, 2007
-==============================
- * fixed qPloneCaptchas PFM forms layout breakage
-
- * fixed the polish translation file
- 
- * added captchas for join form
-
-
-1.1.0 - February 27,2007
-==============================
- * fixed the '0.jpg' issue
-
- * added status messages translation
- 
-
-1.0 - November 28, 2006
-==============================
-
- * qPloneComments related code moved to the comments product
-
- * refactored skins for easier dynamic/static captchas switch
-
-
-0.9 - Released October 9, 2006
-==============================
-
- * Added implementation of dynamic generation of captcha images using PIL
-
- * Changed verification method of entered catchas word.
-
- * Implemented locking for used captchas keys to prevent possibility the past keys reusage.
-
- * Support of Plone 2.5 added
Index: qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply.cpy
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply.cpy	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply.cpy	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply.cpy.metadata
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply.cpy.metadata	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply.cpy.metadata	(revision 340)
@@ -0,0 +1,5 @@
+[validators]
+validators=captcha_validator
+
+[actions]
+action.failure=traverse_to:string:discussion_reply_form
Index: qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply_form.cpt
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply_form.cpt	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply_form.cpt	(revision 340)
@@ -0,0 +1,183 @@
+<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 tal:condition="nocall:here/captcha_widget|nothing"
+                 tal:omit-tag="">
+              <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: qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply_form.cpt.metadata
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply_form.cpt.metadata	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_discussion/3.1/discussion_reply_form.cpt.metadata	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_join_form/3.1/join_form.cpt
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_join_form/3.1/join_form.cpt	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_join_form/3.1/join_form.cpt	(revision 340)
@@ -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" />
+
+    <input tal:replace="structure context/@@authenticator/authenticator" />
+
+  </form>
+</div>
+
+</body>
+</html>
Index: qPloneCaptchas/trunk/skins/captchas_join_form/3.1/join_form.cpt.metadata
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_join_form/3.1/join_form.cpt.metadata	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_join_form/3.1/join_form.cpt.metadata	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_join_form/3.1/register.cpy
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_join_form/3.1/register.cpy	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_join_form/3.1/register.cpy	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_join_form/3.1/register.cpy.metadata
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_join_form/3.1/register.cpy.metadata	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_join_form/3.1/register.cpy.metadata	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto.cpy
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto.cpy	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto.cpy	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto.cpy.metadata
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto.cpy.metadata	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto.cpy.metadata	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto_form.cpt
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto_form.cpt	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto_form.cpt	(revision 340)
@@ -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: qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto_form.cpt.metadata
===================================================================
--- qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto_form.cpt.metadata	(revision 340)
+++ qPloneCaptchas/trunk/skins/captchas_sendto_form/3.1/sendto_form.cpt.metadata	(revision 340)
@@ -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: qPloneCaptchas/trunk/tests/testCaptchaWidget.py
===================================================================
--- qPloneCaptchas/trunk/tests/testCaptchaWidget.py	(revision 240)
+++ qPloneCaptchas/trunk/tests/testCaptchaWidget.py	(revision 340)
@@ -21,5 +21,5 @@
         self.addProduct('qPloneCaptchas')
         self.portal.invokeFactory('Document', 'index_html')
-        self.portal['index_html'].allow_discussion = True
+        self.portal['index_html'].allowDiscussion(True)
         self.absolute_url = self.portal['index_html'].absolute_url_path()
 
@@ -36,5 +36,5 @@
         path = '%s/discussion_reply_form'%self.absolute_url
         resp1 = self.publish(path, self.basic_auth, request_method='GET').getBody()
-        patt = re.compile('<img\s+src="%s(/getCaptchaImage/[0-9a-fA-F]+)"'%self.portal.absolute_url())
+        patt = re.compile('\s+src="%s(/getCaptchaImage/[0-9a-fA-F]+)"'%self.portal.absolute_url())
         match_obj = patt.search(resp1)
         img_url = match_obj.group(1)
Index: qPloneCaptchas/trunk/version.txt
===================================================================
--- qPloneCaptchas/trunk/version.txt	(revision 240)
+++ qPloneCaptchas/trunk/version.txt	(revision 340)
@@ -1,1 +1,1 @@
-1.3.2
+1.3.3
