1<?php 2/* 3 * e107 website system 4 * 5 * Copyright (C) 2008-2009 e107 Inc (e107.org) 6 * Released under the terms and conditions of the 7 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 8 * 9 * Handler - general purpose validation functions 10 * 11 * $Source: /cvs_backup/e107_0.8/e107_handlers/validator_class.php,v $ 12 * $Revision$ 13 * $Date$ 14 * $Author$ 15 * 16*/ 17 18if (!defined('e107_INIT')) { exit; } 19 20// List of error numbers which may be returned from validation 21define('ERR_MISSING_VALUE','01'); 22define('ERR_UNEXPECTED_VALUE','02'); 23define('ERR_INVALID_CHARS', '03'); 24define('ERR_TOO_SHORT', '04'); 25define('ERR_TOO_LONG', '05'); 26define('ERR_DUPLICATE', '06'); 27define('ERR_DISALLOWED_TEXT', '07'); 28define('ERR_DISALLOWED_TEXT_EXACT_MATCH', '23'); 29define('ERR_FIELD_DISABLED', '08'); 30define('ERR_INVALID_WORD', '09'); 31define('ERR_PASSWORDS_DIFFERENT', '10'); 32define('ERR_BANNED_EMAIL', '11'); 33define('ERR_INVALID_EMAIL', '12'); 34define('ERR_ARRAY_EXPECTED', '13'); 35define('ERR_BANNED_USER', '14'); 36define('ERR_FIELDS_DIFFERENT', '15'); 37define('ERR_CODE_ERROR', '16'); 38define('ERR_TOO_LOW', '17'); 39define('ERR_TOO_HIGH', '18'); 40define('ERR_GENERIC', '19'); // This requires coder-defined error text 41define('ERR_IMAGE_TOO_WIDE', '20'); 42define('ERR_IMAGE_TOO_HIGH', '21'); 43 44// Default error messages 45e107::includeLan(e_LANGUAGEDIR.e_LANGUAGE.'/admin/lan_validator.php'); 46 47/** 48 * Validator class - used by e_model and its child classes 49 * 50 * @package e107 51 * @category e107_handlers 52 * @version 1.0 53 * @author SecretR 54 * @copyright Copyright (c) 2009, e107 Inc. 55 */ 56class e_validator 57{ 58 /** 59 * @var integer Unknown error code 60 */ 61 const ERR_UNKNOWN = 0; 62 63 /** 64 * @var integer Value not found error code 65 */ 66 const ERR_MISSING_VALUE = 101; 67 68 /** 69 * @var integer Unexpected value type error code (bad rule) 70 */ 71 const ERR_UNEXPECTED_VALUE = 102; 72 73 /** 74 * @var integer Invalid characters error code 75 */ 76 const ERR_INVALID_CHARS = 103; 77 78 /** 79 * @var integer Invalid email error code 80 */ 81 const ERR_INVALID_EMAIL = 104; 82 83 /** 84 * @var integer Field doesn't match error code 85 */ 86 const ERR_FIELDS_MATCH = 105; 87 88 /** 89 * @var integer String too short error code 90 */ 91 const ERR_TOO_SHORT = 131; 92 93 /** 94 * @var integer String too long error code 95 */ 96 const ERR_TOO_LONG = 132; 97 98 /** 99 * @var integer Number too low error code 100 */ 101 const ERR_TOO_LOW = 133; 102 103 /** 104 * @var integer Number too high error code 105 */ 106 const ERR_TOO_HIGH = 134; 107 108 /** 109 * @var integer Array count too low error code 110 */ 111 const ERR_ARRCOUNT_LOW = 135; 112 113 /** 114 * @var integer Array count high error code 115 */ 116 const ERR_ARRCOUNT_HIGH = 136; 117 118 /** 119 * @var integer Type of integer expected error code 120 */ 121 const ERR_INT_EXPECTED = 151; 122 123 /** 124 * @var integer Type of float expected error code 125 */ 126 const ERR_FLOAT_EXPECTED = 152; 127 128 /** 129 * @var integer Instance type expected error code 130 */ 131 const ERR_INSTANCEOF_EXPECTED = 153; 132 133 /** 134 * @var integer Array type expected error code 135 */ 136 const ERR_ARRAY_EXPECTED = 154; 137 138 /** 139 * @var integer Generic (empty value) error code 140 */ 141 const ERR_GENERIC = 191; 142 143 /** 144 * @var integer File not exists or not a file error code 145 */ 146 const ERR_NOT_FILE = 201; 147 148 /** 149 * @var integer File not writable error code 150 */ 151 const ERR_WRITABLE_FILE = 202; 152 153 /** 154 * @var integer File exceeds allowed file size error code 155 */ 156 const ERR_SIZEMIN_FILE = 203; 157 158 /** 159 * @var integer File lower than minimal file size error code 160 */ 161 const ERR_SIZEMAX_FILE = 204; 162 163 /** 164 * Required rules - Used by validate method 165 * 166 * Structure: array(type, condition, field title LAN[, condition help, validation error message]); 167 * 168 * @example $_required_rules['download_category_id'] = array('int', '1', 'Download Category', 'choose category') 169 * 170 * Validation array structure: 171 * - type | condition = 172 * - regex | regex string 173 * - email | no condition required 174 * - int/integer | number range in format 'min:max' 175 * - float | number range in format 'min:max' 176 * - str/string | number string length range in format 'min:max' 177 * - required | no condition required 178 * - callback | string function name or array(class name|object, method) (static call) 179 * - instanceof | string class name 180 * - enum | (string) values separated by '#' e.g. 'match1#match2#match3' 181 * - array | array count range in format 'min:max' 182 * - compare | string field_name, value should be in format field_name => array('value1', 'value1') 183 * if value1 === value1, field_name => value1 will be added to $_valid_data array 184 * - field title LAN = 185 * human readable field (data key) name 186 * - [optional] condition help = 187 * can be used for both inline field help and validation error message 188 * - [optional] validation error message = 189 * if empty condition help will be used 190 * 191 * @var array 192 */ 193 protected $_required_rules = array(); 194 195 /** 196 * Check data only if exist/non-empty 197 * 198 * @var array 199 */ 200 protected $_optional_rules = array(); 201 202 /** 203 * Contains validation error codes in format 'field=>error_code' 204 * @var array 205 */ 206 protected $_validation_results = array(); 207 208 /** 209 * Stores validated data (only after successful {@link validateField()} call 210 * @var array 211 */ 212 protected $_valid_data = array(); 213 214 /** 215 * Stores validate check result 216 * @var boolean 217 */ 218 protected $_is_valid_data = true; 219 220 /** 221 * eMessage handler namespace 222 * 223 * @var string 224 */ 225 protected $_message_stack = 'validator'; 226 227 /** 228 * Constructore 229 * @param string [optional] $message_stack [optional] eMessage handler namespace 230 * @param array [optional] $rules validation rules 231 * @param array [optional] $optrules optional validation rules 232 */ 233 public function __construct($message_stack = '', $rules = array(), $optrules = array()) 234 { 235 $this->setMessageStack($message_stack) 236 ->setRules($rules) 237 ->setOptionalRules($optrules); 238 } 239 240 /** 241 * Set message stack 242 * 243 * @param string $mstack 244 * @return e_validator 245 */ 246 public function setMessageStack($mstack) 247 { 248 if(!$mstack) $mstack = 'validator'; 249 $this->_message_stack = $mstack; 250 return $this; 251 } 252 253 /** 254 * @param array $rules 255 * @return e_validator 256 */ 257 public function setRules($rules) 258 { 259 $this->_required_rules = $rules; 260 return $this; 261 } 262 263 /** 264 * @param array $rules 265 * @return e_validator 266 */ 267 public function setOptionalRules($rules) 268 { 269 $this->_optional_rules = $rules; 270 return $this; 271 } 272 273 /** 274 * Add successfully validated data to the valid array 275 * 276 * @param string $field_name 277 * @param mixed $value 278 * @return e_validator 279 */ 280 protected function addValidData($field_name, $value) 281 { 282 $this->_valid_data[$field_name] = $value; 283 return $this; 284 } 285 286 /** 287 * @return array 288 */ 289 public function getValidData() 290 { 291 return $this->_valid_data; 292 } 293 294 /** 295 * Validate data 296 * 297 * @param array $data 298 * @param boolean $availableOnly check only against available data if true 299 * @return boolean 300 */ 301 function validate($data, $availableOnly = false) 302 { 303 $this->reset(); 304 305 $rules = array_merge(array_keys($this->_required_rules), array_keys($this->_optional_rules)); 306 // no rules, no check 307 if(!$rules) 308 { 309 $this->setIsValidData(true); 310 $this->_valid_data = $data; 311 return true; 312 } 313 314 $fieldList = $rules; 315 if($availableOnly) $fieldList = array_keys($data); 316 317 foreach ($rules as $field_name) 318 { 319 if(!in_array($field_name, $fieldList)) continue; 320 $value = varset($data[$field_name], null); 321 $required = $this->isRequiredField($field_name); 322 if(($required || $this->isOptionalField($field_name)) && !$this->validateField($field_name, $value, $required)) 323 { 324 $this->setIsValidData(false); 325 $this->addValidateMessage($this->getFieldName($field_name, $required), $this->getErrorCode($field_name), $this->getFieldMessage($field_name, $value, $required)); 326 continue; 327 } 328 } 329 330 return $this->_is_valid_data; 331 } 332 333 /** 334 * Check if field is required 335 * 336 * @param string $name 337 * @return boolean 338 */ 339 function isRequiredField($name) 340 { 341 return isset($this->_required_rules[$name]); 342 } 343 344 /** 345 * Check if there is optional rule for this field 346 * 347 * @param string $name 348 * @return boolean 349 */ 350 function isOptionalField($name) 351 { 352 return isset($this->_optional_rules[$name]); 353 } 354 355 /** 356 * Retrieve help for the required field 357 * 358 * @param string $name 359 * @return string 360 */ 361 function getFieldHelp($name, $required = true, $default = '') 362 { 363 if($required) 364 { 365 $msg = (isset($this->_required_rules[$name][3]) ? $this->_required_rules[$name][3] : $default); 366 } 367 else 368 { 369 $msg = (isset($this->_optional_rules[$name][3]) ? $this->_optional_rules[$name][3] : $default); 370 } 371 372 return defset($msg, $msg); 373 } 374 375 /** 376 * Retrieve validation error message for the required field 377 * 378 * @param string $name 379 * @param mixed $value 380 * @return string 381 */ 382 function getFieldMessage($name, $value = '', $required = true) 383 { 384 if($required) 385 { 386 if(!isset($this->_required_rules[$name][4])) 387 { 388 $msg = $this->getFieldHelp($name, true); 389 } 390 else $msg = $this->_required_rules[$name][4]; 391 } 392 else 393 { 394 if(!isset($this->_optional_rules[$name][4])) 395 { 396 $msg = $this->getFieldHelp($name, false); 397 } 398 else $msg = $this->_optional_rules[$name][4]; 399 } 400 401 return ($msg ? defset($msg, $msg) : ''); 402 } 403 404 /** 405 * @param string $name 406 * @return string 407 */ 408 function getFieldName($name, $required = true) 409 { 410 if($required) 411 { 412 $msg = (isset($this->_required_rules[$name][2]) ? $this->_required_rules[$name][2] : $name); 413 } 414 else 415 { 416 $msg = (isset($this->_optional_rules[$name][2]) ? $this->_optional_rules[$name][2] : $name); 417 } 418 419 return defset($msg, $msg); 420 } 421 422 /** 423 * Validate single field 424 * 425 * @param string $name 426 * @param string $newval 427 * @param boolean $required 428 * @return boolean 429 */ 430 function validateField($name, $value, $required = true) 431 { 432 if($required) 433 { 434 $type = $this->_required_rules[$name][0]; 435 $cond = $this->_required_rules[$name][1]; 436 } 437 else 438 { 439 if(empty($value)) 440 { 441 switch($this->_optional_rules[$name][0]) 442 { 443 case 'int': 444 case 'integer': 445 $value = 0; 446 break; 447 448 case 'float': 449 $value = 0.00; 450 break; 451 452 case 'array': 453 $value = array(); 454 break; 455 456 default: 457 $value = ''; 458 break; 459 } 460 $this->addValidData($name, $value); 461 return true; 462 } 463 $type = $this->_optional_rules[$name][0]; 464 $cond = $this->_optional_rules[$name][1]; 465 } 466 467 switch ($type) 468 { 469 case 'required': 470 if(empty($value)) 471 { 472 $this->addValidateResult($name, self::ERR_GENERIC); 473 return false; 474 } 475 $this->addValidData($name, $value); 476 return true; 477 break; 478 479 case 'email': 480 if(!check_email($value)) 481 { 482 $this->addValidateResult($name, self::ERR_INVALID_EMAIL); 483 return false; 484 } 485 $this->addValidData($name, $value); 486 return true; 487 break; 488 489 case 'regexp': 490 case 'regex': 491 if(!preg_match($cond, $value)) 492 { 493 $this->addValidateResult($name, self::ERR_INVALID_CHARS); 494 return false; 495 } 496 $this->addValidData($name, $value); 497 return true; 498 break; 499 500 case 'callback': 501 if(!call_user_func($cond, $value)) 502 { 503 $this->addValidateResult($name, self::ERR_INVALID_CHARS); 504 return false; 505 } 506 $this->addValidData($name, $value); 507 return true; 508 break; 509 510 case 'instanceof': 511 if(!(is_object($value) && $value instanceof $cond)) 512 { 513 $this->addValidateResult($name, self::ERR_INSTANCEOF_EXPECTED); 514 return false; 515 } 516 $this->addValidData($name, $value); 517 return true; 518 break; 519 520 case 'int': 521 case 'integer': 522 if(!preg_match('/^-?[\d]+$/', $value)) // negative values support 523 { 524 $this->addValidateResult($name, self::ERR_INT_EXPECTED); 525 return false; 526 } 527 // BC! Will be removed after we replace '-' with ':' separator! 528 $tmp = $this->parseMinMax($cond); 529 if(is_numeric($tmp[0]) && (integer) $tmp[0] > (integer) $value) 530 { 531 $this->addValidateResult($name, self::ERR_TOO_LOW); 532 return false; 533 } 534 if(is_numeric(varset($tmp[1])) && (integer) $tmp[1] < (integer) $value) 535 { 536 $this->addValidateResult($name, self::ERR_TOO_HIGH); 537 return false; 538 } 539 $this->addValidData($name, intval($value)); 540 return true; 541 break; 542 543 case 'str': 544 case 'string': 545 case 'text': 546 case 'varchar': 547 $tmp = $this->parseMinMax($cond); 548 $length = e107::getParser()->ustrlen($value); 549 if(is_numeric($tmp[0]) && (integer) $tmp[0] > $length) 550 { 551 $this->addValidateResult($name, self::ERR_TOO_SHORT); 552 return false; 553 } 554 555 if('varchar' == $type && !varset($tmp[1])) $tmp[1] = 255; 556 557 if(is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $length) 558 { 559 $this->addValidateResult($name, self::ERR_TOO_LONG); 560 return false; 561 } 562 $this->addValidData($name, (string) $value); 563 return true; 564 break; 565 566 case 'set': 567 case 'enum': 568 $tmp = array_map('trim', explode(',', $cond)); 569 if(!$value || !in_array($value, $tmp)) 570 { 571 $this->addValidateResult($name, self::ERR_FIELDS_MATCH); 572 return false; 573 } 574 575 $this->addValidData($name, (string) $value); 576 return true; 577 break; 578 579 case 'float': 580 $value = e107::getParser()->toNumber($value); 581 if(!is_numeric($value)) 582 { 583 $this->addValidateResult($name, self::ERR_FLOAT_EXPECTED); 584 return false; 585 } 586 $tmp = $this->parseMinMax($cond); 587 if(is_numeric($tmp[0]) && (float) $tmp[0] > (float) $value) 588 { 589 $this->addValidateResult($name, self::ERR_TOO_LOW); 590 return false; 591 } 592 if(is_numeric(varset($tmp[1])) && (float) $tmp[1] < (float) $value) 593 { 594 $this->addValidateResult($name, self::ERR_TOO_HIGH); 595 return false; 596 } 597 $this->addValidData($name, $value); 598 return true; 599 break; 600 601 case 'array': 602 if(!is_array($value)) 603 { 604 $this->addValidateResult($name, self::ERR_ARRAY_EXPECTED); 605 return false; 606 } 607 $length = count($value); 608 $tmp = $this->parseMinMax($cond); 609 if(is_numeric($tmp[0]) && (integer) $tmp[0] > $length) 610 { 611 $this->addValidateResult($name, self::ERR_ARRCOUNT_LOW); 612 return false; 613 } 614 if(is_numeric(varset($tmp[1])) && (float) $tmp[1] < $length) 615 { 616 $this->addValidateResult($name, self::ERR_ARRCOUNT_HIGH); 617 return false; 618 } 619 $this->addValidData($name, $value); 620 return true; 621 break; 622 623 case 'file': // TODO - type image - validate dimensions? 624 parse_str($cond, $params); 625 $path = e107::getParser()->replaceConstants(varset($params['base']).$value); 626 if(!$value || !is_file($path)) 627 { 628 $this->addValidateResult($name, self::ERR_NOT_FILE); 629 return false; 630 } 631 if(vartrue($params['writable']) && !is_writable($path)) 632 { 633 $this->addValidateResult($name, self::ERR_WRITABLE_FILE); 634 return false; 635 } 636 if(vartrue($params['size'])) 637 { 638 $tmp = $this->parseMinMax($cond); 639 $fs = filesize($path); 640 if(!$fs || (integer) $tmp[0] > $fs) 641 { 642 $this->addValidateResult($name, self::ERR_SIZEMIN_FILE); 643 return false; 644 } 645 elseif(is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $fs) 646 { 647 $this->addValidateResult($name, self::ERR_SIZEMAX_FILE); 648 return false; 649 } 650 } 651 if(is_numeric(varset($params['maxlen'])) && (integer) $params['maxlen'] < e107::getParser()->ustrlen($value)) 652 { 653 $this->addValidateResult($name, self::ERR_TOO_LONG); 654 return false; 655 } 656 $this->addValidData($name, $value); 657 return true; 658 break; 659 660 case 'compare': 661 if(!is_array($value)) 662 { 663 $this->addValidateResult($name, self::ERR_UNEXPECTED_VALUE); 664 return false; 665 } 666 667 if(!($value[0] && $value[1] && $value[0] == $value[1])) 668 { 669 $this->addValidateResult($name, self::ERR_FIELDS_MATCH); 670 return false; 671 } 672 673 // check length 674 if($cond) 675 { 676 $tmp = $this->parseMinMax($cond); 677 $length = e107::getParser()->ustrlen($value[0]); 678 if(is_numeric($tmp[0]) && (integer) $tmp[0] > $length) 679 { 680 $this->addValidateResult($name, self::ERR_TOO_SHORT); 681 return false; 682 } 683 if(is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $length) 684 { 685 $this->addValidateResult($name, self::ERR_TOO_LONG); 686 return false; 687 } 688 } 689 $this->addValidData($name, $value[0]); 690 return true; 691 break; 692 693 case 'compare_strict': 694 if(!is_array($value)) 695 { 696 $this->addValidateResult($name, self::ERR_UNEXPECTED_VALUE); 697 return false; 698 } 699 if(!($value[0] && $value[1] && $value[0] === $value[1])) 700 { 701 $this->addValidateResult($name, self::ERR_FIELDS_MATCH); 702 return false; 703 } 704 705 // check length 706 if($cond) 707 { 708 $tmp = $this->parseMinMax($cond); 709 $length = e107::getParser()->ustrlen($value[0]); 710 if(is_numeric($tmp[0]) && (integer) $tmp[0] > $length) 711 { 712 $this->addValidateResult($name, self::ERR_TOO_SHORT); 713 return false; 714 } 715 if(is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $length) 716 { 717 $this->addValidateResult($name, self::ERR_TOO_LONG); 718 return false; 719 } 720 } 721 $this->addValidData($name, $value[0]); 722 return true; 723 break; 724 725 default: 726 $this->addValidateResult($name, self::ERR_UNEXPECTED_VALUE); 727 return false; 728 break; 729 } 730 } 731 732 // moved to e_parse 733 // public function toNumber($value) 734 // { 735 // $larr = localeconv(); 736 // $search = array( 737 // $larr['decimal_point'], 738 // $larr['mon_decimal_point'], 739 // $larr['thousands_sep'], 740 // $larr['mon_thousands_sep'], 741 // $larr['currency_symbol'], 742 // $larr['int_curr_symbol'] 743 // ); 744 // $replace = array('.', '.', '', '', '', ''); 745 746 // return str_replace($search, $replace, $value); 747 // } 748 749 protected function parseMinMax($string) 750 { 751 return explode(':', $this->_convertConditionBC($string), 2); 752 } 753 754 private function _convertConditionBC($condition) 755 { 756 // BC! Will be removed after we replace '-' with ':' separator! 757 if(strpos($condition, ':') === false) 758 { 759 return preg_replace('/^([0-9]+)-([0-9]+)$/', '$1:$2', $condition); 760 } 761 return $condition; 762 } 763 764 /** 765 * Add validation error to validate result stack 766 * 767 * @param string $field_title 768 * @param string $err_code 769 * @param string $err_message 770 * @param string $custom 771 * @return e_validator 772 */ 773 function addValidateMessage($field_title, $err_code = 0, $err_message = '', $custom = '') 774 { 775 $tp = e107::getParser(); 776 $lanVars = array( 777 'x' => $field_title, 778 'y' => $err_code, 779 'z' => $this->getErrorByCode($err_code) 780 ); 781 782 if($custom) 783 { 784 e107::getMessage()->addStack(sprintf($err_message, $err_code, $field_title), $this->_message_stack, (true === $custom ? E_MESSAGE_ERROR : $custom)); 785 return $this; 786 } 787 788 //Additional message 789 $lan = LAN_VALIDATE_FAILMSG; 790 $dbgmsg = false; 791 if($err_message) 792 { 793 $lan = (!$field_title || strpos($err_message, '[x]') !== false ? '' : '[x] - ').$err_message; // custom, e.g. // default '<strong>"%1$s"</strong> field error: Custom error message. ' 794 $dbgmsg = LAN_VALIDATE_FAILMSG; 795 } 796 797 //Core message 798 /* 799 $msg = sprintf( 800 $lan, // default '<strong>"%1$s"</strong> validation error: [#%2$d] %3$s. ' 801 $field_title, 802 $err_code, 803 $this->getErrorByCode($err_code) 804 ); 805 */ 806 807 //NEW - removes need for using sprintf() 808 $msg = $tp->lanVars($lan,$lanVars,true); // '[x] validation error: [y] [z].' 809 810 if($dbgmsg && defset('e107_DEBUG_LEVEL')) 811 { 812 813 e107::getMessage()->addDebug($tp->lanVars($dbgmsg,$lanVars)); 814 815 /* 816 e107::getMessage()->addDebug(sprintf( 817 $dbgmsg, 818 $field_title, 819 $err_code, 820 $this->getErrorByCode($err_code) 821 )); 822 */ 823 } 824 825 e107::getMessage()->addStack($msg, $this->_message_stack, E_MESSAGE_ERROR); 826 827 return $this; 828 } 829 830 /** 831 * Get validate message array 832 * 833 * @param boolean $clear 834 * @return array 835 */ 836 function getValidateMessages($clear = true) 837 { 838 return e107::getMessage()->getAll($this->_message_stack, true, $clear); 839 } 840 841 /** 842 * Render validate messages 843 * 844 * @param boolean $session merge with session messages 845 * @param boolean $clear 846 * @return string 847 */ 848 function renderValidateMessages($session = false, $clear = true) 849 { 850 return e107::getMessage()->render($this->_message_stack, $session, $clear); 851 } 852 853 /** 854 * @param boolean $session clear session messages as well, default true 855 * @return e_validator 856 */ 857 function clearValidateMessages($session = true) 858 { 859 e107::getMessage()->reset(false, $this->_message_stack, $session); 860 return $this; 861 } 862 863 /** 864 * Add validate error code for a field 865 * 866 * @param string $name 867 * @param integer $code 868 * @return e_validator 869 */ 870 function addValidateResult($name, $code) 871 { 872 $this->_validation_results[$name] = $code; 873 return $this; 874 } 875 876 /** 877 * Get validate result array 878 * 879 * @param boolean $clear 880 * @return array 881 */ 882 function getValidateResults($clear = true) 883 { 884 return $this->_validation_results; 885 } 886 887 /** 888 * Get validate result for a field 889 * 890 * @param string $field 891 * @param mixed $default 892 * @return integer error code 893 */ 894 function getErrorCode($field, $default = 0) 895 { 896 return (isset($this->_validation_results[$field]) ? $this->_validation_results[$field] : $default); 897 } 898 899 /** 900 * Get error string by given error code 901 * 902 * @param string $error_code 903 * @return integer error code 904 */ 905 function getErrorByCode($error_code) 906 { 907 $lan = 'LAN_VALIDATE_'.$error_code; 908 return defset($lan, $lan); 909 } 910 911 /** 912 * @return e_validator 913 */ 914 function clearValidateResults() 915 { 916 $this->_validation_results = array(); 917 return $this; 918 } 919 920 /** 921 * @return boolean 922 */ 923 function isValid() 924 { 925 return empty($this->_is_valid_data); 926 } 927 928 /** 929 * Set validation status 930 * 931 * @param boolean $status 932 * @return e_validator 933 */ 934 public function setIsValidData($status) 935 { 936 $this->_is_valid_data = (boolean) $status; 937 return $this; 938 } 939 940 /** 941 * Reset object validate result data 942 * @return e_validator 943 */ 944 function reset() 945 { 946 $this->setIsValidData(true); 947 $this->_valid_data = array(); 948 $this->clearValidateResults() 949 ->clearValidateMessages(); 950 951 return $this; 952 } 953} 954 955/* 956The validator functions use an array of parameters for each variable to be validated. 957 958 The index of the parameter array is the destination field name. 959 960 Possible processing options: 961 'srcname' - specifies the array index of the source data, where its different to the destination index 962 'dbClean' - method for preparing the value to write to the DB (done as final step before returning). Options are: 963 - 'toDB' - passes final value through $tp->toDB() 964 - 'intval' - converts to an integer 965 - 'image' - checks image for size 966 - 'avatar' - checks an image in the avatars directory 967 'stripTags' - strips HTML tags from the value (not an error if there are some) 968 'minLength' - minimum length (in utf-8 characters) for the string 969 'maxLength' - minimum length (in utf-8 characters) for the string 970 'minVal' - lowest allowed value for numerics 971 'maxVal' - highest allowed value for numerics 972 'longTrim' - if set, and the string exceeds maxLength, its trimmed 973 'enablePref' - value is processed only if the named $pref evaluates to true; otherwise any input is discarded without error 974 'dataType' - selects special processing methods: 975 1 - array of numerics (e.g. class membership) 976 977 In general, only define an option if its to be used 978*/ 979 980/* [ Berckoff ] 981 * Added "public static " to each method as the parser generates errors (and methods are called statically everywhere) 982 */ 983class validatorClass 984{ 985 // Passed an array of 'source' fields and an array of definitions to validate. The definition may include the name of a validation function. 986 // Returns three arrays - one of validated results, one of failed fields and one of errors corresponding to the failed fields 987 // Normally processes only those source fields it finds (and for which it has a definition). If $addDefaults is true, sets defaults for those that have 988 // ...one and aren't otherwise defined. 989 public static function validateFields(&$sourceFields, &$definitions, $addDefaults = FALSE) 990 { 991 global $tp, $pref; 992 $ret = array('data' => array(), 'failed' => array(), 'errors' => array()); 993 994 foreach ($definitions as $dest => $defs) 995 { 996 $errNum = 0; // Start with no error 997 998 if(!is_array($defs)) //default rule - dbClean -> toDB 999 { 1000 $defs = array('dbClean', ($defs ? $defs : 'toDB')); 1001 } 1002 $src = varset($defs['srcName'],$dest); // Set source field name 1003 if (!isset($sourceFields[$src])) 1004 { 1005 if ($addDefaults) 1006 { 1007 if (isset($defs['default'])) 1008 { 1009 $ret['data'] = $defs['default']; // Set default value if one is specified 1010 } //...otherwise don't add the value at all 1011 } 1012 else 1013 { 1014 if (!vartrue($defs['fieldOptional'])) 1015 { 1016 $ret['errors'][$dest] = ERR_MISSING_VALUE; // No source value 1017 } 1018 } 1019 } 1020 else 1021 { // Got a field we want, and some data to validate here 1022 $value = $sourceFields[$src]; 1023 if (!$errNum && isset($defs['enablePref'])) 1024 { // Only process this field if a specified pref enables it 1025 if (!vartrue($pref[$defs['enablePref']])) 1026 { 1027 continue; // Just loop to the next field - ignore this one. 1028 } 1029 } 1030 if (!$errNum && isset($defs['stripTags'])) 1031 { 1032 $newValue = trim(strip_tags($value)); 1033 if ($newValue <> $value) 1034 { 1035 $errNum = ERR_INVALID_CHARS; 1036 } 1037 $value = $newValue; 1038 } 1039 if (!$errNum && isset($defs['stripChars'])) 1040 { 1041 $newValue = trim(preg_replace($defs['stripChars'], "", $value)); 1042 if ($newValue <> $value) 1043 { 1044 //echo "Invalid: {$newValue} :: {$value}<br />"; 1045 $errNum = ERR_INVALID_CHARS; 1046 } 1047 $value = $newValue; 1048 } 1049 if (!$errNum && isset($defs['minLength']) && ($tp->ustrlen($value) < $defs['minLength'])) 1050 { 1051 if ($value == '') 1052 { 1053 if (!vartrue($defs['fieldOptional'])) 1054 { 1055 $errNum = ERR_MISSING_VALUE; 1056 } 1057 } 1058 else 1059 { 1060 $errNum = ERR_TOO_SHORT; 1061 } 1062 } 1063 if (!$errNum && isset($defs['maxLength']) && $tp->ustrlen($value) > $defs['maxLength']) 1064 { 1065 if (vartrue($defs['longtrim'])) 1066 { 1067 $value = substr($value,0,$defs['maxLength']); 1068 } 1069 else 1070 { 1071 $errNum = ERR_TOO_LONG; 1072 } 1073 } 1074 if (!$errNum && isset($defs['minVal']) && ($value < $defs['minVal'])) 1075 { 1076 $errNum = ERR_TOO_LOW; 1077 } 1078 if (!$errNum && isset($defs['maxVal']) && ($value < $defs['maxVal'])) 1079 { 1080 $errNum = ERR_TOO_HIGH; 1081 } 1082 if (!$errNum && isset($defs['fixedBlock'])) 1083 { 1084 $newValue = $tp->ustrtolower($value); 1085 $temp = explode(',',$defs['fixedBlock']); 1086 foreach ($temp as $t) 1087 { 1088 if ($newValue == $tp->ustrtolower($t)) 1089 { 1090 $errNum = ERR_INVALID_WORD; 1091 break; 1092 } 1093 } 1094 } 1095 if (!$errNum && isset($defs['dataType'])) 1096 { 1097 switch ($defs['dataType']) 1098 { 1099 case 1 : // Assumes we've passed an array variable to be turned into a comma-separated list of integers 1100 if (is_array($value)) 1101 { 1102 $temp = array(); 1103 foreach ($value as $v) 1104 { 1105 $v = trim($v); 1106 if (is_numeric($v)) 1107 { 1108 $temp[] = intval($v); 1109 } 1110 } 1111 $value = implode(',', array_unique($temp)); 1112 } 1113 else 1114 { 1115 $errNum = ERR_ARRAY_EXPECTED; 1116 } 1117 break; 1118 case 2 : // Assumes we're processing a dual password field - array name for second value is one more than for first 1119 $src2 = substr($src,0,-1).(substr($src,-1,1) + 1); 1120 if (!isset($sourceFields[$src2]) || ($sourceFields[$src2] != $value)) 1121 { 1122 $errNum = ERR_PASSWORDS_DIFFERENT; 1123 } 1124 break; 1125 default : 1126 $errNum = ERR_CODE_ERROR; // Pick up bad values 1127 } 1128 } 1129 if (!$errNum) 1130 { 1131 if (isset($defs['dbClean'])) 1132 { 1133 switch ($defs['dbClean']) 1134 { 1135 case 'toDB' : 1136 $value = $tp->toDB($value); 1137 break; 1138 case 'intval' : 1139 $value = intval($value); 1140 break; 1141 case 'avatar' : // Special case of an image - may be found in the avatars directory 1142 if (preg_match('#[0-9\._]#', $value)) 1143 { 1144 if (strpos('-upload-', $value) === 0) 1145 { 1146 $img = e_AVATAR_UPLOAD.str_replace('-upload-', '', $value); // Its a user-uploaded image 1147 } 1148 elseif (strpos($value, '//') !== false) 1149 { 1150 $img = $value; // Its a remote image 1151 } 1152 else 1153 { 1154 $img = e_AVATAR_DEFAULT.$value; // Its a server-stored image 1155 } 1156 } 1157 // Deliberately fall through into normal image processing 1158 case 'image' : // File is an image name. $img may be set if we fall through from 'avatar' option - its the 'true' path to the image 1159 if (!isset($img) && isset($defs['imagePath'])) 1160 { 1161 $img = $defs['imagePath'].$value; 1162 } 1163 $img = varset($img,$value); 1164 //XXX There should be no size limits - as image sizes are handled by thumb.php 1165 if ($size = getimagesize($img)) 1166 { 1167 // echo "Image {$img} size: {$size[0]} x {$size[1]}<br />"; 1168 if (isset($defs['maxWidth']) && $size[0] > $defs['maxWidth']) 1169 { // Image too wide 1170 // $errNum = ERR_IMAGE_TOO_WIDE; 1171 } 1172 if (isset($defs['maxHeight']) && $size[1] > $defs['maxHeight']) 1173 { // Image too high 1174 // $errNum = ERR_IMAGE_TOO_HIGH; 1175 } 1176 } 1177 else 1178 { 1179 // echo "Image {$img} not found or cannot size - original value {$value}<br />"; 1180 } 1181 unset($img); 1182 break; 1183 default : 1184 echo "Invalid dbClean method: {$defs['dbClean']}<br />"; // Debug message 1185 } 1186 } 1187 $ret['data'][$dest] = $value; // Success!! 1188 } 1189 } 1190 if ($errNum) 1191 { // error to report 1192 $ret['errors'][$dest] = $errNum; 1193 if ($defs['dataType'] == 2) 1194 { 1195 $ret['failed'][$dest] = str_repeat('*',strlen($sourceFields[$src])); // Save value with error - obfuscated 1196 } 1197 else 1198 { 1199 $ret['failed'][$dest] = $sourceFields[$src]; // Save value with error 1200 } 1201 } 1202 } 1203 return $ret; 1204 } 1205 1206 1207/* 1208 // Validate data against a DB table 1209 // Inspects the passed array of user data (not necessarily containing all possible fields) and validates against the DB where appropriate. 1210 // Just skips over fields for which we don't have a validation routine without an error 1211 // The target array is as returned from validateFields(), so has 'data', 'failed' and 'errors' first-level sub-arrays 1212 // All the 'vetting methods' begin 'vet', and don't overlap with validateFields(), so the same definition array may be used for both 1213 // Similarly, error numbers don't overlap with validateFields() 1214 // Typically checks for unacceptable duplicates, banned users etc 1215 // Any errors are reflected by updating the passed array. 1216 // Returns TRUE if all data validates, FALSE if any field fails to validate. Checks all fields which are present, regardless 1217 // For some things we need to know the user_id of the data being validated, so may return an error if that isn't specified 1218 1219 Parameters: 1220 'vetMethod' - see list below. To use more than one method, specify comma-separated 1221 'vetParam' - possible parameter for some vet methods 1222 1223 Valid 'vetMethod' values (use comma separated list for multiple vetting): 1224 0 - Null method 1225 1 - Check for duplicates - field name in table must be the same as array index unless 'dbFieldName' specifies otherwise 1226 2 - Check against the comma-separated wordlist in the $pref named in vetParam['signup_disallow_text'] 1227 3 - Check email address against remote server, only if option enabled 1228 1229*/ 1230 public static function dbValidateArray(&$targetData, &$definitions, $targetTable, $userID = 0) 1231 { 1232 global $pref; 1233 $u_sql = new db; 1234 $allOK = TRUE; 1235 $userID = intval($userID); // Precautionary 1236 $errMsg = ''; 1237 if (!$targetTable) return FALSE; 1238 foreach ($targetData['data'] as $f => $v) 1239 { 1240 $errMsg = ''; 1241 if (isset($definitions[$f])) 1242 { 1243 $options = $definitions[$f]; // Validation options to use 1244 if (!vartrue($options['fieldOptional']) || ($v != '')) 1245 { 1246 $toDo = explode(',',$options['vetMethod']); 1247 foreach ($toDo as $vm) 1248 { 1249 switch ($vm) 1250 { 1251 case 0 : // Shouldn't get this - just do nothing if we do 1252 break; 1253 case 1 : // Check for duplicates. 1254 if ($v == '') 1255 { 1256 $errMsg = ERR_MISSING_VALUE; 1257 break; 1258 } 1259 $field = varset($options['dbFieldName'],$f); 1260 if ($temp = $u_sql->count($targetTable, "(*)", "WHERE `{$f}`='".filter_var($v, FILTER_SANITIZE_STRING)."' AND `user_id` != ".$userID)) 1261 { 1262 $errMsg = ERR_DUPLICATE; 1263 } 1264// echo "Duplicate check: {$f} = {$v} Result: {$temp}<br />"; 1265 break; 1266 case 2 : // Check against $pref 1267 if (isset($options['vetParam']) && isset($pref[$options['vetParam']])) 1268 { 1269 $tmp = explode(",", $pref[$options['vetParam']]); 1270 foreach($tmp as $disallow) 1271 { 1272 if ('!' == substr(trim($disallow), -1) && $v == str_replace('!', '', $disallow)) 1273 { // Exact match search (noticed with exclamation mark in the end of the word) 1274 $errMsg = ERR_DISALLOWED_TEXT_EXACT_MATCH; 1275 } 1276 elseif(stristr($v, trim($disallow))) 1277 { // Wild card search 1278 $errMsg = ERR_DISALLOWED_TEXT; 1279 } 1280 } 1281 unset($tmp); 1282 } 1283 break; 1284 case 3 : // Check email address against remote server 1285 1286 /* if (vartrue($pref['signup_remote_emailcheck'])) 1287 { 1288 require_once(e_HANDLER."mail_validation_class.php"); 1289 list($adminuser,$adminhost) = split ("@", SITEADMINEMAIL); 1290 $validator = new email_validation_class; 1291 $validator->localuser= $adminuser; 1292 $validator->localhost= $adminhost; 1293 $validator->timeout=3; 1294 // $validator->debug=1; 1295 // $validator->html_debug=1; 1296 if($validator->ValidateEmailBox(trim($v)) != 1) 1297 { 1298 $errMsg = ERR_INVALID_EMAIL; 1299 } 1300 } 1301 */ 1302 break; 1303 default : 1304 echo 'Invalid vetMethod: '.$options['vetMethod'].'<br />'; // Really a debug aid - should never get here 1305 } 1306 if ($errMsg) { break; } // Just trap first error 1307 } 1308 // Add in other validation methods here 1309 } 1310 } 1311 if ($errMsg) 1312 { // Update the error 1313 $targetData['errors'][$f] = $errMsg; 1314 $targetData['failed'][$f] = $v; 1315 unset($targetData['data'][$f]); // Remove the valid entry 1316 $allOK = FALSE; 1317 } 1318 } 1319 return $allOK; 1320 } 1321 1322 1323 // Given a comma-separated string of required fields, and an array of data, adds an error message for each field which doesn't already have an entry. 1324 // Returns TRUE if no changes (which doesn't mean there are no errors - other routines may have found them). FALSE if new errors 1325 public static function checkMandatory($fieldList, &$target) 1326 { 1327 $fields = explode(',', $fieldList); 1328 $allOK = TRUE; 1329 foreach ($fields as $f) 1330 { 1331 if (!isset($target['data'][$f]) && !isset($target['errors'][$f])) 1332 { 1333 $allOK = FALSE; 1334 $targetData['errors'][$f] = ERR_MISSING_VALUE; 1335 } 1336 } 1337 return $allOK; 1338 } 1339 1340 1341 // Adds the _FIELD_TYPES array to the data, ready for saving in the DB. 1342 // $fieldList is the standard definition array 1343 public static function addFieldTypes($fieldList, &$target, $auxList=FALSE) 1344 { 1345 $target['_FIELD_TYPES'] = array(); // We should always want to recreate the array, even if it exists 1346 foreach ($target['data'] as $k => $v) 1347 { 1348 if (isset($fieldList[$k]) && isset($fieldList[$k]['fieldType'])) 1349 { 1350 $target['_FIELD_TYPES'][$k] = $fieldList[$k]['fieldType']; 1351 } 1352 elseif (is_array($auxList) && isset($auxList[$k])) 1353 { 1354 $target['_FIELD_TYPES'][$k] = $auxList[$k]; 1355 } 1356 } 1357 } 1358 1359 1360 1361 // Given two arrays, returns an array of those elements in $input which are different from the corresponding element in $refs. 1362 // If $addMissing == TRUE, includes any element in $input for which there isn't a corresponding element in $refs 1363 public static function findChanges(&$input, &$refs, $addMissing = FALSE) 1364 { 1365 $ret = array(); 1366 foreach ($input as $k => $v) 1367 { 1368 if (array_key_exists($k, $refs)) 1369 { 1370 if ($refs[$k] != $v) { $ret[$k] = $v; } 1371 } 1372 else 1373 { 1374 if ($addMissing) { $ret[$k] = $v; } 1375 } 1376 } 1377 return $ret; 1378 } 1379 1380 1381 // Given a vetted array of variables, generates a list of errors using the specified format string. 1382 // %n is the error number (as stored on the array) 1383 // %t is the corresponding error message, made by concatenating $constPrefix and the error number to form a constant (e.g. $constPrefix = 'USER_ERROR_') 1384 // %v calls up the entered value 1385 // %f is the field name 1386 // %x is the 'nice name' - possible if parameter list passed. Otherwise field name added 1387 // $EOL is inserted after all messages except the last. 1388 // If $EOL is an empty string, returns an array of messages. 1389 public static function makeErrorList($vars, $constPrefix, $format = '%n - %x %t: %v', $EOL = '<br />', $niceNames = NULL) 1390 { 1391 if (count($vars['errors']) == 0) return ''; 1392 $eList = array(); 1393 $checkNice = ($niceNames != NULL) && is_array($niceNames); 1394 foreach ($vars['errors'] as $f => $n) 1395 { 1396 $curLine = $format; 1397 $curLine = str_replace('%n', $n, $curLine); 1398 if (($n == ERR_GENERIC) && isset($vars['errortext'][$f])) 1399 { 1400 $curLine = str_replace('%t', $vars['errortext'][$f], $curLine); // Coder-defined specific error text 1401 } 1402 else 1403 { 1404 $curLine = str_replace('%t', constant($constPrefix.$n), $curLine); // Standard messages 1405 } 1406 if(empty($vars['failed'][$f])) 1407 { 1408 $vars['failed'][$f] = LAN_VALIDATE_191; 1409 // print_a($vars['failed']); 1410 } 1411 $curLine = str_replace('%v', filter_var($vars['failed'][$f], FILTER_SANITIZE_SPECIAL_CHARS), $curLine); 1412 $curLine = str_replace('%f', $f, $curLine); 1413 if ($checkNice & isset($niceNames[$f]['niceName'])) 1414 { 1415 $curLine = str_replace('%x', $niceNames[$f]['niceName'], $curLine); 1416 } 1417 else 1418 { 1419 $curLine = str_replace('%x', $f, $curLine); // Just use the field name 1420 } 1421 $eList[] = $curLine; 1422 } 1423 if ($EOL == '') return $eList; 1424 return implode($EOL, $eList); 1425 } 1426} 1427 1428 1429