Initial import.
svn path=/plone.formwidget.captcha/trunk/; revision=28417
This commit is contained in:
commit
9bca6025ee
|
@ -0,0 +1,4 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
1.0 - Unreleased
|
||||
----------------
|
||||
|
||||
* Initial release
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
plone.formwidget.captcha Installation
|
||||
-------------------------------------
|
||||
|
||||
To install plone.formwidget.captcha into the global Python environment (or a workingenv),
|
||||
using a traditional Zope 2 instance, you can do this:
|
||||
|
||||
* When you're reading this you have probably already run
|
||||
``easy_install plone.formwidget.captcha``. Find out how to install setuptools
|
||||
(and EasyInstall) here:
|
||||
http://peak.telecommunity.com/DevCenter/EasyInstall
|
||||
|
||||
* Create a file called ``plone.formwidget.captcha-configure.zcml`` in the
|
||||
``/path/to/instance/etc/package-includes`` directory. The file
|
||||
should only contain this::
|
||||
|
||||
<include package="plone.formwidget.captcha" />
|
||||
|
||||
|
||||
Alternatively, if you are using zc.buildout and the plone.recipe.zope2instance
|
||||
recipe to manage your project, you can do this:
|
||||
|
||||
* Add ``plone.formwidget.captcha`` to the list of eggs to install, e.g.:
|
||||
|
||||
[buildout]
|
||||
...
|
||||
eggs =
|
||||
...
|
||||
plone.formwidget.captcha
|
||||
|
||||
* Tell the plone.recipe.zope2instance recipe to install a ZCML slug:
|
||||
|
||||
[instance]
|
||||
recipe = plone.recipe.zope2instance
|
||||
...
|
||||
zcml =
|
||||
plone.formwidget.captcha
|
||||
|
||||
* Re-run buildout, e.g. with:
|
||||
|
||||
$ ./bin/buildout
|
||||
|
||||
You can skip the ZCML slug if you are going to explicitly include the package
|
||||
from another package's configure.zcml file.
|
|
@ -0,0 +1,222 @@
|
|||
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
|
|
@ -0,0 +1,16 @@
|
|||
plone.formwidget.captcha is copyright Timo Stollenwerk
|
||||
|
||||
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.
|
|
@ -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__)
|
|
@ -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__)
|
|
@ -0,0 +1,4 @@
|
|||
from zope.i18nmessageid import MessageFactory
|
||||
MessageFactory = MessageFactory('plone.formwidget.captcha')
|
||||
|
||||
from plone.formwidget.captcha.widget import CaptchaWidget
|
|
@ -0,0 +1,625 @@
|
|||
STARTFONT 2.1
|
||||
COMMENT
|
||||
COMMENT Converted from OpenType font "VeraMoIt.ttf" by "otf2bdf 3.0".
|
||||
COMMENT The Bitstream Vera fonts are available for free copying and
|
||||
COMMENT redistribution. They can be modified as long as the font name is
|
||||
COMMENT changed.
|
||||
COMMENT This one has been modified by conversion to BDF, so is now named
|
||||
COMMENT Arev Mono Italic, and has been reduced to only the characters used
|
||||
COMMENT by collective.captcha.
|
||||
COMMENT
|
||||
FONT -FreeType-collective.captcha Arev Mono-Medium-I-Normal--17-120-100-100-P-82-ASCII-1
|
||||
SIZE 12 100 100
|
||||
FONTBOUNDINGBOX 12 20 -1 -4
|
||||
CHARS 32
|
||||
STARTCHAR 0032
|
||||
ENCODING 50
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
1F00
|
||||
3180
|
||||
0180
|
||||
0180
|
||||
0180
|
||||
0300
|
||||
0600
|
||||
0C00
|
||||
1800
|
||||
3000
|
||||
E000
|
||||
FF00
|
||||
ENDCHAR
|
||||
STARTCHAR 0033
|
||||
ENCODING 51
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
3F00
|
||||
2380
|
||||
0180
|
||||
0180
|
||||
0300
|
||||
1C00
|
||||
0700
|
||||
0300
|
||||
0100
|
||||
0300
|
||||
8600
|
||||
FC00
|
||||
ENDCHAR
|
||||
STARTCHAR 0034
|
||||
ENCODING 52
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
0380
|
||||
0380
|
||||
0500
|
||||
0B00
|
||||
1B00
|
||||
3300
|
||||
6300
|
||||
4200
|
||||
FF80
|
||||
0600
|
||||
0600
|
||||
0600
|
||||
ENDCHAR
|
||||
STARTCHAR 0035
|
||||
ENCODING 53
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
1F80
|
||||
1000
|
||||
3000
|
||||
3000
|
||||
3E00
|
||||
2300
|
||||
0100
|
||||
0100
|
||||
0100
|
||||
0300
|
||||
C600
|
||||
FC00
|
||||
ENDCHAR
|
||||
STARTCHAR 0036
|
||||
ENCODING 54
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 8 12 1 0
|
||||
BITMAP
|
||||
0F
|
||||
31
|
||||
60
|
||||
40
|
||||
DC
|
||||
E6
|
||||
C3
|
||||
C3
|
||||
82
|
||||
86
|
||||
CC
|
||||
78
|
||||
ENDCHAR
|
||||
STARTCHAR 0037
|
||||
ENCODING 55
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 8 12 2 0
|
||||
BITMAP
|
||||
FF
|
||||
02
|
||||
06
|
||||
0C
|
||||
0C
|
||||
18
|
||||
10
|
||||
30
|
||||
60
|
||||
60
|
||||
C0
|
||||
C0
|
||||
ENDCHAR
|
||||
STARTCHAR 0038
|
||||
ENCODING 56
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
0F00
|
||||
3980
|
||||
3080
|
||||
2180
|
||||
3100
|
||||
1E00
|
||||
6300
|
||||
4100
|
||||
C100
|
||||
C300
|
||||
6700
|
||||
3C00
|
||||
ENDCHAR
|
||||
STARTCHAR 0039
|
||||
ENCODING 57
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 8 12 1 0
|
||||
BITMAP
|
||||
3C
|
||||
66
|
||||
43
|
||||
C3
|
||||
C3
|
||||
C3
|
||||
C7
|
||||
7A
|
||||
06
|
||||
04
|
||||
1C
|
||||
F0
|
||||
ENDCHAR
|
||||
STARTCHAR 0041
|
||||
ENCODING 65
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 -1 0
|
||||
BITMAP
|
||||
0300
|
||||
0700
|
||||
0700
|
||||
0D80
|
||||
0D80
|
||||
1980
|
||||
1980
|
||||
3180
|
||||
3F80
|
||||
6080
|
||||
6080
|
||||
C0C0
|
||||
ENDCHAR
|
||||
STARTCHAR 0042
|
||||
ENCODING 66
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
3F00
|
||||
3180
|
||||
3080
|
||||
3080
|
||||
2180
|
||||
7E00
|
||||
6300
|
||||
6180
|
||||
6180
|
||||
4180
|
||||
C300
|
||||
FE00
|
||||
ENDCHAR
|
||||
STARTCHAR 0043
|
||||
ENCODING 67
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 1 0
|
||||
BITMAP
|
||||
0F80
|
||||
3980
|
||||
3000
|
||||
6000
|
||||
4000
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
6200
|
||||
3C00
|
||||
ENDCHAR
|
||||
STARTCHAR 0044
|
||||
ENCODING 68
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
3E00
|
||||
3380
|
||||
2180
|
||||
2180
|
||||
6180
|
||||
6180
|
||||
6180
|
||||
4180
|
||||
4100
|
||||
C300
|
||||
CE00
|
||||
F800
|
||||
ENDCHAR
|
||||
STARTCHAR 0045
|
||||
ENCODING 69
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 1 0
|
||||
BITMAP
|
||||
3F80
|
||||
6000
|
||||
6000
|
||||
6000
|
||||
6000
|
||||
7F00
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
8000
|
||||
FE00
|
||||
ENDCHAR
|
||||
STARTCHAR 0046
|
||||
ENCODING 70
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 1 0
|
||||
BITMAP
|
||||
3F80
|
||||
2000
|
||||
2000
|
||||
6000
|
||||
6000
|
||||
7F00
|
||||
4000
|
||||
4000
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
ENDCHAR
|
||||
STARTCHAR 0047
|
||||
ENCODING 71
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 8 12 1 0
|
||||
BITMAP
|
||||
0F
|
||||
39
|
||||
60
|
||||
40
|
||||
C0
|
||||
C0
|
||||
C7
|
||||
83
|
||||
82
|
||||
C2
|
||||
E6
|
||||
7C
|
||||
ENDCHAR
|
||||
STARTCHAR 0048
|
||||
ENCODING 72
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 0 0
|
||||
BITMAP
|
||||
30C0
|
||||
30C0
|
||||
20C0
|
||||
2080
|
||||
6080
|
||||
7F80
|
||||
6180
|
||||
4180
|
||||
4100
|
||||
C100
|
||||
C300
|
||||
C300
|
||||
ENDCHAR
|
||||
STARTCHAR 004A
|
||||
ENCODING 74
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
0F80
|
||||
0180
|
||||
0100
|
||||
0100
|
||||
0300
|
||||
0300
|
||||
0300
|
||||
0200
|
||||
0200
|
||||
0600
|
||||
CC00
|
||||
F800
|
||||
ENDCHAR
|
||||
STARTCHAR 004B
|
||||
ENCODING 75
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 0 0
|
||||
BITMAP
|
||||
30C0
|
||||
3180
|
||||
2300
|
||||
2600
|
||||
6C00
|
||||
7C00
|
||||
7C00
|
||||
4600
|
||||
4600
|
||||
C300
|
||||
C300
|
||||
C180
|
||||
ENDCHAR
|
||||
STARTCHAR 004C
|
||||
ENCODING 76
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 7 12 1 0
|
||||
BITMAP
|
||||
30
|
||||
20
|
||||
60
|
||||
60
|
||||
60
|
||||
60
|
||||
40
|
||||
40
|
||||
C0
|
||||
C0
|
||||
C0
|
||||
FE
|
||||
ENDCHAR
|
||||
STARTCHAR 004D
|
||||
ENCODING 77
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 0 0
|
||||
BITMAP
|
||||
30C0
|
||||
31C0
|
||||
71C0
|
||||
7BC0
|
||||
6AC0
|
||||
4C80
|
||||
4C80
|
||||
C880
|
||||
C180
|
||||
C180
|
||||
8100
|
||||
8100
|
||||
ENDCHAR
|
||||
STARTCHAR 004E
|
||||
ENCODING 78
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 0 0
|
||||
BITMAP
|
||||
38C0
|
||||
38C0
|
||||
28C0
|
||||
2880
|
||||
6880
|
||||
6D80
|
||||
6D80
|
||||
4580
|
||||
4500
|
||||
C500
|
||||
C700
|
||||
C300
|
||||
ENDCHAR
|
||||
STARTCHAR 0050
|
||||
ENCODING 80
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 1 0
|
||||
BITMAP
|
||||
3F00
|
||||
6300
|
||||
6180
|
||||
6180
|
||||
6180
|
||||
4300
|
||||
FE00
|
||||
C000
|
||||
C000
|
||||
C000
|
||||
8000
|
||||
8000
|
||||
ENDCHAR
|
||||
STARTCHAR 0051
|
||||
ENCODING 81
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 8 14 1 -2
|
||||
BITMAP
|
||||
1E
|
||||
33
|
||||
61
|
||||
41
|
||||
C1
|
||||
C3
|
||||
C3
|
||||
83
|
||||
82
|
||||
86
|
||||
CC
|
||||
78
|
||||
08
|
||||
08
|
||||
ENDCHAR
|
||||
STARTCHAR 0052
|
||||
ENCODING 82
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
3F00
|
||||
3180
|
||||
3080
|
||||
2080
|
||||
2180
|
||||
6100
|
||||
7E00
|
||||
6600
|
||||
6300
|
||||
4100
|
||||
C100
|
||||
C180
|
||||
ENDCHAR
|
||||
STARTCHAR 0053
|
||||
ENCODING 83
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 0 0
|
||||
BITMAP
|
||||
0F80
|
||||
3880
|
||||
2000
|
||||
2000
|
||||
3000
|
||||
3C00
|
||||
0F00
|
||||
0100
|
||||
0180
|
||||
0100
|
||||
C700
|
||||
7C00
|
||||
ENDCHAR
|
||||
STARTCHAR 0054
|
||||
ENCODING 84
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 1 0
|
||||
BITMAP
|
||||
FFC0
|
||||
0C00
|
||||
0C00
|
||||
0800
|
||||
1800
|
||||
1800
|
||||
1800
|
||||
1800
|
||||
1000
|
||||
3000
|
||||
3000
|
||||
3000
|
||||
ENDCHAR
|
||||
STARTCHAR 0055
|
||||
ENCODING 85
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 1 0
|
||||
BITMAP
|
||||
6180
|
||||
6180
|
||||
6180
|
||||
4100
|
||||
C300
|
||||
C300
|
||||
C300
|
||||
C300
|
||||
8200
|
||||
8600
|
||||
CC00
|
||||
7800
|
||||
ENDCHAR
|
||||
STARTCHAR 0056
|
||||
ENCODING 86
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 2 0
|
||||
BITMAP
|
||||
8180
|
||||
8300
|
||||
8300
|
||||
C600
|
||||
C600
|
||||
CC00
|
||||
CC00
|
||||
C800
|
||||
D800
|
||||
5000
|
||||
7000
|
||||
6000
|
||||
ENDCHAR
|
||||
STARTCHAR 0057
|
||||
ENCODING 87
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 1 0
|
||||
BITMAP
|
||||
C0C0
|
||||
C0C0
|
||||
C080
|
||||
9980
|
||||
9980
|
||||
9900
|
||||
B900
|
||||
AB00
|
||||
EA00
|
||||
CE00
|
||||
CE00
|
||||
8C00
|
||||
ENDCHAR
|
||||
STARTCHAR 0058
|
||||
ENCODING 88
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 11 12 -1 0
|
||||
BITMAP
|
||||
1820
|
||||
0860
|
||||
0CC0
|
||||
0D80
|
||||
0700
|
||||
0600
|
||||
0600
|
||||
0F00
|
||||
1900
|
||||
3180
|
||||
6180
|
||||
C080
|
||||
ENDCHAR
|
||||
STARTCHAR 0059
|
||||
ENCODING 89
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 9 12 2 0
|
||||
BITMAP
|
||||
8180
|
||||
C300
|
||||
4600
|
||||
6C00
|
||||
6800
|
||||
3800
|
||||
3000
|
||||
3000
|
||||
2000
|
||||
2000
|
||||
6000
|
||||
6000
|
||||
ENDCHAR
|
||||
STARTCHAR 005A
|
||||
ENCODING 90
|
||||
SWIDTH 600 0
|
||||
DWIDTH 10 0
|
||||
BBX 10 12 0 0
|
||||
BITMAP
|
||||
3FC0
|
||||
00C0
|
||||
0180
|
||||
0300
|
||||
0600
|
||||
0600
|
||||
0C00
|
||||
1800
|
||||
3000
|
||||
6000
|
||||
C000
|
||||
FF00
|
||||
ENDCHAR
|
||||
ENDFONT
|
|
@ -0,0 +1,135 @@
|
|||
# Zope Captcha generation
|
||||
import os.path
|
||||
import random
|
||||
import re
|
||||
import sha
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
from skimpyGimpy import skimpyAPI
|
||||
|
||||
from zope.interface import implements
|
||||
from zope.component import getUtility
|
||||
from Acquisition import aq_inner
|
||||
from App.config import getConfiguration
|
||||
from Globals import package_home
|
||||
from Products.Five import BrowserView
|
||||
from plone.keyring.interfaces import IKeyManager
|
||||
|
||||
from interfaces import ICaptchaView
|
||||
|
||||
CHARS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||||
# note: no 0/o/O and i/I/1 confusion
|
||||
|
||||
COOKIE_ID = 'captchasessionid'
|
||||
WORDLENGTH = 7
|
||||
|
||||
_package_home = package_home(globals())
|
||||
WAVSOUNDS = os.path.join(_package_home, 'waveIndex.zip')
|
||||
VERAMONO = os.path.join(_package_home, 'arevmoit.bdf')
|
||||
|
||||
_TEST_TIME = None
|
||||
|
||||
class Captcha(BrowserView):
|
||||
implements(ICaptchaView)
|
||||
|
||||
_session_id = None
|
||||
__name__ = 'captcha'
|
||||
|
||||
def _setcookie(self, id):
|
||||
"""Set the session cookie"""
|
||||
resp = self.request.response
|
||||
if COOKIE_ID in resp.cookies:
|
||||
# clear the cookie first, clearing out any expiration cookie
|
||||
# that may have been set during verification
|
||||
del resp.cookies[COOKIE_ID]
|
||||
resp.setCookie(COOKIE_ID, id, path='/')
|
||||
|
||||
def _generate_session(self):
|
||||
"""Create a new session id"""
|
||||
if self._session_id is None:
|
||||
id = sha.new(str(random.randrange(sys.maxint))).hexdigest()
|
||||
self._session_id = id
|
||||
self._setcookie(id)
|
||||
|
||||
def _verify_session(self):
|
||||
"""Ensure session id and cookie exist"""
|
||||
if not self.request.has_key(COOKIE_ID):
|
||||
if self._session_id is None:
|
||||
# This may happen e.g. when the user clicks the back button
|
||||
self._generate_session()
|
||||
else:
|
||||
# This may happen e.g. when the user does not accept the cookie
|
||||
self._setcookie(self._session_id)
|
||||
# Put the cookie value into the request for immediate consumption
|
||||
self.request.cookies[COOKIE_ID] = self._session_id
|
||||
|
||||
def _generate_words(self):
|
||||
"""Create words for the current session
|
||||
|
||||
We generate one for the current 5 minutes, plus one for the previous
|
||||
5. This way captcha sessions have a livespan of 10 minutes at most.
|
||||
|
||||
"""
|
||||
session = self.request[COOKIE_ID]
|
||||
nowish = _TEST_TIME or int(time.time() / 300)
|
||||
secret = getUtility(IKeyManager).secret()
|
||||
seeds = [sha.new(secret + session + str(nowish)).digest(),
|
||||
sha.new(secret + session + str(nowish - 5)).digest()]
|
||||
|
||||
words = []
|
||||
for seed in seeds:
|
||||
word = []
|
||||
for i in range(WORDLENGTH):
|
||||
index = ord(seed[i]) % len(CHARS)
|
||||
word.append(CHARS[index])
|
||||
words.append(''.join(word))
|
||||
return words
|
||||
|
||||
def _url(self, type):
|
||||
return '%s/@@%s/%s' % (
|
||||
aq_inner(self.context).absolute_url(), self.__name__, type)
|
||||
|
||||
def image_tag(self):
|
||||
self._generate_session()
|
||||
return '<img src="%s" />' % (self._url('image'),)
|
||||
|
||||
def audio_url(self):
|
||||
self._generate_session()
|
||||
return self._url('audio')
|
||||
|
||||
def verify(self, input):
|
||||
result = False
|
||||
try:
|
||||
for word in self._generate_words():
|
||||
result = result or input.upper() == word.upper()
|
||||
# Delete the session key, we are done with this captcha
|
||||
self.request.response.expireCookie(COOKIE_ID, path='/')
|
||||
except KeyError:
|
||||
pass # No cookie
|
||||
|
||||
return result
|
||||
|
||||
# Binary data subpages
|
||||
|
||||
def _setheaders(self, type):
|
||||
resp = self.request.response
|
||||
resp.setHeader('content-type', type)
|
||||
# no caching please
|
||||
resp.setHeader('cache-control', 'no-cache, no-store')
|
||||
resp.setHeader('pragma', 'no-cache')
|
||||
resp.setHeader('expires', 'now')
|
||||
|
||||
def image(self):
|
||||
"""Generate a captcha image"""
|
||||
self._verify_session()
|
||||
self._setheaders('image/png')
|
||||
return skimpyAPI.Png(self._generate_words()[0],
|
||||
fontpath=VERAMONO).data()
|
||||
|
||||
def audio(self):
|
||||
"""Generate a captcha audio file"""
|
||||
self._verify_session()
|
||||
self._setheaders('audio/wav')
|
||||
return skimpyAPI.Wave(self._generate_words()[0], WAVSOUNDS).data()
|
|
@ -0,0 +1,206 @@
|
|||
========
|
||||
Captchas
|
||||
========
|
||||
|
||||
Generation
|
||||
----------
|
||||
|
||||
To use the captcha view, simply look up the view using the component architecture.
|
||||
|
||||
Here, we'll just import the view directly to demonstrate it's use:
|
||||
|
||||
>>> from collective.captcha.browser.captcha import Captcha, COOKIE_ID
|
||||
>>> request = DummyRequest()
|
||||
>>> context = DummyContext()
|
||||
>>> view = Captcha(context, request)
|
||||
|
||||
We can now use the view to generate an image tag, and a audio_url:
|
||||
|
||||
>>> view.image_tag()
|
||||
'<img src="dummyurl/@@captcha/image" />'
|
||||
>>> view.audio_url()
|
||||
'dummyurl/@@captcha/audio'
|
||||
|
||||
The request now holds the state in a cookie:
|
||||
|
||||
>>> COOKIE_ID in request.response.cookies
|
||||
True
|
||||
|
||||
Verification
|
||||
------------
|
||||
|
||||
With that state, we can verify that the user has given us the correct word. The view
|
||||
doesn't actually give us the word, but we can replay an existing session for the test:
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> request.cookies[COOKIE_ID] = '6552fec8867ee2a85a44784dda007e49efcf50ef'
|
||||
>>> view = Captcha(context, request)
|
||||
|
||||
The verify method, then, tells you if the user entered the correct word:
|
||||
|
||||
>>> view.verify('DLXV4XV')
|
||||
True
|
||||
|
||||
Note that the view immediately invalidates the cookie by expiring it:
|
||||
|
||||
>>> request.response.cookies[COOKIE_ID].get('expires', '')
|
||||
'Wed, 31-Dec-97 23:59:59 GMT'
|
||||
|
||||
The verify method works case-insensitively:
|
||||
|
||||
>>> view.verify('dlxv4xv')
|
||||
True
|
||||
|
||||
The words are valid for a 10 minute period, with a new word for the session every
|
||||
5 minutes. Thus, the word for the previous 5 minute period should still be valid:
|
||||
|
||||
>>> view.verify('YMFRQWT')
|
||||
True
|
||||
|
||||
Verification will fail for both incorrect input and a missing cookie:
|
||||
|
||||
>>> view.verify('incorrect')
|
||||
False
|
||||
|
||||
>>> del request.other[COOKIE_ID]
|
||||
>>> del request.cookies[COOKIE_ID]
|
||||
>>> view.verify('DLXV4XV')
|
||||
False
|
||||
|
||||
To facilitate displaying a new captcha when verification fails or validation
|
||||
of a form fails for other reasons, the view makes sure to not expire the
|
||||
cookie but to set a new value instead:
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> request.response.setCookie(COOKIE_ID, '6552fec8867ee2a85a44784dda007e49efcf50ef')
|
||||
>>> view = Captcha(context, request)
|
||||
|
||||
>>> request.response.cookies[COOKIE_ID].get('value', '') == '6552fec8867ee2a85a44784dda007e49efcf50ef'
|
||||
True
|
||||
|
||||
>>> view.audio_url()
|
||||
'dummyurl/@@captcha/audio'
|
||||
|
||||
>>> request.response.cookies[COOKIE_ID].get('expires', False)
|
||||
False
|
||||
>>> request.response.cookies[COOKIE_ID].get('value', '') == '6552fec8867ee2a85a44784dda007e49efcf50ef'
|
||||
False
|
||||
|
||||
Displaying
|
||||
----------
|
||||
|
||||
The point of course is that the end-user gets to view the captcha image or listen to the
|
||||
audio file:
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> request.cookies[COOKIE_ID] = '6552fec8867ee2a85a44784dda007e49efcf50ef'
|
||||
>>> view = Captcha(context, request)
|
||||
|
||||
>>> image = view.image()
|
||||
>>> image.startswith('\x89PNG')
|
||||
True
|
||||
>>> request.response.headers['content-type']
|
||||
'image/png'
|
||||
|
||||
>>> audio = view.audio()
|
||||
>>> audio.startswith('RIFF')
|
||||
True
|
||||
>>> request.response.headers['content-type']
|
||||
'audio/wav'
|
||||
|
||||
Bugs
|
||||
----
|
||||
|
||||
view.image() and view.audio() raise exceptions if there is no cookie
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> view = Captcha(context, request)
|
||||
|
||||
>>> view._session_id is None
|
||||
True
|
||||
|
||||
>>> COOKIE_ID in request
|
||||
False
|
||||
|
||||
>>> COOKIE_ID in request.response.cookies
|
||||
False
|
||||
|
||||
>>> image = view.image()
|
||||
>>> image.startswith('\x89PNG')
|
||||
True
|
||||
>>> request.response.headers['content-type']
|
||||
'image/png'
|
||||
|
||||
After the fix, we get a new session id and cookie instead
|
||||
|
||||
>>> view._session_id is None
|
||||
False
|
||||
|
||||
>>> COOKIE_ID in request
|
||||
True
|
||||
|
||||
>>> COOKIE_ID in request.response.cookies
|
||||
True
|
||||
|
||||
Same for audio
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> view = Captcha(context, request)
|
||||
|
||||
>>> audio = view.audio()
|
||||
>>> audio.startswith('RIFF')
|
||||
True
|
||||
>>> request.response.headers['content-type']
|
||||
'audio/wav'
|
||||
|
||||
>>> view._session_id is None
|
||||
False
|
||||
|
||||
>>> COOKIE_ID in request
|
||||
True
|
||||
|
||||
>>> COOKIE_ID in request.response.cookies
|
||||
True
|
||||
|
||||
Now execute the "impossible" branch, session_id without cookie:
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> view = Captcha(context, request)
|
||||
>>> view._session_id = 'foo'
|
||||
|
||||
>>> image = view.image()
|
||||
>>> image.startswith('\x89PNG')
|
||||
True
|
||||
>>> request.response.headers['content-type']
|
||||
'image/png'
|
||||
|
||||
>>> view._session_id
|
||||
'foo'
|
||||
|
||||
>>> COOKIE_ID in request
|
||||
True
|
||||
|
||||
>>> COOKIE_ID in request.response.cookies
|
||||
True
|
||||
|
||||
Same for audio
|
||||
|
||||
>>> request = DummyRequest()
|
||||
>>> view = Captcha(context, request)
|
||||
>>> view._session_id = 'foo'
|
||||
|
||||
>>> audio = view.audio()
|
||||
>>> audio.startswith('RIFF')
|
||||
True
|
||||
>>> request.response.headers['content-type']
|
||||
'audio/wav'
|
||||
|
||||
>>> view._session_id
|
||||
'foo'
|
||||
|
||||
>>> COOKIE_ID in request
|
||||
True
|
||||
|
||||
>>> COOKIE_ID in request.response.cookies
|
||||
True
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
i18n_domain="collective.captcha">
|
||||
|
||||
<browser:view
|
||||
name="captcha"
|
||||
for="*"
|
||||
permission="zope2.Public"
|
||||
provides=".interfaces.ICaptchaView"
|
||||
class=".captcha.Captcha"
|
||||
>
|
||||
|
||||
<browser:page name="image" attribute="image" />
|
||||
<browser:page name="audio" attribute="audio" />
|
||||
|
||||
</browser:view>
|
||||
|
||||
</configure>
|
|
@ -0,0 +1,38 @@
|
|||
from zope.interface import Interface
|
||||
|
||||
class ICaptchaView(Interface):
|
||||
"""Captcha generating and verifying view
|
||||
|
||||
Usage:
|
||||
|
||||
- Use the view from a page to generate an image tag and/or an audio
|
||||
URL. Use the 'image_tag' and 'audio_url' methods for these.
|
||||
|
||||
- Place the image tag and/or audio url in the page
|
||||
|
||||
- The image tag will load the captcha for the user, or the user will
|
||||
use the audio url to listen to the aural captcha.
|
||||
|
||||
- The user will identify the word, and tell the server through a form
|
||||
submission.
|
||||
|
||||
- Use the user input to verify.
|
||||
|
||||
The view will ensure that captcha state is preserved until verification
|
||||
has taken place. The image tag and audio url for a given instance of the
|
||||
captcha view will give the same word.
|
||||
|
||||
"""
|
||||
|
||||
def image_tag(self):
|
||||
"""Generate an image tag linking to a captcha"""
|
||||
|
||||
def audio_url(self):
|
||||
"""A URL for an aural captcha"""
|
||||
|
||||
def verify(self, input):
|
||||
"""Verify the user-supplied input.
|
||||
|
||||
Returns a boolean value indicating if the input matched
|
||||
|
||||
"""
|
|
@ -0,0 +1,42 @@
|
|||
import unittest
|
||||
from zope.component import provideUtility
|
||||
from zope.testing import doctest, cleanup
|
||||
from plone.keyring.interfaces import IKeyManager
|
||||
|
||||
# Set the secret and test time to constants to keep the tests workable
|
||||
import collective.captcha.browser.captcha as captcha
|
||||
captcha._TEST_TIME = 5
|
||||
|
||||
# Use a real Request and Response; there are too many subtleties
|
||||
from ZPublisher.Request import Request
|
||||
from ZPublisher.Response import Response
|
||||
|
||||
class DummyRequest(Request):
|
||||
def __init__(self):
|
||||
env = {'SERVER_NAME': 'nohost',
|
||||
'SERVER_PORT': '80',
|
||||
'REQUEST_METHOD': 'GET'}
|
||||
Request.__init__(self, None, env, Response())
|
||||
|
||||
class DummyContext(object):
|
||||
def absolute_url(self):
|
||||
return 'dummyurl'
|
||||
|
||||
class DummyKeyManager(object):
|
||||
def secret(self):
|
||||
return 'tests-only-stable-value'
|
||||
|
||||
def captchaSetUp(test):
|
||||
provideUtility(DummyKeyManager(), IKeyManager)
|
||||
|
||||
def tearDown(test):
|
||||
cleanup.cleanUp()
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite((
|
||||
doctest.DocFileSuite('captcha.txt', globs=globals(),
|
||||
setUp=captchaSetUp, tearDown=tearDown),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest="test_suite")
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
<div class="captchaImage"
|
||||
tal:content="structure view/captchaImage"></div>
|
||||
<div class="captchaAudio">
|
||||
<a href="" target="_blank"
|
||||
tal:attributes="href view/captchaAudio">Listen to audio for this captcha</a>
|
||||
</div>
|
||||
<input id="form-widgets-captcha" class="text-widget textline-field" type="text" value="" name="form.widgets.captcha"/>
|
|
@ -0,0 +1,59 @@
|
|||
<configure
|
||||
xmlns="http://namespaces.zope.org/zope"
|
||||
xmlns:z3c="http://namespaces.zope.org/z3c"
|
||||
xmlns:five="http://namespaces.zope.org/five"
|
||||
xmlns:browser="http://namespaces.zope.org/browser"
|
||||
xmlns:gs="http://namespaces.zope.org/genericsetup"
|
||||
i18n_domain="plone.formwidget.captcha">
|
||||
|
||||
<include package="plone.z3cform" />
|
||||
|
||||
<include package=".browser" />
|
||||
|
||||
<class class=".widget.CaptchaWidget">
|
||||
<require
|
||||
permission="zope.Public"
|
||||
interface="plone.formwidget.captcha.interfaces.ICaptchaWidget"
|
||||
/>
|
||||
</class>
|
||||
|
||||
<!-- this widget is not configured for any field by default -->
|
||||
<z3c:widgetTemplate
|
||||
mode="display"
|
||||
widget="plone.formwidget.captcha.interfaces.ICaptchaWidget"
|
||||
layer="z3c.form.interfaces.IFormLayer"
|
||||
template="widget.pt"
|
||||
/>
|
||||
|
||||
<z3c:widgetTemplate
|
||||
mode="input"
|
||||
widget="plone.formwidget.captcha.interfaces.ICaptchaWidget"
|
||||
layer="z3c.form.interfaces.IFormLayer"
|
||||
template="captcha_input.pt"
|
||||
/>
|
||||
|
||||
<!--
|
||||
<browser:page
|
||||
name="autocomplete-search"
|
||||
for=".interfaces.IAutocompleteWidget"
|
||||
permission="zope.Public"
|
||||
class=".widget.AutocompleteSearch"
|
||||
/>-->
|
||||
|
||||
<!--<browser:resourceDirectory
|
||||
name="plone.formwidget.autocomplete"
|
||||
directory="jquery-autocomplete"
|
||||
/>-->
|
||||
<!--
|
||||
<gs:registerProfile
|
||||
name="default"
|
||||
title="Captcha widget"
|
||||
directory="profiles/default"
|
||||
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
||||
provides="Products.GenericSetup.interfaces.EXTENSION"
|
||||
/>-->
|
||||
|
||||
<!-- Demo view for testing -->
|
||||
<!-- <include file="demo.zcml" /> -->
|
||||
|
||||
</configure>
|
|
@ -0,0 +1,5 @@
|
|||
from zope.interface import Interface
|
||||
|
||||
class ICaptchaWidget(Interface):
|
||||
"""Marker interface for the captcha widget
|
||||
"""
|
|
@ -0,0 +1,54 @@
|
|||
import unittest
|
||||
|
||||
from zope.testing import doctestunit
|
||||
from zope.component import testing
|
||||
from Testing import ZopeTestCase as ztc
|
||||
|
||||
from Products.Five import zcml
|
||||
from Products.Five import fiveconfigure
|
||||
from Products.PloneTestCase import PloneTestCase as ptc
|
||||
from Products.PloneTestCase.layer import PloneSite
|
||||
ptc.setupPloneSite()
|
||||
|
||||
import plone.formwidget.captcha
|
||||
|
||||
class TestCase(ptc.PloneTestCase):
|
||||
class layer(PloneSite):
|
||||
@classmethod
|
||||
def setUp(cls):
|
||||
fiveconfigure.debug_mode = True
|
||||
zcml.load_config('configure.zcml',
|
||||
plone.formwidget.captcha)
|
||||
fiveconfigure.debug_mode = False
|
||||
|
||||
@classmethod
|
||||
def tearDown(cls):
|
||||
pass
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestSuite([
|
||||
|
||||
# Unit tests
|
||||
#doctestunit.DocFileSuite(
|
||||
# 'README.txt', package='plone.formwidget.captcha',
|
||||
# setUp=testing.setUp, tearDown=testing.tearDown),
|
||||
|
||||
#doctestunit.DocTestSuite(
|
||||
# module='plone.formwidget.captcha.mymodule',
|
||||
# setUp=testing.setUp, tearDown=testing.tearDown),
|
||||
|
||||
|
||||
# Integration tests that use PloneTestCase
|
||||
#ztc.ZopeDocFileSuite(
|
||||
# 'README.txt', package='plone.formwidget.captcha',
|
||||
# test_class=TestCase),
|
||||
|
||||
#ztc.FunctionalDocFileSuite(
|
||||
# 'browser.txt', package='plone.formwidget.captcha',
|
||||
# test_class=TestCase),
|
||||
|
||||
])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
|
@ -0,0 +1,5 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
||||
tal:omit-tag="">
|
||||
<h1>hi widget</h1>
|
||||
</html>
|
|
@ -0,0 +1,28 @@
|
|||
import zope.component
|
||||
import zope.interface
|
||||
import zope.schema.interfaces
|
||||
|
||||
from Acquisition import aq_inner
|
||||
from z3c.form import interfaces
|
||||
from z3c.form import widget
|
||||
from z3c.form import converter
|
||||
from z3c.form.browser import text
|
||||
from interfaces import ICaptchaWidget
|
||||
|
||||
class CaptchaWidget(text.TextWidget):
|
||||
maxlength = 7
|
||||
size = 8
|
||||
|
||||
zope.interface.implementsOnly(ICaptchaWidget)
|
||||
|
||||
def captchaImage(self):
|
||||
self.captcha = zope.component.getMultiAdapter((aq_inner(self.context), self.request), name='captcha')
|
||||
return self.captcha.image_tag()
|
||||
|
||||
def captchaAudio(self):
|
||||
self.captcha = zope.component.getMultiAdapter((aq_inner(self.context), self.request), name='captcha')
|
||||
return self.captcha.audio_url()
|
||||
|
||||
def CaptchaFieldWidget(field, request):
|
||||
"""IFieldWidget factory for CaptchaWidget."""
|
||||
return widget.FieldWidget(field, CaptchaWidget(request))
|
|
@ -0,0 +1,7 @@
|
|||
[zopeskel]
|
||||
template = plone
|
||||
|
||||
[egg_info]
|
||||
tag_build = dev
|
||||
tag_svn_revision = true
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
from setuptools import setup, find_packages
|
||||
import os
|
||||
|
||||
version = '1.0'
|
||||
|
||||
setup(name='plone.formwidget.captcha',
|
||||
version=version,
|
||||
description="",
|
||||
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='',
|
||||
author='Timo Stollenwerk',
|
||||
author_email='timo@zmag.de',
|
||||
url='http://svn.plone.org/svn/plone/plone.example',
|
||||
license='GPL',
|
||||
packages=find_packages(exclude=['ez_setup']),
|
||||
namespace_packages=['plone'],
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
'setuptools',
|
||||
# -*- Extra requirements: -*-
|
||||
'plone.z3cform',
|
||||
'skimpyGimpy',
|
||||
'plone.keyring > 1.0',
|
||||
],
|
||||
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
|
||||
""",
|
||||
paster_plugins = ["ZopeSkel"],
|
||||
)
|
Reference in New Issue