1<?php
2/**
3 * Copyright 2001-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file LICENSE for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @author   Robert E. Coyle <robertecoyle@hotmail.com>
9 * @category Horde
10 * @license  http://www.horde.org/licenses/lgpl21 LGPL
11 * @package  Form
12 */
13
14/**
15 * Horde_Form_Type Class
16 *
17 * @author    Robert E. Coyle <robertecoyle@hotmail.com>
18 * @category  Horde
19 * @copyright 2001-2017 Horde LLC
20 * @license   http://www.horde.org/licenses/lgpl21 LGPL
21 * @package   Form
22 */
23class Horde_Form_Type
24{
25    function getProperty($property)
26    {
27        $prop = '_' . $property;
28        return isset($this->$prop) ? $this->$prop : null;
29    }
30
31    function __get($property)
32    {
33        return $this->getProperty($property);
34    }
35
36    function setProperty($property, $value)
37    {
38        $prop = '_' . $property;
39        $this->$prop = $value;
40    }
41
42    function __set($property, $value)
43    {
44        return $this->setProperty($property, $value);
45    }
46
47    /**
48     * Initialize (kind of constructor) - Parameter list may vary on overloading
49     */
50    function init()
51    {
52    }
53
54    function onSubmit()
55    {
56    }
57
58    function isValid(&$var, &$vars, $value, &$message)
59    {
60        $message = '<strong>Error:</strong> Horde_Form_Type::isValid() called - should be overridden<br />';
61        return false;
62    }
63
64    function getTypeName()
65    {
66        return str_replace('horde_form_type_', '', Horde_String::lower(get_class($this)));
67    }
68
69    function getValues()
70    {
71        return null;
72    }
73
74    function getInfo(&$vars, &$var, &$info)
75    {
76        $info = $var->getValue($vars);
77    }
78
79}
80
81class Horde_Form_Type_spacer extends Horde_Form_Type {
82
83    function isValid(&$var, &$vars, $value, &$message)
84    {
85        return true;
86    }
87
88    /**
89     * Return info about field type.
90     */
91    function about()
92    {
93        return array('name' => Horde_Form_Translation::t("Spacer"));
94    }
95
96}
97
98class Horde_Form_Type_header extends Horde_Form_Type {
99
100    function isValid(&$var, &$vars, $value, &$message)
101    {
102        return true;
103    }
104
105    /**
106     * Return info about field type.
107     */
108    function about()
109    {
110        return array('name' => Horde_Form_Translation::t("Header"));
111    }
112
113}
114
115class Horde_Form_Type_description extends Horde_Form_Type {
116
117    function isValid(&$var, &$vars, $value, &$message)
118    {
119        return true;
120    }
121
122    /**
123     * Return info about field type.
124     */
125    function about()
126    {
127        return array('name' => Horde_Form_Translation::t("Description"));
128    }
129
130}
131
132/**
133 * Simply renders its raw value in both active and inactive rendering.
134 */
135class Horde_Form_Type_html extends Horde_Form_Type {
136
137    function isValid(&$var, &$vars, $value, &$message)
138    {
139        return true;
140    }
141
142    /**
143     * Return info about field type.
144     */
145    function about()
146    {
147        return array('name' => Horde_Form_Translation::t("HTML"));
148    }
149
150}
151
152class Horde_Form_Type_number extends Horde_Form_Type {
153
154    var $_fraction;
155
156    function init($fraction = null)
157    {
158        $this->_fraction = $fraction;
159    }
160
161    function isValid(&$var, &$vars, $value, &$message)
162    {
163        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
164            $message = Horde_Form_Translation::t("This field is required.");
165            return false;
166        } elseif (empty($value)) {
167            return true;
168        }
169
170        /* If matched, then this is a correct numeric value. */
171        if (preg_match($this->_getValidationPattern(), $value)) {
172            return true;
173        }
174
175        $message = Horde_Form_Translation::t("This field must be a valid number.");
176        return false;
177    }
178
179    function _getValidationPattern()
180    {
181        static $pattern = '';
182        if (!empty($pattern)) {
183            return $pattern;
184        }
185
186        /* Get current locale information. */
187        $linfo = Horde_Nls::getLocaleInfo();
188
189        /* Build the pattern. */
190        $pattern = '(-)?';
191
192        /* Only check thousands separators if locale has any. */
193        if (!empty($linfo['mon_thousands_sep'])) {
194            /* Regex to check for correct thousands separators (if any). */
195            $pattern .= '((\d+)|((\d{0,3}?)([' . $linfo['mon_thousands_sep'] . ']\d{3})*?))';
196        } else {
197            /* No locale thousands separator, check for only digits. */
198            $pattern .= '(\d+)';
199        }
200        /* If no decimal point specified default to dot. */
201        if (empty($linfo['mon_decimal_point'])) {
202            $linfo['mon_decimal_point'] = '.';
203        }
204        /* Regex to check for correct decimals (if any). */
205        if (empty($this->_fraction)) {
206            $fraction = '*';
207        } else {
208            $fraction = '{0,' . $this->_fraction . '}';
209        }
210        $pattern .= '([' . $linfo['mon_decimal_point'] . '](\d' . $fraction . '))?';
211
212        /* Put together the whole regex pattern. */
213        $pattern = '/^' . $pattern . '$/';
214
215        return $pattern;
216    }
217
218    function getInfo(&$vars, &$var, &$info)
219    {
220        $value = $vars->get($var->getVarName());
221        $linfo = Horde_Nls::getLocaleInfo();
222        $value = str_replace($linfo['mon_thousands_sep'], '', $value);
223        $info = str_replace($linfo['mon_decimal_point'], '.', $value);
224    }
225
226    /**
227     * Return info about field type.
228     */
229    function about()
230    {
231        return array('name' => Horde_Form_Translation::t("Number"));
232    }
233
234}
235
236/**
237 * A Form type for an input line validating to an integer
238 */
239class Horde_Form_Type_int extends Horde_Form_Type {
240
241    function isValid(&$var, &$vars, $value, &$message)
242    {
243        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
244            $message = Horde_Form_Translation::t("This field is required.");
245            return false;
246        }
247
248        if (empty($value) || preg_match('/^[0-9]+$/', $value)) {
249            return true;
250        }
251
252        $message = Horde_Form_Translation::t("This field may only contain integers.");
253        return false;
254    }
255
256    /**
257     * Return info about field type.
258     */
259    function about()
260    {
261        return array('name' => Horde_Form_Translation::t("Integer"));
262    }
263
264}
265
266class Horde_Form_Type_octal extends Horde_Form_Type {
267
268    function isValid(&$var, &$vars, $value, &$message)
269    {
270        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
271            $message = Horde_Form_Translation::t("This field is required.");
272            return false;
273        }
274
275        if (empty($value) || preg_match('/^[0-7]+$/', $value)) {
276            return true;
277        }
278
279        $message = Horde_Form_Translation::t("This field may only contain octal values.");
280        return false;
281    }
282
283    /**
284     * Return info about field type.
285     */
286    function about()
287    {
288        return array('name' => Horde_Form_Translation::t("Octal"));
289    }
290
291}
292
293class Horde_Form_Type_intlist extends Horde_Form_Type {
294
295    function isValid(&$var, &$vars, $value, &$message)
296    {
297        if (empty($value) && $var->isRequired()) {
298            $message = Horde_Form_Translation::t("This field is required.");
299            return false;
300        }
301
302        if (empty($value) || preg_match('/^[0-9 ,]+$/', $value)) {
303            return true;
304        }
305
306        $message = Horde_Form_Translation::t("This field must be a comma or space separated list of integers");
307        return false;
308    }
309
310    /**
311     * Return info about field type.
312     */
313    function about()
314    {
315        return array('name' => Horde_Form_Translation::t("Integer list"));
316    }
317
318}
319
320/**
321 * A Text Box form type
322 */
323class Horde_Form_Type_text extends Horde_Form_Type {
324
325    var $_regex;
326    var $_size;
327    var $_maxlength;
328
329    /**
330     * The initialisation function for the text variable type.
331     *
332     * @access private
333     *
334     * @param string $regex       Any valid PHP PCRE pattern syntax that
335     *                            needs to be matched for the field to be
336     *                            considered valid. If left empty validity
337     *                            will be checked only for required fields
338     *                            whether they are empty or not.
339     *                            If using this regex test it is advisable
340     *                            to enter a description for this field to
341     *                            warn the user what is expected, as the
342     *                            generated error message is quite generic
343     *                            and will not give any indication where
344     *                            the regex failed.
345     * @param integer $size       The size of the input field.
346     * @param integer $maxlength  The max number of characters.
347     */
348    function init($regex = '', $size = 40, $maxlength = null)
349    {
350        $this->_regex     = $regex;
351        $this->_size      = $size;
352        $this->_maxlength = $maxlength;
353    }
354
355    function isValid(&$var, &$vars, $value, &$message)
356    {
357        $valid = true;
358
359        if (!empty($this->_maxlength) && Horde_String::length($value) > $this->_maxlength) {
360            $valid = false;
361            $message = sprintf(Horde_Form_Translation::t("Value is over the maximum length of %d."), $this->_maxlength);
362        } elseif ($var->isRequired() && empty($this->_regex)) {
363            $valid = strlen(trim($value)) > 0;
364
365            if (!$valid) {
366                $message = Horde_Form_Translation::t("This field is required.");
367            }
368        } elseif (!empty($this->_regex)) {
369            $valid = preg_match($this->_regex, $value);
370
371            if (!$valid) {
372                $message = Horde_Form_Translation::t("You must enter a valid value.");
373            }
374        }
375
376        return $valid;
377    }
378
379    function getSize()
380    {
381        return $this->_size;
382    }
383
384    function getMaxLength()
385    {
386        return $this->_maxlength;
387    }
388
389    /**
390     * Return info about field type.
391     */
392    function about()
393    {
394        return array(
395            'name' => Horde_Form_Translation::t("Text"),
396            'params' => array(
397                'regex'     => array('label' => Horde_Form_Translation::t("Regex"),
398                                     'type'  => 'text'),
399                'size'      => array('label' => Horde_Form_Translation::t("Size"),
400                                     'type'  => 'int'),
401                'maxlength' => array('label' => Horde_Form_Translation::t("Maximum length"),
402                                     'type'  => 'int')));
403    }
404
405}
406
407class Horde_Form_Type_stringlist extends Horde_Form_Type_text {
408
409    /**
410     * Return info about field type.
411     */
412    function about()
413    {
414        return array(
415            'name' => Horde_Form_Translation::t("String list"),
416            'params' => array(
417                'regex'     => array('label' => Horde_Form_Translation::t("Regex"),
418                                     'type'  => 'text'),
419                'size'      => array('label' => Horde_Form_Translation::t("Size"),
420                                     'type'  => 'int'),
421                'maxlength' => array('label' => Horde_Form_Translation::t("Maximum length"),
422                                     'type'  => 'int')),
423        );
424    }
425
426}
427
428class Horde_Form_Type_stringarray extends Horde_Form_Type_stringlist {
429
430    function getInfo(&$vars, &$var, &$info)
431    {
432        $info = array_map('trim', explode(',', $vars->get($var->getVarName())));
433    }
434
435    /**
436     * Return info about field type.
437     */
438    function about()
439    {
440        return array(
441            'name' => Horde_Form_Translation::t("String list returning an array"),
442            'params' => array(
443                'regex'     => array('label' => Horde_Form_Translation::t("Regex"),
444                                     'type'  => 'text'),
445                'size'      => array('label' => Horde_Form_Translation::t("Size"),
446                                     'type'  => 'int'),
447                'maxlength' => array('label' => Horde_Form_Translation::t("Maximum length"),
448                                     'type'  => 'int')),
449        );
450    }
451
452}
453
454class Horde_Form_Type_phone extends Horde_Form_Type {
455
456    /**
457     * The size of the input field.
458     *
459     * @var integer
460     */
461    var $_size;
462
463    /**
464     * @param integer $size  The size of the input field.
465     */
466    function init($size = 15)
467    {
468        $this->_size = $size;
469    }
470
471    function isValid(&$var, &$vars, $value, &$message)
472    {
473        if (!strlen(trim($value))) {
474            if ($var->isRequired()) {
475                $message = Horde_Form_Translation::t("This field is required.");
476                return false;
477            }
478        } elseif (!preg_match('/^\+?[\d()\-\/.\s]*$/u', $value)) {
479            $message = Horde_Form_Translation::t("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix.");
480            return false;
481        }
482
483        return true;
484    }
485
486    function getSize()
487    {
488        return $this->_size;
489    }
490
491    /**
492     * Return info about field type.
493     */
494    function about()
495    {
496        return array(
497            'name' => Horde_Form_Translation::t("Phone number"),
498            'params' => array(
499                'size'      => array('label' => Horde_Form_Translation::t("Size"),
500                                     'type'  => 'int'),
501            ),
502        );
503    }
504
505}
506
507class Horde_Form_Type_cellphone extends Horde_Form_Type_phone {
508
509    /**
510     * Return info about field type.
511     */
512    function about()
513    {
514        return array('name' => Horde_Form_Translation::t("Mobile phone number"));
515    }
516
517}
518
519class Horde_Form_Type_ipaddress extends Horde_Form_Type_text {
520
521    function isValid(&$var, &$vars, $value, &$message)
522    {
523        $valid = true;
524
525        if (strlen(trim($value)) > 0) {
526            $ip = explode('.', $value);
527            $valid = (count($ip) == 4);
528            if ($valid) {
529                foreach ($ip as $part) {
530                    if (!is_numeric($part) ||
531                        $part > 255 ||
532                        $part < 0) {
533                        $valid = false;
534                        break;
535                    }
536                }
537            }
538
539            if (!$valid) {
540                $message = Horde_Form_Translation::t("Please enter a valid IP address.");
541            }
542        } elseif ($var->isRequired()) {
543            $valid = false;
544            $message = Horde_Form_Translation::t("This field is required.");
545        }
546
547        return $valid;
548    }
549
550    /**
551     * Return info about field type.
552     */
553    function about()
554    {
555        return array('name' => Horde_Form_Translation::t("IP address"));
556    }
557
558}
559
560class Horde_Form_Type_ip6address extends Horde_Form_Type_text {
561
562    function isValid(&$var, &$vars, $value, &$message)
563    {
564        $valid = true;
565
566        if (strlen(trim($value)) > 0) {
567            $valid = @inet_pton($value);
568
569            if ($valid === false) {
570                $message = Horde_Form_Translation::t("Please enter a valid IP address.");
571            }
572        } elseif ($var->isRequired()) {
573            $valid = false;
574            $message = Horde_Form_Translation::t("This field is required.");
575        }
576
577        return true;
578    }
579
580    /**
581     * Return info about field type.
582     */
583    function about()
584    {
585        return array('name' => Horde_Form_Translation::t("IPv6 address"));
586    }
587
588}
589
590class Horde_Form_Type_longtext extends Horde_Form_Type_text {
591
592    var $_rows;
593    var $_cols;
594    var $_helper = array();
595
596    function init($rows = 8, $cols = 80, $helper = array())
597    {
598        if (!is_array($helper)) {
599            $helper = array($helper);
600        }
601
602        $this->_rows = $rows;
603        $this->_cols = $cols;
604        $this->_helper = $helper;
605    }
606
607    function getRows()
608    {
609        return $this->_rows;
610    }
611
612    function getCols()
613    {
614        return $this->_cols;
615    }
616
617    function hasHelper($option = '')
618    {
619        if (empty($option)) {
620            /* No option specified, check if any helpers have been
621             * activated. */
622            return !empty($this->_helper);
623        } elseif (empty($this->_helper)) {
624            /* No helpers activated at all, return false. */
625            return false;
626        } else {
627            /* Check if given helper has been activated. */
628            return in_array($option, $this->_helper);
629        }
630    }
631
632    /**
633     * Return info about field type.
634     */
635    function about()
636    {
637        return array(
638            'name' => Horde_Form_Translation::t("Long text"),
639            'params' => array(
640                'rows'   => array('label' => Horde_Form_Translation::t("Number of rows"),
641                                  'type'  => 'int'),
642                'cols'   => array('label' => Horde_Form_Translation::t("Number of columns"),
643                                  'type'  => 'int'),
644                'helper' => array('label' => Horde_Form_Translation::t("Helpers"),
645                                  'type'  => 'stringarray')));
646    }
647
648}
649
650class Horde_Form_Type_countedtext extends Horde_Form_Type_longtext {
651
652    var $_chars;
653
654    function init($rows = null, $cols = null, $chars = 1000)
655    {
656        parent::init($rows, $cols);
657        $this->_chars = $chars;
658    }
659
660    function isValid(&$var, &$vars, $value, &$message)
661    {
662        $valid = true;
663
664        $length = Horde_String::length(trim($value));
665
666        if ($var->isRequired() && $length <= 0) {
667            $valid = false;
668            $message = Horde_Form_Translation::t("This field is required.");
669        } elseif ($length > $this->_chars) {
670            $valid = false;
671            $message = sprintf(Horde_Form_Translation::ngettext("There are too many characters in this field. You have entered %d character; ", "There are too many characters in this field. You have entered %d characters; ", $length), $length)
672                . sprintf(Horde_Form_Translation::t("you must enter less than %d."), $this->_chars);
673        }
674
675        return $valid;
676    }
677
678    function getChars()
679    {
680        return $this->_chars;
681    }
682
683    /**
684     * Return info about field type.
685     */
686    function about()
687    {
688        return array(
689            'name' => Horde_Form_Translation::t("Counted text"),
690            'params' => array(
691                'rows'  => array('label' => Horde_Form_Translation::t("Number of rows"),
692                                 'type'  => 'int'),
693                'cols'  => array('label' => Horde_Form_Translation::t("Number of columns"),
694                                 'type'  => 'int'),
695                'chars' => array('label' => Horde_Form_Translation::t("Number of characters"),
696                                 'type'  => 'int')));
697    }
698
699}
700
701class Horde_Form_Type_address extends Horde_Form_Type_longtext {
702
703    function parse($address)
704    {
705        $info = array();
706        $aus_state_regex = '(?:ACT|NSW|NT|QLD|SA|TAS|VIC|WA)';
707
708        if (preg_match('/(?s)(.*?)(?-s)\r?\n(?:(.*?)\s+)?((?:A[BL]|B[ABDHLNRST]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[CHNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTWY]?|T[ADFNQRSW]|UB|W[ACDFNRSV]?|YO|ZE)\d(?:\d|[A-Z])? \d[A-Z]{2})/', $address, $addressParts)) {
709            /* UK postcode detected. */
710            $info = array('country' => 'uk', 'zip' => $addressParts[3]);
711            if (!empty($addressParts[1])) {
712                $info['street'] = $addressParts[1];
713            }
714            if (!empty($addressParts[2])) {
715                $info['city'] = $addressParts[2];
716            }
717        } elseif (preg_match('/\b' . $aus_state_regex . '\b/', $address)) {
718            /* Australian state detected. */
719            /* Split out the address, line-by-line. */
720            $addressLines = preg_split('/\r?\n/', $address);
721            $info = array('country' => 'au');
722            for ($i = 0; $i < count($addressLines); $i++) {
723                /* See if it's the street number & name. */
724                if (preg_match('/(\d+\s*\/\s*)?(\d+|\d+[a-zA-Z])\s+([a-zA-Z ]*)/', $addressLines[$i], $lineParts)) {
725                    $info['street'] = $addressLines[$i];
726                    $info['streetNumber'] = $lineParts[2];
727                    $info['streetName'] = $lineParts[3];
728                }
729                /* Look for "Suburb, State". */
730                if (preg_match('/([a-zA-Z ]*),?\s+(' . $aus_state_regex . ')/', $addressLines[$i], $lineParts)) {
731                    $info['city'] = $lineParts[1];
732                    $info['state'] = $lineParts[2];
733                }
734                /* Look for "State <4 digit postcode>". */
735                if (preg_match('/(' . $aus_state_regex . ')\s+(\d{4})/', $addressLines[$i], $lineParts)) {
736                    $info['state'] = $lineParts[1];
737                    $info['zip'] = $lineParts[2];
738                }
739            }
740        } elseif (preg_match('/(?s)(.*?)(?-s)\r?\n(.*)\s*,\s*(\w+)\.?\s+(\d+|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d)/', $address, $addressParts)) {
741            /* American/Canadian address style. */
742            $info = array('country' => 'us');
743            if (!empty($addressParts[4]) &&
744                preg_match('|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d|', $addressParts[4])) {
745                $info['country'] = 'ca';
746            }
747            if (!empty($addressParts[1])) {
748                $info['street'] = $addressParts[1];
749            }
750            if (!empty($addressParts[2])) {
751                $info['city'] = $addressParts[2];
752            }
753            if (!empty($addressParts[3])) {
754                $info['state'] = $addressParts[3];
755            }
756            if (!empty($addressParts[4])) {
757                $info['zip'] = $addressParts[4];
758            }
759        } elseif (preg_match('/(?:(?s)(.*?)(?-s)(?:\r?\n|,\s*))?(?:([A-Z]{1,3})-)?(\d{4,5})\s+(.*)(?:\r?\n(.*))?/i', $address, $addressParts)) {
760            /* European address style. */
761            $info = array();
762            if (!empty($addressParts[1])) {
763                $info['street'] = $addressParts[1];
764            }
765            if (!empty($addressParts[2])) {
766                include 'Horde/Nls/Carsigns.php';
767                $country = array_search(Horde_String::upper($addressParts[2]), $carsigns);
768                if ($country) {
769                    $info['country'] = $country;
770                }
771            }
772            if (!empty($addressParts[5])) {
773                include 'Horde/Nls/Countries.php';
774                $country = array_search($addressParts[5], $countries);
775                if ($country) {
776                    $info['country'] = Horde_String::lower($country);
777                } elseif (!isset($info['street'])) {
778                    $info['street'] = trim($addressParts[5]);
779                } else {
780                    $info['street'] .= "\n" . $addressParts[5];
781                }
782            }
783            if (!empty($addressParts[3])) {
784                $info['zip'] = $addressParts[3];
785            }
786            if (!empty($addressParts[4])) {
787                $info['city'] = trim($addressParts[4]);
788            }
789        }
790
791        return $info;
792    }
793
794    /**
795     * Return info about field type.
796     */
797    function about()
798    {
799        return array(
800            'name' => Horde_Form_Translation::t("Address"),
801            'params' => array(
802                'rows' => array('label' => Horde_Form_Translation::t("Number of rows"),
803                                'type'  => 'int'),
804                'cols' => array('label' => Horde_Form_Translation::t("Number of columns"),
805                                'type'  => 'int')));
806    }
807
808}
809
810class Horde_Form_Type_addresslink extends Horde_Form_Type_address {
811
812    function isValid(&$var, &$vars, $value, &$message)
813    {
814        return true;
815    }
816
817    /**
818     * Return info about field type.
819     */
820    function about()
821    {
822        return array('name' => Horde_Form_Translation::t("Address Link"));
823    }
824
825}
826
827class Horde_Form_Type_pgp extends Horde_Form_Type_longtext {
828
829    /**
830     * Path to the GnuPG binary.
831     *
832     * @var string
833     */
834    var $_gpg;
835
836    /**
837     * A temporary directory.
838     *
839     * @var string
840     */
841    var $_temp;
842
843    function init($gpg, $temp_dir = null, $rows = null, $cols = null)
844    {
845        $this->_gpg = $gpg;
846        $this->_temp = $temp_dir;
847        parent::init($rows, $cols);
848    }
849
850    /**
851     * Returns a parameter hash for the Horde_Crypt_pgp constructor.
852     *
853     * @return array  A parameter hash.
854     */
855    function getPGPParams()
856    {
857        return array('program' => $this->_gpg, 'temp' => $this->_temp);
858    }
859
860    /**
861     * Return info about field type.
862     */
863    function about()
864    {
865        return array(
866            'name' => Horde_Form_Translation::t("PGP Key"),
867            'params' => array(
868                'gpg'      => array('label' => Horde_Form_Translation::t("Path to the GnuPG binary"),
869                                    'type'  => 'string'),
870                'temp_dir' => array('label' => Horde_Form_Translation::t("A temporary directory"),
871                                    'type'  => 'string'),
872                'rows'     => array('label' => Horde_Form_Translation::t("Number of rows"),
873                                    'type'  => 'int'),
874                'cols'     => array('label' => Horde_Form_Translation::t("Number of columns"),
875                                    'type'  => 'int')));
876    }
877
878}
879
880class Horde_Form_Type_smime extends Horde_Form_Type_longtext {
881
882    /**
883     * A temporary directory.
884     *
885     * @var string
886     */
887    var $_temp;
888
889    function init($temp_dir = null, $rows = null, $cols = null)
890    {
891        $this->_temp = $temp_dir;
892        parent::init($rows, $cols);
893    }
894
895    /**
896     * Returns a parameter hash for the Horde_Crypt_smime constructor.
897     *
898     * @return array  A parameter hash.
899     */
900    function getSMIMEParams()
901    {
902        return array('temp' => $this->_temp);
903    }
904
905    /**
906     * Return info about field type.
907     */
908    function about()
909    {
910        return array(
911            'name' => Horde_Form_Translation::t("S/MIME Key"),
912            'params' => array(
913                'temp_dir' => array('label' => Horde_Form_Translation::t("A temporary directory"),
914                                    'type'  => 'string'),
915                'rows'     => array('label' => Horde_Form_Translation::t("Number of rows"),
916                                    'type'  => 'int'),
917                'cols'     => array('label' => Horde_Form_Translation::t("Number of columns"),
918                                    'type'  => 'int')));
919    }
920
921}
922
923class Horde_Form_Type_country extends Horde_Form_Type_enum {
924
925    function init($prompt = null)
926    {
927        parent::init(Horde_Nls::getCountryISO(), $prompt);
928    }
929
930    /**
931     * Return info about field type.
932     */
933    function about()
934    {
935        return array(
936            'name' => Horde_Form_Translation::t("Country drop down list"),
937            'params' => array(
938                'prompt' => array('label' => Horde_Form_Translation::t("Prompt text"),
939                                  'type'  => 'text')));
940    }
941
942}
943
944class Horde_Form_Type_file extends Horde_Form_Type {
945
946    function isValid(&$var, &$vars, $value, &$message)
947    {
948        if ($var->isRequired()) {
949            try {
950                $GLOBALS['browser']->wasFileUploaded($var->getVarName());
951            } catch (Horde_Browser_Exception $e) {
952                $message = $e->getMessage();
953                return false;
954            }
955        }
956
957        return true;
958    }
959
960    function getInfo(&$vars, &$var, &$info)
961    {
962        $name = $var->getVarName();
963        try {
964            $GLOBALS['browser']->wasFileUploaded($name);
965            $info['name'] = Horde_Util::dispelMagicQuotes($_FILES[$name]['name']);
966            $info['type'] = $_FILES[$name]['type'];
967            $info['tmp_name'] = $_FILES[$name]['tmp_name'];
968            $info['file'] = $_FILES[$name]['tmp_name'];
969            $info['error'] = $_FILES[$name]['error'];
970            $info['size'] = $_FILES[$name]['size'];
971        } catch (Horde_Browser_Exception $e) {}
972    }
973
974    /**
975     * Return info about field type.
976     */
977    function about()
978    {
979        return array('name' => Horde_Form_Translation::t("File upload"));
980    }
981
982}
983
984class Horde_Form_Type_image extends Horde_Form_Type {
985
986    /**
987     * Has a file been uploaded on this form submit?
988     *
989     * @var boolean
990     */
991    var $_uploaded = null;
992
993    /**
994     * Show the upload button?
995     *
996     * @var boolean
997     */
998    var $_show_upload = true;
999
1000    /**
1001     * Show the option to upload also original non-modified image?
1002     *
1003     * @var boolean
1004     */
1005    var $_show_keeporig = false;
1006
1007    /**
1008     * Limit the file size?
1009     *
1010     * @var integer
1011     */
1012    var $_max_filesize = null;
1013
1014    /**
1015     * Hash containing the previously uploaded image info.
1016     *
1017     * @var array
1018     */
1019    var $_img;
1020
1021    /**
1022     * A random id that identifies the image information in the session data.
1023     *
1024     * @var string
1025     */
1026    var $_random;
1027
1028    function init($show_upload = true, $show_keeporig = false, $max_filesize = null)
1029    {
1030        $this->_show_upload   = $show_upload;
1031        $this->_show_keeporig = $show_keeporig;
1032        $this->_max_filesize  = $max_filesize;
1033    }
1034
1035    function onSubmit(&$var, &$vars)
1036    {
1037        /* Are we removing an image? */
1038        if ($vars->get('remove_' . $var->getVarName())) {
1039            $GLOBALS['session']->remove('horde', 'form/' . $this->getRandomId());
1040            $this->_img = null;
1041            return;
1042        }
1043
1044        /* Get the upload. */
1045        $this->getImage($vars, $var);
1046
1047        /* If this was done through the upload button override the submitted
1048         * value of the form. */
1049        if ($vars->get('do_' . $var->getVarName())) {
1050            $var->form->setSubmitted(false);
1051            if ($this->_uploaded instanceof Horde_Browser_Exception) {
1052                $this->_img = array('hash' => $this->getRandomId(),
1053                                    'error' => $this->_uploaded->getMessage());
1054            }
1055        }
1056    }
1057
1058    /**
1059     * @param Horde_Form_Variable $var  The Form field object to check
1060     * @param Horde_Variables $vars     The form state to check this field for
1061     * @param array $value              The field value array - should contain a key ['hash'] which holds the key for the image on temp storage
1062     * @param something  $message       Not clear what this field does
1063     */
1064
1065    function isValid(&$var, &$vars, $value, &$message)
1066    {
1067        if ($vars->get('remove_' . $var->getVarName())) {
1068            return true;
1069        }
1070
1071        /* Get the upload. */
1072        $this->getImage($vars, $var);
1073        $field = $vars->get($var->getVarName());
1074
1075        /* The upload generated a PEAR Error. */
1076        if ($this->_uploaded instanceof Horde_Browser_Exception) {
1077            /* Not required and no image upload attempted. */
1078            if (!$var->isRequired() && empty($field['hash']) &&
1079                $this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) {
1080                return true;
1081            }
1082
1083            if (($this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) &&
1084                empty($field['hash'])) {
1085                /* Nothing uploaded and no older upload. */
1086                $message = Horde_Form_Translation::t("This field is required.");
1087                return false;
1088            } elseif (!empty($field['hash'])) {
1089                if ($this->_img && isset($this->_img['error'])) {
1090                    $message = $this->_img['error'];
1091                    return false;
1092                }
1093                /* Nothing uploaded but older upload present. */
1094                return true;
1095            } else {
1096                /* Some other error message. */
1097                $message = $this->_uploaded->getMessage();
1098                return false;
1099            }
1100        } elseif (empty($this->_img['img']['size'])) {
1101            $message = Horde_Form_Translation::t("The image file size could not be determined or it was 0 bytes. The upload may have been interrupted.");
1102            return false;
1103        } elseif ($this->_max_filesize &&
1104                  $this->_img['img']['size'] > $this->_max_filesize) {
1105            $message = sprintf(Horde_Form_Translation::t("The image file was larger than the maximum allowed size (%d bytes)."), $this->_max_filesize);
1106            return false;
1107        }
1108
1109        return true;
1110    }
1111
1112    function getInfo(&$vars, &$var, &$info)
1113    {
1114        /* Get the upload. */
1115        $this->getImage($vars, $var);
1116
1117        /* Get image params stored in the hidden field. */
1118        $value = $var->getValue($vars);
1119        $info = $this->_img['img'];
1120        if (empty($info['file'])) {
1121            unset($info['file']);
1122            return;
1123        }
1124        if ($this->_show_keeporig) {
1125            $info['keep_orig'] = !empty($value['keep_orig']);
1126        }
1127
1128        /* Set the uploaded value (either true or Horde_Browser_Exception). */
1129        $info['uploaded'] = &$this->_uploaded;
1130
1131        /* If a modified file exists move it over the original. */
1132        if ($this->_show_keeporig && $info['keep_orig']) {
1133            /* Requested the saving of original file also. */
1134            $info['orig_file'] = Horde::getTempDir() . '/' . $info['file'];
1135            $info['file'] = Horde::getTempDir() . '/mod_' . $info['file'];
1136            /* Check if a modified file actually exists. */
1137            if (!file_exists($info['file'])) {
1138                $info['file'] = $info['orig_file'];
1139                unset($info['orig_file']);
1140            }
1141        } else {
1142            /* Saving of original not required. */
1143            $mod_file = Horde::getTempDir() . '/mod_' . $info['file'];
1144            $info['file'] = Horde::getTempDir() . '/' . $info['file'];
1145
1146            if (file_exists($mod_file)) {
1147                /* Unlink first (has to be done on Windows machines?) */
1148                unlink($info['file']);
1149                rename($mod_file, $info['file']);
1150            }
1151        }
1152    }
1153
1154    /**
1155     * Gets the upload and sets up the upload data array. Either
1156     * fetches an upload done with this submit or retrieves stored
1157     * upload info.
1158     * @param Horde_Variables $vars     The form state to check this field for
1159     * @param Horde_Form_Variable $var  The Form field object to check
1160     *
1161     */
1162    function _getUpload(&$vars, &$var)
1163    {
1164        global $session;
1165
1166        /* Don't bother with this function if already called and set
1167         * up vars. */
1168        if (!empty($this->_img)) {
1169            return true;
1170        }
1171
1172        /* Check if file has been uploaded. */
1173        $varname = $var->getVarName();
1174
1175        try {
1176            $GLOBALS['browser']->wasFileUploaded($varname . '[new]');
1177            $this->_uploaded = true;
1178
1179            /* A file has been uploaded on this submit. Save to temp dir for
1180             * preview work. */
1181            $this->_img['img']['type'] = $this->getUploadedFileType($varname . '[new]');
1182
1183            /* Get the other parts of the upload. */
1184            Horde_Array::getArrayParts($varname . '[new]', $base, $keys);
1185
1186            /* Get the temporary file name. */
1187            $keys_path = array_merge(array($base, 'tmp_name'), $keys);
1188            $this->_img['img']['file'] = Horde_Array::getElement($_FILES, $keys_path);
1189
1190            /* Get the actual file name. */
1191            $keys_path = array_merge(array($base, 'name'), $keys);
1192            $this->_img['img']['name'] = Horde_Array::getElement($_FILES, $keys_path);
1193
1194            /* Get the file size. */
1195            $keys_path = array_merge(array($base, 'size'), $keys);
1196            $this->_img['img']['size'] = Horde_Array::getElement($_FILES, $keys_path);
1197
1198            /* Get any existing values for the image upload field. */
1199            $upload = $vars->get($var->getVarName());
1200            if (!empty($upload['hash'])) {
1201                $upload['img'] = $session->get('horde', 'form/' . $upload['hash']);
1202                $session->remove('horde', 'form/' . $upload['hash']);
1203                if (!empty($upload['img']['file'])) {
1204                    $tmp_file = Horde::getTempDir() . '/' . basename($upload['img']['file']);
1205                } else {
1206                    $tmp_file = Horde::getTempFile('Horde', false);
1207                }
1208            } else {
1209                $tmp_file = Horde::getTempFile('Horde', false);
1210            }
1211
1212            /* Move the browser created temp file to the new temp file. */
1213            move_uploaded_file($this->_img['img']['file'], $tmp_file);
1214            $this->_img['img']['file'] = basename($tmp_file);
1215        } catch (Horde_Browser_Exception $e) {
1216            $this->_uploaded = $e;
1217
1218            /* File has not been uploaded. */
1219            $upload = $vars->get($var->getVarName());
1220
1221            /* File is explicitly removed */
1222            if ($vars->get('remove_' . $var->getVarName())) {
1223                $this->_img = null;
1224                $session->remove('horde', 'form/' . $upload['hash']);
1225                return;
1226            }
1227
1228            if ($this->_uploaded->getCode() == 4 &&
1229                !empty($upload['hash']) &&
1230                $session->exists('horde', 'form/' . $upload['hash'])) {
1231                $this->_img['img'] = $session->get('horde', 'form/' . $upload['hash']);
1232                $session->remove('horde', 'form/' . $upload['hash']);
1233                if (isset($this->_img['error'])) {
1234                    $this->_uploaded = PEAR::raiseError($this->_img['error']);
1235                }
1236            }
1237        }
1238        if (isset($this->_img['img'])) {
1239            $session->set('horde', 'form/' . $this->getRandomId(), $this->_img['img']);
1240        }
1241    }
1242
1243    function getUploadedFileType($field)
1244    {
1245        /* Get any index on the field name. */
1246        $index = Horde_Array::getArrayParts($field, $base, $keys);
1247
1248        if ($index) {
1249            /* Index present, fetch the mime type var to check. */
1250            $keys_path = array_merge(array($base, 'type'), $keys);
1251            $type = Horde_Array::getElement($_FILES, $keys_path);
1252            $keys_path = array_merge(array($base, 'tmp_name'), $keys);
1253            $tmp_name = Horde_Array::getElement($_FILES, $keys_path);
1254        } else {
1255            /* No index, simple set up of vars to check. */
1256            $type = $_FILES[$field]['type'];
1257            $tmp_name = $_FILES[$field]['tmp_name'];
1258        }
1259
1260        if (empty($type) || ($type == 'application/octet-stream')) {
1261            /* Type wasn't set on upload, try analising the upload. */
1262            if (!($type = Horde_Mime_Magic::analyzeFile($tmp_name, isset($GLOBALS['conf']['mime']['magic_db']) ? $GLOBALS['conf']['mime']['magic_db'] : null))) {
1263                if ($index) {
1264                    /* Get the name value. */
1265                    $keys_path = array_merge(array($base, 'name'), $keys);
1266                    $name = Horde_Array::getElement($_FILES, $keys_path);
1267
1268                    /* Work out the type from the file name. */
1269                    $type = Horde_Mime_Magic::filenameToMime($name);
1270
1271                    /* Set the type. */
1272                    $keys_path = array_merge(array($base, 'type'), $keys);
1273                    Horde_Array::getElement($_FILES, $keys_path, $type);
1274                } else {
1275                    /* Work out the type from the file name. */
1276                    $type = Horde_Mime_Magic::filenameToMime($_FILES[$field]['name']);
1277
1278                    /* Set the type. */
1279                    $_FILES[$field]['type'] = Horde_Mime_Magic::filenameToMime($_FILES[$field]['name']);
1280                }
1281            }
1282        }
1283
1284        return $type;
1285    }
1286
1287    /**
1288     * Returns the current image information.
1289     *
1290     * @param Horde_Variables $vars     The form state to check this field for
1291     * @param Horde_Form_Variable $var  The Form field object to check
1292     * @return array  The current image hash.
1293     */
1294    function getImage($vars, $var)
1295    {
1296        $this->_getUpload($vars, $var);
1297        if (!isset($this->_img)) {
1298            $image = $vars->get($var->getVarName());
1299            if ($image) {
1300                $this->loadImageData($image);
1301                if (isset($image['img'])) {
1302                    $this->_img = $image;
1303                    $GLOBALS['session']->set('horde', 'form/' . $this->getRandomId(), $this->_img['img']);
1304                }
1305            }
1306        }
1307        return $this->_img;
1308    }
1309
1310    /**
1311     * Loads any existing image data into the image field. Requires that the
1312     * array $image passed to it contains the structure:
1313     *   $image['load']['file'] - the filename of the image;
1314     *   $image['load']['data'] - the raw image data.
1315     *
1316     * @param array $image  The image array.
1317     */
1318    function loadImageData(&$image)
1319    {
1320        /* No existing image data to load. */
1321        if (!isset($image['load'])) {
1322            return;
1323        }
1324
1325        /* Save the data to the temp dir. */
1326        $tmp_file = Horde::getTempDir() . '/' . $image['load']['file'];
1327        if ($fd = fopen($tmp_file, 'w')) {
1328            fwrite($fd, $image['load']['data']);
1329            fclose($fd);
1330        }
1331
1332        $image['img'] = array('file' => $image['load']['file']);
1333        unset($image['load']);
1334    }
1335
1336    function getRandomId()
1337    {
1338        if (!isset($this->_random)) {
1339            $this->_random = uniqid(mt_rand());
1340        }
1341        return $this->_random;
1342    }
1343
1344    /**
1345     * Return info about field type.
1346     */
1347    function about()
1348    {
1349        return array(
1350            'name' => Horde_Form_Translation::t("Image upload"),
1351            'params' => array(
1352                'show_upload'   => array('label' => Horde_Form_Translation::t("Show upload?"),
1353                                         'type'  => 'boolean'),
1354                'show_keeporig' => array('label' => Horde_Form_Translation::t("Show option to keep original?"),
1355                                         'type'  => 'boolean'),
1356                'max_filesize'  => array('label' => Horde_Form_Translation::t("Maximum file size in bytes"),
1357                                         'type'  => 'int')));
1358    }
1359
1360}
1361
1362class Horde_Form_Type_boolean extends Horde_Form_Type {
1363
1364    function isValid(&$var, &$vars, $value, &$message)
1365    {
1366        return true;
1367    }
1368
1369    function getInfo(&$vars, &$var, &$info)
1370    {
1371        $info = Horde_String::lower($vars->get($var->getVarName())) == 'on';
1372    }
1373
1374    /**
1375     * Return info about field type.
1376     */
1377    function about()
1378    {
1379        return array('name' => Horde_Form_Translation::t("True or false"));
1380    }
1381
1382}
1383
1384class Horde_Form_Type_link extends Horde_Form_Type {
1385
1386    /**
1387     * List of hashes containing link parameters. Possible keys: 'url', 'text',
1388     * 'target', 'onclick', 'title', 'accesskey', 'class'.
1389     *
1390     * @var array
1391     */
1392    var $values;
1393
1394    function init($values)
1395    {
1396        $this->values = $values;
1397    }
1398
1399    function isValid(&$var, &$vars, $value, &$message)
1400    {
1401        return true;
1402    }
1403
1404    /**
1405     * Return info about field type.
1406     */
1407    function about()
1408    {
1409        return array(
1410            'name' => Horde_Form_Translation::t("Link"),
1411            'params' => array(
1412                'url' => array(
1413                    'label' => Horde_Form_Translation::t("Link URL"),
1414                    'type' => 'text'),
1415                'text' => array(
1416                    'label' => Horde_Form_Translation::t("Link text"),
1417                    'type' => 'text'),
1418                'target' => array(
1419                    'label' => Horde_Form_Translation::t("Link target"),
1420                    'type' => 'text'),
1421                'onclick' => array(
1422                    'label' => Horde_Form_Translation::t("Onclick event"),
1423                    'type' => 'text'),
1424                'title' => array(
1425                    'label' => Horde_Form_Translation::t("Link title attribute"),
1426                    'type' => 'text'),
1427                'accesskey' => array(
1428                    'label' => Horde_Form_Translation::t("Link access key"),
1429                    'type' => 'text'),
1430                'class' => array(
1431                    'label' => Horde_Form_Translation::t("Link CSS class"),
1432                    'type' => 'text')
1433            )
1434        );
1435    }
1436
1437}
1438
1439class Horde_Form_Type_email extends Horde_Form_Type {
1440
1441    /**
1442     * Allow multiple addresses?
1443     *
1444     * @var boolean
1445     */
1446    var $_allow_multi = false;
1447
1448    /**
1449     * Protect address from spammers?
1450     *
1451     * @var boolean
1452     */
1453    var $_strip_domain = false;
1454
1455    /**
1456     * Link the email address to the compose page when displaying?
1457     *
1458     * @var boolean
1459     */
1460    var $_link_compose = false;
1461
1462    /**
1463     * Whether to check the domain's SMTP server whether the address exists.
1464     *
1465     * @var boolean
1466     */
1467    var $_check_smtp = false;
1468
1469    /**
1470     * The name to use when linking to the compose page
1471     *
1472     * @var boolean
1473     */
1474    var $_link_name;
1475
1476    /**
1477     * A string containing valid delimiters (default is just comma).
1478     *
1479     * @var string
1480     */
1481    var $_delimiters = ',';
1482
1483    /**
1484     * The size of the input field.
1485     *
1486     * @var integer
1487     */
1488    var $_size;
1489
1490    /**
1491     * @param boolean $allow_multi   Allow multiple addresses?
1492     * @param boolean $strip_domain  Protect address from spammers?
1493     * @param boolean $link_compose  Link the email address to the compose page
1494     *                               when displaying?
1495     * @param string $link_name      The name to use when linking to the
1496     *                               compose page.
1497     * @param string $delimiters     Character to split multiple addresses with.
1498     * @param integer $size          The size of the input field.
1499     */
1500    function init($allow_multi = false, $strip_domain = false,
1501                  $link_compose = false, $link_name = null,
1502                  $delimiters = ',', $size = null)
1503    {
1504        $this->_allow_multi = $allow_multi;
1505        $this->_strip_domain = $strip_domain;
1506        $this->_link_compose = $link_compose;
1507        $this->_link_name = $link_name;
1508        $this->_delimiters = $delimiters;
1509        $this->_size = $size;
1510    }
1511
1512    /**
1513     */
1514    function isValid(&$var, &$vars, $value, &$message)
1515    {
1516        // Split into individual addresses.
1517        $emails = $this->splitEmailAddresses($value);
1518
1519        // Check for too many.
1520        if (!$this->_allow_multi && count($emails) > 1) {
1521            $message = Horde_Form_Translation::t("Only one email address is allowed.");
1522            return false;
1523        }
1524
1525        // Check for all valid and at least one non-empty.
1526        $nonEmpty = 0;
1527        foreach ($emails as $email) {
1528            if (!strlen($email)) {
1529                continue;
1530            }
1531            if (!$this->validateEmailAddress($email)) {
1532                $message = sprintf(Horde_Form_Translation::t("\"%s\" is not a valid email address."), htmlspecialchars($email));
1533                return false;
1534            }
1535            ++$nonEmpty;
1536        }
1537
1538        if (!$nonEmpty && $var->isRequired()) {
1539            if ($this->_allow_multi) {
1540                $message = Horde_Form_Translation::t("You must enter at least one email address.");
1541            } else {
1542                $message = Horde_Form_Translation::t("You must enter an email address.");
1543            }
1544            return false;
1545        }
1546
1547        return true;
1548    }
1549
1550    /**
1551     * Explodes an RFC 2822 string, ignoring a delimiter if preceded
1552     * by a "\" character, or if the delimiter is inside single or
1553     * double quotes.
1554     *
1555     * @param string $string     The RFC 822 string.
1556     *
1557     * @return array  The exploded string in an array.
1558     */
1559    function splitEmailAddresses($string)
1560    {
1561        // Trim off any trailing delimiters
1562        $string = trim($string, $this->_delimiters . ' ');
1563
1564        $quotes = array('"', "'");
1565        $emails = array();
1566        $pos = 0;
1567        $in_quote = null;
1568        $in_group = false;
1569        $prev = null;
1570
1571        if (!strlen($string)) {
1572            return array();
1573        }
1574
1575        $char = $string[0];
1576        if (in_array($char, $quotes)) {
1577            $in_quote = $char;
1578        } elseif ($char == ':') {
1579            $in_group = true;
1580        } elseif (strpos($this->_delimiters, $char) !== false) {
1581            $emails[] = '';
1582            $pos = 1;
1583        }
1584
1585        for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) {
1586            $char = $string[$i];
1587            if (in_array($char, $quotes)) {
1588                if ($prev !== '\\') {
1589                    if ($in_quote === $char) {
1590                        $in_quote = null;
1591                    } elseif (is_null($in_quote)) {
1592                        $in_quote = $char;
1593                    }
1594                }
1595            } elseif ($in_group) {
1596                if ($char == ';') {
1597                    $emails[] = substr($string, $pos, $i - $pos + 1);
1598                    $pos = $i + 1;
1599                    $in_group = false;
1600                }
1601            } elseif ($char == ':') {
1602                $in_group = true;
1603            } elseif (strpos($this->_delimiters, $char) !== false &&
1604                      $prev !== '\\' &&
1605                      is_null($in_quote)) {
1606                $emails[] = substr($string, $pos, $i - $pos);
1607                $pos = $i + 1;
1608            }
1609            $prev = $char;
1610        }
1611
1612        if ($pos != $i) {
1613            /* The string ended without a delimiter. */
1614            $emails[] = substr($string, $pos, $i - $pos);
1615        }
1616
1617        return $emails;
1618    }
1619
1620    /**
1621     * @param string $email An individual email address to validate.
1622     *
1623     * @return boolean
1624     */
1625    function validateEmailAddress($email)
1626    {
1627        $result = $this->_isRfc3696ValidEmailAddress($email);
1628        if ($result && $this->_check_smtp) {
1629            $result = $this->validateEmailAddressSmtp($email);
1630        }
1631
1632        return $result;
1633    }
1634
1635    /**
1636     * Attempt partial delivery of mail to an address to validate it.
1637     *
1638     * @param string $email An individual email address to validate.
1639     *
1640     * @return boolean
1641     */
1642    function validateEmailAddressSmtp($email)
1643    {
1644        list(, $maildomain) = explode('@', $email, 2);
1645
1646        // Try to get the real mailserver from MX records.
1647        if (function_exists('getmxrr') &&
1648            @getmxrr($maildomain, $mxhosts, $mxpriorities)) {
1649            // MX record found.
1650            array_multisort($mxpriorities, $mxhosts);
1651            $mailhost = $mxhosts[0];
1652        } else {
1653            // No MX record found, try the root domain as the mail
1654            // server.
1655            $mailhost = $maildomain;
1656        }
1657
1658        $fp = @fsockopen($mailhost, 25, $errno, $errstr, 5);
1659        if (!$fp) {
1660            return false;
1661        }
1662
1663        // Read initial response.
1664        fgets($fp, 4096);
1665
1666        // HELO
1667        fputs($fp, "HELO $mailhost\r\n");
1668        fgets($fp, 4096);
1669
1670        // MAIL FROM
1671        fputs($fp, "MAIL FROM: <root@example.com>\r\n");
1672        fgets($fp, 4096);
1673
1674        // RCPT TO - gets the result we want.
1675        fputs($fp, "RCPT TO: <$email>\r\n");
1676        $result = trim(fgets($fp, 4096));
1677
1678        // QUIT
1679        fputs($fp, "QUIT\r\n");
1680        fgets($fp, 4096);
1681        fclose($fp);
1682
1683        return substr($result, 0, 1) == '2';
1684    }
1685
1686    function getSize()
1687    {
1688        return $this->_size;
1689    }
1690
1691    function allowMulti()
1692    {
1693        return $this->_allow_multi;
1694    }
1695
1696    /**
1697     * Return info about field type.
1698     */
1699    function about()
1700    {
1701        return array(
1702            'name' => Horde_Form_Translation::t("Email"),
1703            'params' => array(
1704                'allow_multi' => array(
1705                    'label' => Horde_Form_Translation::t("Allow multiple addresses?"),
1706                    'type'  => 'boolean'),
1707                'strip_domain' => array(
1708                    'label' => Horde_Form_Translation::t("Protect address from spammers?"),
1709                    'type' => 'boolean'),
1710                'link_compose' => array(
1711                    'label' => Horde_Form_Translation::t("Link the email address to the compose page when displaying?"),
1712                    'type' => 'boolean'),
1713                'link_name' => array(
1714                    'label' => Horde_Form_Translation::t("The name to use when linking to the compose page"),
1715                    'type' => 'text'),
1716                'delimiters' => array(
1717                    'label' => Horde_Form_Translation::t("Character to split multiple addresses with"),
1718                    'type' => 'text'),
1719                'size' => array(
1720                    'label' => Horde_Form_Translation::t("Size"),
1721                    'type'  => 'int'),
1722            ),
1723        );
1724    }
1725
1726    /**
1727     * RFC3696 Email Parser
1728     *
1729     * By Cal Henderson <cal@iamcal.com>
1730     *
1731     * This code is dual licensed:
1732     * CC Attribution-ShareAlike 2.5 - http://creativecommons.org/licenses/by-sa/2.5/
1733     * GPLv3 - http://www.gnu.org/copyleft/gpl.html
1734     */
1735    protected function _isRfc3696ValidEmailAddress($email)
1736    {
1737        ####################################################################################
1738        #
1739        # NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
1740        #                         %d11 /          ;  that do not include the
1741        #                         %d12 /          ;  carriage return, line feed,
1742        #                         %d14-31 /       ;  and white space characters
1743        #                         %d127
1744        # ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
1745        # DIGIT          =  %x30-39
1746
1747        $no_ws_ctl  = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]";
1748        $alpha      = "[\\x41-\\x5a\\x61-\\x7a]";
1749        $digit      = "[\\x30-\\x39]";
1750        $cr     = "\\x0d";
1751        $lf     = "\\x0a";
1752        $crlf       = "(?:$cr$lf)";
1753
1754
1755        ####################################################################################
1756        #
1757        # obs-char        =       %d0-9 / %d11 /          ; %d0-127 except CR and
1758        #                         %d12 / %d14-127         ;  LF
1759        # obs-text        =       *LF *CR *(obs-char *LF *CR)
1760        # text            =       %d1-9 /         ; Characters excluding CR and LF
1761        #                         %d11 /
1762        #                         %d12 /
1763        #                         %d14-127 /
1764        #                         obs-text
1765        # obs-qp          =       "\" (%d0-127)
1766        # quoted-pair     =       ("\" text) / obs-qp
1767
1768        $obs_char   = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]";
1769        $obs_text   = "(?:$lf*$cr*(?:$obs_char$lf*$cr*)*)";
1770        $text       = "(?:[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)";
1771
1772        #
1773        # there's an issue with the definition of 'text', since 'obs_text' can
1774        # be blank and that allows qp's with no character after the slash. we're
1775        # treating that as bad, so this just checks we have at least one
1776        # (non-CRLF) character
1777        #
1778
1779        $text       = "(?:$lf*$cr*$obs_char$lf*$cr*)";
1780        $obs_qp     = "(?:\\x5c[\\x00-\\x7f])";
1781        $quoted_pair    = "(?:\\x5c$text|$obs_qp)";
1782
1783
1784        ####################################################################################
1785        #
1786        # obs-FWS         =       1*WSP *(CRLF 1*WSP)
1787        # FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
1788        #                         obs-FWS
1789        # ctext           =       NO-WS-CTL /     ; Non white space controls
1790        #                         %d33-39 /       ; The rest of the US-ASCII
1791        #                         %d42-91 /       ;  characters not including "(",
1792        #                         %d93-126        ;  ")", or "\"
1793        # ccontent        =       ctext / quoted-pair / comment
1794        # comment         =       "(" *([FWS] ccontent) [FWS] ")"
1795        # CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
1796
1797        #
1798        # note: we translate ccontent only partially to avoid an infinite loop
1799        # instead, we'll recursively strip *nested* comments before processing
1800        # the input. that will leave 'plain old comments' to be matched during
1801        # the main parse.
1802        #
1803
1804        $wsp        = "[\\x20\\x09]";
1805        $obs_fws    = "(?:$wsp+(?:$crlf$wsp+)*)";
1806        $fws        = "(?:(?:(?:$wsp*$crlf)?$wsp+)|$obs_fws)";
1807        $ctext      = "(?:$no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])";
1808        $ccontent   = "(?:$ctext|$quoted_pair)";
1809        $comment    = "(?:\\x28(?:$fws?$ccontent)*$fws?\\x29)";
1810        $cfws       = "(?:(?:$fws?$comment)*(?:$fws?$comment|$fws))";
1811
1812
1813        #
1814        # these are the rules for removing *nested* comments. we'll just detect
1815        # outer comment and replace it with an empty comment, and recurse until
1816        # we stop.
1817        #
1818
1819        $outer_ccontent_dull    = "(?:$fws?$ctext|$quoted_pair)";
1820        $outer_ccontent_nest    = "(?:$fws?$comment)";
1821        $outer_comment      = "(?:\\x28$outer_ccontent_dull*(?:$outer_ccontent_nest$outer_ccontent_dull*)+$fws?\\x29)";
1822
1823
1824        ####################################################################################
1825        #
1826        # atext           =       ALPHA / DIGIT / ; Any character except controls,
1827        #                         "!" / "#" /     ;  SP, and specials.
1828        #                         "$" / "%" /     ;  Used for atoms
1829        #                         "&" / "'" /
1830        #                         "*" / "+" /
1831        #                         "-" / "/" /
1832        #                         "=" / "?" /
1833        #                         "^" / "_" /
1834        #                         "`" / "{" /
1835        #                         "|" / "}" /
1836        #                         "~"
1837        # atom            =       [CFWS] 1*atext [CFWS]
1838
1839        $atext      = "(?:$alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2f\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])";
1840        $atom       = "(?:$cfws?(?:$atext)+$cfws?)";
1841
1842
1843        ####################################################################################
1844        #
1845        # qtext           =       NO-WS-CTL /     ; Non white space controls
1846        #                         %d33 /          ; The rest of the US-ASCII
1847        #                         %d35-91 /       ;  characters not including "\"
1848        #                         %d93-126        ;  or the quote character
1849        # qcontent        =       qtext / quoted-pair
1850        # quoted-string   =       [CFWS]
1851        #                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
1852        #                         [CFWS]
1853        # word            =       atom / quoted-string
1854
1855        $qtext      = "(?:$no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])";
1856        $qcontent   = "(?:$qtext|$quoted_pair)";
1857        $quoted_string  = "(?:$cfws?\\x22(?:$fws?$qcontent)*$fws?\\x22$cfws?)";
1858
1859        #
1860        # changed the '*' to a '+' to require that quoted strings are not empty
1861        #
1862
1863        $quoted_string  = "(?:$cfws?\\x22(?:$fws?$qcontent)+$fws?\\x22$cfws?)";
1864        $word       = "(?:$atom|$quoted_string)";
1865
1866
1867        ####################################################################################
1868        #
1869        # obs-local-part  =       word *("." word)
1870        # obs-domain      =       atom *("." atom)
1871
1872        $obs_local_part = "(?:$word(?:\\x2e$word)*)";
1873        $obs_domain = "(?:$atom(?:\\x2e$atom)*)";
1874
1875
1876        ####################################################################################
1877        #
1878        # dot-atom-text   =       1*atext *("." 1*atext)
1879        # dot-atom        =       [CFWS] dot-atom-text [CFWS]
1880
1881        $dot_atom_text  = "(?:$atext+(?:\\x2e$atext+)*)";
1882        $dot_atom   = "(?:$cfws?$dot_atom_text$cfws?)";
1883
1884
1885        ####################################################################################
1886        #
1887        # domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
1888        # dcontent        =       dtext / quoted-pair
1889        # dtext           =       NO-WS-CTL /     ; Non white space controls
1890        #
1891        #                         %d33-90 /       ; The rest of the US-ASCII
1892        #                         %d94-126        ;  characters not including "[",
1893        #                                         ;  "]", or "\"
1894
1895        $dtext      = "(?:$no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])";
1896        $dcontent   = "(?:$dtext|$quoted_pair)";
1897        $domain_literal = "(?:$cfws?\\x5b(?:$fws?$dcontent)*$fws?\\x5d$cfws?)";
1898
1899
1900        ####################################################################################
1901        #
1902        # local-part      =       dot-atom / quoted-string / obs-local-part
1903        # domain          =       dot-atom / domain-literal / obs-domain
1904        # addr-spec       =       local-part "@" domain
1905
1906        $local_part = "(($dot_atom)|($quoted_string)|($obs_local_part))";
1907        $domain     = "(($dot_atom)|($domain_literal)|($obs_domain))";
1908        $addr_spec  = "$local_part\\x40$domain";
1909
1910
1911
1912        #
1913        # see http://www.dominicsayers.com/isemail/ for details, but this should probably be 254
1914        #
1915
1916        if (strlen($email) > 256) return 0;
1917
1918
1919        #
1920        # we need to strip nested comments first - we replace them with a simple comment
1921        #
1922
1923        $email = $this->_rfc3696StripComments($outer_comment, $email, "(x)");
1924
1925
1926        #
1927        # now match what's left
1928        #
1929
1930        if (!preg_match("!^$addr_spec$!", $email, $m)){
1931
1932            return 0;
1933        }
1934
1935        $bits = array(
1936            'local'         => isset($m[1]) ? $m[1] : '',
1937            'local-atom'        => isset($m[2]) ? $m[2] : '',
1938            'local-quoted'      => isset($m[3]) ? $m[3] : '',
1939            'local-obs'     => isset($m[4]) ? $m[4] : '',
1940            'domain'        => isset($m[5]) ? $m[5] : '',
1941            'domain-atom'       => isset($m[6]) ? $m[6] : '',
1942            'domain-literal'    => isset($m[7]) ? $m[7] : '',
1943            'domain-obs'        => isset($m[8]) ? $m[8] : '',
1944        );
1945
1946
1947        #
1948        # we need to now strip comments from $bits[local] and $bits[domain],
1949        # since we know they're i the right place and we want them out of the
1950        # way for checking IPs, label sizes, etc
1951        #
1952
1953        $bits['local']  = $this->_rfc3696StripComments($comment, $bits['local']);
1954        $bits['domain'] = $this->_rfc3696StripComments($comment, $bits['domain']);
1955
1956
1957        #
1958        # length limits on segments
1959        #
1960
1961        if (strlen($bits['local']) > 64) return 0;
1962        if (strlen($bits['domain']) > 255) return 0;
1963
1964
1965        #
1966        # restrictions on domain-literals from RFC2821 section 4.1.3
1967        #
1968
1969        if (strlen($bits['domain-literal'])){
1970
1971            $Snum           = "(\d{1,3})";
1972            $IPv4_address_literal   = "$Snum\.$Snum\.$Snum\.$Snum";
1973
1974            $IPv6_hex       = "(?:[0-9a-fA-F]{1,4})";
1975
1976            $IPv6_full      = "IPv6\:$IPv6_hex(:?\:$IPv6_hex){7}";
1977
1978            $IPv6_comp_part     = "(?:$IPv6_hex(?:\:$IPv6_hex){0,5})?";
1979            $IPv6_comp      = "IPv6\:($IPv6_comp_part\:\:$IPv6_comp_part)";
1980
1981            $IPv6v4_full        = "IPv6\:$IPv6_hex(?:\:$IPv6_hex){5}\:$IPv4_address_literal";
1982
1983            $IPv6v4_comp_part   = "$IPv6_hex(?:\:$IPv6_hex){0,3}";
1984            $IPv6v4_comp        = "IPv6\:((?:$IPv6v4_comp_part)?\:\:(?:$IPv6v4_comp_part\:)?)$IPv4_address_literal";
1985
1986
1987            #
1988            # IPv4 is simple
1989            #
1990
1991            if (preg_match("!^\[$IPv4_address_literal\]$!", $bits['domain'], $m)) {
1992                if (intval($m[1]) > 255) return 0;
1993                if (intval($m[2]) > 255) return 0;
1994                if (intval($m[3]) > 255) return 0;
1995                if (intval($m[4]) > 255) return 0;
1996            } else {
1997                #
1998                # this should be IPv6 - a bunch of tests are needed here :)
1999                #
2000
2001                while (1) {
2002
2003                    if (preg_match("!^\[$IPv6_full\]$!", $bits['domain'])){
2004                        break;
2005                    }
2006
2007                    if (preg_match("!^\[$IPv6_comp\]$!", $bits['domain'], $m)){
2008                        list($a, $b) = explode('::', $m[1]);
2009                        $folded = (strlen($a) && strlen($b)) ? "$a:$b" : "$a$b";
2010                        $groups = explode(':', $folded);
2011                        if (count($groups) > 6) return 0;
2012                        break;
2013                    }
2014
2015                    if (preg_match("!^\[$IPv6v4_full\]$!", $bits['domain'], $m)) {
2016                        if (intval($m[1]) > 255) return 0;
2017                        if (intval($m[2]) > 255) return 0;
2018                        if (intval($m[3]) > 255) return 0;
2019                        if (intval($m[4]) > 255) return 0;
2020                        break;
2021                    }
2022
2023                    if (preg_match("!^\[$IPv6v4_comp\]$!", $bits['domain'], $m)) {
2024                        list($a, $b) = explode('::', $m[1]);
2025                        $b = substr($b, 0, -1); # remove the trailing colon before the IPv4 address
2026                        $folded = (strlen($a) && strlen($b)) ? "$a:$b" : "$a$b";
2027                        $groups = explode(':', $folded);
2028                        if (count($groups) > 4) return 0;
2029                        break;
2030                    }
2031
2032                    return 0;
2033                }
2034            }
2035        } else {
2036            #
2037            # the domain is either dot-atom or obs-domain - either way, it's
2038            # made up of simple labels and we split on dots
2039            #
2040
2041            $labels = explode('.', $bits['domain']);
2042
2043
2044            #
2045            # this is allowed by both dot-atom and obs-domain, but is un-routeable on the
2046            # public internet, so we'll fail it (e.g. user@localhost)
2047            #
2048
2049            if (count($labels) == 1) return 0;
2050
2051
2052            #
2053            # checks on each label
2054            #
2055
2056            foreach ($labels as $label) {
2057                if (strlen($label) > 63) return 0;
2058                if (substr($label, 0, 1) == '-') return 0;
2059                if (substr($label, -1) == '-') return 0;
2060            }
2061
2062
2063            #
2064            # last label can't be all numeric
2065            #
2066
2067            if (preg_match('!^[0-9]+$!', array_pop($labels))) return 0;
2068        }
2069
2070        return 1;
2071    }
2072
2073    /**
2074     * RFC3696 Email Parser
2075     *
2076     * By Cal Henderson <cal@iamcal.com>
2077     *
2078     * This code is dual licensed:
2079     * CC Attribution-ShareAlike 2.5 - http://creativecommons.org/licenses/by-sa/2.5/
2080     * GPLv3 - http://www.gnu.org/copyleft/gpl.html
2081     *
2082     * $Revision: 5039 $
2083     */
2084    protected function _rfc3696StripComments($comment, $email, $replace = '')
2085    {
2086        while (1) {
2087            $new = preg_replace("!$comment!", $replace, $email);
2088            if (strlen($new) == strlen($email)) {
2089                return $email;
2090            }
2091            $email = $new;
2092        }
2093    }
2094}
2095
2096class Horde_Form_Type_matrix extends Horde_Form_Type {
2097
2098    var $_cols;
2099    var $_rows;
2100    var $_matrix;
2101    var $_new_input;
2102
2103    /**
2104     * Initializes the variable.
2105     *
2106     * Example:
2107     * <code>
2108     * init(array('Column A', 'Column B'),
2109     *      array(1 => 'Row One', 2 => 'Row 2', 3 => 'Row 3'),
2110     *      array(array(true, true, false),
2111     *            array(true, false, true),
2112     *            array(fasle, true, false)),
2113     *      array('Row 4', 'Row 5'));
2114     * </code>
2115     *
2116     * @param array $cols               A list of column headers.
2117     * @param array $rows               A hash with row IDs as the keys and row
2118     *                                  labels as the values.
2119     * @param array $matrix             A two dimensional hash with the field
2120     *                                  values.
2121     * @param boolean|array $new_input  If true, a free text field to add a new
2122     *                                  row is displayed on the top, a select
2123     *                                  box if this parameter is a value.
2124     */
2125    function init($cols, $rows = array(), $matrix = array(), $new_input = false)
2126    {
2127        $this->_cols       = $cols;
2128        $this->_rows       = $rows;
2129        $this->_matrix     = $matrix;
2130        $this->_new_input  = $new_input;
2131    }
2132
2133    function isValid(&$var, &$vars, $value, &$message)
2134    {
2135        return true;
2136    }
2137
2138    function getCols()     { return $this->_cols; }
2139    function getRows()     { return $this->_rows; }
2140    function getMatrix()   { return $this->_matrix; }
2141    function getNewInput() { return $this->_new_input; }
2142
2143    function getInfo(&$vars, &$var, &$info)
2144    {
2145        $values = $vars->get($var->getVarName());
2146        if (!empty($values['n']['r']) && isset($values['n']['v'])) {
2147            $new_row = $values['n']['r'];
2148            $values['r'][$new_row] = $values['n']['v'];
2149            unset($values['n']);
2150        }
2151
2152        $info = (isset($values['r']) ? $values['r'] : array());
2153    }
2154
2155    function about()
2156    {
2157        return array(
2158            'name' => Horde_Form_Translation::t("Field matrix"),
2159            'params' => array(
2160                'cols' => array('label' => Horde_Form_Translation::t("Column titles"),
2161                                'type'  => 'stringarray')));
2162    }
2163
2164}
2165
2166class Horde_Form_Type_emailConfirm extends Horde_Form_Type {
2167
2168    function isValid(&$var, &$vars, $value, &$message)
2169    {
2170        if ($var->isRequired() && empty($value['original'])) {
2171            $message = Horde_Form_Translation::t("This field is required.");
2172            return false;
2173        }
2174
2175        if ($value['original'] != $value['confirm']) {
2176            $message = Horde_Form_Translation::t("Email addresses must match.");
2177            return false;
2178        }
2179
2180        $addr_ob = $GLOBALS['injector']->getInstance('Horde_Mail_Rfc822')->parseAddressList($value['original']);
2181
2182        switch (count($addr_ob)) {
2183        case 0:
2184            $message = Horde_Form_Translation::t("You did not enter a valid email address.");
2185            return false;
2186
2187        case 1:
2188            return true;
2189
2190        default:
2191            $message = Horde_Form_Translation::t("Only one email address allowed.");
2192            return false;
2193        }
2194    }
2195
2196    /**
2197     * Return info about field type.
2198     */
2199    function about()
2200    {
2201        return array('name' => Horde_Form_Translation::t("Email with confirmation"));
2202    }
2203
2204}
2205
2206class Horde_Form_Type_password extends Horde_Form_Type {
2207
2208    function isValid(&$var, &$vars, $value, &$message)
2209    {
2210        $valid = true;
2211
2212        if ($var->isRequired()) {
2213            $valid = strlen(trim($value)) > 0;
2214
2215            if (!$valid) {
2216                $message = Horde_Form_Translation::t("This field is required.");
2217            }
2218        }
2219
2220        return $valid;
2221    }
2222
2223    /**
2224     * Return info about field type.
2225     */
2226    function about()
2227    {
2228        return array('name' => Horde_Form_Translation::t("Password"));
2229    }
2230
2231}
2232
2233class Horde_Form_Type_passwordconfirm extends Horde_Form_Type {
2234
2235    function isValid(&$var, &$vars, $value, &$message)
2236    {
2237        if ($var->isRequired() && empty($value['original'])) {
2238            $message = Horde_Form_Translation::t("This field is required.");
2239            return false;
2240        }
2241
2242        if ($value['original'] != $value['confirm']) {
2243            $message = Horde_Form_Translation::t("Passwords must match.");
2244            return false;
2245        }
2246
2247        return true;
2248    }
2249
2250    function getInfo(&$vars, &$var, &$info)
2251    {
2252        $value = $vars->get($var->getVarName());
2253        $info = $value['original'];
2254    }
2255
2256    /**
2257     * Return info about field type.
2258     */
2259    function about()
2260    {
2261        return array('name' => Horde_Form_Translation::t("Password with confirmation"));
2262    }
2263
2264}
2265/**
2266 * Horde_Form_Type for selecting a single value out of a list
2267 * For selecting multiple values, use Horde_Form_Type_multienum
2268 */
2269class Horde_Form_Type_enum extends Horde_Form_Type {
2270
2271    var $_values;
2272    var $_prompt;
2273    /**
2274     * Initialize (kind of constructor)
2275     * @param array $values            A hash map where the key is the internal 'value' to process and the value is the caption presented to the user
2276     * @param string|boolean  $prompt  A null value text to prompt user selecting a value. Use a default if boolean true, else use the supplied string. No prompt on false.
2277     */
2278    function init($values, $prompt = null)
2279    {
2280        $this->setValues($values);
2281
2282        if ($prompt === true) {
2283            $this->_prompt = Horde_Form_Translation::t("-- select --");
2284        } else {
2285            $this->_prompt = $prompt;
2286        }
2287    }
2288
2289    function isValid(&$var, &$vars, $value, &$message)
2290    {
2291        if ($var->isRequired() && $value == '' && !isset($this->_values[$value])) {
2292            $message = Horde_Form_Translation::t("This field is required.");
2293            return false;
2294        }
2295
2296        if (count($this->_values) == 0 || isset($this->_values[$value]) ||
2297            ($this->_prompt && empty($value))) {
2298            return true;
2299        }
2300
2301        $message = Horde_Form_Translation::t("Invalid data submitted.");
2302        return false;
2303    }
2304
2305    function getValues()
2306    {
2307        return $this->_values;
2308    }
2309
2310    function setValues($values)
2311    {
2312        $this->_values = $values;
2313    }
2314
2315    function getPrompt()
2316    {
2317        return $this->_prompt;
2318    }
2319
2320    /**
2321     * Return info about field type.
2322     */
2323    function about()
2324    {
2325        return array(
2326            'name' => Horde_Form_Translation::t("Drop down list"),
2327            'params' => array(
2328                'values' => array('label' => Horde_Form_Translation::t("Values to select from"),
2329                                  'type'  => 'stringarray'),
2330                'prompt' => array('label' => Horde_Form_Translation::t("Prompt text"),
2331                                  'type'  => 'text')));
2332    }
2333
2334}
2335
2336class Horde_Form_Type_mlenum extends Horde_Form_Type {
2337
2338    var $_values;
2339    var $_prompts;
2340
2341    function init(&$values, $prompts = null)
2342    {
2343        $this->_values = &$values;
2344
2345        if ($prompts === true) {
2346            $this->_prompts = array(Horde_Form_Translation::t("-- select --"), Horde_Form_Translation::t("-- select --"));
2347        } elseif (!is_array($prompts)) {
2348            $this->_prompts = array($prompts, $prompts);
2349        } else {
2350            $this->_prompts = $prompts;
2351        }
2352    }
2353
2354    function onSubmit(&$var, &$vars)
2355    {
2356        $varname = $var->getVarName();
2357        $value = $vars->get($varname);
2358
2359        if ($value['1'] != $value['old']) {
2360            $var->form->setSubmitted(false);
2361        }
2362    }
2363
2364    function isValid(&$var, &$vars, $value, &$message)
2365    {
2366        if ($var->isRequired() && (empty($value['1']) || empty($value['2']))) {
2367            $message = Horde_Form_Translation::t("This field is required.");
2368            return false;
2369        }
2370
2371        if (!count($this->_values) || isset($this->_values[$value['1']]) ||
2372            (!empty($this->_prompts) && empty($value['1']))) {
2373            return true;
2374        }
2375
2376        $message = Horde_Form_Translation::t("Invalid data submitted.");
2377        return false;
2378    }
2379
2380    function getValues()
2381    {
2382        return $this->_values;
2383    }
2384
2385    function getPrompts()
2386    {
2387        return $this->_prompts;
2388    }
2389
2390    function getInfo(&$vars, &$var, &$info)
2391    {
2392        $info = $vars->get($var->getVarName());
2393        return $info['2'];
2394    }
2395
2396    /**
2397     * Return info about field type.
2398     */
2399    function about()
2400    {
2401        return array(
2402            'name' => Horde_Form_Translation::t("Multi-level drop down lists"),
2403            'params' => array(
2404                'values' => array('label' => Horde_Form_Translation::t("Values to select from"),
2405                                  'type'  => 'stringarray'),
2406                'prompt' => array('label' => Horde_Form_Translation::t("Prompt text"),
2407                                  'type'  => 'text')));
2408    }
2409
2410}
2411
2412
2413/**
2414 * A Horde_Form_Type_multienum for a multiselect box
2415 * @see Horde_Form_Type_enum
2416 */
2417class Horde_Form_Type_multienum extends Horde_Form_Type_enum {
2418
2419    var $size = 5;
2420
2421    /**
2422     * Initialize (kind of constructor)
2423     * @param array $values  A hash map where the key is the internal 'value' to process and the value is the caption presented to the user
2424     * @param integer $size  The number of rows the multienum should display before scrolling
2425     */
2426    function init($values, $size = null)
2427    {
2428        if (!is_null($size)) {
2429            $this->size = (int)$size;
2430        }
2431
2432        parent::init($values);
2433    }
2434
2435    function isValid(&$var, &$vars, $value, &$message)
2436    {
2437        if (is_array($value)) {
2438            foreach ($value as $val) {
2439                if (!$this->isValid($var, $vars, $val, $message)) {
2440                    return false;
2441                }
2442            }
2443            return true;
2444        }
2445
2446        if (empty($value) && ((string)(int)$value !== $value)) {
2447            if ($var->isRequired()) {
2448                $message = Horde_Form_Translation::t("This field is required.");
2449                return false;
2450            } else {
2451                return true;
2452            }
2453        }
2454
2455        if (count($this->_values) == 0 || isset($this->_values[$value])) {
2456            return true;
2457        }
2458
2459        $message = Horde_Form_Translation::t("Invalid data submitted.");
2460        return false;
2461    }
2462
2463    /**
2464     * Return info about field type.
2465     */
2466    function about()
2467    {
2468        return array(
2469            'name' => Horde_Form_Translation::t("Multiple selection"),
2470            'params' => array(
2471                'values' => array('label' => Horde_Form_Translation::t("Values"),
2472                                  'type'  => 'stringarray'),
2473                'size'   => array('label' => Horde_Form_Translation::t("Size"),
2474                                  'type'  => 'int'))
2475        );
2476    }
2477
2478}
2479
2480class Horde_Form_Type_keyval_multienum extends Horde_Form_Type_multienum {
2481
2482    function getInfo(&$vars, &$var, &$info)
2483    {
2484        $value = $vars->get($var->getVarName());
2485        $info = array();
2486        foreach ($value as $key) {
2487            $info[$key] = $this->_values[$key];
2488        }
2489    }
2490
2491    /**
2492     * Return info about field type.
2493     */
2494    function about()
2495    {
2496        $about = parent::about();
2497        $about['name'] = Horde_Form_Translation::t("Multiple selection, preserving keys");
2498    }
2499
2500}
2501
2502class Horde_Form_Type_radio extends Horde_Form_Type_enum {
2503
2504    /* Entirely implemented by Horde_Form_Type_enum; just a different
2505     * view. */
2506
2507    /**
2508     * Return info about field type.
2509     */
2510    function about()
2511    {
2512        return array(
2513            'name' => Horde_Form_Translation::t("Radio selection"),
2514            'params' => array(
2515                'values' => array('label' => Horde_Form_Translation::t("Values"),
2516                                  'type'  => 'stringarray')));
2517    }
2518
2519}
2520
2521class Horde_Form_Type_set extends Horde_Form_Type {
2522
2523    var $_values;
2524    var $_checkAll = false;
2525
2526    function init($values, $checkAll = false)
2527    {
2528        $this->_values = $values;
2529        $this->_checkAll = $checkAll;
2530    }
2531
2532    function isValid(&$var, &$vars, $value, &$message)
2533    {
2534        if (count($this->_values) == 0 || count($value) == 0) {
2535            return true;
2536        }
2537        foreach ($value as $item) {
2538            if (!isset($this->_values[$item])) {
2539                $error = true;
2540                break;
2541            }
2542        }
2543        if (!isset($error)) {
2544            return true;
2545        }
2546
2547        $message = Horde_Form_Translation::t("Invalid data submitted.");
2548        return false;
2549    }
2550
2551    function getValues()
2552    {
2553        return $this->_values;
2554    }
2555
2556    /**
2557     * Return info about field type.
2558     */
2559    function about()
2560    {
2561        return array(
2562            'name' => Horde_Form_Translation::t("Set"),
2563            'params' => array(
2564                'values' => array('label' => Horde_Form_Translation::t("Values"),
2565                                  'type'  => 'stringarray')));
2566    }
2567
2568}
2569
2570class Horde_Form_Type_date extends Horde_Form_Type {
2571
2572    var $_format;
2573
2574    function init($format = '%a %d %B')
2575    {
2576        $this->_format = $format;
2577    }
2578
2579    function isValid(&$var, &$vars, $value, &$message)
2580    {
2581        $valid = true;
2582
2583        if ($var->isRequired()) {
2584            $valid = strlen(trim($value)) > 0;
2585
2586            if (!$valid) {
2587                $message = sprintf(Horde_Form_Translation::t("%s is required"), $var->getHumanName());
2588            }
2589        }
2590
2591        return $valid;
2592    }
2593
2594    /**
2595     * @static
2596     *
2597     * @param mixed $date  The date to calculate the difference from. Can be
2598     *                     either a timestamp integer value, or an array
2599     *                     with date parts: 'day', 'month', 'year'.
2600     *
2601     * @return string
2602     */
2603    function getAgo($date)
2604    {
2605        if ($date === null) {
2606            return '';
2607        }
2608
2609        try {
2610            $today = new Horde_Date(time());
2611            $date = new Horde_Date($date);
2612            $ago = $date->toDays() - $today->toDays();
2613        } catch (Horde_Date_Exception $e) {
2614            return '';
2615        }
2616
2617        if ($ago < -1) {
2618            return sprintf(Horde_Form_Translation::t(" (%s days ago)"), abs($ago));
2619        } elseif ($ago == -1) {
2620            return Horde_Form_Translation::t(" (yesterday)");
2621        } elseif ($ago == 0) {
2622            return Horde_Form_Translation::t(" (today)");
2623        } elseif ($ago == 1) {
2624            return Horde_Form_Translation::t(" (tomorrow)");
2625        } else {
2626            return sprintf(Horde_Form_Translation::t(" (in %s days)"), $ago);
2627        }
2628    }
2629
2630    function getFormattedTime($timestamp, $format = null, $showago = true)
2631    {
2632        if (empty($format)) {
2633            $format = $this->_format;
2634        }
2635        if (!empty($timestamp)) {
2636            return strftime($format, $timestamp) . ($showago ? Horde_Form_Type_date::getAgo($timestamp) : '');
2637        } else {
2638            return '';
2639        }
2640    }
2641
2642    /**
2643     * Return info about field type.
2644     */
2645    function about()
2646    {
2647        return array('name' => Horde_Form_Translation::t("Date"));
2648    }
2649
2650}
2651
2652class Horde_Form_Type_time extends Horde_Form_Type {
2653
2654    function isValid(&$var, &$vars, $value, &$message)
2655    {
2656        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
2657            $message = Horde_Form_Translation::t("This field is required.");
2658            return false;
2659        }
2660
2661        if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) {
2662            return true;
2663        }
2664
2665        $message = Horde_Form_Translation::t("This field may only contain numbers and the colon.");
2666        return false;
2667    }
2668
2669    /**
2670     * Return info about field type.
2671     */
2672    function about()
2673    {
2674        return array('name' => Horde_Form_Translation::t("Time"));
2675    }
2676
2677}
2678
2679class Horde_Form_Type_hourminutesecond extends Horde_Form_Type {
2680
2681    var $_show_seconds;
2682
2683    function init($show_seconds = false)
2684    {
2685        $this->_show_seconds = $show_seconds;
2686    }
2687
2688    function isValid(&$var, &$vars, $value, &$message)
2689    {
2690        $time = $vars->get($var->getVarName());
2691        if (!$this->_show_seconds && count($time) && !isset($time['second'])) {
2692            $time['second'] = 0;
2693        }
2694
2695        if (!$this->emptyTimeArray($time) && !$this->checktime($time['hour'], $time['minute'], $time['second'])) {
2696            $message = Horde_Form_Translation::t("Please enter a valid time.");
2697            return false;
2698        } elseif ($this->emptyTimeArray($time) && $var->isRequired()) {
2699            $message = Horde_Form_Translation::t("This field is required.");
2700            return false;
2701        }
2702
2703        return true;
2704    }
2705
2706    function checktime($hour, $minute, $second)
2707    {
2708        if (!isset($hour) || $hour == '' || ($hour < 0 || $hour > 23)) {
2709            return false;
2710        }
2711        if (!isset($minute) || $minute == '' || ($minute < 0 || $minute > 60)) {
2712            return false;
2713        }
2714        if (!isset($second) || $second === '' || ($second < 0 || $second > 60)) {
2715            return false;
2716        }
2717
2718        return true;
2719    }
2720
2721    /**
2722     * Return the time supplied as a Horde_Date object.
2723     *
2724     * @param string $time_in  Date in one of the three formats supported by
2725     *                         Horde_Form and Horde_Date (ISO format
2726     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
2727     *                         UNIX epoch).
2728     *
2729     * @return Horde_Date  The time object.
2730     */
2731    function getTimeOb($time_in)
2732    {
2733        if (is_array($time_in)) {
2734            if (!$this->emptyTimeArray($time_in)) {
2735                $time_in = sprintf('1970-01-01 %02d:%02d:%02d', $time_in['hour'], $time_in['minute'], $this->_show_seconds ? $time_in['second'] : 0);
2736            }
2737        }
2738
2739        return new Horde_Date($time_in);
2740    }
2741
2742    /**
2743     * Return the time supplied split up into an array.
2744     *
2745     * @param string $time_in  Time in one of the three formats supported by
2746     *                         Horde_Form and Horde_Date (ISO format
2747     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
2748     *                         UNIX epoch).
2749     *
2750     * @return array  Array with three elements - hour, minute and seconds.
2751     */
2752    function getTimeParts($time_in)
2753    {
2754        if (is_array($time_in)) {
2755            /* This is probably a failed isValid input so just return the
2756             * parts as they are. */
2757            return $time_in;
2758        } elseif (empty($time_in)) {
2759            /* This is just an empty field so return empty parts. */
2760            return array('hour' => '', 'minute' => '', 'second' => '');
2761        }
2762        $time = $this->getTimeOb($time_in);
2763        return array('hour' => $time->hour,
2764                     'minute' => $time->min,
2765                     'second' => $time->sec);
2766    }
2767
2768    function emptyTimeArray($time)
2769    {
2770        return (is_array($time)
2771                && (!isset($time['hour']) || !strlen($time['hour']))
2772                && (!isset($time['minute']) || !strlen($time['minute']))
2773                && (!$this->_show_seconds || !strlen($time['second'])));
2774    }
2775
2776    /**
2777     * Return info about field type.
2778     */
2779    function about()
2780    {
2781        return array(
2782            'name' => Horde_Form_Translation::t("Time selection"),
2783            'params' => array(
2784                'seconds' => array('label' => Horde_Form_Translation::t("Show seconds?"),
2785                                   'type'  => 'boolean')));
2786    }
2787
2788}
2789
2790class Horde_Form_Type_monthyear extends Horde_Form_Type {
2791
2792    var $_start_year;
2793    var $_end_year;
2794
2795    function init($start_year = null, $end_year = null)
2796    {
2797        if (empty($start_year)) {
2798            $start_year = 1920;
2799        }
2800        if (empty($end_year)) {
2801            $end_year = date('Y');
2802        }
2803
2804        $this->_start_year = $start_year;
2805        $this->_end_year = $end_year;
2806    }
2807
2808    function isValid(&$var, &$vars, $value, &$message)
2809    {
2810        if (!$var->isRequired()) {
2811            return true;
2812        }
2813
2814        if (!$vars->get($this->getMonthVar($var)) ||
2815            !$vars->get($this->getYearVar($var))) {
2816            $message = Horde_Form_Translation::t("Please enter a month and a year.");
2817            return false;
2818        }
2819
2820        return true;
2821    }
2822
2823    function getMonthVar($var)
2824    {
2825        return $var->getVarName() . '[month]';
2826    }
2827
2828    function getYearVar($var)
2829    {
2830        return $var->getVarName() . '[year]';
2831    }
2832
2833    /**
2834     * Return info about field type.
2835     */
2836    function about()
2837    {
2838        return array('name' => Horde_Form_Translation::t("Month and year"),
2839                     'params' => array(
2840                         'start_year' => array('label' => Horde_Form_Translation::t("Start year"),
2841                                               'type'  => 'int'),
2842                         'end_year'   => array('label' => Horde_Form_Translation::t("End year"),
2843                                               'type'  => 'int')));
2844    }
2845
2846}
2847
2848class Horde_Form_Type_monthdayyear extends Horde_Form_Type {
2849
2850    var $_start_year;
2851    var $_end_year;
2852    var $_picker;
2853    var $_format_in = null;
2854    var $_format_out = '%x';
2855
2856    /**
2857     * Return the date supplied as a Horde_Date object.
2858     *
2859     * @param integer $start_year  The first available year for input.
2860     * @param integer $end_year    The last available year for input.
2861     * @param boolean $picker      Do we show the DHTML calendar?
2862     * @param integer $format_in   The format to use when sending the date
2863     *                             for storage. Defaults to Unix epoch.
2864     *                             Similar to the strftime() function.
2865     * @param integer $format_out  The format to use when displaying the
2866     *                             date. Similar to the strftime() function.
2867     */
2868    function init($start_year = '', $end_year = '', $picker = true,
2869                  $format_in = null, $format_out = '%x')
2870    {
2871        if (empty($start_year)) {
2872            $start_year = date('Y');
2873        }
2874        if (empty($end_year)) {
2875            $end_year = date('Y') + 10;
2876        }
2877
2878        $this->_start_year = $start_year;
2879        $this->_end_year = $end_year;
2880        $this->_picker = $picker;
2881        $this->_format_in = $format_in;
2882        $this->_format_out = $format_out;
2883    }
2884
2885    function isValid(&$var, &$vars, $value, &$message)
2886    {
2887        $date = $vars->get($var->getVarName());
2888        $empty = $this->emptyDateArray($date);
2889
2890        if ($empty == 1 && $var->isRequired()) {
2891            $message = Horde_Form_Translation::t("This field is required.");
2892            return false;
2893        } elseif ($empty == 0 && !checkdate($date['month'],
2894                                            $date['day'],
2895                                            $date['year'])) {
2896            $message = Horde_Form_Translation::t("Please enter a valid date, check the number of days in the month.");
2897            return false;
2898        } elseif ($empty == -1) {
2899            $message = Horde_Form_Translation::t("Select all date components.");
2900            return false;
2901        }
2902
2903        return true;
2904    }
2905
2906    /**
2907     * Determine if the provided date value is completely empty, partially empty
2908     * or non-empty.
2909     *
2910     * @param mixed $date  String or date part array representation of date.
2911     *
2912     * @return integer  0 for non-empty, 1 for completely empty or -1 for
2913     *                  partially empty.
2914     */
2915    function emptyDateArray($date)
2916    {
2917        if (!is_array($date)) {
2918            return (int)empty($date);
2919        }
2920        $empty = 0;
2921        /* Check each date array component. */
2922        foreach (array('day', 'month', 'year') as $key) {
2923            if (empty($date[$key])) {
2924                $empty++;
2925            }
2926        }
2927
2928        /* Check state of empty. */
2929        if ($empty == 0) {
2930            /* If no empty parts return 0. */
2931            return 0;
2932        } elseif ($empty == 3) {
2933            /* If all empty parts return 1. */
2934            return 1;
2935        } else {
2936            /* If some empty parts return -1. */
2937            return -1;
2938        }
2939    }
2940
2941    /**
2942     * Return the date supplied split up into an array.
2943     *
2944     * @param string $date_in  Date in one of the three formats supported by
2945     *                         Horde_Form and Horde_Date (ISO format
2946     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS
2947     *                         and UNIX epoch) plus the fourth YYYY-MM-DD.
2948     *
2949     * @return array  Array with three elements - year, month and day.
2950     */
2951    function getDateParts($date_in)
2952    {
2953        if (is_array($date_in)) {
2954            /* This is probably a failed isValid input so just return
2955             * the parts as they are. */
2956            return $date_in;
2957        } elseif (empty($date_in)) {
2958            /* This is just an empty field so return empty parts. */
2959            return array('year' => '', 'month' => '', 'day' => '');
2960        }
2961
2962        $date = $this->getDateOb($date_in);
2963        return array('year' => $date->year,
2964                     'month' => $date->month,
2965                     'day' => $date->mday);
2966    }
2967
2968    /**
2969     * Return the date supplied as a Horde_Date object.
2970     *
2971     * @param string $date_in  Date in one of the three formats supported by
2972     *                         Horde_Form and Horde_Date (ISO format
2973     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS
2974     *                         and UNIX epoch) plus the fourth YYYY-MM-DD.
2975     *
2976     * @return Horde_Date  The date object.
2977     */
2978    function getDateOb($date_in)
2979    {
2980        if (is_array($date_in)) {
2981            /* If passed an array change it to the ISO format. */
2982            if ($this->emptyDateArray($date_in) == 0) {
2983                $date_in = sprintf('%04d-%02d-%02d 00:00:00',
2984                                   $date_in['year'],
2985                                   $date_in['month'],
2986                                   $date_in['day']);
2987            }
2988        } elseif (preg_match('/^\d{4}-?\d{2}-?\d{2}$/', $date_in)) {
2989            /* Fix the date if it is the shortened ISO. */
2990            $date_in = $date_in . ' 00:00:00';
2991        }
2992
2993        return new Horde_Date($date_in);
2994    }
2995
2996    /**
2997     * Return the date supplied as a Horde_Date object.
2998     *
2999     * @param string $date  Either an already set up Horde_Date object or a
3000     *                      string date in one of the three formats supported
3001     *                      by Horde_Form and Horde_Date (ISO format
3002     *                      YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
3003     *                      UNIX epoch) plus the fourth YYYY-MM-DD.
3004     *
3005     * @return string  The date formatted according to the $format_out
3006     *                 parameter when setting up the monthdayyear field.
3007     */
3008    function formatDate($date)
3009    {
3010        if (!($date instanceof Horde_Date)) {
3011            $date = $this->getDateOb($date);
3012        }
3013
3014        return $date->strftime($this->_format_out);
3015    }
3016
3017    /**
3018     * Insert the date input through the form into $info array, in the format
3019     * specified by the $format_in parameter when setting up monthdayyear
3020     * field.
3021     */
3022    function getInfo(&$vars, &$var, &$info)
3023    {
3024        $info = $this->_validateAndFormat($var->getValue($vars), $var);
3025    }
3026
3027    /**
3028     * Validate/format a date submission.
3029     */
3030    function _validateAndFormat($value, &$var)
3031    {
3032        /* If any component is empty consider it a bad date and return the
3033         * default. */
3034        if ($this->emptyDateArray($value) == 1) {
3035            $value = $var->getDefault();
3036        }
3037
3038        // If any component is empty consider it a bad date and return null
3039        if ($this->emptyDateArray($value) != 0) {
3040            return null;
3041        } else {
3042            $date = $this->getDateOb($value);
3043            if (!strlen($this->_format_in)) {
3044                return $date->timestamp();
3045            } else {
3046                return $date->strftime($this->_format_in);
3047            }
3048        }
3049    }
3050
3051    /**
3052     * Return info about field type.
3053     */
3054    function about()
3055    {
3056        return array(
3057            'name' => Horde_Form_Translation::t("Date selection"),
3058            'params' => array(
3059                'start_year' => array('label' => Horde_Form_Translation::t("Start year"),
3060                                      'type'  => 'int'),
3061                'end_year'   => array('label' => Horde_Form_Translation::t("End year"),
3062                                      'type'  => 'int'),
3063                'picker'     => array('label' => Horde_Form_Translation::t("Show picker?"),
3064                                      'type'  => 'boolean'),
3065                'format_in'  => array('label' => Horde_Form_Translation::t("Storage format"),
3066                                      'type'  => 'text'),
3067                'format_out' => array('label' => Horde_Form_Translation::t("Display format"),
3068                                      'type'  => 'text')));
3069    }
3070
3071}
3072
3073class Horde_Form_Type_datetime extends Horde_Form_Type {
3074
3075    var $_mdy;
3076    var $_hms;
3077    var $_show_seconds;
3078
3079    /**
3080     * Return the date supplied as a Horde_Date object.
3081     *
3082     * @param integer $start_year  The first available year for input.
3083     * @param integer $end_year    The last available year for input.
3084     * @param boolean $picker      Do we show the DHTML calendar?
3085     * @param integer $format_in   The format to use when sending the date
3086     *                             for storage. Defaults to Unix epoch.
3087     *                             Similar to the strftime() function.
3088     * @param integer $format_out  The format to use when displaying the
3089     *                             date. Similar to the strftime() function.
3090     * @param boolean $show_seconds Include a form input for seconds.
3091     */
3092    function init($start_year = '', $end_year = '', $picker = true,
3093                  $format_in = null, $format_out = '%x', $show_seconds = false)
3094    {
3095        $this->_mdy = new Horde_Form_Type_monthdayyear();
3096        $this->_mdy->init($start_year, $end_year, $picker, $format_in, $format_out);
3097
3098        $this->_hms = new Horde_Form_Type_hourminutesecond();
3099        $this->_hms->init($show_seconds);
3100        $this->_show_seconds = $show_seconds;
3101    }
3102
3103    function isValid(&$var, &$vars, $value, &$message)
3104    {
3105        $date = $vars->get($var->getVarName());
3106        if (!$this->_show_seconds && !isset($date['second'])) {
3107            $date['second'] = '';
3108        }
3109        $mdy_empty = $this->emptyDateArray($date);
3110        $hms_empty = $this->emptyTimeArray($date);
3111
3112        $valid = true;
3113
3114        /* Require all fields if one field is not empty */
3115        if ($var->isRequired() || $mdy_empty != 1 || !$hms_empty) {
3116            $old_required = $var->required;
3117            $var->required = true;
3118
3119            $mdy_valid = $this->_mdy->isValid($var, $vars, $value, $message);
3120            $hms_valid = $this->_hms->isValid($var, $vars, $value, $message);
3121            $var->required = $old_required;
3122
3123            $valid = $mdy_valid && $hms_valid;
3124            if ($mdy_valid && !$hms_valid) {
3125                $message = Horde_Form_Translation::t("You must choose a time.");
3126            } elseif ($hms_valid && !$mdy_valid) {
3127                $message = Horde_Form_Translation::t("You must choose a date.");
3128            }
3129        }
3130
3131        return $valid;
3132    }
3133
3134    function getInfo(&$vars, &$var, &$info)
3135    {
3136        /* If any component is empty consider it a bad date and return the
3137         * default. */
3138        $value = $var->getValue($vars);
3139        if ($this->emptyDateArray($value) == 1 || $this->emptyTimeArray($value)) {
3140            $this->_getInfo($var->getDefault(), $info);
3141            return;
3142        }
3143
3144        $this->_getInfo($value, $info);
3145    }
3146
3147    function _getInfo($value, &$info)
3148    {
3149        // If any component is empty consider it a bad date and return null
3150        if ($this->emptyDateArray($value) != 0 || $this->emptyTimeArray($value)) {
3151            $info = null;
3152            return;
3153        }
3154
3155        $date = $this->getDateOb($value);
3156        $time = $this->getTimeOb($value);
3157        $date->hour = $time->hour;
3158        $date->min = $time->min;
3159        $date->sec = $time->sec;
3160        if ($this->getProperty('format_in') === null) {
3161            $info = $date->timestamp();
3162        } else {
3163            $info = $date->strftime($this->getProperty('format_in'));
3164        }
3165    }
3166
3167    function getProperty($property)
3168    {
3169        if ($property == 'show_seconds') {
3170            return $this->_hms->getProperty($property);
3171        } else {
3172            return $this->_mdy->getProperty($property);
3173        }
3174    }
3175
3176    function setProperty($property, $value)
3177    {
3178        if ($property == 'show_seconds') {
3179            $this->_hms->setProperty($property, $value);
3180        } else {
3181            $this->_mdy->setProperty($property, $value);
3182        }
3183    }
3184
3185    function checktime($hour, $minute, $second)
3186    {
3187        return $this->_hms->checktime($hour, $minute, $second);
3188    }
3189
3190    function getTimeOb($time_in)
3191    {
3192        return $this->_hms->getTimeOb($time_in);
3193    }
3194
3195    function getTimeParts($time_in)
3196    {
3197        return $this->_hms->getTimeParts($time_in);
3198    }
3199
3200    function emptyTimeArray($time)
3201    {
3202        return $this->_hms->emptyTimeArray($time);
3203    }
3204
3205    function emptyDateArray($date)
3206    {
3207        return $this->_mdy->emptyDateArray($date);
3208    }
3209
3210    function getDateParts($date_in)
3211    {
3212        return $this->_mdy->getDateParts($date_in);
3213    }
3214
3215    function getDateOb($date_in)
3216    {
3217        return $this->_mdy->getDateOb($date_in);
3218    }
3219
3220    function formatDate($date)
3221    {
3222        if ($this->_mdy->emptyDateArray($date)) {
3223            return '';
3224        }
3225        return $this->_mdy->formatDate($date);
3226    }
3227
3228    function about()
3229    {
3230        return array(
3231            'name' => Horde_Form_Translation::t("Date and time selection"),
3232            'params' => array(
3233                'start_year' => array('label' => Horde_Form_Translation::t("Start year"),
3234                                      'type'  => 'int'),
3235                'end_year'   => array('label' => Horde_Form_Translation::t("End year"),
3236                                      'type'  => 'int'),
3237                'picker'     => array('label' => Horde_Form_Translation::t("Show picker?"),
3238                                      'type'  => 'boolean'),
3239                'format_in'  => array('label' => Horde_Form_Translation::t("Storage format"),
3240                                      'type'  => 'text'),
3241                'format_out' => array('label' => Horde_Form_Translation::t("Display format"),
3242                                      'type'  => 'text'),
3243                'seconds'    => array('label' => Horde_Form_Translation::t("Show seconds?"),
3244                                      'type'  => 'boolean')));
3245    }
3246
3247}
3248
3249class Horde_Form_Type_colorpicker extends Horde_Form_Type {
3250
3251    function isValid(&$var, &$vars, $value, &$message)
3252    {
3253        if ($var->isRequired() && empty($value)) {
3254            $message = Horde_Form_Translation::t("This field is required.");
3255            return false;
3256        }
3257
3258        if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) {
3259            return true;
3260        }
3261
3262        $message = Horde_Form_Translation::t("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
3263        return false;
3264    }
3265
3266    /**
3267     * Return info about field type.
3268     */
3269    function about()
3270    {
3271        return array('name' => Horde_Form_Translation::t("Colour selection"));
3272    }
3273
3274}
3275
3276class Horde_Form_Type_sound extends Horde_Form_Type {
3277
3278    var $_sounds = array();
3279
3280    function init()
3281    {
3282        $this->_sounds = array_keys(Horde_Themes::soundList());
3283    }
3284
3285    function getSounds()
3286    {
3287        return $this->_sounds;
3288    }
3289
3290    function isValid(&$var, &$vars, $value, &$message)
3291    {
3292        if ($var->isRequired() && empty($value)) {
3293            $message = Horde_Form_Translation::t("This field is required.");
3294            return false;
3295        }
3296
3297        if (empty($value) || in_array($value, $this->_sounds)) {
3298            return true;
3299        }
3300
3301        $message = Horde_Form_Translation::t("Please choose a sound.");
3302        return false;
3303    }
3304
3305    /**
3306     * Return info about field type.
3307     */
3308    function about()
3309    {
3310        return array('name' => Horde_Form_Translation::t("Sound selection"));
3311    }
3312
3313}
3314
3315class Horde_Form_Type_sorter extends Horde_Form_Type {
3316
3317    var $_instance;
3318    var $_values;
3319    var $_size;
3320    var $_header;
3321
3322    function init($values, $size = 8, $header = '')
3323    {
3324        static $horde_sorter_instance = 0;
3325
3326        /* Get the next progressive instance count for the horde
3327         * sorter so that multiple sorters can be used on one page. */
3328        $horde_sorter_instance++;
3329        $this->_instance = 'horde_sorter_' . $horde_sorter_instance;
3330        $this->_values = $values;
3331        $this->_size   = $size;
3332        $this->_header = $header;
3333    }
3334
3335    function isValid(&$var, &$vars, $value, &$message)
3336    {
3337        return true;
3338    }
3339
3340    function getValues()
3341    {
3342        return $this->_values;
3343    }
3344
3345    function getSize()
3346    {
3347        return $this->_size;
3348    }
3349
3350    function getHeader()
3351    {
3352        if (!empty($this->_header)) {
3353            return $this->_header;
3354        }
3355        return '';
3356    }
3357
3358    function getOptions($keys = null)
3359    {
3360        $html = '';
3361        if ($this->_header) {
3362            $html .= '<option value="">' . htmlspecialchars($this->_header) . '</option>';
3363        }
3364
3365        if (empty($keys)) {
3366            $keys = array_keys($this->_values);
3367        } else {
3368            $keys = explode("\t", $keys['array']);
3369        }
3370        foreach ($keys as $sl_key) {
3371            $html .= '<option value="' . $sl_key . '">' . htmlspecialchars($this->_values[$sl_key]) . '</option>';
3372        }
3373
3374        return $html;
3375    }
3376
3377    function getInfo(&$vars, &$var, &$info)
3378    {
3379        $value = $vars->get($var->getVarName());
3380        $info = explode("\t", $value['array']);
3381    }
3382
3383    /**
3384     * Return info about field type.
3385     */
3386    function about()
3387    {
3388        return array(
3389            'name' => Horde_Form_Translation::t("Sort order selection"),
3390            'params' => array(
3391                'values' => array('label' => Horde_Form_Translation::t("Values"),
3392                                  'type'  => 'stringarray'),
3393                'size'   => array('label' => Horde_Form_Translation::t("Size"),
3394                                  'type'  => 'int'),
3395                'header' => array('label' => Horde_Form_Translation::t("Header"),
3396                                  'type'  => 'text')));
3397    }
3398
3399}
3400
3401class Horde_Form_Type_selectfiles extends Horde_Form_Type {
3402
3403    /**
3404     * The text to use in the link.
3405     *
3406     * @var string
3407     */
3408    var $_link_text;
3409
3410    /**
3411     * The style to use for the link.
3412     *
3413     * @var string
3414     */
3415    var $_link_style;
3416
3417    /**
3418     *  Create the link with an icon instead of text?
3419     *
3420     * @var boolean
3421     */
3422    var $_icon;
3423
3424    /**
3425     * Contains gollem selectfile selectionID
3426     *
3427     * @var string
3428     */
3429    var $_selectid;
3430
3431    function init($selectid, $link_text = null, $link_style = '',
3432                  $icon = false)
3433    {
3434        $this->_selectid = $selectid;
3435        if (is_null($link_text)) {
3436            $link_text = Horde_Form_Translation::t("Select Files");
3437        }
3438        $this->_link_text = $link_text;
3439        $this->_link_style = $link_style;
3440        $this->_icon = $icon;
3441    }
3442
3443    function isValid(&$var, &$vars, $value, &$message)
3444    {
3445        return true;
3446    }
3447
3448    function getInfo(&$var, &$vars, &$info)
3449    {
3450        $value = $vars->getValue($var);
3451        $info = $GLOBALS['registry']->call('files/selectlistResults', array($value));
3452    }
3453
3454    function about()
3455    {
3456        return array(
3457            'name' => Horde_Form_Translation::t("File selection"),
3458            'params' => array(
3459                'selectid'   => array('label' => Horde_Form_Translation::t("Id"),
3460                                      'type' => 'text'),
3461                'link_text'  => array('label' => Horde_Form_Translation::t("Link text"),
3462                                      'type' => 'text'),
3463                'link_style' => array('label' => Horde_Form_Translation::t("Link style"),
3464                                      'type' => 'text'),
3465                'icon'       => array('label' => Horde_Form_Translation::t("Show icon?"),
3466                                      'type' => 'boolean')));
3467    }
3468
3469}
3470
3471class Horde_Form_Type_assign extends Horde_Form_Type {
3472
3473    var $_leftValues;
3474    var $_rightValues;
3475    var $_leftHeader;
3476    var $_rightHeader;
3477    var $_size;
3478    var $_width;
3479
3480    function init($leftValues, $rightValues, $leftHeader = '',
3481                  $rightHeader = '', $size = 8, $width = '200px')
3482    {
3483        $this->_leftValues = $leftValues;
3484        $this->_rightValues = $rightValues;
3485        $this->_leftHeader = $leftHeader;
3486        $this->_rightHeader = $rightHeader;
3487        $this->_size = $size;
3488        $this->_width = $width;
3489    }
3490
3491    function isValid(&$var, &$vars, $value, &$message)
3492    {
3493        return true;
3494    }
3495
3496    function getValues($side)
3497    {
3498        return $side ? $this->_rightValues : $this->_leftValues;
3499    }
3500
3501    function setValues($side, $values)
3502    {
3503        if ($side) {
3504            $this->_rightValues = $values;
3505        } else {
3506            $this->_leftValues = $values;
3507        }
3508    }
3509
3510    function getHeader($side)
3511    {
3512        return $side ? $this->_rightHeader : $this->_leftHeader;
3513    }
3514
3515    function getSize()
3516    {
3517        return $this->_size;
3518    }
3519
3520    function getWidth()
3521    {
3522        return $this->_width;
3523    }
3524
3525    function getOptions($side, $formname, $varname)
3526    {
3527        $html = '';
3528        $headers = false;
3529        if ($side) {
3530            $values = $this->_rightValues;
3531            if (!empty($this->_rightHeader)) {
3532                $values = array('' => $this->_rightHeader) + $values;
3533                $headers = true;
3534            }
3535        } else {
3536            $values = $this->_leftValues;
3537            if (!empty($this->_leftHeader)) {
3538                $values = array('' => $this->_leftHeader) + $values;
3539                $headers = true;
3540            }
3541        }
3542
3543        foreach ($values as $key => $val) {
3544            $html .= '<option value="' . htmlspecialchars($key) . '"';
3545            if ($headers) {
3546                $headers = false;
3547            } else {
3548                $html .= ' ondblclick="Horde_Form_Assign.move(\'' . $formname . '\', \'' . $varname . '\', ' . (int)$side . ');"';
3549            }
3550            $html .= '>' . htmlspecialchars($val) . '</option>';
3551        }
3552
3553        return $html;
3554    }
3555
3556    function getInfo(&$vars, &$var, &$info)
3557    {
3558        $value = $vars->get($var->getVarName() . '__values');
3559        if (strpos($value, "\t\t") === false) {
3560            $left = $value;
3561            $right = '';
3562        } else {
3563            list($left, $right) = explode("\t\t", $value);
3564        }
3565        if (empty($left)) {
3566            $info['left'] = array();
3567        } else {
3568            $info['left'] = explode("\t", $left);
3569        }
3570        if (empty($right)) {
3571            $info['right'] = array();
3572        } else {
3573            $info['right'] = explode("\t", $right);
3574        }
3575    }
3576
3577    /**
3578     * Return info about field type.
3579     */
3580    function about()
3581    {
3582        return array(
3583            'name' => Horde_Form_Translation::t("Assignment columns"),
3584            'params' => array(
3585                'leftValues'  => array('label' => Horde_Form_Translation::t("Left values"),
3586                                       'type'  => 'stringarray'),
3587                'rightValues' => array('label' => Horde_Form_Translation::t("Right values"),
3588                                       'type'  => 'stringarray'),
3589                'leftHeader'  => array('label' => Horde_Form_Translation::t("Left header"),
3590                                       'type'  => 'text'),
3591                'rightHeader' => array('label' => Horde_Form_Translation::t("Right header"),
3592                                       'type'  => 'text'),
3593                'size'        => array('label' => Horde_Form_Translation::t("Size"),
3594                                       'type'  => 'int'),
3595                'width'       => array('label' => Horde_Form_Translation::t("Width in CSS units"),
3596                                       'type'  => 'text')));
3597    }
3598
3599}
3600
3601class Horde_Form_Type_creditcard extends Horde_Form_Type {
3602
3603    function isValid(&$var, &$vars, $value, &$message)
3604    {
3605        if (empty($value) && $var->isRequired()) {
3606            $message = Horde_Form_Translation::t("This field is required.");
3607            return false;
3608        }
3609
3610        if (!empty($value)) {
3611            /* getCardType() will also verify the checksum. */
3612            $type = $this->getCardType($value);
3613            if ($type === false || $type == 'unknown') {
3614                $message = Horde_Form_Translation::t("This does not seem to be a valid card number.");
3615                return false;
3616            }
3617        }
3618
3619        return true;
3620    }
3621
3622    function getChecksum($ccnum)
3623    {
3624        $len = strlen($ccnum);
3625        if (!is_long($len / 2)) {
3626            $weight = 2;
3627            $digit = $ccnum[0];
3628        } elseif (is_long($len / 2)) {
3629            $weight = 1;
3630            $digit = $ccnum[0] * 2;
3631        }
3632        if ($digit > 9) {
3633            $digit = $digit - 9;
3634        }
3635        $i = 1;
3636        $checksum = $digit;
3637        while ($i < $len) {
3638            if ($ccnum[$i] != ' ') {
3639                $digit = $ccnum[$i] * $weight;
3640                $weight = ($weight == 1) ? 2 : 1;
3641                if ($digit > 9) {
3642                    $digit = $digit - 9;
3643                }
3644                $checksum += $digit;
3645            }
3646            $i++;
3647        }
3648
3649        return $checksum;
3650    }
3651
3652    function getCardType($ccnum)
3653    {
3654        $sum = $this->getChecksum($ccnum);
3655        $l = strlen($ccnum);
3656
3657        // Screen checksum.
3658        if (($sum % 10) != 0) {
3659            return false;
3660        }
3661
3662        // Check for Visa.
3663        if ((($l == 16) || ($l == 13)) &&
3664            ($ccnum[0] == 4)) {
3665            return 'visa';
3666        }
3667
3668        // Check for MasterCard.
3669        if (($l == 16) &&
3670            ($ccnum[0] == 5) &&
3671            ($ccnum[1] >= 1) &&
3672            ($ccnum[1] <= 5)) {
3673            return 'mastercard';
3674        }
3675
3676        // Check for Amex.
3677        if (($l == 15) &&
3678            ($ccnum[0] == 3) &&
3679            (($ccnum[1] == 4) || ($ccnum[1] == 7))) {
3680            return 'amex';
3681        }
3682
3683        // Check for Discover (Novus).
3684        if (strlen($ccnum) == 16 &&
3685            substr($ccnum, 0, 4) == '6011') {
3686            return 'discover';
3687        }
3688
3689        // If we got this far, then no card matched.
3690        return 'unknown';
3691    }
3692
3693    /**
3694     * Return info about field type.
3695     */
3696    function about()
3697    {
3698        return array('name' => Horde_Form_Translation::t("Credit card number"));
3699    }
3700
3701}
3702
3703class Horde_Form_Type_obrowser extends Horde_Form_Type {
3704
3705    function isValid(&$var, &$vars, $value, &$message)
3706    {
3707        return true;
3708    }
3709
3710    /**
3711     * Return info about field type.
3712     */
3713    function about()
3714    {
3715        return array('name' => Horde_Form_Translation::t("Relationship browser"));
3716    }
3717
3718}
3719
3720class Horde_Form_Type_dblookup extends Horde_Form_Type_enum {
3721
3722    function init($db, $sql, $prompt = null)
3723    {
3724        $values = array();
3725        try {
3726            $col = $db->selectValues($sql);
3727            $values = array_combine($col, $col);
3728        } catch (Horde_Db_Exception $e) {
3729        }
3730        parent::init($values, $prompt);
3731    }
3732
3733    /**
3734     * Return info about field type.
3735     */
3736    function about()
3737    {
3738        return array(
3739            'name' => Horde_Form_Translation::t("Database lookup"),
3740            'params' => array(
3741                'dsn' => array('label' => Horde_Form_Translation::t("DSN (see http://pear.php.net/manual/en/package.database.db.intro-dsn.php)"),
3742                               'type'  => 'text'),
3743                'sql' => array('label' => Horde_Form_Translation::t("SQL statement for value lookups"),
3744                               'type'  => 'text'),
3745                'prompt' => array('label' => Horde_Form_Translation::t("Prompt text"),
3746                                  'type'  => 'text'))
3747            );
3748    }
3749
3750}
3751
3752class Horde_Form_Type_figlet extends Horde_Form_Type {
3753
3754    var $_text;
3755    var $_font;
3756
3757    function init($text, $font)
3758    {
3759        $this->_text = $text;
3760        $this->_font = $font;
3761    }
3762
3763    function isValid(&$var, &$vars, $value, &$message)
3764    {
3765        if (empty($value) && $var->isRequired()) {
3766            $message = Horde_Form_Translation::t("This field is required.");
3767            return false;
3768        }
3769
3770        if (Horde_String::lower($value) != Horde_String::lower($this->_text)) {
3771            $message = Horde_Form_Translation::t("The text you entered did not match the text on the screen.");
3772            return false;
3773        }
3774
3775        return true;
3776    }
3777
3778    function getFont()
3779    {
3780        return $this->_font;
3781    }
3782
3783    function getText()
3784    {
3785        return $this->_text;
3786    }
3787
3788    /**
3789     * Return info about field type.
3790     */
3791    function about()
3792    {
3793        return array(
3794            'name' => Horde_Form_Translation::t("Figlet CAPTCHA"),
3795            'params' => array(
3796                'text' => array('label' => Horde_Form_Translation::t("Text"),
3797                                'type'  => 'text'),
3798                'font' => array('label' => Horde_Form_Translation::t("Figlet font"),
3799                                'type'  => 'text'))
3800            );
3801    }
3802
3803}
3804
3805class Horde_Form_Type_captcha extends Horde_Form_Type_figlet {
3806
3807    /**
3808     * Return info about field type.
3809     */
3810    function about()
3811    {
3812        return array(
3813            'name' => Horde_Form_Translation::t("Image CAPTCHA"),
3814            'params' => array(
3815                'text' => array('label' => Horde_Form_Translation::t("Text"),
3816                                'type'  => 'text'),
3817                'font' => array('label' => Horde_Form_Translation::t("Font"),
3818                                'type'  => 'text'))
3819            );
3820    }
3821
3822}
3823
3824class Horde_Form_Type_category extends Horde_Form_Type {
3825
3826    function getInfo(&$vars, &$var, &$info)
3827    {
3828        $info = $var->getValue($vars);
3829        if ($info == '*new*') {
3830            $info = array('new' => true,
3831                          'value' => $vars->get('new_category'));
3832        } else {
3833            $info = array('new' => false,
3834                          'value' => $info);
3835        }
3836    }
3837
3838    /**
3839     * Return info about field type.
3840     */
3841    function about()
3842    {
3843        return array('name' => Horde_Form_Translation::t("Category"));
3844    }
3845
3846    function isValid(&$var, &$vars, $value, &$message)
3847    {
3848        if (empty($value) && $var->isRequired()) {
3849            $message = Horde_Form_Translation::t("This field is required.");
3850            return false;
3851        }
3852
3853        return true;
3854    }
3855
3856}
3857
3858class Horde_Form_Type_invalid extends Horde_Form_Type {
3859
3860    var $message;
3861
3862    function init($message)
3863    {
3864        $this->message = $message;
3865    }
3866
3867    function isValid(&$var, &$vars, $value, &$message)
3868    {
3869        return false;
3870    }
3871
3872}
3873