<?php
/**
 * Horde_Form Class
 *
 * The Horde_Form:: package provides form rendering, validation, and
 * other functionality for the Horde Application Framework.
 *
 * $Horde: horde/lib/Form.php,v 1.134 2003/08/22 06:11:48 mdjukic Exp $
 *
 * Copyright 2001-2003 Robert E. Coyle <robertecoyle@hotmail.com>
 * Copyright 2001-2003 Chuck Hagenbuch <chuck@horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Robert E. Coyle <robertecoyle@hotmail.com>
 * @version $Revision: 1.134 $
 * @since   Horde 3.0
 * @package horde.form
 */
class Horde_Form {

    var $_name = '';
    var $_title = '';
    var $_vars;
    var $_submit = array();
    var $_reset = false;
    var $_errors = array();
    var $_submitted = null;
    var $_sections = array();
    var $_currentSection = array();
    var $_variables = array();
    var $_hiddenVariables = array();
    var $_useFormToken = true;
    var $_autofilled = false;
    var $_enctype = null;
    var $_help = false;

    function Horde_Form(&$vars, $title = '', $name = null)
    {
        if (is_null($name)) {
            $name = get_class($this);
        }

        $this->_vars = &$vars;
        $this->_title = $title;
        $this->_name = $name;
    }

    function &factory($form, &$vars, $title = '', $name = null)
    {
        if (class_exists($form)) {
            return new $form($vars, $title, $name);
        } else {
            return new Horde_Form($vars, $title, $name);
        }
    }

    function &singleton($form, &$vars, $title = '', $name = null)
    {
        static $instances;

        $signature = serialize(array($form, $vars, $title, $name));
        if (!isset($instances[$signature])) {
            $instances[$signature] = &Horde_Form::factory($form, $vars, $title, $name);
        }

        return $instances[$signature];
    }

    function &getDefaultVars()
    {
        require_once HORDE_BASE . '/lib/Horde.php';
        return new Horde_Form_Vars(Horde::dispelMagicQuotes($_REQUEST));
    }

    function getTitle()
    {
        return $this->_title;
    }

    function setTitle($title)
    {
        $this->_title = $title;
    }

    function getName()
    {
        return $this->_name;
    }

    function getCustomRenderer()
    {
        return null;
    }

    function &getType($type, $params = array())
    {
        $type_class = 'Horde_Form_Type_' . $type;
        if (!class_exists($type_class)) {
            Horde::fatal(PEAR::raiseError(sprintf('Inexistant class for field type "%s"', $type)), __FILE__, __LINE__);
        }
        $type_ob = &new $type_class();
        call_user_func_array(array(&$type_ob, 'init'), $params);
        return $type_ob;
    }

    function setSection($section = '', $desc = '', $expanded = true)
    {
        $this->_currentSection = $section;
        $this->_sections[$section]['desc'] = $desc;
        $this->_sections[$section]['expanded'] = $expanded;

    }

    function getSectionDesc($section)
    {
        return $this->_sections[$section]['desc'];
    }

    function getSectionExpandedState($section, $boolean = true)
    {
        if ($boolean) {
            /* Only the boolean value is required. */
            return $this->_sections[$section]['expanded'];
        }

        /* Need to return the values for use in styles. */
        if ($this->_sections[$section]['expanded']) {
            return 'block';
        } else {
            return 'none';
        }
    }

    /**
     * @access protected
     */
    function &addVariable($humanName, $varName, $type, $required,
                          $readonly = false, $description = null,
                          $params = array())
    {
        $type = $this->getType($type, $params);
        $var = &new Horde_Form_Variable($humanName, $varName, $type,
                                        $required, $readonly, $description);

        /* Set the form object reference in the var. */
        $var->setFormOb($this);

        if ($var->getTypeName() == 'enum' &&
            count($var->getValues()) == 1) {
            $vals = array_keys($var->getValues());
            $this->_vars->addVar($var->varName, $vals[0]);
            $var->_autofilled = true;
        } elseif ($var->getTypeName() == 'file') {
            $this->_enctype = 'multipart/form-data';
        }
        if (empty($this->_currentSection)) {
            $this->_currentSection = '__base';
        }
        $this->_variables[$this->_currentSection][] = &$var;

        return $var;
    }

    /**
     * @access protected
     */
    function &addHidden($humanName, $varName, $type, $required,
                        $readonly = false, $description = null)
    {
        $type = $this->getType($type);
        $var = &new Horde_Form_Variable($humanName, $varName, $type,
                                       $required, $readonly, $description);
        $this->_hiddenVariables[] = &$var;
        return $var;
    }

    function &getVariables($flat = true, $withHidden = false)
    {
        if ($flat) {
            $vars = array();
            foreach ($this->_variables as $section) {
                foreach ($section as $var) {
                    $vars[] = $var;
                }
            }
            if ($withHidden) {
                foreach ($this->_hiddenVariables as $var) {
                    $vars[] = $var;
                }
            }
            return $vars;
        } else {
            return $this->_variables;
        }
    }

    function open(&$renderer, &$vars, $action, $method = 'get', $enctype = null)
    {
        if (is_null($enctype) && !is_null($this->_enctype)) {
            $enctype = $this->_enctype;
        }
        $renderer->open($action, $method, $this->_name, $enctype);
        $renderer->listFormVars($this);

        if (!empty($this->_name)) {
            $vars->_preserveVarByPost('formname', $this->_name);
        }

        if ($this->_useFormToken) {
            require_once HORDE_BASE . '/lib/Token.php';
            $vars->_preserveVarByPost('__formToken_' . $this->_name, Horde_Token::generateId($this->_name));
        }

        foreach ($this->_hiddenVariables as $var) {
            $vars->preserveVarByPost($var->getVarName());
        }
    }

    function close(&$renderer)
    {
        $renderer->close();
    }

    function renderActive(&$renderer, &$vars, $action, $method = 'get', $enctype = null)
    {
        if (is_null($enctype) && !is_null($this->_enctype)) {
            $enctype = $this->_enctype;
        }
        $renderer->open($action, $method, $this->getName(), $enctype);
        $renderer->listFormVars($this);

        if (!empty($this->_name)) {
            $vars->_preserveVarByPost('formname', $this->_name);
        }

        if ($this->_useFormToken) {
            require_once HORDE_BASE . '/lib/Token.php';
            $vars->_preserveVarByPost('__formToken_' . $this->_name, Horde_Token::generateId($this->_name));
        }

        foreach ($this->_hiddenVariables as $var) {
            $vars->preserveVarByPost($var->getVarName());
        }

        $renderer->beginActive($this->getTitle());
        $renderer->renderFormActive($this, $vars);
        $renderer->submit($this->_submit, $this->_reset);
        $renderer->end();
        $renderer->close();
    }

    function renderInactive(&$renderer, $vars)
    {
        $renderer->beginInactive($this->getTitle());
        $renderer->renderFormInactive($this, $vars);
        $renderer->end();
    }

    function preserve($vars)
    {
        if ($this->_useFormToken) {
            require_once HORDE_BASE . '/lib/Token.php';
            $vars->_preserveVarByPost('__formToken_' . $this->_name, Horde_Token::generateId($this->_name));
        }

        $variables = $this->getVariables();
        foreach ($variables as $var) {
            switch ($var->getTypeName()) {

            case 'passwordconfirm':
            case 'emailconfirm':
                $vars->preserveVarByPost($var->getVarName() . '[original]');
                $vars->preserveVarByPost($var->getVarName() . '[confirm]');
                break;

            case 'monthyear':
                $vars->preserveVarByPost($var->getVarName() . '[month]');
                $vars->preserveVarByPost($var->getVarName() . '[year]');
                break;

            case 'monthdayyear':
                $vars->preserveVarByPost($var->getVarName() . '[month]');
                $vars->preserveVarByPost($var->getVarName() . '[day]');
                $vars->preserveVarByPost($var->getVarName() . '[year]');
                break;
            }

            $vars->preserveVarByPost($var->getVarName());
        }
        foreach ($this->_hiddenVariables as $var) {
            $vars->preserveVarByPost($var->getVarName());
        }
    }

    function unsetVars(&$vars)
    {
        foreach ($this->getVariables() as $var) {
            $vars->unsetVar($var->getVarName());
        }
    }

    function validate(&$vars)
    {
        $message = '';
        $this->_autofilled = true;

        if ($this->_useFormToken) {
            require_once HORDE_BASE . '/lib/Token.php';
            $tokenSource = &Horde_Token::singleton($GLOBALS['conf']['token']['driver']);
            if (!$tokenSource->verify($vars->getVar('__formToken_' . $this->_name))) {
                $this->_errors['__formToken'] = _("This form has already been processed.");
            }
        }

        foreach ($this->getVariables() as $var) {
            $this->_autofilled = $var->_autofilled && $this->_autofilled;
            if (!$var->validate($vars, $message)) {
                $this->_errors[$var->getVarName()] = $message;
            }
        }

        if ($this->_autofilled) {
            unset($this->_errors['__formToken']);
        }

        foreach ($this->_hiddenVariables as $var) {
            if (!$var->validate($vars, $message)) {
                $this->_errors[$var->getVarName()] = $message;
            }
        }

        return $this->isValid();
    }

    function clearValidation()
    {
        $this->_errors = array();
    }

    function getError($var)
    {
        if (is_a($var, 'Horde_Form_Variable')) {
            $name = $var->getVarName();
        } else {
            $name = $var;
        }
        return isset($this->_errors[$name]) ? $this->_errors[$name] : null;
    }

    function setError($var, $message)
    {
        if (is_a($var, 'Horde_Form_Variable')) {
            $name = $var->getVarName();
        } else {
            $name = $var;
        }
        $this->_errors[$name] = $message;
    }

    function isValid()
    {
        return ($this->_autofilled || count($this->_errors) == 0);
    }

    function execute(&$vars)
    {
        Horde::logMessage('Warning: Horde_Form::execute() called, should be overridden', __FILE__, __LINE__, PEAR_LOG_DEBUG);
    }

    function getInfo(&$vars, &$info)
    {
        $this->_getInfoFromVariables($this->getVariables(), $vars, $info);
        $this->_getInfoFromVariables($this->_hiddenVariables, $vars, $info);
    }

    function _getInfoFromVariables(&$variables, &$vars, &$info)
    {
        foreach ($variables as $var) {
            if ($var->isArrayVal()) {
                $var->getInfo($vars, $values);
                if (is_array($values)) {
                    $varName = str_replace('[]', '', $var->getVarName());
                    foreach ($values as $i => $val) {
                        $info[$i][$varName] = $val;
                    }
                }
            } else {
                require_once HORDE_BASE . '/lib/Array.php';
                if (Horde_Array::getArrayParts($var->getVarName(), $base, $keys)) {
                    if (!isset($info[$base])) {
                        $info[$base] = array();
                    }
                    $pointer = &$info[$base];
                    while (count($keys)) {
                        $key = array_shift($keys);
                        if (!isset($pointer[$key])) {
                            $pointer[$key] = array();
                        }
                        $pointer = &$pointer[$key];
                    }
                    $var->getInfo($vars, $pointer);
                } else {
                    $var->getInfo($vars, $info[$var->getVarName()]);
                }
            }
        }
    }

    function hasHelp()
    {
        return $this->_help;
    }

    /**
     * Determines if the form has been submitted or not. If the class var
     * _submitted is null then it will check for the presence of the formname
     * in the form variables.
     * Other events can explicitly set the _submitted variable to false to
     * indicate a form submit but not for actual posting of data (eg. onChange
     * events to update the display of fields).
     *
     * @returns bool  True or false indicating if the form has been submitted.
     */
    function isSubmitted()
    {
        if (is_null($this->_submitted)) {
            if (!is_null($this->_vars->getVar('formname'))) {
                $this->_submitted = true;
                $this->onSubmit();
            } else {
                $this->_submitted = false;
            }
        }

        return $this->_submitted;
    }

    /**
     * Check if there is anything to do on the submission of the form by looping
     * through each variable's onSubmit() function.
     */
    function onSubmit()
    {
        $variables = $this->getVariables();
        foreach ($variables as $var) {
            $var->type->onSubmit($var, $this->_vars);
        }
    }

    /**
     * Explicit setting of the state of the form submit. An event can override
     * the automatic determination of the submit state in the isSubmitted()
     * function.
     *
     * @params optional bool  True or false to indicate the submit state of the
     *                        form.
     */
    function setSubmitted($state = true)
    {
        $this->_submitted = $state;
    }

}

/**
 * Horde_Form_Vars Class
 *
 * @author  Robert E. Coyle <robertecoyle@hotmail.com>
 * @package horde.form
 */
class Horde_Form_Vars {

    var $_vars;
    var $_setvars = array();

    function Horde_Form_Vars($vars = array())
    {
        if (isset($vars['_formvars'])) {
            $this->_setvars = @unserialize($vars['_formvars']);
            unset($vars['_formvars']);
        }
        $this->_vars = $vars;
    }

    function unsetVar($varname, $index = null)
    {
        require_once HORDE_BASE . '/lib/Array.php';
        Horde_Array::getArrayParts($varname, $base, $keys);
        if (!is_null($base)) {
            eval("unset(\$this->_vars['" . $base . "']['" . join("']['", $keys) . "']);");
        } elseif (!is_null($index)) {
            unset($this->_vars[$varname][$index]);
        } else {
            unset($this->_vars[$varname]);
        }
    }

    function getVar($varname)
    {
        return $this->_getVar($varname, $ignore);
    }

    function setVar($varname, $value, $index = null)
    {
        if (!is_null($index)) {
            $this->_vars[$varname][$index] = $value;
        } else {
            $this->_vars[$varname] = $value;
        }
    }

    function addVar($varname, $value)
    {
        if ($this->getVar($varname)) {
            return false;
        }
        $this->_vars[$varname] = $value;
    }

    function addVars($vars)
    {
        foreach ($vars as $varname => $value) {
            $this->addVar($varname, $value);
        }
    }

    function getVarWasset($varname, &$wasset)
    {
        $result = $this->_getVar($varname, $wasset);
        return $result;
    }

    function isVarSet($varname)
    {
        if (count($this->_setvars) &&
            $this->_isset($this->_setvars, $varname, false)) {
            return true;
        }
        return $this->_isset($this->_vars, $varname, false);
    }

    function preserveByGet($baseurl)
    {
        foreach ($this->_vars as $name => $value) {
            $baseurl = Horde::addParameter($baseurl, $name, $value);
        }

        return $baseurl;
    }

    function preserveByPost()
    {
        foreach ($this->_vars as $name => $value) {
            $this->preserveVarByPost($name, $value);
        }
    }

    function preserveVarByPost($varname)
    {
        $value = $this->_getVar($varname, $wasset);

        if ($wasset) {
            $this->_preserveVarByPost($varname, $value);
        }
    }

    /**
     * Find out whether or not $varname was set in $array.
     *
     * @access private
     *
     * @param array  $array                The array to search in (usually
     *                                     either $this->_vars or
     *                                     $this->_setvars).
     * @param string $varname              The name of the variable to look for.
     * @param optional bool $checkSetvars  If we don't find $varname, should we
     *                                     check $this->_setvars to see if it
     *                                     should have  existed (like a checkbox     *                                     or select multiple).
     *
     * @return  Whether or not the variable was set (or, if we've checked
     *          $this->_setVars, should have been set).
     */
    function _isset($array, $varname, $checkSetvars = true)
    {
        return $this->_getVarIsset($array, $varname, $value, $checkSetvars);
    }

    /**
     * Fetch the requested variable ($varname) into $value, and return whether
     * or not the variable was set in $array.
     *
     * @access private
     *
     * @param array   $array               The array to search in (usually
     *                                     either $this->_vars or
     *                                     $this->_setvars).
     * @param string  $varname             The name of the variable to look for.
     * @param mixed  &$value               $varname's value gets assigned to
     *                                     this variable.
     * @param optional bool $checkSetvars  If we don't find $varname, should we
     *                                     check $this->_setvars to see if it
     *                                     should have existed (like a checkbox
     *                                     or select multiple).
     *
     * @return  Whether or not the variable was set (or, if we've checked
     *          $this->_setVars, should have been set).
     */
    function _getVarIsset($array, $varname, &$value, $checkSetvars = true)
    {
        require_once HORDE_BASE . '/lib/Array.php';
        if (Horde_Array::getArrayParts($varname, $base, $keys)) {
            if (!isset($array[$base])) {
                $value = null;
                // If we're supposed to check $this->_setvars, do so,
                // but make sure not to check it again.
                return $checkSetvars ? $this->_isset($this->_setvars, $varname, false) : false;
            } else {
                $searchspace = &$array[$base];
                while (count($keys)) {
                    $key = array_shift($keys);
                    if (!isset($searchspace[$key])) {
                        $value = null;
                        // If we're supposed to check $this->_setvars,
                        // do so, but make sure not to check it again.
                        return $checkSetvars ? $this->_isset($this->_setvars, $varname, false) : false;
                    }
                    $searchspace = &$searchspace[$key];
                }
                $value = $searchspace;
                return true;
            }
        } else {
            $value = isset($array[$varname]) ? $array[$varname] : null;
            if (!is_null($value)) {
                return true;
            } elseif ($checkSetvars) {
                // If we're supposed to check $this->_setvars, do so,
                // but make sure not to check it again.
                return $this->_isset($this->_setvars, $varname, false);
            } else {
                return false;
            }
        }
    }

    /**
     * @access private
     */
    function _getVar($varname, &$wasset)
    {
        $wasset = $this->_getVarIsset($this->_vars, $varname, $value);
        return $value;
    }

    /**
     * @access private
     */
    function _preserveVarByPost($varname, $value)
    {
        $varname = htmlspecialchars($varname);
        if (is_array($value)) {
            foreach ($value as $val) {
                $val = htmlspecialchars($val);
                echo '<input type="hidden" name="' . $varname . '[]" value="' . $val . "\" />\n";
            }
        } else {
            $value = htmlspecialchars($value);
            echo '<input type="hidden" name="' . $varname . '" value="' . $value . "\" />\n";
        }
    }

}

/**
 * Horde_Form_Type Class
 *
 * @author  Robert E. Coyle <robertecoyle@hotmail.com>
 * @package horde.form
 */
class Horde_Form_Type {

    function Horde_Form_Type()
    {
    }

    function init()
    {
    }

    function onSubmit()
    {
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        $message = "<b>Error:</b> Horde_Form_Type::isValid() called - should be overridden<br />";
        return false;
    }

    function getTypeName()
    {
        require_once HORDE_BASE . '/lib/String.php';
        return str_replace('horde_form_type_', '', String::lower(get_class($this)));
    }

    function getValues()
    {
        return null;
    }

    function getInfo(&$vars, &$var, &$info)
    {
        $info = $var->getValue($vars);
    }

}

class Horde_Form_Type_spacer extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Spacer");
        return $about;
    }

}

class Horde_Form_Type_header extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Header");
        return $about;
    }

}

class Horde_Form_Type_description extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Description");
        return $about;
    }

}

class Horde_Form_Type_html extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("HTML");
        return $about;
    }

}

class Horde_Form_Type_number extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
            $message = _("This field is required.");
            return false;
        }

        if (empty($value) || is_numeric($value)) {
            return true;
        }

        $message = _("This field may only contain numbers.");
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Number");
        return $about;
    }

}

class Horde_Form_Type_int extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
            $message = _("This field is required.");
            return false;
        }

        if (empty($value) || preg_match('/^[0-9]+$/', $value)) {
            return true;
        }

        $message = _("This field may only contain integers.");
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Integer");
        return $about;
    }

}

class Horde_Form_Type_intList extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if (empty($value) && $var->isRequired()) {
            $message = _("This field is required.");
            return false;
        }

        if (empty($value) || preg_match('/^[0-9 ,]+$/', $value)) {
            return true;
        }

        $message = _("This field must be a comma or space separated list of integers");
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Integer list");
        return $about;
    }

}

class Horde_Form_Type_text extends Horde_Form_Type {

    var $_regex;
    var $_size;
    var $_maxlength;

    /**
     * The initialisation function for the text variable type.
     *
     * @param optional string $regex  Any valid PHP PCRE pattern syntax that
     *                                needs to be matched for the field to be
     *                                considered valid. If left empty validity
     *                                will be checked only for required fields
     *                                whether they are empty or not.
     *                                If using this regex test it is advisable
     *                                to enter a description for this field to
     *                                warn the user what is expected, as the
     *                                generated error message is quite generic
     *                                and will not give any indication where the
     *                                regex failed.
     */
    function init($regex = '', $size = 40, $maxlength = null)
    {
        $this->_regex     = $regex;
        $this->_size      = $size;
        $this->_maxlength = $maxlength; 
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        $valid = true;

        if ($var->isRequired() && empty($this->_regex)) {
            $valid = strlen(trim($value)) > 0;

            if (!$valid) {
                $message = _("This field is required.");
            }
        } elseif (!empty($this->_regex)) {
            $valid = preg_match($this->_regex, $value);

            if (!$valid) {
                $message = _("You have to enter a valid value.");
            }
        }

        return $valid;
    }

    function getSize() {
        return $this->_size;
    }

    function getMaxLength() {
        return $this->_maxlength;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Text");
        $about['params'] = array(
            'regex'     => array('label' => _("Regex"),
                                 'type'  => 'text'),
            'size'      => array('label' => _("Size"),
                                 'type'  => 'int'),
            'maxlength' => array('label' => _("Maximum length"),
                                 'type'  => 'int'),
        );
        return $about;
    }

}

class Horde_Form_Type_stringlist extends Horde_Form_Type_text {

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("String list");
        $about['params'] = array(
            'regex'     => array('label' => _("Regex"),
                                 'type'  => 'text'),
            'size'      => array('label' => _("Size"),
                                 'type'  => 'int'),
            'maxlength' => array('label' => _("Maximum length"),
                                 'type'  => 'int'),
        );
        return $about;
    }

}

class Horde_Form_Type_longText extends Horde_Form_Type_text {

    var $_rows;
    var $_cols;
    var $_html_helper = array();

    function init($rows = 8, $cols = 80, $html_helper = '')
    {
        $this->_rows = $rows;
        $this->_cols = $cols;
        $this->_html_helper = $html_helper;
    }

    function getRows()
    {
        return $this->_rows;
    }

    function getCols()
    {
        return $this->_cols;
    }

    function hasHtmlHelper($option = '')
    {
        if (empty($option)) {
            return !empty($this->_html_helper);
        } else {
            return in_array($option, $this->_html_helper);
        }
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Long text");
        $about['params'] = array(
            'rows' => array('label' => _("Number of rows"),
                            'type'  => 'int'),
            'cols' => array('label' => _("Number of columns"),
                            'type'  => 'int'),
        );
        return $about;
    }

}

class Horde_Form_Type_countedText extends Horde_Form_Type_longText {

    var $_chars;

    function init($rows = null, $cols = null, $chars = 1000)
    {
        parent::init($rows, $cols);
        $this->_chars = $chars;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        $valid = true;

        if ($var->isRequired()) {
            $length = String::length(trim($value));

            if ($length <= 0) {
                $valid = false;
                $message = _("This field is required.");
            } elseif ($length > $this->_chars) {
                $valid = false;
                $message = sprintf(_("There are too many characters in this field. You have entered %s characters; you must enter less than %s."), String::length(trim($value)), $this->_chars);
            }
        }

        return $valid;
    }

    function getChars()
    {
        return $this->_chars;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Counted text");
        $about['params'] = array(
            'rows'  => array('label' => _("Number of rows"),
                             'type'  => 'int'),
            'cols'  => array('label' => _("Number of columns"),
                             'type'  => 'int'),
            'chars' => array('label' => _("Number of characters"),
                             'type'  => 'int')
        );
        return $about;
    }

}

class Horde_Form_Type_address extends Horde_Form_Type_longText {

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Address");
        $about['params'] = array(
            'rows' => array('label' => _("Number of rows"),
                            'type'  => 'int'),
            'cols' => array('label' => _("Number of columns"),
                            'type'  => 'int')
        );
        return $about;
    }

}

class Horde_Form_Type_file extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired()) {
            require_once HORDE_BASE . '/lib/Server.php';
            $uploaded = Server::wasFileUploaded($var->getVarName());
            if (is_a($uploaded, 'PEAR_Error')) {
                $message = $uploaded->getMessage();
                return false;
            }
        }

        return true;
    }

    function getInfo(&$vars, &$var, &$info)
    {
        require_once HORDE_BASE . '/lib/Server.php';
        $name = $var->getVarName();
        if (Server::wasFileUploaded($name)) {
            $info['name'] = $_FILES[$name]['name'];
            $info['type'] = $_FILES[$name]['type'];
            $info['tmp_name'] = $_FILES[$name]['tmp_name'];
            $info['error'] = $_FILES[$name]['error'];
            $info['size'] = $_FILES[$name]['size'];
        }
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("File upload");
        return $about;
    }

}

class Horde_Form_Type_image extends Horde_Form_Type {

    function onSubmit(&$var, &$vars)
    {
        /* Check if file has been uploaded. */
        require_once HORDE_BASE . '/lib/Server.php';
        $field = $var->getVarName();
        $uploaded = Server::wasFileUploaded($field . '[new]');

        if ($uploaded === true) {
            $old = array();
            /* File ok, save to temp dir for preview work. */
            $old['type'] = Server::getUploadedFileType($field . '[new]');

            /* Get the other parts of the upload. */
            require_once HORDE_BASE . '/lib/Array.php';
            Horde_Array::getArrayParts($field, $base, $keys);
            $keys_path= array_merge(array($base, 'tmp_name'), $keys);
            $old['tmp_name'] = Horde_Array::getElement($_FILES, $keys_path);
            $keys_path= array_merge(array($base, 'name'), $keys);
            $old['name'] = Horde_Array::getElement($_FILES, $keys_path);

            /* Get the temp file if already one uploaded, otherwise create a new
               temporary file. */
            $upload = $vars->getVar($var->getVarName());

            require_once HORDE_BASE . '/lib/Serialize.php';
            $upload['old'] = Horde_Serialize::unserialize($upload['old'], SERIALIZE_UTF7_BASIC);
            if (!empty($upload['old']['tmp_name'])) {
                $tmp_file = Horde::getTempDir() . '/' . $upload['old']['tmp_name'];
            } else {
                $tmp_file = Horde::getTempFile('Horde', false);
            }

            /* Move the browser created temp file to the new temp file. */
            move_uploaded_file($_FILES[$field]['tmp_name']['new'], $tmp_file);

            /* Store the uploaded image file data to the hidden field. */
            $upload['old'] = $old;
            $upload['old']['tmp_name'] = basename($tmp_file);
            $upload['old'] = Horde_Serialize::serialize($upload['old'], SERIALIZE_UTF7_BASIC);
            $vars->setVar($var->getVarName(), $upload);
        }

        /* If this was done through the upload button override the submitted
           value of the form. */
        if ($vars->getVar('_do_' . $var->getVarName())) {
            $var->form->setSubmitted(false);
        }
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        $field = $vars->getVar($var->getVarName());

        /* Not required and no image upload attempted. */
        if (!$var->isRequired() && empty($field['old'])) {
            return true;
        }

        /* Do checks if the file is required or bad upload. */
        if (empty($field['old'])) {
            /* Maybe the file is being uploaded with this submit. */
            require_once HORDE_BASE . '/lib/Server.php';
            $uploaded = Server::wasFileUploaded($field . '[new]');
        }

        /* No file uploaded with this submit but there is one from before. */
        if ($_FILES[$var->getVarName()]['error'] == '4' && !empty($uploaded)) {
            return true;
        }

        if (is_a($uploaded, 'PEAR_Error')) {
            /* Bad upload. */
            $message = $uploaded->getMessage();
            return false;
        } elseif (empty($uploaded)) {
            /* No previous upload. */
            $message = _("This field is required.");
            return false;
        }

        return true;
    }

    function getInfo(&$vars, &$var, &$info)
    {
        $uploaded = $vars->getVar($var->getVarName());

        if (empty($info['old'])) {
            /* No upload already stored, check if one done now. */
            require_once HORDE_BASE . '/lib/Server.php';
            $name = $var->getVarName();
            $value = $vars->getVar($var->getVarName());
            if (Server::wasFileUploaded($name)) {
                /* Upload done now, get the upload array data. */
                $info['name'] = $_FILES[$name]['name'];
                $info['type'] = $_FILES[$name]['type'];
                $info['tmp_name'] = $_FILES[$name]['tmp_name'];
                $info['error'] = $_FILES[$name]['error'];
                $info['size'] = $_FILES[$name]['size'];
            }
        } else {
            /* Stored previous upload, shift the old array data into new. */
            $info['name'] = $info['old']['name'];
            $info['type'] = $info['old']['type'];
            $info['tmp_name'] = Horde::getTempDir() . '/' . $info['old']['tmp_name'];
            $info['error'] = $info['old']['error'];
            $info['size'] = $info['old']['size'];

            /* If a modified file exists move it over the original. */
            $mod_file = Horde::getTempDir() . '/mod_' . $info['old']['tmp_name'];
            if (file_exists($mod_file)) {
                unlink($info['tmp_name']);
                rename($mod_file, $info['tmp_name']);
            }
        }
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Image upload");
        return $about;
    }

}

class Horde_Form_Type_boolean extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("True or false");
        return $about;
    }

}

class Horde_Form_Type_link extends Horde_Form_Type {

    function init($values)
    {
        $this->values = $values;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Link");
        return $about;
    }

}

class Horde_Form_Type_email extends Horde_Form_Type {

    var $_allow_multi;

    function init($allow_multi = false)
    {
        $this->_allow_multi = $allow_multi;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value)) {
            $message = _("This field is required.");
            return false;
        } elseif (!empty($value)) {
            require_once 'Mail/RFC822.php';
            $parsed_email = Mail_RFC822::parseAddressList($value, false, true);

            if ((!$this->_allow_multi) && count($parsed_email) > 1) {
                $message = _("Only one email address allowed.");
                return false;
            }
            if (empty($parsed_email[0]->mailbox)) {
                $message = _("You did not enter a valid email address.");
                return false;
            }
        }

        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Email");
        return $about;
    }

}

class Horde_Form_Type_emailConfirm extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value['original'])) {
            $message = _("This field is required.");
            return false;
        }

        if ($value['original'] != $value['confirm']) {
            $message = _("Email addresses must match.");
            return false;
        } else {
            require_once 'Mail/RFC822.php';
            $parsed_email = Mail_RFC822::parseAddressList($value['original'], false, true);

            if (count($parsed_email) > 1) {
                $message = _("Only one email address allowed.");
                return false;
            }
            if (empty($parsed_email[0]->mailbox)) {
                $message = _("You did not enter a valid email address.");
                return false;
            }
        }

        return true;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Email with confirmation");
        return $about;
    }

}

class Horde_Form_Type_password extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        $valid = true;

        if ($var->isRequired()) {
            $valid = strlen(trim($value)) > 0;

            if (!$valid) {
                $message = _("This field is required.");
            }
        }

        return $valid;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Password");
        return $about;
    }

}

class Horde_Form_Type_passwordConfirm extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value['original'])) {
            $message = _("This field is required.");
            return false;
        }

        if ($value['original'] != $value['confirm']) {
            $message = _("Passwords must match.");
            return false;
        }

        return true;
    }

    function getInfo(&$vars, &$var, &$info)
    {
        $value = $vars->getVar($var->getVarName());
        $info = $value['original'];
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Password with confirmation");
        return $about;
    }

}

class Horde_Form_Type_enum extends Horde_Form_Type {

    var $_values;
    var $_prompt;

    function init($values, $prompt = null)
    {
        $this->_values = $values;

        if ($prompt === true) {
            $this->_prompt = _("-- select --");
        } else {
            $this->_prompt = $prompt;
        }
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && $value == '') {
            $message = _("This field is required.");
            return false;
        }

        if (count($this->_values) == 0 || isset($this->_values[$value])) {
            return true;
        }

        $message = _("Invalid data submitted.");
        return false;
    }

    function getValues()
    {
        return $this->_values;
    }

    function getPrompt()
    {
        return $this->_prompt;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Drop down list");
        return $about;
    }

}

class Horde_Form_Type_multienum extends Horde_Form_Type_enum {

    var $size = 5;

    function init($values, $size = null)
    {
        if (!is_null($size)) {
            $this->size = (int)$size;
        }

        parent::init($values);
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        if (is_array($value)) {
            foreach ($value as $val) {
                if (!$this->isValid($var, $vars, $val, $message)) {
                    return false;
                }
            }
            return true;
        }

        if (empty($value) && ((string)(int)$value !== $value)) {
            if ($var->isRequired()) {
                $message = _("This field is required.");
                return false;
            } else {
                return true;
            }
        }

        if (count($this->_values) == 0 || isset($this->_values[$value])) {
            return true;
        }

        $message = _("Invalid data submitted.");
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Multiple selection");
        return $about;
    }

}

class Horde_Form_Type_radio extends Horde_Form_Type_enum {
    /* Entirely implemented by Horde_Form_Type_enum; just a different view. */

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Radio selection");
        return $about;
    }

}

class Horde_Form_Type_set extends Horde_Form_Type {

    var $_values;

    function init(&$values)
    {
        $this->_values = $values;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        if (count($this->_values) == 0 || count($value) == 0) {
            return true;
        }
        foreach ($value as $item) {
            if (!isset($this->_values[$item])) {
                $error = true;
                break;
            }
        }
        if (!isset($error)) {
            return true;
        }

        $message = _("Invalid data submitted.");
        return false;
    }

    function getValues()
    {
        return $this->_values;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Set");
        return $about;
    }

}

class Horde_Form_Type_date extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        $valid = true;

        if ($var->isRequired()) {
            $valid = strlen(trim($value)) > 0;

            if (!$valid) {
                $message = sprintf(_("%s is required"), $var->getHumanName());
            }
        }

        return $valid;
    }

    function _getAgo($timestamp)
    {
        if (time() - $timestamp < time() - mktime(0, 0, 0)) {
            $ago = 0;
        } else {
            $timestamp = $timestamp - (time() - mktime(0, 0, 0));
            $ago = ceil((time() - $timestamp) / 86400);
        }

        if ($ago == 0) {
            return _(" (today)");
        } elseif ($ago == 1) {
            return _(" (yesterday)");
        } else {
            return sprintf(_(" (%s days ago)"), $ago);
        }
    }

    function getFormattedTime($timestamp, $format = '%a %d %B', $showago = true)
    {
        if (!empty($timestamp)) {
            return strftime($format, $timestamp) . ($showago ? Horde_Form_Type_date::_getAgo($timestamp) : '');
        } else {
            return '';
        }
    }

    function getFormattedTimeFull($timestamp, $format = '%c', $showago = true)
    {
        if (!empty($timestamp)) {
            return strftime($format, $timestamp) . ($showago ? Horde_Form_Type_date::_getAgo($timestamp) : '');
        } else {
            return '';
        }
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Date");
        return $about;
    }

}

class Horde_Form_Type_time extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
            $message = _("This field is required.");
            return false;
        }

        if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) {
            return true;
        }

        $message = _("This field may only contain numbers and the colon.");
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Time");
        return $about;
    }

}

class Horde_Form_Type_HourMinuteSecond extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        $time = $vars->getVar($var->getVarName());
        if (!$this->emptyTimeArray($time) && !$this->checktime($time['hour'], $time['minute'], $time['second'])) {
            $message = _("Please enter a valid time.");
            return false;
        } elseif ($this->emptyTimeArray($time) && $var->isRequired()) {
            $message = _("This field is required.");
            return false;
        }
        return true;
    }

    function checktime($hour, $minute, $second) {
        if (!isset($hour) || $hour == '' || ($hour < 0 || $hour > 23)) {
            return false;
        }
        if (!isset($minute) || $minute == '' || ($minute < 0 || $minute > 60)) {
            return false;
        }
        if (!isset($second) || $second == '' || ($second < 0 || $second > 60)) {
            return false;
        }
        return true;
    }

    /**
     * Return the time supplied as a PEAR Date object.
     *
     * @param string $time_in  Date in one of the three formats supported by
     *                         Horde_Form and PEAR's Date class (ISO format
     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
     *                         UNIX epoch).
     *
     * @return object  The time object.
     */
    function &getTimeOb($time_in)
    {
        require_once 'Date.php';

        /* Fix the time if it is the shortened ISO. */
        if (is_array($time_in)) {
            if (!$this->emptyTimeArray($time_in)) {
                $time_in = sprintf('%02d:%02d:%02d', $time_in['hour'], $time_in['minute'], $time_in['second']);
            }
        }

        /* Return the PEAR time object. */
        return new Date($time_in);
    }

    /**
     * Return the time supplied split up into an array.
     *
     * @param string $time_in  Time in one of the three formats supported by
     *                         Horde_Form and PEAR's Date class (ISO format
     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
     *                         UNIX epoch).
     *
     * @return array  Array with three elements - hour, minute and seconds.
     */
    function getTimeParts($time_in)
    {
        if (is_array($time_in)) {
            /* This is probably a failed isValid input
               so just return the parts as they are. */
            return $time_in;
        } elseif (empty($time_in)) {
            /* This is just an empty field so return
               empty parts. */
            return array('hour' => '', 'minute' => '', 'second' => '');
        }
        $time = &$this->getTimeOb($time_in);
        return array('hour' => $time->getHour(),
                     'minute' => $time->getMinute(),
                     'second' => $time->getSecond());
    }

    function emptyTimeArray($time)
    {
        if (is_array($time) && empty($time['hour']) && empty($time['minute']) && empty($time['second'])) {
            return true;
        }
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Time selection");
        return $about;
    }

}

class Horde_Form_Type_monthYear extends Horde_Form_Type {

    var $_startYear;
    var $_endYear;
    var $_bare;

    function init($startYear = null, $endYear = null, $bare = false)
    {
        if (is_null($startYear)) {
            $startYear = date('Y');
        }
        if (is_null($endYear)) {
            $endYear = 1920;
        }

        $this->_startYear = $startYear;
        $this->_endYear = $endYear;
        $this->_bare = $bare;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        if (!$var->isRequired()) {
            return true;
        }

        if (!$vars->getVar($this->getMonthVar($var)) ||
            !$vars->getVar($this->getYearVar($var))) {
            $message = _("Please enter a month and a year.");
            return false;
        }

        return true;
    }

    function getMonthVar($var)
    {
        if ($this->_bare) {
            return 'month';
        } else {
            return $var->getVarName() . '[month]';
        }
    }

    function getYearVar($var)
    {
        if ($this->_bare) {
            return 'year';
        } else {
            return $var->getVarName() . '[year]';
        }
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Month and year");
        return $about;
    }

}

class Horde_Form_Type_monthDayYear extends Horde_Form_Type {

    var $_startYear;
    var $_endYear;
    var $_picker;
    var $_format_in;
    var $_format_out;

    /**
     * Return the date supplied as a PEAR Date object.
     *
     * @param optional int  $startYear  The first available year for input.
     * @param optional int  $endYear    The last available year for input.
     * @param optional bool $picker     Do we show the DHTML calendar?
     * @param optional int  $format_in  The format to use when sending the date
     *                                  for storage. Defaults to PEAR Date's
     *                                  default - YYYY-MM-DD HH:MM:SS. Can take
     *                                  the following values:
     *                                    1 - YYYY-MM-DD HH:MM:SS
     *                                    2 - YYYYMMDDHHMMSS
     *                                    3 - Unix epoch
     *                                  See the PEAR Date class for more info.
     * @param optional int $format_out  The format to use when displaying the
     *                                  date. Similar to the strftime()
     *                                  function.
     *
     */
    function init($startYear = '', $endYear = '', $picker = true, $format_in = null, $format_out = '%d %B, %Y')
    {
        if (empty($startYear)) {
            $startYear = date('Y');
        }
        if (empty($endYear)) {
            $endYear = date('Y') - 100;
        }

        $this->_startYear = $startYear;
        $this->_endYear = $endYear;
        $this->_picker = $picker;
        $this->_format_in = $format_in;
        $this->_format_out = $format_out;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        $date = $vars->getVar($var->getVarName());
        if (!$this->emptyDateArray($date) && !checkdate($date['month'], $date['day'], $date['year'])) {
            $message = _("Please enter a valid date; check the number of days in the month.");
            return false;
        } elseif ($this->emptyDateArray($date) && $var->isRequired()) {
            $message = _("This field is required.");
            return false;
        }
        return true;
    }

    function emptyDateArray($date)
    {
        if (is_array($date) && empty($date['year']) && empty($date['month']) && empty($date['day'])) {
            return true;
        }
        return false;
    }

    /**
     * Return the date supplied split up into an array.
     *
     * @param string $date_in  Date in one of the three formats supported by
     *                         Horde_Form and PEAR's Date class (ISO format
     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
     *                         UNIX epoch) plus the fourth YYYY-MM-DD.
     *
     * @return array  Array with three elements - year, month and day.
     */
    function getDateParts($date_in)
    {
        if (is_array($date_in)) {
            /* This is probably a failed isValid input
               so just return the parts as they are. */
            return $date_in;
        } elseif (empty($date_in)) {
            /* This is just an empty field so return
               empty parts. */
            return array('year' => '', 'month' => '', 'day' => '');
        }
        $date = &$this->getDateOb($date_in);
        return array('year' => $date->getYear(),
                     'month' => $date->getMonth(),
                     'day' => $date->getDay());
    }

    /**
     * Return the date supplied as a PEAR Date object.
     *
     * @param string $date_in  Date in one of the three formats supported by
     *                         Horde_Form and PEAR's Date class (ISO format
     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
     *                         UNIX epoch) plus the fourth YYYY-MM-DD.
     *
     * @return object          The date object.
     */
    function &getDateOb($date_in)
    {
        require_once 'Date.php';

        /* Fix the date if it is the shortened ISO. */
        if (is_array($date_in)) {
            if (!$this->emptyDateArray($date_in)) {
                $date_in = sprintf('%04d-%02d-%02d 00:00:00', $date_in['year'], $date_in['month'], $date_in['day']);
            }
        } elseif (preg_match('/\d{4}-\d{2}-\d{2}/', $date_in)) {
            $date_in = $date_in . ' 00:00:00';
        }

        /* Return the PEAR date object. */
        return new Date($date_in);
    }

    /**
     * Return the date supplied as a PEAR Date object.
     *
     * @param string $date  Either an already set up PEAR Date object or a
     *                      string date in one of the three formats supported by
     *                      Horde_Form and PEAR's Date class (ISO format
     *                      YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
     *                      UNIX epoch) plus the fourth YYYY-MM-DD.
     *
     * @return string  The date formatted according to the $format_out parameter     *                 when setting up the monthdayyear field.
     */
    function formatDate($date)
    {
        if (!is_a($date, 'Date')) {
            $date = &$this->getDateOb($date);
        }

        return $date->format($this->_format_out);
    }

    /**
     * Insert the date input through the form into $info array, in the format
     * specified by the $format_in parameter when setting up monthdayyear field.
     */
    function getInfo(&$vars, &$var, &$info)
    {
        $info = $this->_validateAndFormat($var->getValue($vars), $var);
    }

    /**
     * Validate/format a date submission.
     */
    function _validateAndFormat($value, $var)
    {
        /* If any component is empty consider it a bad date
           and return the default. */
        if ($this->emptyDateArray($value)) {
            return $var->getDefault();
        } else {
            $date = &$this->getDateOb(sprintf('%04d-%02d-%02d 00:00:00',
                                             $value['year'],
                                             $value['month'],
                                             $value['day']));
            if (is_null($this->_format_in)) {
                $this->_format_in = DATE_FORMAT_ISO;
            }
            return $date->getDate($this->_format_in);
        }
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Date selection");
        return $about;
    }

}

class Horde_Form_Type_colorPicker extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($var->isRequired() && empty($value)) {
            $message = _("This field is required.");
            return false;
        }

        if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) {
            return true;
        }

        $message = _("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
        return false;
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Colour selection");
        return $about;
    }

}

class Horde_Form_Type_sorter extends Horde_Form_Type {

    var $_instance;
    var $_values;
    var $_size;
    var $_header;

    function init($values, $size = 8, $header = '')
    {
        static $horde_sorter_instance = 0;

        /* Get the next progressive instance count for the horde
           sorter so that multiple sorters can be used on one page. */
        $horde_sorter_instance++;
        $this->_instance = 'horde_sorter_' . $horde_sorter_instance;

        if (!empty($header)) {
            $values = array('' => $header) + $values;
        }
        $this->_values = $values;
        $this->_size   = $size;
        $this->_header = $header;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        return true;
    }

    function getValues()
    {
        return $this->_values;
    }

    function getSize()
    {
        return $this->_size;
    }

    function getHeader()
    {
        if (!empty($this->_header)) {
            return $this->_header;
        }
        return '';
    }

    function getOptions()
    {
        $html = '';
        foreach ($this->_values as $sl_key => $sl_val) {
            $html .= '<option value="' . $sl_key . '">' . $sl_val . '</option>';
        }

        return $html;
    }

    function getInfo(&$vars, &$var, &$info)
    {
        $value = $vars->getVar($var->getVarName());
        $info = explode("\t", $value['array']);
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Sort order selection");
        return $about;
    }

}

class Horde_Form_Type_creditcard extends Horde_Form_Type {

    function isValid(&$var, &$vars, $value, &$message)
    {
        if ($this->getChecksum($value) % 10 != 0) {
            $message = _("This does not seem to be a valid card number.");
            return false;
        }

        return true;
    }

    function getChecksum($ccnum)
    {
        $len = strlen($ccnum);
        if (!is_long($len / 2)) {
            $weight = 2;
            $digit = $ccnum[0];
        } elseif (is_long($len / 2)) {
            $weight = 1;
            $digit = $ccnum[0] * 2;
        }
        if ($digit > 9) {
            $digit = $digit - 9;
        }
        $i = 1;
        $checksum = $digit;
        while ($i < $len) {
            if ($ccnum[$i] != ' ') {
                $digit = $ccnum[$i] * $weight;
                $weight = ($weight == 1) ? 2 : 1;
                if ($digit > 9) {
                    $digit = $digit - 9;
                }
                $checksum += $digit;
            }
            $i++;
        }

        return $checksum;
    }

    function getCardType($ccnum)
    {
        $sum = cc::getChecksum($ccnum);
        $l = strlen($ccnum);

        // Screen checksum.
        if (($sum % 10) != 0) {
            return false;
        }

        // Check for Visa.
        if ((($l == 16) || ($l == 13)) &&
            ($ccnum[0] == 4)) {
            return 'VI';
        }

        // Check for Master Card.
        if (($l == 16) &&
            ($ccnum[0] == 5) &&
            ($ccnum[1] >= 1) &&
            ($ccnum[1] <= 5)) {
            return 'CA';
        }

        // Check for Amex.
        if (($l == 15) &&
            ($ccnum[0] == 3) &&
            (($ccnum[1] == 4) || ($ccnum[1] == 7))) {
            return 'AX';
        }

        // If we got this far, then no card matched.
        return 'unknown';
    }

    /**
     * Return info about field type.
     */
    function about()
    {
        $about = array();
        $about['name'] = _("Credit card");
        return $about;
    }

}

class Horde_Form_Type_invalid extends Horde_Form_Type {

    var $message;

    function init($message)
    {
        $this->message = $message;
    }

    function isValid(&$var, &$vars, $value, &$message)
    {
        return false;
    }

    function getInfo()
    {
        return _("Integer");
    }

}

/**
 * Horde_Form_Variable Class
 *
 * @author  Robert E. Coyle <robertecoyle@hotmail.com>
 * @package horde.form
 */
class Horde_Form_Variable {

    var $form;
    var $humanName;
    var $varName;
    var $type;
    var $required;
    var $readonly;
    var $description;
    var $help;
    var $_arrayVal;
    var $_defValue = null;
    var $_action;
    var $_autofilled = false;

    function Horde_Form_Variable($humanName, $varName, &$type, $required,
                                   $readonly = false, $description = null)
    {
        $this->humanName   = $humanName;
        $this->varName     = $varName;
        $this->type        = $type;
        $this->required    = $required;
        $this->readonly    = $readonly;
        $this->description = $description;
        $this->_arrayVal   = strstr($varName, '[]');
    }

    function setFormOb(&$form)  { $this->form = &$form; }
    function setDefault($value) { $this->_defValue = $value; }
    function setAction($action) { $this->_action = $action; }
    function setHelp($help)     { $this->form->_help = true; $this->help = $help; }
    function hasHelp()          { return !empty($this->help); }
    function getHelp()          { return $this->help; }

    function  getHumanName()   { return $this->humanName; }
    function  getVarName()     { return $this->varName; }
    function &getType()        { return $this->type; }
    function  getTypeName()    { return $this->type->getTypeName(); }
    function  getDefault()     { return $this->_defValue; }
    function  isRequired()     { return $this->required; }
    function  isReadonly()     { return $this->readonly; }
    function  getValues()      { return $this->type->getValues(); }
    function  hasDescription() { return !empty($this->description); }
    function  getDescription() { return $this->description; }
    function  isArrayVal()     { return $this->_arrayVal; }
    function  hasAction()      { return !is_null($this->_action); }
    function  isUpload()       { return($this->type->getTypeName() == 'file'); }

    function getInfo(&$vars, &$info)
    {
        return $this->type->getInfo($vars, $this, $info);
    }

    function validate(&$vars, &$message)
    {
        if ($this->_arrayVal) {
            $vals = $this->getValue($vars);
            if (!is_array($vals)) {
                if ($this->required) {
                    $message = _("This field is required.");
                    return false;
                } else {
                    return true;
                }
            }
            foreach ($vals as $i => $value) {
                if ($value === null && $this->required) {
                    $message = _("This field is required.");
                    return false;
                } else {
                    if (!$this->type->isValid($this, $vars, $value, $message)) {
                        return false;
                    }
                }
            }
        } else {
            $value = $this->getValue($vars);
            return $this->type->isValid($this, $vars, $value, $message);
        }

        return true;
    }

    function getValue(&$vars, $index = null)
    {
        if ($this->_arrayVal) {
            $name = str_replace('[]', '', $this->varName);
        } else {
            $name = $this->varName;
        }
        $value = $vars->getVarWasset($name, $wasset);

        if (!$wasset) {
            $value = $this->getDefault();
        }

        if ($this->_arrayVal && !is_null($index)) {
            if (!$wasset && !is_array($value)) {
                $return = $value;
            } else {
                $return = isset($value[$index]) ? $value[$index] : null;
            }
        } else {
            $return = $value;
        }

        if ($this->hasAction()) {
            $this->_action->setValues($vars, $return, $index, $this->_arrayVal);
        }
        return $return;
    }

}
