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