Explorar el Código

Adding new type called captch (merge from 1.9.x)

Julio Montoya hace 11 años
padre
commit
8d078f6ba2

+ 268 - 0
main/inc/lib/pear/HTML/QuickForm/CAPTCHA.php

@@ -0,0 +1,268 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+/**
+ * Common class for HTML_QuickForm elements to display a CAPTCHA
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA question (image, riddle, etc...)
+ *
+ * This package requires the use of a PHP session ($_SESSION).
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2006-2008, Philippe Jausions / 11abacus
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   - Neither the name of 11abacus nor the names of its contributors may
+ *     be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   CVS: $Id: CAPTCHA.php,v 1.1 2008/04/26 23:27:28 jausions Exp $
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ */
+
+/**
+ * Required packages
+ */
+require_once 'HTML/QuickForm/input.php';
+require_once 'Text/CAPTCHA.php';
+
+/**
+ * Common class for HTML_QuickForm elements to display a CAPTCHA
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA question (image, riddle, etc...)
+ *
+ * This package requires the use of a PHP session ($_SESSION).
+ *
+ * Because the CAPTCHA element is serialized in the PHP session,
+ * you need to include the class declaration BEFORE the session starts.
+ * So BEWARE if you have php.ini session.auto_start enabled, you won't be
+ * able to use this element, unless you're also using PHP 5's __autoload()
+ * or php.ini's unserialize_callback_func setting
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   Release: 0.3.0
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ * @abstract
+ */
+class HTML_QuickForm_CAPTCHA extends HTML_QuickForm_input
+{
+    /**
+     * Default options
+     *
+     * @var array
+     * @access protected
+     */
+    var $_options = array(
+            'sessionVar' => '_HTML_QuickForm_CAPTCHA',
+            'phrase'     => null,
+            );
+
+    /**
+     * CAPTCHA driver
+     *
+     * @var string
+     * @access protected
+     */
+    var $_CAPTCHA_driver;
+
+    /**
+     * Class constructor
+     *
+     * @param string $elementName  Name
+     * @param mixed  $elementLabel Label for the CAPTCHA
+     * @param array  $options      Options for the Text_CAPTCHA package
+     * <ul>
+     *  <li>'sessionVar' (string)  name of session variable containing
+     *                             the Text_CAPTCHA instance (defaults to
+     *                             _HTML_QuickForm_CAPTCHA.)</li>
+     *  <li>Other options depend on the driver used</li>
+     * </ul>
+     * @param mixed  $attributes   HTML Attributes for the <a> tag surrounding
+     *                             the image. Can be a string or array.
+     *
+     * @access public
+     */
+    function HTML_QuickForm_CAPTCHA($elementName = null, $elementLabel = null,
+                                    $options = null, $attributes = null)
+    {
+        HTML_QuickForm_input::HTML_QuickForm_input($elementName, $elementLabel, $attributes);
+        $this->setType('CAPTCHA_'.$this->_CAPTCHA_driver);
+
+        if (is_array($options)) {
+            $this->_options = array_merge($this->_options, $options);
+        }
+    }
+
+    /**
+     * Initializes the CAPTCHA instance (if needed)
+     *
+     * @return boolean TRUE or PEAR_Error on error
+     * @access protected
+     */
+    function _initCAPTCHA()
+    {
+        $sessionVar = $this->_options['sessionVar'];
+
+        if (empty($_SESSION[$sessionVar])) {
+
+            $_SESSION[$sessionVar] = Text_CAPTCHA::factory($this->_CAPTCHA_driver);
+
+            if (PEAR::isError($_SESSION[$sessionVar])) {
+                return $_SESSION[$sessionVar];
+            }
+            $result = $_SESSION[$sessionVar]->init($this->_options);
+            if (PEAR::isError($result)) {
+                return $result;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the answer/phrase of the CAPTCHA
+     *
+     * @param mixed &$values Ignored by this element
+     *
+     * @return string
+     * @access private
+     */
+    function _findValue(&$values)
+    {
+        return $this->getValue();
+    }
+
+    /**
+     * Returns the answer/phrase of the CAPTCHA
+     *
+     * @return string
+     * @access public
+     */
+    function getValue()
+    {
+        $sessionVar = $this->_options['sessionVar'];
+
+        return (!empty($_SESSION[$sessionVar]))
+                 ? $_SESSION[$sessionVar]->getPhrase()
+                 : null;
+    }
+
+    /**
+     * Returns the answer/phrase of the CAPTCHA
+     *
+     * @param mixed   &$submitValues Ignored by this element
+     * @param boolean $assoc         Whether to return an array
+     *
+     * @return string
+     * @access public
+     */
+    function exportValue(&$submitValues, $assoc = false)
+    {
+        return ($assoc)
+               ? array($this->getName() => $this->getValue())
+               : $this->getValue();
+    }
+
+    /**
+     * Sets the CAPTCHA question/phrase
+     *
+     * Pass NULL or no argument for a random question/phrase to be generated
+     *
+     * @param string $phrase Value of the CAPTCHA to set
+     *
+     * @return void
+     * @access public
+     */
+    function setPhrase($phrase = null)
+    {
+        $this->_options['phrase'] = $phrase;
+
+        if (!empty($_SESSION[$this->_options['sessionVar']])) {
+            $_SESSION[$this->_options['sessionVar']]->setPhrase($phrase);
+        }
+    }
+
+    /**
+     * Destroys the CAPTCHA instance to prevent reuse
+     *
+     * @return void
+     * @access public
+     */
+    function destroy()
+    {
+        unset($_SESSION[$this->_options['sessionVar']]);
+    }
+
+    /**
+     * Returns the HTML for the CAPTCHA
+     *
+     * This can be overwritten by sub-classes for specific output behavior
+     * (for instance the Image CAPTCHA displays an image)
+     *
+     * @return string
+     * @access public
+     */
+    function toHtml()
+    {
+        $result = $this->_initCAPTCHA();
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        $captcha = $_SESSION[$this->_options['sessionVar']]->getCAPTCHA();
+
+        $attr = $this->_attributes;
+        unset($attr['type']);
+        unset($attr['value']);
+        unset($attr['name']);
+
+        $html = $this->_getTabs()
+                . '<span' . $this->_getAttrString($attr) . '>'
+                . htmlspecialchars($captcha)
+                . '</span>';
+        return $html;
+    }
+}
+
+/**
+ * Register the rule with QuickForm
+ */
+require_once 'HTML/QuickForm/Rule/CAPTCHA.php';
+
+?>

+ 93 - 0
main/inc/lib/pear/HTML/QuickForm/CAPTCHA/Equation.php

@@ -0,0 +1,93 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA equation
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA equation.
+ *
+ * This package requires the use of a PHP session.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   CVS: $Id: Equation.php,v 1.1 2008/04/26 23:27:30 jausions Exp $
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ */
+
+/**
+ * Required packages
+ */
+require_once 'HTML/QuickForm/CAPTCHA.php';
+require_once 'Text/CAPTCHA/Driver/Equation.php';
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA equation question
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA equation question.
+ *
+ * Options for the element
+ * <ul>
+ *  <li>'min'           (integer)  Minimal number to use in an equation.</li>
+ *  <li>'max'           (integer)  Maximal number to use in an equation.</li>
+ *  <li>'severity'      (integer)  Complexity of the equation to resolve
+ *                                 (1 = easy, 2 = harder)</li>
+ *  <li>'numbersToText' (boolean)  Whether to use the Numbers_Words
+ *                                 package to convert numbers to text,</li>
+ *  <li>'sessionVar'    (string)   name of session variable containing
+ *                                 the Text_CAPTCHA instance (defaults to
+ *                                 _HTML_QuickForm_CAPTCHA.)</li>
+ * </ul>
+ *
+ * This package requires the use of a PHP session.
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   Release: 0.3.0
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ * @see       Text_CAPTCHA_Driver_Equation
+ */
+class HTML_QuickForm_CAPTCHA_Equation extends HTML_QuickForm_CAPTCHA
+{
+    /**
+     * Default options
+     *
+     * @var    array
+     * @access protected
+     */
+    var $_options = array(
+            'sessionVar'    => '_HTML_QuickForm_CAPTCHA',
+            'severity'      => 1,
+            'numbersToText' => false,
+            'phrase'        => null,
+            );
+
+    /**
+     * CAPTCHA driver
+     *
+     * @var    string
+     * @access protected
+     */
+    var $_CAPTCHA_driver = 'Equation';
+}
+
+/**
+ * Registers the class with QuickForm
+ */
+if (class_exists('HTML_QuickForm')) {
+    HTML_QuickForm::registerElementType('CAPTCHA_Equation',
+            'HTML/QuickForm/CAPTCHA/Equation.php',
+            'HTML_QuickForm_CAPTCHA_Equation');
+}
+
+?>

+ 126 - 0
main/inc/lib/pear/HTML/QuickForm/CAPTCHA/Figlet.php

@@ -0,0 +1,126 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA figlet
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA figlet.
+ *
+ * This package requires the use of a PHP session.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   CVS: $Id: Figlet.php,v 1.1 2008/04/26 23:27:30 jausions Exp $
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ */
+
+/**
+ * Required packages
+ */
+require_once 'HTML/QuickForm/CAPTCHA.php';
+require_once 'Text/CAPTCHA/Driver/Figlet.php';
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA figlet
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA figlet
+ *
+ * Options for the element
+ * <ul>
+ *  <li>'width'      (integer)  Width of figlet (default is 200px)</li>
+ *  <li>'output'     (string)   Output format: "html", "text" or
+ *                              "javascript" (default is "html").</li>
+ *  <li>'length'     (integer)  number of letters in the figlet
+ *                              (default is 6)</li>
+ *  <li>'options'    (array)    only index supported is "font_file", which
+ *                              should either be one figlet font file path,
+ *                              or an array of figlet font file paths
+ *                              (one we be picked randomly)</li>
+ *  <li>'sessionVar' (string)   name of session variable containing
+ *                              the Text_CAPTCHA instance (defaults to
+ *                              _HTML_QuickForm_CAPTCHA.)</li>
+ * </ul>
+ *
+ * This package requires the use of a PHP session.
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   Release: 0.3.0
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ * @see       Text_CAPTCHA_Driver_Figlet
+ */
+class HTML_QuickForm_CAPTCHA_Figlet extends HTML_QuickForm_CAPTCHA
+{
+    /**
+     * Default options
+     *
+     * @var    array
+     * @access protected
+     */
+    var $_options = array(
+            'sessionVar'    => '_HTML_QuickForm_CAPTCHA',
+            'output'        => 'html',
+            'width'         => 200,
+            'length'        => 6,
+            'phrase'        => null,
+            );
+
+    /**
+     * CAPTCHA driver
+     *
+     * @var    string
+     * @access protected
+     */
+    var $_CAPTCHA_driver = 'Figlet';
+
+
+    /**
+     * Returns the HTML for the CAPTCHA
+     *
+     * This can be overwritten by sub-classes for specific output behavior
+     * (for instance the Image CAPTCHA displays an image)
+     *
+     * @access public
+     * @return string
+     */
+    function toHtml()
+    {
+        $result = $this->_initCAPTCHA();
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        $attr = $this->_attributes;
+        unset($attr['type']);
+        unset($attr['value']);
+        unset($attr['name']);
+
+        $html = $this->_getTabs()
+                . '<div' . $this->_getAttrString($attr) . '>'
+                . $_SESSION[$this->_options['sessionVar']]->getCAPTCHA()
+                . '</div>';
+        return $html;
+    }
+}
+
+/**
+ * Registers the class with QuickForm
+ */
+if (class_exists('HTML_QuickForm')) {
+    HTML_QuickForm::registerElementType('CAPTCHA_Figlet',
+            'HTML/QuickForm/CAPTCHA/Figlet.php',
+            'HTML_QuickForm_CAPTCHA_Figlet');
+}
+
+?>

+ 160 - 0
main/inc/lib/pear/HTML/QuickForm/CAPTCHA/Image.php

@@ -0,0 +1,160 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA image
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA image.
+ *
+ * This package requires the use of a PHP session.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   CVS: $Id: Image.php,v 1.1 2008/04/26 23:27:30 jausions Exp $
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ */
+
+/**
+ * Required packages
+ */
+require_once 'HTML/QuickForm/CAPTCHA.php';
+require_once 'Text/CAPTCHA/Driver/Image.php';
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA image
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA image.
+ *
+ * Options for the element
+ * <ul>
+ *  <li>'width'        (integer) width of the image,</li>
+ *  <li>'height'       (integer) height of the image,</li>
+ *  <li>'imageOptions' (array)   options passed to the Image_Text
+ *                               constructor,</li>
+ *  <li>'callback'     (string)  URL of callback script that will generate
+ *                               and output the image itself,</li>
+ *  <li>'alt'          (string)  the alt text for the image,</li>
+ *  <li>'sessionVar'   (string)  name of session variable containing
+ *                               the Text_CAPTCHA instance (defaults to
+ *                               _HTML_QuickForm_CAPTCHA.)</li>
+ * </ul>
+ *
+ * This package requires the use of a PHP session.
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   Release: 0.3.0
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ * @see       Text_CAPTCHA_Driver_Image
+ */
+class HTML_QuickForm_CAPTCHA_Image extends HTML_QuickForm_CAPTCHA
+{
+
+    function HTML_QuickForm_CAPTCHA_Image($elementName = null, $elementLabel = null, $options = null, $attributes = null) {
+        return parent::HTML_QuickForm_CAPTCHA($elementName, $elementLabel, $options, $attributes);
+    }
+
+    /**
+     * Default options
+     *
+     * @var    array
+     * @access protected
+     */
+    var $_options = array(
+            'sessionVar'   => '_HTML_QuickForm_CAPTCHA',
+            'width'        => '200',
+            'height'       => '80',
+            'alt'          => 'Click to view another image',
+            'callback'     => '',
+            'imageOptions' => null,
+            'phrase'       => null,
+    );
+
+    /**
+     * CAPTCHA driver
+     *
+     * @var    string
+     * @access protected
+     */
+    var $_CAPTCHA_driver = 'Image';
+
+    /**
+     * Returns the HTML for the CAPTCHA image
+     *
+     * @return string
+     * @access public
+     */
+    function toHtml()
+    {
+        if ($this->_flagFrozen) {
+            return '';
+        }
+
+        $result = parent::_initCAPTCHA();
+
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        $html      = '';
+        $tabs      = $this->_getTabs();
+        $inputName = $this->getName();
+        $imgName   = 'QF_CAPTCHA_'.$inputName;
+
+        if ($this->getComment() != '') {
+            $html .= $tabs.'<!-- '.$this->getComment().' // -->';
+        }
+
+        $attr = $this->_attributes;
+        unset($attr['type']);
+        unset($attr['value']);
+        unset($attr['name']);
+
+        $html = $tabs.'<a href="'.$this->_options['callback']
+               .'" target="_blank" '
+               .$this->_getAttrString($attr)
+               .' onclick="var cancelClick = false; '
+               .$this->getOnclickJs($imgName)
+               .' return !cancelClick;"><img src="'
+               .$this->_options['callback'].'" name="'.$imgName
+               .'" id="'.$imgName.'" width="'.$this->_options['width']
+               .'" height="'.$this->_options['height'].'" title="'
+               .htmlspecialchars($this->_options['alt']).'" /></a>';
+
+        return $html;
+    }
+
+    /**
+     * Creates the javascript for the onclick event which will
+     * reload a new CAPTCHA image
+     *
+     * @param string $imageName The image name/id
+     *
+     * @return string
+     * @access public
+     */
+    function getOnclickJs($imageName)
+    {
+        $onclickJs = ''
+            .'if (document.images) {'
+            .'  var img = new Image();'
+            .'  var d = new Date();'
+            .'  img.src = this.href + ((this.href.indexOf(\'?\') == -1) '
+                                     .'? \'?\' : \'&\') + d.getTime();'
+            .'  document.images[\''.addslashes($imageName).'\'].src = img.src;'
+            .'  cancelClick = true;'
+            .'}';
+        return $onclickJs;
+    }
+}

+ 90 - 0
main/inc/lib/pear/HTML/QuickForm/CAPTCHA/Word.php

@@ -0,0 +1,90 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA "Word"
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA "word".
+ *
+ * This package requires the use of a PHP session.
+ *
+ * PHP versions 4 and 5
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   CVS: $Id: Word.php,v 1.1 2008/04/26 23:27:30 jausions Exp $
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ */
+
+/**
+ * Required packages
+ */
+require_once 'HTML/QuickForm/CAPTCHA.php';
+require_once 'Text/CAPTCHA/Driver/Word.php';
+
+/**
+ * Element for HTML_QuickForm to display a CAPTCHA "word" question
+ *
+ * The HTML_QuickForm_CAPTCHA package adds an element to the
+ * HTML_QuickForm package to display a CAPTCHA "word" question.
+ *
+ * Options for the element
+ * <ul>
+ *  <li>'length'     (integer)  the length of the Word.</li>
+ *  <li>'mode'       (string)   'single' for separated words.</li>
+ *  <li>'locale'     (string)   locale for Numbers_Words package</li>
+ *  <li>'sessionVar' (string)   name of session variable containing
+ *                              the Text_CAPTCHA instance (defaults to
+ *                              _HTML_QuickForm_CAPTCHA.)</li>
+ * </ul>
+ *
+ * This package requires the use of a PHP session.
+ *
+ * @category  HTML
+ * @package   HTML_QuickForm_CAPTCHA
+ * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2006-2008 by Philippe Jausions / 11abacus
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
+ * @version   Release: 0.3.0
+ * @link      http://pear.php.net/package/HTML_QuickForm_CAPTCHA
+ * @see       Text_CAPTCHA_Driver_Equation
+ */
+class HTML_QuickForm_CAPTCHA_Word extends HTML_QuickForm_CAPTCHA
+{
+    /**
+     * Default options
+     *
+     * @var    array
+     * @access protected
+     */
+    var $_options = array(
+            'sessionVar' => '_HTML_QuickForm_CAPTCHA',
+            'length'     => 4,
+            'mode'       => 'single',
+            'locale'     => 'en_US',
+            'phrase'     => null,
+            );
+
+    /**
+     * CAPTCHA driver
+     *
+     * @var    string
+     * @access protected
+     */
+    var $_CAPTCHA_driver = 'Word';
+}
+
+/**
+ * Registers the class with QuickForm
+ */
+if (class_exists('HTML_QuickForm')) {
+    HTML_QuickForm::registerElementType('CAPTCHA_Word',
+            'HTML/QuickForm/CAPTCHA/Word.php', 'HTML_QuickForm_CAPTCHA_Word');
+}
+
+?>

+ 1183 - 0
main/inc/lib/pear/Image/Text.php

@@ -0,0 +1,1183 @@
+<?php
+/**
+ * Image_Text.
+ *
+ * This is the main file of the Image_Text package. This file has to be included for
+ * usage of Image_Text.
+ *
+ * This is a simple example script, showing Image_Text's facilities.
+ *
+ * PHP version 5
+ *
+ * @category  Image
+ * @package   Image_Text
+ * @author    Tobias Schlitt <toby@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license   http://www.php.net/license/3_01.txt PHP License
+ * @version   CVS: $Id$
+ * @link      http://pear.php.net/package/Image_Text
+ * @since     File available since Release 0.0.1
+ */
+require_once 'Image/Text/Exception.php';
+/**
+ * Image_Text - Advanced text manipulations in images.
+ *
+ * Image_Text provides advanced text manipulation facilities for GD2 image generation
+ * with PHP. Simply add text clippings to your images, let the class automatically
+ * determine lines, rotate text boxes around their center or top left corner. These
+ * are only a couple of features Image_Text provides.
+ *
+ * @category  Image
+ * @package   Image_Text
+ * @author    Tobias Schlitt <toby@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license   http://www.php.net/license/3_01.txt PHP License
+ * @version   Release: @package_version@
+ * @link      http://pear.php.net/package/Image_Text
+ * @since     0.0.1
+ */
+class Image_Text
+{
+    /**
+     * Regex to match HTML style hex triples.
+     */
+    const IMAGE_TEXT_REGEX_HTMLCOLOR
+        = "/^[#|]([a-f0-9]{2})?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i";
+
+    /**
+     * Defines horizontal alignment to the left of the text box. (This is standard.)
+     */
+    const IMAGE_TEXT_ALIGN_LEFT = "left";
+
+    /**
+     * Defines horizontal alignment to the center of the text box.
+     */
+    const IMAGE_TEXT_ALIGN_RIGHT = "right";
+
+    /**
+     * Defines horizontal alignment to the center of the text box.
+     */
+    const IMAGE_TEXT_ALIGN_CENTER = "center";
+
+    /**
+     * Defines vertical alignment to the to the top of the text box. (This is
+     * standard.)
+     */
+    const IMAGE_TEXT_ALIGN_TOP = "top";
+
+    /**
+     * Defines vertical alignment to the to the middle of the text box.
+     */
+    const IMAGE_TEXT_ALIGN_MIDDLE = "middle";
+
+    /**
+     * Defines vertical alignment to the to the bottom of the text box.
+     */
+    const IMAGE_TEXT_ALIGN_BOTTOM = "bottom";
+
+    /**
+     * TODO: This constant is useless until now, since justified alignment does not
+     * work yet
+     */
+    const IMAGE_TEXT_ALIGN_JUSTIFY = "justify";
+
+    /**
+     * Options array. these options can be set through the constructor or the set()
+     * method.
+     *
+     * Possible options to set are:
+     * <pre>
+     *   'x'                | This sets the top left coordinates (using x/y) or the
+     *   'y'                | center point coordinates (using cx/cy) for your text
+     *   'cx'               | box. The values from cx/cy will overwrite x/y.
+     *   'cy'               |
+     *
+     *   'canvas'           | You can set different values as a canvas:
+     *                      |   - A gd image resource.
+     *                      |   - An array with 'width' and 'height'.
+     *                      |   - Nothing (the canvas will be measured after the real
+     *                      |     text size).
+     *
+     *   'antialias'        | This is usually true. Set it to false to switch
+     *                      | antialiasing off.
+     *
+     *   'width'            | The width and height for your text box.
+     *   'height'           |
+     *
+     *   'halign'           | Alignment of your text inside the textbox. Use
+     *   'valign'           | alignment constants to define vertical and horizontal
+     *                      | alignment.
+     *
+     *   'angle'            | The angle to rotate your text box.
+     *
+     *   'color'            | An array of color values. Colors will be rotated in the
+     *                      | mode you choose (linewise or paragraphwise). Can be in
+     *                      | the following formats:
+     *                      |   - String representing HTML style hex couples
+     *                      |     (+ unusual alpha couple in the first place,
+     *                      |      optional).
+     *                      |   - Array of int values using 'r', 'g', 'b' and
+     *                      |     optionally 'a' as keys.
+     *
+     *   'color_mode'       | The color rotation mode for your color sets. Does only
+     *                      | apply if you defined multiple colors. Use 'line' or
+     *                      | 'paragraph'.
+     *
+     *   'background_color' | defines the background color. NULL sets it transparent
+     *   'enable_alpha'     | if alpha channel should be enabled. Automatically
+     *                      | enabled when background_color is set to NULL
+     *
+     *   'font_path'        | Location of the font to use. The path only gives the
+     *                      | directory path (ending with a /).
+     *   'font_file'        | The fontfile is given in the 'font_file' option.
+     *
+     *   'font_size'        | The font size to render text in (will be overwriten, if
+     *                      | you use automeasurize).
+     *
+     *   'line_spacing'     | Measure for the line spacing to use. Default is 0.5.
+     *
+     *   'min_font_size'    | Automeasurize settings. Try to keep this area as small
+     *   'max_font_size'    | as possible to get better performance.
+     *
+     *   'image_type'       | The type of image (use image type constants). Is
+     *                      | default set to PNG.
+     *
+     *   'dest_file'        | The destination to (optionally) save your file.
+     * </pre>
+     *
+     * @var array
+     * @see Image_Text::set()
+     */
+
+    private $_options = array(
+        // orientation
+        'x' => 0,
+        'y' => 0,
+
+        // surface
+        'canvas' => null,
+        'antialias' => true,
+
+        // text clipping
+        'width' => 0,
+        'height' => 0,
+
+        // text alignment inside the clipping
+        'halign' => self::IMAGE_TEXT_ALIGN_LEFT,
+        'valign' => self::IMAGE_TEXT_ALIGN_TOP,
+
+        // angle to rotate the text clipping
+        'angle' => 0,
+
+        // color settings
+        'color' => array('#000000'),
+
+        'color_mode' => 'line',
+
+        'background_color' => '#000000',
+        'enable_alpha' => false,
+
+        // font settings
+        'font_path' => "./",
+        'font_file' => null,
+        'font_size' => 2,
+        'line_spacing' => 0.5,
+
+        // automasurizing settings
+        'min_font_size' => 1,
+        'max_font_size' => 100,
+
+        //max. lines to render
+        'max_lines' => 100,
+
+        // misc settings
+        'image_type' => IMAGETYPE_PNG,
+        'dest_file' => ''
+    );
+
+    /**
+     * Contains option names, which can cause re-initialization force.
+     *
+     * @var array
+     */
+    private $_reInits = array(
+        'width', 'height', 'canvas', 'angle', 'font_file', 'font_path', 'font_size'
+    );
+
+    /**
+     * The text you want to render.
+     *
+     * @var string
+     */
+    private $_text;
+
+    /**
+     * Resource ID of the image canvas.
+     *
+     * @var resource
+     */
+    private $_img;
+
+    /**
+     * Tokens (each word).
+     *
+     * @var array
+     */
+    private $_tokens = array();
+
+    /**
+     * Fullpath to the font.
+     *
+     * @var string
+     */
+    private $_font;
+
+    /**
+     * Contains the bbox of each rendered lines.
+     *
+     * @var array
+     */
+    private $_bbox = array();
+
+    /**
+     * Defines in which mode the canvas has be set.
+     *
+     * @var array
+     */
+    private $_mode = '';
+
+    /**
+     * Color indices returned by imagecolorallocatealpha.
+     *
+     * @var array
+     */
+    private $_colors = array();
+
+    /**
+     * Width and height of the (rendered) text.
+     *
+     * @var array
+     */
+    private $_realTextSize = array('width' => false, 'height' => false);
+
+    /**
+     * Measurized lines.
+     *
+     * @var array
+     */
+    private $_lines = false;
+
+    /**
+     * Fontsize for which the last measuring process was done.
+     *
+     * @var array
+     */
+    private $_measurizedSize = false;
+
+    /**
+     * Is the text object initialized?
+     *
+     * @var bool
+     */
+    private $_init = false;
+
+    /**
+     * Constructor
+     *
+     * Set the text and options. This initializes a new Image_Text object. You must
+     * set your text here. Optionally you can set all options here using the $options
+     * parameter. If you finished switching all options you have to call the init()
+     * method first before doing anything further! See Image_Text::set() for further
+     * information.
+     *
+     * @param string $text    Text to print.
+     * @param array  $options Options.
+     *
+     * @see Image_Text::set(), Image_Text::construct(), Image_Text::init()
+     */
+    public function __construct($text, $options = null)
+    {
+        $this->set('text', $text);
+        if (!empty($options)) {
+            $this->_options = array_merge($this->_options, $options);
+        }
+    }
+
+    /**
+     * Construct and initialize an Image_Text in one step.
+     * This method is called statically and creates plus initializes an Image_Text
+     * object. Beware: You will have to recall init() if you set an option afterwards
+     * manually.
+     *
+     * @param string $text    Text to print.
+     * @param array  $options Options.
+     *
+     * @return Image_Text
+     * @see Image_Text::set(), Image_Text::Image_Text(), Image_Text::init()
+     */
+    public static function construct($text, $options)
+    {
+        $itext = new Image_Text($text, $options);
+        $itext->init();
+        return $itext;
+    }
+
+    /**
+     * Set options
+     *
+     * Set a single or multiple options. It may happen that you have to reinitialize
+     * the Image_Text object after changing options. For possible options, please
+     * take a look at the class options array!
+     *
+     * @param array|string $option A single option name or the options array.
+     * @param mixed        $value  Option value if $option is string.
+     *
+     * @return void
+     * @see    Image_Text::Image_Text()
+     * @throws Image_Text_Exception setting the value failed
+     */
+    public function set($option, $value = null)
+    {
+        $reInits = array_flip($this->_reInits);
+        if (!is_array($option)) {
+            if (!isset($value)) {
+                throw new Image_Text_Exception('No value given.');
+            }
+            $option = array($option => $value);
+        }
+        foreach ($option as $opt => $val) {
+            switch ($opt) {
+            case 'color':
+                $this->setColors($val);
+                break;
+            case 'text':
+                if (is_array($val)) {
+                    $this->_text = implode('\n', $val);
+                } else {
+                    $this->_text = $val;
+                }
+                break;
+            default:
+                $this->_options[$opt] = $val;
+                break;
+            }
+            if (isset($reInits[$opt])) {
+                $this->_init = false;
+            }
+        }
+    }
+
+    /**
+     * Set the color-set
+     *
+     * Using this method you can set multiple colors for your text. Use a simple
+     * numeric array to determine their order and give it to this function. Multiple
+     * colors will be cycled by the options specified 'color_mode' option. The given
+     * array will overwrite the existing color settings!
+     *
+     * The following colors syntaxes are understood by this method:
+     * <ul>
+     * <li>"#ffff00" hexadecimal format (HTML style), with and without #.</li>
+     * <li>"#08ffff00" hexadecimal format (HTML style) with alpha channel (08),
+     * with and without #.</li>
+     * <li>array with 'r','g','b' and (optionally) 'a' keys, using int values.</li>
+     * </ul>
+     * A single color or an array of colors are allowed here.
+     *
+     * @param array|string $colors Single color or array of colors.
+     *
+     * @return void
+     * @see Image_Text::setColor(), Image_Text::set()
+     * @throws Image_Text_Exception
+     */
+    public function setColors($colors)
+    {
+        $i = 0;
+        if (is_array($colors) && (is_string($colors[0]) || is_array($colors[0]))) {
+            foreach ($colors as $color) {
+                $this->setColor($color, $i++);
+            }
+        } else {
+            $this->setColor($colors, $i);
+        }
+    }
+
+    /**
+     * Set a color
+     *
+     * This method is used to set a color at a specific color ID inside the color
+     * cycle.
+     *
+     * The following colors syntaxes are understood by this method:
+     * <ul>
+     * <li>"#ffff00" hexadecimal format (HTML style), with and without #.</li>
+     * <li>"#08ffff00" hexadecimal format (HTML style) with alpha channel (08), with
+     * and without #.</li>
+     * <li>array with 'r','g','b' and (optionally) 'a' keys, using int values.</li>
+     * </ul>
+     *
+     * @param array|string $color Color value.
+     * @param int          $id    ID (in the color array) to set color to.
+     *
+     * @return void
+     * @see Image_Text::setColors(), Image_Text::set()
+     * @throws Image_Text_Exception
+     */
+
+    function setColor($color, $id = 0)
+    {
+        if (is_array($color)) {
+            if (isset($color['r']) && isset($color['g']) && isset($color['b'])) {
+                $color['a'] = isset($color['a']) ? $color['a'] : 0;
+                $this->_options['colors'][$id] = $color;
+            } else if (isset($color[0]) && isset($color[1]) && isset($color[2])) {
+                $color['r'] = $color[0];
+                $color['g'] = $color[1];
+                $color['b'] = $color[2];
+                $color['a'] = isset($color[3]) ? $color[3] : 0;
+                $this->_options['colors'][$id] = $color;
+            } else {
+                throw new Image_Text_Exception(
+                    'Use keys 1,2,3 (optionally) 4 or r,g,b and (optionally) a.'
+                );
+            }
+        } elseif (is_string($color)) {
+            $color = $this->convertString2RGB($color);
+            if ($color) {
+                $this->_options['color'][$id] = $color;
+            } else {
+                throw new Image_Text_Exception('Invalid color.');
+            }
+        }
+        if ($this->_img) {
+            $aaFactor = ($this->_options['antialias']) ? 1 : -1;
+            if (function_exists('imagecolorallocatealpha') && isset($color['a'])) {
+                $this->_colors[$id] = $aaFactor *
+                    imagecolorallocatealpha(
+                        $this->_img,
+                        $color['r'], $color['g'], $color['b'], $color['a']
+                    );
+            } else {
+                $this->_colors[$id] = $aaFactor *
+                    imagecolorallocate(
+                        $this->_img,
+                        $color['r'], $color['g'], $color['b']
+                    );
+            }
+            if ($this->_colors[$id] == 0 && $aaFactor == -1) {
+                // correction for black with antialiasing OFF
+                // since color cannot be negative zero
+                $this->_colors[$id] = -256;
+            }
+        }
+    }
+
+    /**
+     * Initialize the Image_Text object.
+     *
+     * This method has to be called after setting the options for your Image_Text
+     * object. It initializes the canvas, normalizes some data and checks important
+     * options. Be sure to check the initialization after you switched some options.
+     * The set() method may force you to reinitialize the object.
+     *
+     * @return void
+     * @see Image_Text::set()
+     * @throws Image_Text_Exception
+     */
+    public function init()
+    {
+        // Does the fontfile exist and is readable?
+        $fontFile = rtrim($this->_options['font_path'], '/\\');
+        $fontFile .= defined('OS_WINDOWS') && OS_WINDOWS ? '\\' : '/';
+        $fontFile .= $this->_options['font_file'];
+        $fontFile = realpath($fontFile);
+
+        if (empty($fontFile)) {
+            throw new Image_Text_Exception('You must supply a font file.');
+        } elseif (!file_exists($fontFile)) {
+            throw new Image_Text_Exception('Font file was not found.');
+        } elseif (!is_readable($fontFile)) {
+            throw new Image_Text_Exception('Font file is not readable.');
+        } elseif (!imagettfbbox(1, 1, $fontFile, 1)) {
+            throw new Image_Text_Exception('Font file is not valid.');
+        }
+        $this->_font = $fontFile;
+
+        // Is the font size to small?
+        if ($this->_options['width'] < 1) {
+            throw new Image_Text_Exception('Width too small. Has to be > 1.');
+        }
+
+        // Check and create canvas
+        $image_canvas = false;
+        switch (true) {
+
+        case (empty($this->_options['canvas'])):
+            // Create new image from width && height of the clipping
+            $this->_img = imagecreatetruecolor(
+                $this->_options['width'], $this->_options['height']
+            );
+            if (!$this->_img) {
+                throw new Image_Text_Exception('Could not create image canvas.');
+            }
+            break;
+
+        case (is_resource($this->_options['canvas']) &&
+            get_resource_type($this->_options['canvas']) == 'gd'):
+            // The canvas is an image resource
+            $image_canvas = true;
+            $this->_img = $this->_options['canvas'];
+            break;
+
+        case (is_array($this->_options['canvas']) &&
+            isset($this->_options['canvas']['width']) &&
+            isset($this->_options['canvas']['height'])):
+
+            // Canvas must be a width and height measure
+            $this->_img = imagecreatetruecolor(
+                $this->_options['canvas']['width'],
+                $this->_options['canvas']['height']
+            );
+            break;
+
+        case (is_array($this->_options['canvas']) &&
+            isset($this->_options['canvas']['size']) &&
+            ($this->_options['canvas']['size'] = 'auto')):
+
+        case (is_string($this->_options['canvas']) &&
+            ($this->_options['canvas'] = 'auto')):
+            $this->_mode = 'auto';
+            break;
+
+        default:
+            throw new Image_Text_Exception('Could not create image canvas.');
+        }
+
+        if ($this->_img) {
+            $this->_options['canvas'] = array();
+            $this->_options['canvas']['width'] = imagesx($this->_img);
+            $this->_options['canvas']['height'] = imagesy($this->_img);
+        }
+
+        if ($this->_options['enable_alpha']) {
+            imagesavealpha($this->_img, true);
+            imagealphablending($this->_img, false);
+        }
+
+        if ($this->_options['background_color'] === null) {
+            $this->_options['enable_alpha'] = true;
+            imagesavealpha($this->_img, true);
+            imagealphablending($this->_img, false);
+            $colBg = imagecolorallocatealpha($this->_img, 255, 255, 255, 127);
+        } else {
+            $arBg = $this->convertString2RGB($this->_options['background_color']);
+            if ($arBg === false) {
+                throw new Image_Text_Exception('Background color is invalid.');
+            }
+            $colBg = imagecolorallocatealpha(
+                $this->_img, $arBg['r'], $arBg['g'], $arBg['b'], $arBg['a']
+            );
+        }
+        if ($image_canvas === false) {
+            imagefilledrectangle(
+                $this->_img,
+                0, 0,
+                $this->_options['canvas']['width'] - 1,
+                $this->_options['canvas']['height'] - 1,
+                $colBg
+            );
+        }
+
+        // Save and repair angle
+        $angle = $this->_options['angle'];
+        while ($angle < 0) {
+            $angle += 360;
+        }
+        if ($angle > 359) {
+            $angle = $angle % 360;
+        }
+        $this->_options['angle'] = $angle;
+
+        // Set the color values
+        $this->setColors($this->_options['color']);
+
+        $this->_lines = null;
+
+        // Initialization is complete
+        $this->_init = true;
+    }
+
+    /**
+     * Auto measurize text
+     *
+     * Automatically determines the greatest possible font size to fit the text into
+     * the text box. This method may be very resource intensive on your webserver. A
+     * good tweaking point are the $start and $end parameters, which specify the
+     * range of font sizes to search through. Anyway, the results should be cached if
+     * possible. You can optionally set $start and $end here as a parameter or the
+     * settings of the options array are used.
+     *
+     * @param int|bool $start Fontsize to start testing with.
+     * @param int|bool $end   Fontsize to end testing with.
+     *
+     * @return int Fontsize measured
+     * @see  Image_Text::measurize()
+     * @throws Image_Text_Exception
+     * @todo Beware of initialize
+     */
+    public function autoMeasurize($start = false, $end = false)
+    {
+        if (!$this->_init) {
+            throw new Image_Text_Exception('Not initialized. Call ->init() first!');
+        }
+
+        $start = (empty($start)) ? $this->_options['min_font_size'] : $start;
+        $end = (empty($end)) ? $this->_options['max_font_size'] : $end;
+
+        // Run through all possible font sizes until a measurize fails
+        // Not the optimal way. This can be tweaked!
+        for ($i = $start; $i <= $end; $i++) {
+            $this->_options['font_size'] = $i;
+            $res = $this->measurize();
+
+            if ($res === false) {
+                if ($start == $i) {
+                    $this->_options['font_size'] = -1;
+                    throw new Image_Text_Exception("No possible font size found");
+                }
+                $this->_options['font_size'] -= 1;
+                $this->_measurizedSize = $this->_options['font_size'];
+                break;
+            }
+            // Always the last couple of lines is stored here.
+            $this->_lines = $res;
+        }
+        return $this->_options['font_size'];
+    }
+
+    /**
+     * Measurize text into the text box
+     *
+     * This method makes your text fit into the defined textbox by measurizing the
+     * lines for your given font-size. You can do this manually before rendering (or
+     * use even Image_Text::autoMeasurize()) or the renderer will do measurizing
+     * automatically.
+     *
+     * @param bool $force Optionally, default is false, set true to force
+     *                    measurizing.
+     *
+     * @return array Array of measured lines.
+     * @see    Image_Text::autoMeasurize()
+     * @throws Image_Text_Exception
+     */
+    public function measurize($force = false)
+    {
+        if (!$this->_init) {
+            throw new Image_Text_Exception('Not initialized. Call ->init() first!');
+        }
+        $this->_processText();
+
+        // Precaching options
+        $font = $this->_font;
+        $size = $this->_options['font_size'];
+
+        $space = (1 + $this->_options['line_spacing'])
+            * $this->_options['font_size'];
+
+        $max_lines = (int)$this->_options['max_lines'];
+
+        if (($max_lines < 1) && !$force) {
+            return false;
+        }
+
+        $block_width = $this->_options['width'];
+        $block_height = $this->_options['height'];
+
+        $colors_cnt = sizeof($this->_colors);
+
+        $text_line = '';
+
+        $lines_cnt = 0;
+
+        $lines = array();
+
+        $text_height = 0;
+        $text_width = 0;
+
+        $i = 0;
+        $para_cnt = 0;
+        $width = 0;
+
+        $beginning_of_line = true;
+
+        // Run through tokens and order them in lines
+        foreach ($this->_tokens as $token) {
+            // Handle new paragraphs
+            if ($token == "\n") {
+                $bounds = imagettfbbox($size, 0, $font, $text_line);
+                if ((++$lines_cnt >= $max_lines) && !$force) {
+                    return false;
+                }
+                if ($this->_options['color_mode'] == 'paragraph') {
+                    $c = $this->_colors[$para_cnt % $colors_cnt];
+                    $i++;
+                } else {
+                    $c = $this->_colors[$i++ % $colors_cnt];
+                }
+                $lines[] = array(
+                    'string' => $text_line,
+                    'width' => $bounds[2] - $bounds[0],
+                    'height' => $bounds[1] - $bounds[7],
+                    'bottom_margin' => $bounds[1],
+                    'left_margin' => $bounds[0],
+                    'color' => $c
+                );
+                $text_width = max($text_width, ($bounds[2] - $bounds[0]));
+                $text_height += (int)$space;
+                if (($text_height > $block_height) && !$force) {
+                    return false;
+                }
+                $para_cnt++;
+                $text_line = '';
+                $beginning_of_line = true;
+                continue;
+            }
+
+            // Usual lining up
+
+            if ($beginning_of_line) {
+                $text_line = '';
+                $text_line_next = $token;
+                $beginning_of_line = false;
+            } else {
+                $text_line_next = $text_line . ' ' . $token;
+            }
+            $bounds = imagettfbbox($size, 0, $font, $text_line_next);
+            $prev_width = isset($prev_width) ? $width : 0;
+            $width = $bounds[2] - $bounds[0];
+
+            // Handling of automatic new lines
+            if ($width > $block_width) {
+                if ((++$lines_cnt >= $max_lines) && !$force) {
+                    return false;
+                }
+                if ($this->_options['color_mode'] == 'line') {
+                    $c = $this->_colors[$i++ % $colors_cnt];
+                } else {
+                    $c = $this->_colors[$para_cnt % $colors_cnt];
+                    $i++;
+                }
+
+                $lines[] = array(
+                    'string' => $text_line,
+                    'width' => $prev_width,
+                    'height' => $bounds[1] - $bounds[7],
+                    'bottom_margin' => $bounds[1],
+                    'left_margin' => $bounds[0],
+                    'color' => $c
+                );
+                $text_width = max($text_width, ($bounds[2] - $bounds[0]));
+                $text_height += (int)$space;
+                if (($text_height > $block_height) && !$force) {
+                    return false;
+                }
+
+                $text_line = $token;
+                $bounds = imagettfbbox($size, 0, $font, $text_line);
+                $width = $bounds[2] - $bounds[0];
+                $beginning_of_line = false;
+            } else {
+                $text_line = $text_line_next;
+            }
+        }
+        // Store remaining line
+        $bounds = imagettfbbox($size, 0, $font, $text_line);
+        $i++;
+        if ($this->_options['color_mode'] == 'line') {
+            $c = $this->_colors[$i % $colors_cnt];
+        } else {
+            $c = $this->_colors[$para_cnt % $colors_cnt];
+        }
+        $lines[] = array(
+            'string' => $text_line,
+            'width' => $bounds[2] - $bounds[0],
+            'height' => $bounds[1] - $bounds[7],
+            'bottom_margin' => $bounds[1],
+            'left_margin' => $bounds[0],
+            'color' => $c
+        );
+
+        // add last line height, but without the line-spacing
+        $text_height += $this->_options['font_size'];
+
+        $text_width = max($text_width, ($bounds[2] - $bounds[0]));
+
+        if (($text_height > $block_height) && !$force) {
+            return false;
+        }
+
+        $this->_realTextSize = array(
+            'width' => $text_width, 'height' => $text_height
+        );
+        $this->_measurizedSize = $this->_options['font_size'];
+
+        return $lines;
+    }
+
+    /**
+     * Render the text in the canvas using the given options.
+     *
+     * This renders the measurized text or automatically measures it first. The
+     * $force parameter can be used to switch of measurizing problems (this may cause
+     * your text being rendered outside a given text box or destroy your image
+     * completely).
+     *
+     * @param bool $force Optional, initially false, set true to silence measurize
+     *                    errors.
+     *
+     * @return void
+     * @throws Image_Text_Exception
+     */
+    public function render($force = false)
+    {
+        if (!$this->_init) {
+            throw new Image_Text_Exception('Not initialized. Call ->init() first!');
+        }
+
+        if (!$this->_tokens) {
+            $this->_processText();
+        }
+
+        if (empty($this->_lines)
+            || ($this->_measurizedSize != $this->_options['font_size'])
+        ) {
+            $this->_lines = $this->measurize($force);
+        }
+        $lines = $this->_lines;
+
+        if ($this->_mode === 'auto') {
+            $this->_img = imagecreatetruecolor(
+                $this->_realTextSize['width'],
+                $this->_realTextSize['height']
+            );
+            if (!$this->_img) {
+                throw new Image_Text_Exception('Could not create image canvas.');
+            }
+            $this->_mode = '';
+            $this->setColors($this->_options['color']);
+        }
+
+        $block_width = $this->_options['width'];
+
+        $max_lines = $this->_options['max_lines'];
+
+        $angle = $this->_options['angle'];
+        $radians = round(deg2rad($angle), 3);
+
+        $font = $this->_font;
+        $size = $this->_options['font_size'];
+
+        $line_spacing = $this->_options['line_spacing'];
+
+        $align = $this->_options['halign'];
+
+        $offset = $this->_getOffset();
+
+        $start_x = $offset['x'];
+        $start_y = $offset['y'];
+
+        $sinR = sin($radians);
+        $cosR = cos($radians);
+
+        switch ($this->_options['valign']) {
+        case self::IMAGE_TEXT_ALIGN_TOP:
+            $valign_space = 0;
+            break;
+        case self::IMAGE_TEXT_ALIGN_MIDDLE:
+            $valign_space = ($this->_options['height']
+                    - $this->_realTextSize['height']) / 2;
+            break;
+        case self::IMAGE_TEXT_ALIGN_BOTTOM:
+            $valign_space = $this->_options['height']
+                - $this->_realTextSize['height'];
+            break;
+        default:
+            $valign_space = 0;
+        }
+
+        $space = (1 + $line_spacing) * $size;
+
+        // Adjustment of align + translation of top-left-corner to bottom-left-corner
+        // of first line
+        $new_posx = $start_x + ($sinR * ($valign_space + $size));
+        $new_posy = $start_y + ($cosR * ($valign_space + $size));
+
+        $lines_cnt = min($max_lines, sizeof($lines));
+
+        $bboxes = array();
+        // Go thorugh lines for rendering
+        for ($i = 0; $i < $lines_cnt; $i++) {
+
+            // Calc the new start X and Y (only for line>0)
+            // the distance between the line above is used
+            if ($i > 0) {
+                $new_posx += $sinR * $space;
+                $new_posy += $cosR * $space;
+            }
+
+            // Calc the position of the 1st letter. We can then get the left and
+            // bottom margins 'i' is really not the same than 'j' or 'g'.
+            $left_margin = $lines[$i]['left_margin'];
+            $line_width = $lines[$i]['width'];
+
+            // Calc the position using the block width, the current line width and
+            // obviously the angle. That gives us the offset to slide the line.
+            switch ($align) {
+            case self::IMAGE_TEXT_ALIGN_LEFT:
+                $hyp = 0;
+                break;
+            case self::IMAGE_TEXT_ALIGN_RIGHT:
+                $hyp = $block_width - $line_width - $left_margin;
+                break;
+            case self::IMAGE_TEXT_ALIGN_CENTER:
+                $hyp = ($block_width - $line_width) / 2 - $left_margin;
+                break;
+            default:
+                $hyp = 0;
+                break;
+            }
+
+            $posx = $new_posx + $cosR * $hyp;
+            $posy = $new_posy - $sinR * $hyp;
+
+            $c = $lines[$i]['color'];
+
+            // Render textline
+            $bboxes[] = imagettftext(
+                $this->_img, $size, $angle, $posx, $posy,
+                $c, $font, $lines[$i]['string']
+            );
+        }
+        $this->_bbox = $bboxes;
+    }
+
+    /**
+     * Return the image ressource.
+     *
+     * Get the image canvas.
+     *
+     * @return resource Used image resource
+     */
+    public function getImg()
+    {
+        return $this->_img;
+    }
+
+    /**
+     * Display the image (send it to the browser).
+     *
+     * This will output the image to the users browser. You can use the standard
+     * IMAGETYPE_* constants to determine which image type will be generated.
+     * Optionally you can save your image to a destination you set in the options.
+     *
+     * @param bool $save Save or not the image on printout.
+     * @param bool $free Free the image on exit.
+     *
+     * @return  bool         True on success
+     * @see Image_Text::save()
+     * @throws Image_Text_Exception
+     */
+    public function display($save = false, $free = false)
+    {
+        if (!headers_sent()) {
+            header(
+                "Content-type: " .
+                image_type_to_mime_type($this->_options['image_type'])
+            );
+        } else {
+            throw new Image_Text_Exception('Header already sent.');
+        }
+        switch ($this->_options['image_type']) {
+        case IMAGETYPE_PNG:
+            $imgout = 'imagepng';
+            break;
+        case IMAGETYPE_JPEG:
+            $imgout = 'imagejpeg';
+            break;
+        case IMAGETYPE_BMP:
+            $imgout = 'imagebmp';
+            break;
+        default:
+            throw new Image_Text_Exception('Unsupported image type.');
+        }
+        if ($save) {
+            $imgout($this->_img);
+            $this->save();
+        } else {
+            $imgout($this->_img);
+        }
+
+        if ($free) {
+            $res = imagedestroy($this->_img);
+            if (!$res) {
+                throw new Image_Text_Exception('Destroying image failed.');
+            }
+        }
+    }
+
+    /**
+     * Save image canvas.
+     *
+     * Saves the image to a given destination. You can leave out the destination file
+     * path, if you have the option for that set correctly. Saving is possible with
+     * the display() method, too.
+     *
+     * @param bool|string $destFile The destination to save to (optional, uses
+     *                              options value else).
+     *
+     * @see Image_Text::display()
+     * @return void
+     * @throws Image_Text_Exception
+     */
+    public function save($destFile = false)
+    {
+        if (!$destFile) {
+            $destFile = $this->_options['dest_file'];
+        }
+        if (!$destFile) {
+            throw new Image_Text_Exception("Invalid desitination file.");
+        }
+
+        switch ($this->_options['image_type']) {
+        case IMAGETYPE_PNG:
+            $imgout = 'imagepng';
+            break;
+        case IMAGETYPE_JPEG:
+            $imgout = 'imagejpeg';
+            break;
+        case IMAGETYPE_BMP:
+            $imgout = 'imagebmp';
+            break;
+        default:
+            throw new Image_Text_Exception('Unsupported image type.');
+            break;
+        }
+
+        $res = $imgout($this->_img, $destFile);
+        if (!$res) {
+            throw new Image_Text_Exception('Saving file failed.');
+        }
+    }
+
+    /**
+     * Get completely translated offset for text rendering.
+     *
+     * Get completely translated offset for text rendering. Important for usage of
+     * center coords and angles.
+     *
+     * @return array Array of x/y coordinates.
+     */
+    private function _getOffset()
+    {
+        // Presaving data
+        $width = $this->_options['width'];
+        $height = $this->_options['height'];
+        $angle = $this->_options['angle'];
+        $x = $this->_options['x'];
+        $y = $this->_options['y'];
+        // Using center coordinates
+        if (!empty($this->_options['cx']) && !empty($this->_options['cy'])) {
+            $cx = $this->_options['cx'];
+            $cy = $this->_options['cy'];
+            // Calculation top left corner
+            $x = $cx - ($width / 2);
+            $y = $cy - ($height / 2);
+            // Calculating movement to keep the center point on himslf after rotation
+            if ($angle) {
+                $ang = deg2rad($angle);
+                // Vector from the top left cornern ponting to the middle point
+                $vA = array(($cx - $x), ($cy - $y));
+                // Matrix to rotate vector
+                // sinus and cosinus
+                $sin = round(sin($ang), 14);
+                $cos = round(cos($ang), 14);
+                // matrix
+                $mRot = array(
+                    $cos, (-$sin),
+                    $sin, $cos
+                );
+                // Multiply vector with matrix to get the rotated vector
+                // This results in the location of the center point after rotation
+                $vB = array(
+                    ($mRot[0] * $vA[0] + $mRot[2] * $vA[0]),
+                    ($mRot[1] * $vA[1] + $mRot[3] * $vA[1])
+                );
+                // To get the movement vector, we subtract the original middle
+                $vC = array(
+                    ($vA[0] - $vB[0]),
+                    ($vA[1] - $vB[1])
+                );
+                // Finally we move the top left corner coords there
+                $x += $vC[0];
+                $y += $vC[1];
+            }
+        }
+        return array('x' => (int)round($x, 0), 'y' => (int)round($y, 0));
+    }
+
+    /**
+     * Convert a color to an array.
+     *
+     * The following colors syntax must be used:
+     * "#08ffff00" hexadecimal format with alpha channel (08)
+     *
+     * @param string $scolor string of colorcode.
+     *
+     * @see Image_Text::IMAGE_TEXT_REGEX_HTMLCOLOR
+     * @return bool|array false if string can't be converted to array
+     */
+    public static function convertString2RGB($scolor)
+    {
+        if (preg_match(self::IMAGE_TEXT_REGEX_HTMLCOLOR, $scolor, $matches)) {
+            return array(
+                'r' => hexdec($matches[2]),
+                'g' => hexdec($matches[3]),
+                'b' => hexdec($matches[4]),
+                'a' => hexdec(!empty($matches[1]) ? $matches[1] : 0),
+            );
+        }
+        return false;
+    }
+
+    /**
+     * Extract the tokens from the text.
+     *
+     * @return void
+     */
+    private function _processText()
+    {
+        if (!isset($this->_text)) {
+            return;
+        }
+        $this->_tokens = array();
+
+        // Normalize linebreak to "\n"
+        $this->_text = preg_replace("[\r\n]", "\n", $this->_text);
+
+        // Get each paragraph
+        $paras = explode("\n", $this->_text);
+
+        // loop though the paragraphs
+        // and get each word (token)
+        foreach ($paras as $para) {
+            $words = explode(' ', $para);
+            foreach ($words as $word) {
+                $this->_tokens[] = $word;
+            }
+            // add a "\n" to mark the end of a paragraph
+            $this->_tokens[] = "\n";
+        }
+        // we do not need an end paragraph as the last token
+        array_pop($this->_tokens);
+    }
+}

+ 26 - 0
main/inc/lib/pear/Image/Text/Exception.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Exception for Image_Text
+ *
+ * PHP version 5
+ *
+ * @category Image
+ * @package  Image_Text
+ * @author   Daniel O'Connor <daniel.oconnor@gmail.com>
+ * @license  http://www.php.net/license/3_01.txt PHP License
+ * @link     http://pear.php.net/package/Image_Text
+ */
+require_once 'PEAR/Exception.php';
+/**
+ * Exception for Image_Text
+ *
+ * @category Image
+ * @package  Image_Text
+ * @author   Daniel O'Connor <daniel.oconnor@gmail.com>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.php.net/license/3_01.txt PHP License
+ * @link     http://pear.php.net/package/Image_Text
+ */
+class Image_Text_Exception extends PEAR_Exception
+{
+}

+ 163 - 0
main/inc/lib/pear/Text/CAPTCHA.php

@@ -0,0 +1,163 @@
+<?php
+/**
+ * Text_CAPTCHA - creates a CAPTCHA for Turing tests.
+ * Base class file for using Text_CAPTCHA.
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Wenz <wenz@php.net>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+/**
+ * Require Exception class for error handling.
+ */
+require_once 'Text/CAPTCHA/Exception.php';
+/**
+ * Require Text_Password class for generating the phrase.
+ */
+require_once 'Text/Password.php';
+
+/**
+ * Text_CAPTCHA - creates a CAPTCHA for Turing tests.
+ * Class to create a Turing test for websites by creating an image, ASCII art or
+ * something else with some (obfuscated) characters.
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+class Text_CAPTCHA
+{
+    /**
+     * driver for Text_CAPTCHA
+     *
+     * @var Text_CAPTCHA_Driver_Base
+     */
+    private $_driver;
+
+    /**
+     * check if an initial driver init was done.
+     *
+     * @var bool
+     */
+    private $_driverInitDone = false;
+
+    /**
+     * Constructor for the TEXT_CAPTCHA object with the given driver.
+     *
+     * @param Text_CAPTCHA_Driver $driver driver
+     *
+     * @throws Text_CAPTCHA_Exception no driver given
+     */
+    function __construct($driver)
+    {
+        if (is_null($driver)) {
+            throw new Text_CAPTCHA_Exception("No driver given");
+        }
+        $this->_driver = $driver;
+        $this->_driverInitDone = false;
+    }
+
+    /**
+     * Create a new Text_CAPTCHA object.
+     *
+     * @param string $driver name of driver class to initialize
+     *
+     * @return Text_CAPTCHA a newly created Text_CAPTCHA object
+     * @throws Text_CAPTCHA_Exception when driver could not be loaded
+     *
+     */
+    public static function factory($driver)
+    {
+        $driver = basename($driver);
+        $class = 'Text_CAPTCHA_Driver_' . $driver;
+        $file = str_replace('_', '/', $class) . '.php';
+        //check if it exists and can be loaded
+        if (!@fclose(@fopen($file, 'r', true))) {
+            throw new Text_CAPTCHA_Exception(
+                'Driver ' . $driver . ' cannot be loaded.'
+            );
+        }
+        //continue with including the driver
+        include_once $file;
+
+        $driver = new $class;
+
+        return new Text_CAPTCHA($driver);
+    }
+
+    /**
+     * Create random CAPTCHA phrase
+     *
+     * @param boolean|string $newPhrase new Phrase to use or true to generate a new
+     *                                  one
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception when driver is not initialized
+     */
+    public final function generate($newPhrase = false)
+    {
+        if (!$this->_driverInitDone) {
+            throw new Text_CAPTCHA_Exception("Driver not initialized");
+        }
+        if ($newPhrase === true || is_null($this->_driver->getPhrase())) {
+            $this->_driver->createPhrase();
+        } else if (strlen($newPhrase) > 0) {
+            $this->_driver->setPhrase($newPhrase);
+        }
+        $this->_driver->createCAPTCHA();
+    }
+
+    /**
+     * Reinitialize the entire Text_CAPTCHA object.
+     *
+     * @param array $options Options to pass in.
+     *
+     * @return void
+     */
+    public final function init($options = array())
+    {
+        $this->_driver->resetDriver();
+        $this->_driver->initDriver($options);
+        $this->_driverInitDone = true;
+        $this->generate();
+    }
+
+    /**
+     * Place holder for the real getCAPTCHA() method used by extended classes to
+     * return the generated CAPTCHA (as an image resource, as an ASCII text, ...).
+     *
+     * @return string|object
+     */
+    public final function getCAPTCHA()
+    {
+        return $this->_driver->getCAPTCHA();
+    }
+
+    /**
+     * Return secret CAPTCHA phrase.
+     *
+     * @return string secret phrase
+     */
+    public final function getPhrase()
+    {
+        return $this->_driver->getPhrase();
+    }
+
+    /**
+     * Place holder for the real getCAPTCHA() method used by extended classes to
+     * return the generated CAPTCHA (as an image resource, as an ASCII text, ...).
+     *
+     * @return string|object
+     */
+    public function getCAPTCHAAsJPEG()
+    {
+        return $this->_driver->_getCAPTCHAAsJPEG();
+    }
+}

+ 56 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver.php

@@ -0,0 +1,56 @@
+<?php
+/**
+ * Interface for Drivers
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+/**
+ * Interface for Text_CAPTCHA drivers
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+interface Text_CAPTCHA_Driver
+{
+    /**
+     * Clear the internal state of the driver.
+     *
+     * @return void
+     */
+    function resetDriver();
+
+    /**
+     * Initialize the driver with the given options.
+     *
+     * @param array $options options for the driver as array
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception something went wrong during init
+     */
+    function initDriver($options);
+
+    /**
+     * Generate the CAPTCHA.
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception something went wrong during creation of CAPTCHA
+     */
+    function createCAPTCHA();
+
+    /**
+     * Generate the phrase for the CAPTCHA.
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception something went wrong during creation of phrase
+     */
+    function createPhrase();
+}

+ 97 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver/Base.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * Base class file for all Text_CAPTCHA drivers.
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'Text/CAPTCHA/Driver.php';
+/**
+ * Base class file for all Text_CAPTCHA drivers.
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+abstract class Text_CAPTCHA_Driver_Base implements Text_CAPTCHA_Driver
+{
+    /**
+     * Captcha
+     *
+     * @var object|string
+     */
+    private $_captcha;
+
+    /**
+     * Phrase
+     *
+     * @var string
+     */
+    private $_phrase;
+
+    /**
+     * Sets secret CAPTCHA phrase.
+     * This method sets the CAPTCHA phrase (use null for a random phrase)
+     *
+     * @param string $phrase The (new) phrase
+     *
+     * @return void
+     */
+    public final function setPhrase($phrase)
+    {
+        $this->_phrase = $phrase;
+    }
+
+    /**
+     * Return secret CAPTCHA phrase
+     * This method returns the CAPTCHA phrase
+     *
+     * @return  string   secret phrase
+     */
+    public final function getPhrase()
+    {
+        return $this->_phrase;
+    }
+
+    /**
+     * Sets the generated captcha.
+     *
+     * @param object|string $captcha the generated captcha
+     *
+     * @return void
+     */
+    protected final function setCaptcha($captcha)
+    {
+        $this->_captcha = $captcha;
+    }
+
+    /**
+     * Place holder for the real getCAPTCHA() method
+     * used by extended classes to return the generated CAPTCHA
+     * (as an image resource, as an ASCII text, ...)
+     *
+     * @return string|object
+     */
+    public final function getCAPTCHA()
+    {
+        return $this->_captcha;
+    }
+
+    /**
+     * Reset the phrase and the CAPTCHA.
+     *
+     * @return void
+     */
+    public function resetDriver()
+    {
+        $this->setPhrase(null);
+        $this->setCaptcha(null);
+    }
+}

+ 215 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver/Equation.php

@@ -0,0 +1,215 @@
+<?php
+/**
+ * Equation driver for Text_CAPTCHA.
+ * Returns simple equations as string, e.g. "9 - 2"
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Weiske <cweiske@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'Text/CAPTCHA/Driver/Base.php';
+/**
+ * Equation driver for Text_CAPTCHA.
+ * Returns simple equations as string, e.g. "9 - 2"
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Weiske <cweiske@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+class Text_CAPTCHA_Driver_Equation extends Text_CAPTCHA_Driver_Base
+{
+    /**
+     * Operators that may be used in the equation. Two numbers have to be filled in,
+     * and %s is needed since number2text conversion may be applied and strings
+     * filled in.
+     *
+     * @var array
+     */
+    private $_operators = array(
+        '%s * %s',
+        '%s + %s',
+        '%s - %s',
+        'min(%s, %s)',
+        'max(%s, %s)'
+    );
+
+    /**
+     * Minimal number to use in an equation.
+     *
+     * @var int
+     */
+    private $_min = 1;
+
+    /**
+     * Maximum number to use in an equation.
+     *
+     * @var int
+     */
+    private $_max = 10;
+
+    /**
+     * Whether numbers shall be converted to text.
+     *
+     * @var bool
+     */
+    private $_numbersToText = false;
+
+    /**
+     * This variable holds the locale for Numbers_Words.
+     *
+     * @var string
+     */
+    private $_locale = '';
+
+    /**
+     * Complexity of the generated equations.<br>
+     * 1 - simple ones such as "1 + 10"<br>
+     * 2 - harder ones such as "(3-2)*(min(5,6))"
+     *
+     * @var int
+     */
+    private $_severity = 1;
+
+    /**
+     * Initialize the driver.
+     *
+     * @param array $options CAPTCHA options with these keys:<br>
+     *                       min           minimum numeric value
+     *                       max           maximum numeric value
+     *                       numbersToText boolean for number to text conversion
+     *                       locale        locale for number to text conversion
+     *                       severity      number for complexity
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception when numbersToText is true, but Number_Words
+     *                                package is not available
+     */
+    public function initDriver($options = array())
+    {
+        if (isset($options['min'])) {
+            $this->_min = (int)$options['min'];
+        } else {
+            $this->_min = 1;
+        }
+        if (isset($options['max'])) {
+            $this->_max = (int)$options['max'];
+        } else {
+            $this->_max = 10;
+        }
+        if (isset($options['numbersToText'])) {
+            $this->_numbersToText = (bool)$options['numbersToText'];
+        } else {
+            $this->_numbersToText = false;
+        }
+        if (isset($options['locale'])) {
+            $this->_locale = (string)$options['locale'];
+        } else {
+            $this->_locale = '';
+        }
+        if (isset($options['severity'])) {
+            $this->_severity = (int)$options['severity'];
+        } else {
+            $this->_severity = 1;
+        }
+
+        if ($this->_numbersToText) {
+            include_once 'Numbers/Words.php';
+            if (!class_exists('Numbers_Words')) {
+                throw new Text_CAPTCHA_Exception('Number_Words package required');
+            }
+        }
+    }
+
+    /**
+     * Create random CAPTCHA equation.
+     * This method creates a random equation.
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception when invalid severity is specified
+     */
+    public function createCAPTCHA()
+    {
+        switch ($this->_severity) {
+        case 1:
+            list($equation, $phrase) = $this->_createSimpleEquation();
+            break;
+        case 2:
+            list($eq1, $sol1) = $this->_createSimpleEquation();
+            list($eq2, $sol2) = $this->_createSimpleEquation();
+            $op3 = $this->_operators[mt_rand(0, count($this->_operators) - 1)];
+            list(, $phrase) = $this->_solveSimpleEquation($sol1, $sol2, $op3);
+            $equation = sprintf($op3, '(' . $eq1 . ')', '(' . $eq2 . ')');
+            break;
+        default:
+            throw new Text_CAPTCHA_Exception(
+                'Equation complexity of ' . $this->_severity . ' not supported'
+            );
+        }
+        $this->setCaptcha($equation);
+        $this->setPhrase($phrase);
+    }
+
+    /**
+     * Creates a simple equation of type (number operator number).
+     *
+     * @return array Array with equation and solution
+     */
+    private function _createSimpleEquation()
+    {
+        $one = mt_rand($this->_min, $this->_max);
+        $two = mt_rand($this->_min, $this->_max);
+        $operator = $this->_operators[mt_rand(0, count($this->_operators) - 1)];
+
+        return $this->_solveSimpleEquation($one, $two, $operator);
+    }
+
+    /**
+     * Solves a simple equation with two given numbers and one operator as defined
+     * in $this->_operators.
+     * Also converts the numbers to words if required.
+     *
+     * @param int    $one      First number
+     * @param int    $two      Second number
+     * @param string $operator Operator used with those two numbers
+     *
+     * @return array    Array with equation and solution
+     */
+    private function _solveSimpleEquation($one, $two, $operator)
+    {
+        $equation = sprintf($operator, $one, $two);
+
+        $function = create_function('', 'return ' . $equation . ';');
+
+        if ($this->_numbersToText) {
+            $numberWords = new Numbers_Words();
+            $equation = sprintf(
+                $operator,
+                $numberWords->toWords($one, $this->_locale),
+                $numberWords->toWords($two, $this->_locale)
+            );
+        }
+        return array($equation, $function());
+    }
+
+    /**
+     * Creates the captcha. This method is a placeholder, since the equation is
+     * created in createCAPTCHA()
+     *
+     * @return void
+     * @see createCAPTCHA()
+     */
+    public function createPhrase()
+    {
+        $this->setPhrase(null);
+    }
+}

+ 233 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver/Figlet.php

@@ -0,0 +1,233 @@
+<?php
+/**
+ * Require Figlet class for rendering the text.
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Aaron Wormus <wormus@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'Text/CAPTCHA/Driver/Base.php';
+require_once 'Text/Figlet.php';
+/**
+ * Text_CAPTCHA_Driver_Figlet - Text_CAPTCHA driver Figlet based CAPTCHAs
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Aaron Wormus <wormus@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ * @todo     define an obfuscation algorithm
+ */
+class Text_CAPTCHA_Driver_Figlet extends Text_CAPTCHA_Driver_Base
+{
+    /**
+     * Text_Password options.
+     *
+     * @var array
+     */
+    private $_textPasswordOptions;
+
+    /**
+     * Width of CAPTCHA
+     *
+     * @var int
+     */
+    private $_width;
+
+    /**
+     * Length of CAPTCHA
+     *
+     * @var int
+     */
+    private $_length;
+
+    /**
+     * Figlet font
+     *
+     * @var string
+     */
+    private $_font;
+
+    /**
+     * Figlet font
+     *
+     * @var array
+     */
+    private $_style = array();
+
+    /**
+     * Output Format
+     *
+     * @var string
+     */
+    private $_output;
+
+    /**
+     * init function
+     *
+     * Initializes the new Text_CAPTCHA_Driver_Figlet object and creates a GD image
+     *
+     * @param array $options CAPTCHA options
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception when no options are given
+     */
+    public function initDriver($options = array())
+    {
+        if (!empty($options['output'])) {
+            $this->_output = (string)$options['output'];
+        } else {
+            $this->_output = 'html';
+        }
+
+        if (isset($options['width']) && $options['width']) {
+            $this->_width = (int)$options['width'];
+        } else {
+            $this->_width = 200;
+        }
+
+        if (!empty($options['length'])) {
+            $this->_length = $options['length'];
+        } else {
+            $this->_length = 6;
+        }
+
+        if (!isset($options['phrase']) || empty($options['phrase'])) {
+            $phraseOptions = (isset($options['phraseOptions'])
+                && is_array($options['phraseOptions']))
+                ? $options['phraseOptions'] : array();
+            $this->_textPasswordOptions = $phraseOptions;
+        } else {
+            $this->setPhrase($options['phrase']);
+        }
+
+        if (!empty($options['style'])
+            && is_array($options['style'])
+        ) {
+            $this->_style = $options['style'];
+        }
+
+        if (empty($this->_style['padding'])) {
+            $this->_style['padding'] = '5px';
+        }
+
+        if (!empty($options['font_file'])) {
+            if (is_array($options['font_file'])) {
+                $arr = $options['font_file'];
+                $this->_font = $arr[array_rand($arr)];
+            } else {
+                $this->_font = $options['font_file'];
+            }
+        }
+    }
+
+    /**
+     * Create the passphrase.
+     *
+     * @return string
+     */
+    public function createPhrase()
+    {
+        $options = $this->_textPasswordOptions;
+        $textPassword = new Text_Password();
+        if (!is_array($options) || count($options) === 0) {
+            $this->setPhrase($textPassword->create($this->_length));
+        } else {
+            if (count($options) === 1) {
+                $this->setPhrase($textPassword->create($this->_length, $options[0]));
+            } else {
+                $this->setPhrase(
+                    $textPassword->create($this->_length, $options[0], $options[1])
+                );
+            }
+        }
+    }
+
+    /**
+     * Create CAPTCHA image.
+     *
+     * This method creates a CAPTCHA image.
+     *
+     * @return void on error
+     * @throws Text_CAPTCHA_Exception when loading font fails
+     */
+    public function createCAPTCHA()
+    {
+        $pear = new PEAR();
+        $figlet = new Text_Figlet();
+        if ($pear->isError($figlet->loadFont($this->_font))) {
+            throw new Text_CAPTCHA_Exception('Error loading Text_Figlet font');
+        }
+
+        $outputString = $figlet->lineEcho($this->getPhrase());
+
+        switch ($this->_output) {
+        case 'text':
+            $this->setCaptcha($outputString);
+            break;
+        case 'html':
+            $this->setCaptcha($this->_getCAPTCHAAsHTML($outputString));
+            break;
+        case 'javascript':
+            $this->setCaptcha($this->_getCAPTCHAAsJavascript($outputString));
+            break;
+        default:
+            throw new Text_CAPTCHA_Exception('Invalid output option given');
+        }
+    }
+
+    /**
+     * Return CAPTCHA as HTML.
+     *
+     * This method returns the CAPTCHA as HTML.
+     *
+     * @param string $figletOutput output string from Figlet.
+     *
+     * @return string HTML Figlet image or PEAR error
+     */
+    private function _getCAPTCHAAsHTML($figletOutput)
+    {
+        $charWidth = strpos($figletOutput, "\n");
+        $data = str_replace("\n", '<br />', $figletOutput);
+        $textSize = ($this->_width / $charWidth) * 1.4;
+        $cssOutput = "";
+        foreach ($this->_style as $key => $value) {
+            $cssOutput .= "$key: $value;";
+        }
+
+        $htmlOutput = '<div style="font-family: courier;
+          font-size: ' . $textSize . 'px;
+          width:' . $this->_width . 'px;
+          text-align:center;">';
+        $htmlOutput .= '<div style="' . $cssOutput . 'margin:0px;">
+          <pre style="padding: 0px; margin: 0px;">' . $data . '</pre></div></div>';
+
+        return $htmlOutput;
+    }
+
+    /**
+     * Return CAPTCHA as Javascript version of HTML.
+     *
+     * This method returns the CAPTCHA as a Javascript string.
+     * I'm not exactly sure what the point of doing this would be.
+     *
+     * @param string $figletOutput output string from Figlet.
+     *
+     * @return string javascript string or PEAR error
+     */
+    private function _getCAPTCHAAsJavascript($figletOutput)
+    {
+        $obfusData = rawurlencode($figletOutput);
+        $javascript = "<script language=\"javascript\">
+          document.write(unescape(\"$obfusData\" ) );
+          </script>";
+        return $javascript;
+    }
+}

+ 290 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver/Image.php

@@ -0,0 +1,290 @@
+<?php
+/**
+ * Require Image_Text class for generating the text.
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'Text/CAPTCHA/Driver/Base.php';
+require_once 'Image/Text.php';
+
+/**
+ * Text_CAPTCHA_Driver_Image - Text_CAPTCHA driver graphical CAPTCHAs
+ *
+ * Class to create a graphical Turing test
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ * @todo     refine the obfuscation algorithm :-)
+ * @todo     consider removing Image_Text dependency
+ */
+class Text_CAPTCHA_Driver_Image extends Text_CAPTCHA_Driver_Base
+{
+    /**
+     * Text_Password options.
+     *
+     * @var array
+     */
+    private $_textPasswordOptions;
+    /**
+     * Width of CAPTCHA
+     *
+     * @var int
+     */
+    private $_width;
+
+    /**
+     * Height of CAPTCHA
+     *
+     * @var int
+     */
+    private $_height;
+
+    /**
+     * CAPTCHA output format
+     *
+     * @var string
+     */
+    private $_output;
+
+    /**
+     * Further options (here: for Image_Text)
+     *
+     * @var array
+     */
+    private $_imageOptions = array(
+        'font_size' => 24,
+        'font_path' => './',
+        'font_file' => 'COUR.TTF',
+        'text_color' => '#000000',
+        'lines_color' => '#CACACA',
+        'background_color' => '#555555',
+        'antialias' => false);
+
+    /**
+     * Init function
+     *
+     * Initializes the new Text_CAPTCHA_Driver_Image object and creates a GD image
+     *
+     * @param array $options CAPTCHA options
+     *
+     * @return void
+     */
+    public function initDriver($options = array())
+    {
+        if (is_array($options)) {
+            if (isset($options['width']) && is_int($options['width'])) {
+                $this->_width = $options['width'];
+            } else {
+                $this->_width = 200;
+            }
+            if (isset($options['height']) && is_int($options['height'])) {
+                $this->_height = $options['height'];
+            } else {
+                $this->_height = 80;
+            }
+            if (!isset($options['phrase']) || empty($options['phrase'])) {
+                $phraseOptions = (isset($options['phraseOptions'])
+                    && is_array($options['phraseOptions']))
+                    ? $options['phraseOptions'] : array();
+                $this->_textPasswordOptions = $phraseOptions;
+            } else {
+                $this->setPhrase($options['phrase']);
+            }
+            if (!isset($options['output']) || empty($options['output'])) {
+                $this->_output = 'jpeg';
+            } else {
+                $this->_output = $options['output'];
+            }
+            if (isset($options['imageOptions'])
+                && is_array($options['imageOptions'])
+                && count($options['imageOptions']) > 0
+            ) {
+                $this->_imageOptions = array_merge(
+                    $this->_imageOptions, $options['imageOptions']
+                );
+            }
+        }
+    }
+
+    /**
+     * Create CAPTCHA image.
+     *
+     * This method creates a CAPTCHA image.
+     *
+     * @return void
+     * @throws Text_CAPTCHA_Exception when image generation with Image_Text produces
+     *               an error
+     */
+    public function createCAPTCHA()
+    {
+        $options['canvas'] = array(
+            'width' => $this->_width,
+            'height' => $this->_height
+        );
+        $options['width'] = $this->_width - 20;
+        $options['height'] = $this->_height - 20;
+        $options['cx'] = ceil(($this->_width) / 2 + 10);
+        $options['cy'] = ceil(($this->_height) / 2 + 10);
+        $options['angle'] = rand(0, 30) - 15;
+        $options['font_size'] = $this->_imageOptions['font_size'];
+        $options['font_path'] = $this->_imageOptions['font_path'];
+        $options['font_file'] = $this->_imageOptions['font_file'];
+        $options['color'] = array($this->_imageOptions['text_color']);
+        $options['background_color'] = $this->_imageOptions['background_color'];
+        $options['max_lines'] = 1;
+        $options['mode'] = 'auto';
+
+        do {
+            $imageText = new Image_Text($this->getPhrase(), $options);
+            $imageText->init();
+            $result = $imageText->measurize();
+        } while ($result === false && --$options['font_size'] > 0);
+        if ($result === false) {
+            throw new Text_CAPTCHA_Exception(
+                'The text provided does not fit in the image dimensions'
+            );
+        }
+        $imageText->render();
+        $image = $imageText->getImg();
+
+        if ($this->_imageOptions['antialias'] && function_exists('imageantialias')) {
+            imageantialias($image, true);
+        }
+
+        $colors = Image_Text::convertString2RGB(
+            $this->_imageOptions['lines_color']
+        );
+        $linesColor = imagecolorallocate(
+            $image, $colors['r'], $colors['g'], $colors['b']
+        );
+        //some obfuscation
+        for ($i = 0; $i < 3; $i++) {
+            $x1 = rand(0, $this->_width - 1);
+            $y1 = rand(0, round($this->_height / 10, 0));
+            $x2 = rand(0, round($this->_width / 10, 0));
+            $y2 = rand(0, $this->_height - 1);
+            imageline($image, $x1, $y1, $x2, $y2, $linesColor);
+            $x1 = rand(0, $this->_width - 1);
+            $y1 = $this->_height - rand(1, round($this->_height / 10, 0));
+            $x2 = $this->_width - rand(1, round($this->_width / 10, 0));
+            $y2 = rand(0, $this->_height - 1);
+            imageline($image, $x1, $y1, $x2, $y2, $linesColor);
+            $cx = rand(0, $this->_width - 50) + 25;
+            $cy = rand(0, $this->_height - 50) + 25;
+            $w = rand(1, 24);
+            imagearc($image, $cx, $cy, $w, $w, 0, 360, $linesColor);
+        }
+
+        // @todo remove hardcoded value
+        $this->_output = 'jpg';
+
+        if ($this->_output == 'gif' && imagetypes() & IMG_GIF) {
+            $this->setCaptcha($this->_getCAPTCHAAsGIF($image));
+        } else if (($this->_output == 'jpg' && imagetypes() & IMG_JPG)
+            || ($this->_output == 'jpeg' && imagetypes() & IMG_JPEG)
+        ) {
+            $this->setCaptcha($this->_getCAPTCHAAsJPEG($image));
+        } else if ($this->_output == 'png' && imagetypes() & IMG_PNG) {
+            $this->setCaptcha($this->_getCAPTCHAAsPNG($image));
+        } else if ($this->_output == 'resource') {
+            $this->setCaptcha($image);
+        } else {
+            throw new Text_CAPTCHA_Exception(
+                "Unknown or unsupported output type specified"
+            );
+        }
+
+        imagedestroy($image);
+    }
+
+    /**
+     * Return CAPTCHA as PNG.
+     *
+     * This method returns the CAPTCHA as PNG.
+     *
+     * @param resource $image generated image
+     *
+     * @return string image contents
+     */
+    private function _getCAPTCHAAsPNG($image)
+    {
+        ob_start();
+        imagepng($image);
+        $data = ob_get_contents();
+        ob_end_clean();
+        return $data;
+    }
+
+    /**
+     * Return CAPTCHA as JPEG.
+     *
+     * This method returns the CAPTCHA as JPEG.
+     *
+     * @param resource $image generated image
+     *
+     * @return string image contents
+     */
+    private function _getCAPTCHAAsJPEG($image)
+    {
+        ob_start();
+        imagejpeg($image);
+        $data = ob_get_contents();
+        ob_end_clean();
+        return $data;
+    }
+
+    /**
+     * Return CAPTCHA as GIF.
+     *
+     * This method returns the CAPTCHA as GIF.
+     *
+     * @param resource $image generated image
+     *
+     * @return string image contents
+     */
+    private function _getCAPTCHAAsGIF($image)
+    {
+        ob_start();
+        imagegif($image);
+        $data = ob_get_contents();
+        ob_end_clean();
+        return $data;
+    }
+
+    /**
+     * Create random CAPTCHA phrase, Image edition (with size check).
+     *
+     * This method creates a random phrase, maximum 8 characters or width / 25,
+     * whatever is smaller.
+     *
+     * @return void
+     */
+    public function createPhrase()
+    {
+        $len = intval(min(8, $this->_width / 25));
+        $options = $this->_textPasswordOptions;
+        $textPassword = new Text_Password();
+        if (!is_array($options) || count($options) === 0) {
+            $this->setPhrase($textPassword->create($len));
+        } else {
+            if (count($options) === 1) {
+                $this->setPhrase($textPassword->create($len, $options[0]));
+            } else {
+                $this->setPhrase(
+                    $textPassword->create($len, $options[0], $options[1])
+                );
+            }
+        }
+    }
+}

+ 268 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver/Numeral.php

@@ -0,0 +1,268 @@
+<?php
+/**
+ * Class used for numeral captchas
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   David Coallier <davidc@agoraproduction.com>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'Text/CAPTCHA/Driver/Base.php';
+/**
+ * Class used for numeral captchas
+ *
+ * This class is intended to be used to generate numeral captchas as such as:
+ * Example:
+ *  Give me the answer to "54 + 2" to prove that you are human.
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   David Coallier <davidc@agoraproduction.com>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+class Text_CAPTCHA_Driver_Numeral extends Text_CAPTCHA_Driver_Base
+{
+    /**
+     * This variable holds the minimum range value default set to "1".
+     *
+     * @var integer $_minValue The minimum value of the number range.
+     */
+    private $_minValue = 1;
+
+    /**
+     * This variable holds the maximum range value default set to "50".
+     *
+     * @var integer $_maxValue The maximum value of the number range.
+     */
+    private $_maxValue = 50;
+
+    /**
+     * The valid operators to use in the numeral captcha. We could use / and * but
+     * not yet.
+     *
+     * @var array $_operators The operations for the captcha.
+     */
+    private $_operators = array('-', '+');
+
+    /**
+     * This variable is basically the operation that we're going to be using in the
+     * numeral captcha we are about to generate.
+     *
+     * @var string $_operator The operation's operator to use.
+     */
+    private $_operator = '';
+
+    /**
+     * This variable holds the first number of the numeral operation we are about to
+     * generate.
+     *
+     * @var integer $_firstNumber The first number of the operation.
+     */
+    private $_firstNumber = 0;
+
+    /**
+     * This variable holds the value of the second variable of the operation we are
+     * about to generate for the captcha.
+     *
+     * @var integer $_secondNumber The second number of the operation.
+     */
+    private $_secondNumber = 0;
+
+    /**
+     * Initialize numeric CAPTCHA.
+     *
+     * @param array $options CAPTCHA options with these keys:<br>
+     *                       minValue minimum value<br>
+     *                       maxValue maximum value
+     *
+     * @return void
+     */
+    public function initDriver($options = array())
+    {
+        if (isset($options['minValue'])) {
+            $this->_minValue = (int)$options['minValue'];
+        } else {
+            $this->_minValue = 1;
+        }
+        if (isset($options['maxValue'])) {
+            $this->_maxValue = (int)$options['maxValue'];
+        } else {
+            $this->_maxValue = 50;
+        }
+        if (isset($options['operator'])) {
+            $this->_operator = $options['operator'];
+        } else {
+            $this->_operator = '';
+        }
+        if (isset($options['firstValue'])) {
+            $this->_firstNumber = (int)$options['firstValue'];
+        } else {
+            $this->_firstNumber = 0;
+        }
+        if (isset($options['secondValue'])) {
+            $this->_secondNumber = (int)$options['secondValue'];
+        } else {
+            $this->_secondNumber = 0;
+        }
+    }
+
+    /**
+     * Create the CAPTCHA (the numeral expression).
+     *
+     * This function determines a random numeral expression and set the associated
+     * class properties.
+     *
+     * @return void
+     * @see _generateFirstNumber()
+     * @see _generateSecondNumber()
+     * @see _generateOperator()
+     * @see _generateOperation()
+     */
+    public function createCAPTCHA()
+    {
+        if ($this->_firstNumber == 0) {
+            $this->_firstNumber = $this->_generateNumber();
+        }
+        if ($this->_secondNumber == 0) {
+            $this->_secondNumber = $this->_generateNumber();
+        }
+        if (empty($this->_operator)) {
+            $this->_operator = $this->_operators[array_rand($this->_operators)];
+        }
+        $this->_generateOperation();
+    }
+
+    /**
+     * Set operation.
+     *
+     * This variable sets the operation variable by taking the firstNumber,
+     * secondNumber and operator.
+     *
+     * @return void
+     * @see _operation
+     * @see _firstNumber
+     * @see _operator
+     * @see _secondNumber
+     */
+    private function _setOperation()
+    {
+        $this->setCaptcha(
+            $this->_firstNumber . ' ' . $this->_operator . ' ' . $this->_secondNumber
+        );
+    }
+
+    /**
+     * Generate a number.
+     *
+     * This function takes the parameters that are in the $this->_maxValue and
+     * $this->_minValue and get the random number from them using mt_rand().
+     *
+     * @return integer Random value between _minValue and _maxValue
+     * @see _minValue
+     * @see _maxValue
+     */
+    private function _generateNumber()
+    {
+        return mt_rand($this->_minValue, $this->_maxValue);
+    }
+
+    /**
+     * Adds values.
+     *
+     * This function will add the firstNumber and the secondNumber value and then
+     * call setAnswer to set the answer value.
+     *
+     * @return void
+     * @see _firstNumber
+     * @see _secondNumber
+     * @see _setAnswer()
+     */
+    private function _doAdd()
+    {
+        $phrase = $this->_firstNumber + $this->_secondNumber;
+        $this->setPhrase($phrase);
+    }
+
+    /**
+     * Does a subtract on the values.
+     *
+     * This function executes a subtraction on the firstNumber and the secondNumber
+     * to then call $this->setAnswer to set the answer value.
+     *
+     * If the _firstNumber value is smaller than the _secondNumber value then we
+     * regenerate the first number and regenerate the operation.
+     *
+     * @return void
+     * @see _firstNumber
+     * @see _secondNumber
+     * @see _setOperation()
+     * @see Text_CAPTCHA::setPhrase()
+     */
+    private function _doSubtract()
+    {
+        $first = $this->_firstNumber;
+        $second = $this->_secondNumber;
+
+        /**
+         * Check if firstNumber is smaller than secondNumber
+         */
+        if ($first < $second) {
+            $this->_firstNumber = $second;
+            $this->_secondNumber = $first;
+            $this->_setOperation();
+        }
+
+        $phrase = $this->_firstNumber - $this->_secondNumber;
+        $this->setPhrase($phrase);
+    }
+
+    /**
+     * Generate the operation
+     *
+     * This function will call the _setOperation() function to set the operation
+     * string that will be called to display the operation, and call the function
+     * necessary depending on which operation is set by this->operator.
+     *
+     * @return void
+     * @see _setOperation()
+     * @see _operator
+     * @see _doAdd()
+     * @see _doSubtract()
+     */
+    private function _generateOperation()
+    {
+        $this->_setOperation();
+        switch ($this->_operator) {
+        case '+':
+            $this->_doAdd();
+            break;
+        case '-':
+            $this->_doSubtract();
+            break;
+        default:
+            $this->_operator = "+";
+            $this->_setOperation();
+            $this->_doAdd();
+            break;
+        }
+    }
+
+    /**
+     * Create random CAPTCHA phrase. This method is a placeholder, since the equation
+     * is created in createCAPTCHA()
+     *
+     * @return string
+     */
+    public function createPhrase()
+    {
+        $this->setCaptcha(null);
+    }
+}

+ 127 - 0
main/inc/lib/pear/Text/CAPTCHA/Driver/Word.php

@@ -0,0 +1,127 @@
+<?php
+/**
+ * Text_CAPTCHA_Driver_Word - Text_CAPTCHA driver word CAPTCHAs
+ * Class to create a textual Turing test
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Tobias Schlitt <schlitt@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'Text/CAPTCHA/Driver/Base.php';
+require_once 'Numbers/Words.php';
+/**
+ * Require Numbers_Words class for generating the text.
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Tobias Schlitt <schlitt@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+class Text_CAPTCHA_Driver_Word extends Text_CAPTCHA_Driver_Base
+{
+    /**
+     * Phrase length.
+     * This variable holds the length of the Word.
+     *
+     * @var integer
+     */
+    private $_length;
+
+    /**
+     * Numbers_Words mode.
+     * This variable holds the mode for Numbers_Words.
+     *
+     * @var String
+     */
+    private $_mode;
+
+    /**
+     * Locale
+     * This variable holds the locale for Numbers_Words
+     *
+     * @var string
+     */
+    private $_locale;
+
+    /**
+     * Initializes the new Text_CAPTCHA_Driver_Word object.
+     *
+     * @param array $options CAPTCHA options with these keys:<br>
+     *                       phrase  The "secret word" of the CAPTCHA<br>
+     *                       length  The number of characters in the phrase<br>
+     *                       locale  The locale for Numbers_Words<br>
+     *                       mode    The mode for Numbers_Words
+     *
+     * @return void
+     */
+    public function initDriver($options = array())
+    {
+        if (isset($options['length']) && is_int($options['length'])) {
+            $this->_length = $options['length'];
+        } else {
+            $this->_length = 4;
+        }
+        if (isset($options['phrase']) && !empty($options['phrase'])) {
+            $this->setPhrase((string)$options['phrase']);
+        } else {
+            $this->createPhrase();
+        }
+        if (isset($options['mode']) && !empty($options['mode'])) {
+            $this->_mode = $options['mode'];
+        } else {
+            $this->_mode = 'single';
+        }
+        if (isset($options['locale']) && !empty($options['locale'])) {
+            $this->_locale = $options['locale'];
+        } else {
+            $this->_locale = 'en_US';
+        }
+    }
+
+    /**
+     * Create random CAPTCHA phrase, "Word edition" (numbers only).
+     * This method creates a random phrase
+     *
+     * @return void
+     */
+    public function createPhrase()
+    {
+        $phrase = new Text_Password();
+        $this->setPhrase(
+            $phrase->create(
+                $this->_length, 'unpronounceable', 'numeric'
+            )
+        );
+    }
+
+    /**
+     * Place holder for the real _createCAPTCHA() method
+     * used by extended classes to generate CAPTCHA from phrase
+     *
+     * @return void
+     */
+    public function createCAPTCHA()
+    {
+        $res = '';
+        $numberWords = new Numbers_Words();
+        $phrase = $this->getPhrase();
+        if ($this->_mode == 'single') {
+            $phraseArr = str_split($phrase);
+            for ($i = 0; $i < strlen($phrase); $i++) {
+                $res .= ' ' . $numberWords->toWords($phraseArr[$i], $this->_locale);
+            }
+        } else {
+            $res = $numberWords->toWords($phrase, $this->_locale);
+        }
+        $this->setCaptcha($res);
+    }
+}

+ 28 - 0
main/inc/lib/pear/Text/CAPTCHA/Exception.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * Exception for Text_CAPTCHA
+ *
+ * PHP version 5
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Christian Weiske <cweiske@php.net>
+ * @author   Christian Wenz <wenz@php.net>
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+require_once 'PEAR/Exception.php';
+/**
+ * Exception for Text_CAPTCHA
+ *
+ * @category Text
+ * @package  Text_CAPTCHA
+ * @author   Michael Cramer <michael@bigmichi1.de>
+ * @license  http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @link     http://pear.php.net/package/Text_CAPTCHA
+ */
+class Text_CAPTCHA_Exception extends PEAR_Exception
+{
+
+}

+ 533 - 0
main/inc/lib/pear/Text/Password.php

@@ -0,0 +1,533 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Class to create passwords
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category   Text
+ * @package    Text_Password
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Olivier Vanhoucke <olivier@php.net>
+ * @copyright  2004-2005 Martin Jansen, Olivier Vanhoucke
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    CVS: $Id: Password.php,v 1.18 2008/11/30 13:38:56 mj Exp $
+ * @link       http://pear.php.net/package/Text_Password
+ */
+
+/**
+ * Number of possible characters in the password
+ */
+$GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = 0;
+
+/**
+ * Main class for the Text_Password package
+ *
+ * @category   Text
+ * @package    Text_Password
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Olivier Vanhoucke <olivier@php.net>
+ * @copyright  2004-2005 Martin Jansen, Olivier Vanhoucke
+ * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/Text_Password
+ */
+class Text_Password {
+
+    /**
+     * Create a single password.
+     *
+     * @access public
+     * @param  integer Length of the password.
+     * @param  string  Type of password (pronounceable, unpronounceable)
+     * @param  string  Character which could be use in the
+     *                 unpronounceable password ex : 'ABCDEFG'
+     *                 or numeric, alphabetical or alphanumeric.
+     * @return string  Returns the generated password.
+     */
+    function create($length = 10, $type = 'pronounceable', $chars = '')
+    {
+        switch ($type) {
+        case 'unpronounceable' :
+            return Text_Password::_createUnpronounceable($length, $chars);
+
+        case 'pronounceable' :
+        default :
+            return Text_Password::_createPronounceable($length);
+        }
+    }
+
+    /**
+     * Create multiple, different passwords
+     *
+     * Method to create a list of different passwords which are
+     * all different.
+     *
+     * @access public
+     * @param  integer Number of different password
+     * @param  integer Length of the password
+     * @param  string  Type of password (pronounceable, unpronounceable)
+     * @param  string  Character which could be use in the
+     *                 unpronounceable password ex : 'A,B,C,D,E,F,G'
+     *                 or numeric, alphabetical or alphanumeric.
+     * @return array   Array containing the passwords
+     */
+    function createMultiple($number, $length = 10, $type = 'pronounceable', $chars = '')
+    {
+        $passwords = array();
+
+        while ($number > 0) {
+            while (true) {
+                $password = Text_Password::create($length, $type, $chars);
+                if (!in_array($password, $passwords)) {
+                    $passwords[] = $password;
+                    break;
+                }
+            }
+            $number--;
+        }
+        return $passwords;
+    }
+
+    /**
+     * Create password from login
+     *
+     * Method to create password from login
+     *
+     * @access public
+     * @param  string  Login
+     * @param  string  Type
+     * @param  integer Key
+     * @return string
+     */
+    function createFromLogin($login, $type, $key = 0)
+    {
+        switch ($type) {
+        case 'reverse':
+            return strrev($login);
+
+        case 'shuffle':
+            return Text_Password::_shuffle($login);
+
+        case 'xor':
+            return Text_Password::_xor($login, $key);
+
+        case 'rot13':
+            return str_rot13($login);
+
+        case 'rotx':
+            return Text_Password::_rotx($login, $key);
+
+        case 'rotx++':
+            return Text_Password::_rotxpp($login, $key);
+
+        case 'rotx--':
+            return Text_Password::_rotxmm($login, $key);
+
+        case 'ascii_rotx':
+            return Text_Password::_asciiRotx($login, $key);
+
+        case 'ascii_rotx++':
+            return Text_Password::_asciiRotxpp($login, $key);
+
+        case 'ascii_rotx--':
+            return Text_Password::_asciiRotxmm($login, $key);
+        }
+    }
+
+    /**
+     * Create multiple, different passwords from an array of login
+     *
+     * Method to create a list of different password from login
+     *
+     * @access public
+     * @param  array   Login
+     * @param  string  Type
+     * @param  integer Key
+     * @return array   Array containing the passwords
+     */
+    function createMultipleFromLogin($login, $type, $key = 0)
+    {
+        $passwords = array();
+        $number    = count($login);
+        $save      = $number;
+
+        while ($number > 0) {
+            while (true) {
+                $password = Text_Password::createFromLogin($login[$save - $number], $type, $key);
+                if (!in_array($password, $passwords)) {
+                    $passwords[] = $password;
+                    break;
+                }
+            }
+            $number--;
+        }
+        return $passwords;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _xor($login, $key)
+    {
+        $tmp = '';
+
+        for ($i = 0; $i < strlen($login); $i++) {
+            $next = ord($login{$i}) ^ $key;
+            if ($next > 255) {
+                $next -= 255;
+            } elseif ($next < 0) {
+                $next += 255;
+            }
+            $tmp .= chr($next);
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     * lowercase only
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _rotx($login, $key)
+    {
+        $tmp = '';
+        $login = strtolower($login);
+
+        for ($i = 0; $i < strlen($login); $i++) {
+            if ((ord($login{$i}) >= 97) && (ord($login{$i}) <= 122)) { // 65, 90 for uppercase
+                $next = ord($login{$i}) + $key;
+                if ($next > 122) {
+                    $next -= 26;
+                } elseif ($next < 97) {
+                    $next += 26;
+                }
+                $tmp .= chr($next);
+            } else {
+                $tmp .= $login{$i};
+            }
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     * lowercase only
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _rotxpp($login, $key)
+    {
+        $tmp = '';
+        $login = strtolower($login);
+
+        for ($i = 0; $i < strlen($login); $i++, $key++) {
+            if ((ord($login{$i}) >= 97) && (ord($login{$i}) <= 122)) { // 65, 90 for uppercase
+                $next = ord($login{$i}) + $key;
+                if ($next > 122) {
+                    $next -= 26;
+                } elseif ($next < 97) {
+                    $next += 26;
+                }
+                $tmp .= chr($next);
+            } else {
+                $tmp .= $login{$i};
+            }
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     * lowercase only
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _rotxmm($login, $key)
+    {
+        $tmp = '';
+        $login = strtolower($login);
+
+        for ($i = 0; $i < strlen($login); $i++, $key--) {
+            if ((ord($login{$i}) >= 97) && (ord($login{$i}) <= 122)) { // 65, 90 for uppercase
+                $next = ord($login{$i}) + $key;
+                if ($next > 122) {
+                    $next -= 26;
+                } elseif ($next < 97) {
+                    $next += 26;
+                }
+                $tmp .= chr($next);
+            } else {
+                $tmp .= $login{$i};
+            }
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _asciiRotx($login, $key)
+    {
+        $tmp = '';
+
+        for ($i = 0; $i < strlen($login); $i++) {
+            $next = ord($login{$i}) + $key;
+            if ($next > 255) {
+                $next -= 255;
+            } elseif ($next < 0) {
+                $next += 255;
+            }
+            switch ($next) { // delete white space
+            case 0x09:
+            case 0x20:
+            case 0x0A:
+            case 0x0D:
+                $next++;
+            }
+            $tmp .= chr($next);
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _asciiRotxpp($login, $key)
+    {
+        $tmp = '';
+
+        for ($i = 0; $i < strlen($login); $i++, $key++) {
+            $next = ord($login{$i}) + $key;
+            if ($next > 255) {
+                $next -= 255;
+            } elseif ($next < 0) {
+                $next += 255;
+            }
+            switch ($next) { // delete white space
+            case 0x09:
+            case 0x20:
+            case 0x0A:
+            case 0x0D:
+                $next++;
+            }
+            $tmp .= chr($next);
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     *
+     * @access private
+     * @param  string  Login
+     * @param  integer Key
+     * @return string
+     */
+    function _asciiRotxmm($login, $key)
+    {
+        $tmp = '';
+
+        for ($i = 0; $i < strlen($login); $i++, $key--) {
+            $next = ord($login{$i}) + $key;
+            if ($next > 255) {
+                $next -= 255;
+            } elseif ($next < 0) {
+                $next += 255;
+            }
+            switch ($next) { // delete white space
+            case 0x09:
+            case 0x20:
+            case 0x0A:
+            case 0x0D:
+                $next++;
+            }
+            $tmp .= chr($next);
+        }
+
+        return $tmp;
+    }
+
+    /**
+     * Helper method to create password
+     *
+     * Method to create a password from a login
+     *
+     * @access private
+     * @param  string  Login
+     * @return string
+     */
+    function _shuffle($login)
+    {
+        $tmp = array();
+
+        for ($i = 0; $i < strlen($login); $i++) {
+            $tmp[] = $login{$i};
+        }
+
+        shuffle($tmp);
+
+        return implode($tmp, '');
+    }
+
+    /**
+     * Create pronounceable password
+     *
+     * This method creates a string that consists of
+     * vowels and consonats.
+     *
+     * @access private
+     * @param  integer Length of the password
+     * @return string  Returns the password
+     */
+    function _createPronounceable($length)
+    {
+
+        $retVal = '';
+
+        /**
+         * List of vowels and vowel sounds
+         */
+        $v = array('a', 'e', 'i', 'o', 'u', 'ae', 'ou', 'io',
+                   'ea', 'ou', 'ia', 'ai'
+                   );
+
+        /**
+         * List of consonants and consonant sounds
+         */
+        $c = array('b', 'c', 'd', 'g', 'h', 'j', 'k', 'l', 'm',
+                   'n', 'p', 'r', 's', 't', 'u', 'v', 'w',
+                   'tr', 'cr', 'fr', 'dr', 'wr', 'pr', 'th',
+                   'ch', 'ph', 'st', 'sl', 'cl'
+                   );
+
+        $v_count = 12;
+        $c_count = 29;
+
+        $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = $v_count + $c_count;
+
+        for ($i = 0; $i < $length; $i++) {
+            $retVal .= $c[mt_rand(0, $c_count-1)] . $v[mt_rand(0, $v_count-1)];
+        }
+
+        return substr($retVal, 0, $length);
+    }
+
+    /**
+     * Create unpronounceable password
+     *
+     * This method creates a random unpronounceable password
+     *
+     * @access private
+     * @param  integer Length of the password
+     * @param  string  Character which could be use in the
+     *                 unpronounceable password ex : 'ABCDEFG'
+     *                 or numeric, alphabetical or alphanumeric.
+     * @return string  Returns the password
+     */
+    function _createUnpronounceable($length, $chars)
+    {
+        $password = '';
+
+        /**
+         * List of character which could be use in the password
+         */
+         switch($chars) {
+
+         case 'alphanumeric':
+             $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+             $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = 62;
+             break;
+
+         case 'alphabetical':
+             $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+             $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = 52;
+             break;
+
+         case 'numeric':
+             $chars = '0123456789';
+             $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = 10;
+             break;
+
+         case '':
+             $chars = '_#@%&ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+             $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = 67;
+             break;
+
+         default:
+             /**
+              * Some characters shouldn't be used
+              */
+             $chars = trim($chars);
+             $chars = str_replace(array('+', '|', '$', '^', '/', '\\', ','), '', $chars);
+
+             $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] = strlen($chars);
+         }
+
+         /**
+          * Generate password
+          */
+         for ($i = 0; $i < $length; $i++) {
+             $num = mt_rand(0, $GLOBALS['_Text_Password_NumberOfPossibleCharacters'] - 1);
+             $password .= $chars{$num};
+         }
+
+         /**
+          * Return password
+          */
+         return $password;
+    }
+}
+?>