/packages/validator
[return to app]1
<?php
2 /**
3 * This comes from the May 2007 php|architect article:
4 * Unifying Server-Side and Client-Side Input Validation
5 */
6 class ValidatedField {
7 protected $_label, $_validation;
8
9 public function __construct($label, $validation) {
10 $this->_label = $label;
11 $this->_validation = $validation;
12 }
13
14 public function __call($name, $val) {
15 if (strpos($name, 'get') === 0) {
16 $property = '_' . strtolower($name[3]) . substr($name, 4);
17 if (isset($this->$property)) {
18 return $this->$property;
19 } else {
20 $error_message = 'There is no property defined as ' . $property
21 . ' or method called ' . $name
22 . ' in the ' . __CLASS__ . ' class';
23 }
24 } else {
25 $error_message = 'Call to undefined method ' . __CLASS__ . '::' . $name . '()';
26 }
27
28 if (isset($error_message)) {
29 trigger_error($error_message, E_USER_ERROR);
30 }
31 }
32 }
33
34 class Validator {
35 protected $_validatedFields = array();
36
37 protected $_commonRegex = array(
38 'match' => array(
39 'HexColor' => '/^#?([\dA-F]{3}){1,2}$/i',
40 'UsTelephone' =>
'/^\(?([2-9]\d{2})\)?[\.\s-]?([2-4|6-9]\d\d|5([0-4|6-9]\d|\d[0-4|6-9]))[\.\s-]?(\d{4})$/',
41 'Email' => '/(^[\w\.!#$%"*+\/=?`{}|~^-]+)@(([-\w]+\.)+[A-Za-z]{2,})$/',
42 'Url' => '/^(https?|ftp):\/\/([-\w]+\.)+[A-Za-z]{2,}(:\d+)?([\\\\\/]\S+)*?[\\\\\/]?(\?\S*)?$/i',
43 'Integer' => '/^-?\d+$/',
44 'Numeric' => '/^-?(\d*\.)?\d+$/',
45 'AlphaNumeric' => '/^[\w\s]+$/i'),
46 'get' => array(
47 'Integer' => '/\d+/',
48 'Numeric' => '/\d+(\.\d+)?/',
49 'Trim' => '/^\s*(.*?)\s*$/')
50 );
51
52 protected $_validCssColors = array(
53 'aqua' => '00FFFF',
54 'black' => '000000',
55 'blue' => '0000FF',
56 'fuchsia' => 'FF00FF',
57 'gray' => '808080',
58 'green' => '008000',
59 'lime' => '00FF00',
60 'maroon' => '800000',
61 'navy' => '000080',
62 'olive' => '808000',
63 'purple' => '800080',
64 'red' => 'FF0000',
65 'silver' => 'C0C0C0',
66 'teal' => '008080',
67 'white' => 'FFFFFF',
68 'yellow' => 'FFFF00'
69 );
70
71 public function getCommonData($dataType) {
72 switch ($dataType) {
73 case 'commonRegex':
74 $x = -1;
75 $return = '{';
76 foreach ($this->_commonRegex as $matchType => $regexArray) {
77 $y = -1;
78 if (++$x) {
79 $return .= ', ' . PHP_EOL;
80 }
81 $return .= "'" . $matchType . "': {" . PHP_EOL;
82 foreach ($regexArray as $key => $regexString) {
83 if (++$y) {
84 $return .= ', ' . PHP_EOL;
85 }
86 $return .= "'" . $key . "': " . $regexString;
87 if ($matchType == 'get') {
88 $return .= 'g';
89 }
90 }
91 $return .= "}";
92 }
93 $return .= '}';
94 break;
95 case 'validations':
96 $return = array();
97 foreach ($this->_validatedFields as $formId => $validatedArray) {
98 foreach ($validatedArray as $inputId => $obj) {
99 $return[$formId][$inputId] = $obj->getValidation();
100 }
101 }
102 $return = json_encode($return);
103 break;
104 }
105 return $return;
106 }
107
108 public function isValidNumeric($val) {
109 return is_numeric($val);
110 }
111
112 public function isValidInteger($val) {
113 return (is_numeric($val) && $val == intval($val));
114 }
115
116 public function getNumbersOnly($val, $integerOnly = 0) {
117 $regexType = ($integerOnly ? 'Integer' : 'Numeric');
118 preg_match_all($this->_commonRegex['get'][$regexType], $val, $matches);
119 return implode($matches[0]);
120 }
121
122 public function isValidTelephone($val, $country = 'US') {
123 if (strtoupper($country) == 'US') {
124 $return = (preg_match($this->_commonRegex['match']['UsTelephone'], $val));
125 } else {
126 $return = (strlen($this->getNumbersOnly($val)) > 6);
127 }
128 return $return;
129 }
130
131 public function isValidHexColor($val) {
132 if (isset($this->_validCssColors[$val])) {
133 $val = '#' . $this->_validCssColors[$val];
134 }
135 return $this->__call('isValidHexColor', array($val));
136 }
137
138 public function setValidatedField($formId, $inputId, $label, $validation) {
139 $this->_validatedFields[$formId][$inputId] = new ValidatedField($label, $validation);
140 }
141
142 public function getClosingJs() {
143 return 'var validations = ' . $this->getCommonData('validations') . ';
144 var thisFormId;
145 for (var formId in validations) {
146 thisFormId = Validator.dom(formId);
147 if (thisFormId) {
148 thisFormId.onsubmit = function() {
149 return Validator.validateForm(formId);
150 }
151 }
152 }';
153 }
154
155 public static $errors = array();
156
157 public function getErrorsFromPost($formId) {
158 self::$errors[$formId] = array();
159 if (isset($_POST[key($this->_validatedFields[$formId])])) {
160 foreach ($this->_validatedFields[$formId] as $inputId => $obj) {
161 $_POST[$inputId] = trim($_POST[$inputId]);
162 if (!$this->{$obj->getValidation()}($_POST[$inputId])) {
163 self::$errors[$formId][$inputId] = $obj->getLabel() . ' is not valid';
164 }
165 }
166 }
167 }
168
169 public function __call($name, $val) {
170 $triggerError = true;
171 if (strpos($name, 'isValid') === 0) {
172 $regexType = substr($name, 7);
173 if (isset($this->_commonRegex['match'][$regexType])) {
174 return preg_match($this->_commonRegex['match'][$regexType], current($val));
175 $triggerError = false;
176 }
177 } else if (strpos($name, 'set') === 0) {
178 $this->{'_' . strtolower($name[3]) . substr($name, 4)} = current($val);
179 $triggerError = false;
180 } else if (strpos($name, 'get') === 0) {
181 $property = '_' . strtolower($name[3]) . substr($name, 4);
182 if (isset($this->$property)) {
183 return $this->$property;
184 $triggerError = false;
185 }
186 }
187
188 if ($triggerError) {
189 trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
190 }
191 }
192 }