1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Form validation helper
5: *
6: * @package PHPLucidFrame\Core
7: * @since PHPLucidFrame v 1.0.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: namespace LucidFrame\Core;
17:
18: /**
19: * Form validation helper
20: */
21: class Validation
22: {
23: const TYPE_MULTI = 'multi';
24: const TYPE_SINGLE = 'single';
25:
26: /** @var array The array of the error messages upon validation */
27: public static $errors = array();
28: /** @var array The array of default error messages */
29: private static $messages;
30: /** @var array The array of the rules for group of inputs validation */
31: private static $batchRules = array('mandatory', 'mandatoryOne', 'mandatoryAll');
32:
33: /**
34: * Setter
35: * @param string $key The property name
36: * @param mixed $value The value for the property
37: * @return void
38: */
39: public static function set($key, $value = null)
40: {
41: self::$$key = $value;
42: }
43:
44: /**
45: * Getter
46: * @param string $key The property name
47: * @return mixed
48: */
49: public static function get($key)
50: {
51: return isset(self::$$key) ? self::$$key : null;
52: }
53:
54: /**
55: * Check all inputs according to the validation rules provided
56: *
57: * @param array $validations The array of the validation rules
58: * @param array $data The optional data array (if no `value` in $validation, it will be looked up in $data)
59: * @param string $type The return form of the error message:
60: * "multi" to return all error messages occurred;
61: * "single" to return the first error message occurred
62: *
63: * @return bool
64: */
65: public static function check($validations, $data, $type = self::TYPE_MULTI)
66: {
67: form_init();
68:
69: $type = strtolower($type);
70: if (!in_array($type, array(self::TYPE_SINGLE, self::TYPE_MULTI))) {
71: $type = self::TYPE_MULTI;
72: }
73:
74: self::$errors = array();
75: foreach ($validations as $id => $v) {
76: if (isset($v['rules']) && is_array($v['rules'])) {
77: if (!isset($v['value'])) {
78: $v['value'] = isset($data[$id]) ? $data[$id] : '';
79: }
80:
81: foreach ($v['rules'] as $rule) {
82: $success = true;
83:
84: if (is_array($v['value']) && in_array($rule, self::$batchRules)) {
85: # Batch validation rules may be applied for array of values
86: $values = $v['value'];
87: $func = 'validate_' . $rule;
88: if (function_exists($func)) {
89: $success = call_user_func_array($func, array($values));
90: if (!$success) {
91: self::setError($id, $rule, $v);
92: }
93: continue; # go to the next rule
94: }
95: # if array of values, the validation function
96: # (apart from the batch validation functions) will be applied to each value
97: } else {
98: if (!is_array($v['value']) || array_key_exists('tmp_name', $v['value'])) {
99: $values = array($v['value']);
100: } else {
101: $values = $v['value'];
102: }
103: }
104:
105: foreach ($values as $value) {
106: # Custom validation function
107: if (strstr($rule, 'validate_')) {
108: $args = array($value);
109: if (isset($v['parameters']) && is_array($v['parameters'])) {
110: $params = (isset($v['parameters'][$rule])) ? $v['parameters'][$rule] : $v['parameters'];
111: $args = array_merge($args, $params);
112: }
113: $success = call_user_func_array($rule, $args);
114: if (!$success) {
115: self::setError($id, $rule, $v);
116: }
117: } else {
118: # Pre-defined validation functions
119: $func = 'validate_' . $rule;
120: if (function_exists($func)) {
121: switch ($rule) {
122: case 'exactLength':
123: # Required property: length
124: if (!isset($v['length'])) {
125: break;
126: }
127:
128: $success = call_user_func_array($func, array($value, $v['length']));
129: if (!$success) {
130: self::setError($id, $rule, $v, $v['length']);
131: }
132: break;
133:
134: case 'min':
135: # Required property: min
136: if (!isset($v['min'])) {
137: break;
138: }
139: $success = call_user_func_array($func, array($value, $v['min']));
140: if (!$success) {
141: self::setError($id, $rule, $v, $v['min']);
142: }
143: break;
144:
145: case 'max':
146: # Required property: max
147: if (!isset($v['max'])) {
148: break;
149: }
150: $success = call_user_func_array($func, array($value, $v['max']));
151: if (!$success) {
152: self::setError($id, $rule, $v, $v['max']);
153: }
154: break;
155:
156: case 'minLength':
157: case 'maxLength':
158: $requiredProperty = $rule == 'minLength' ? 'min' : 'max';
159: if (!isset($v[$requiredProperty])) {
160: break;
161: }
162: $success = call_user_func_array($func, array($value, $v[$requiredProperty]));
163: if (!$success) {
164: self::setError($id, $rule, $v, $v[$requiredProperty]);
165: }
166: break;
167:
168: case 'between':
169: # Required property: min|max
170: if (!isset($v['min']) || !isset($v['max'])) {
171: break;
172: }
173: $success = call_user_func_array($func, array($value, $v['min'], $v['max']));
174: if (!$success) {
175: self::setError($id, $rule, $v, $v['min'], $v['max']);
176: }
177: break;
178:
179: case 'ip':
180: # Required property: protocol
181: if (!isset($v['protocol']) || (isset($v['protocol']) && !in_array($v['protocol'], array('ipv4', 'ipv6')))) {
182: $v['protocol'] = 'ipv4';
183: }
184: $success = call_user_func_array($func, array($value, $v['protocol']));
185: if (!$success) {
186: self::setError($id, $rule, $v);
187: }
188: break;
189:
190: case 'custom':
191: # Required property: pattern
192: if (!isset($v['pattern'])) {
193: break;
194: }
195: $success = call_user_func_array($func, array($value, $v['pattern']));
196: if (!$success) {
197: self::setError($id, $rule, $v);
198: }
199: break;
200:
201: case 'fileMaxSize':
202: # Required property: maxSize
203: if (!isset($v['maxSize'])) {
204: break;
205: }
206: $success = call_user_func_array($func, array($value, $v['maxSize']));
207: if (!$success) {
208: self::setError($id, $rule, $v, $v['maxSize']);
209: }
210: break;
211:
212: case 'fileMaxWidth':
213: # Required property: maxWidth
214: if (!isset($v['maxWidth'])) {
215: break;
216: }
217: $success = call_user_func_array($func, array($value, $v['maxWidth']));
218: if (!$success) {
219: self::setError($id, $rule, $v, $v['maxWidth']);
220: }
221: break;
222:
223: case 'fileMaxHeight':
224: # Required property: maxHeight
225: if (!isset($v['maxHeight'])) {
226: break;
227: }
228: $success = call_user_func_array($func, array($value, $v['maxHeight']));
229: if (!$success) {
230: self::setError($id, $rule, $v, $v['maxHeight']);
231: }
232: break;
233:
234: case 'fileMaxDimension':
235: # Required property: maxWidth, maxHeight
236: if (!isset($v['maxWidth']) || !isset($v['maxHeight'])) {
237: break;
238: }
239: $success = call_user_func_array($func, array($value, $v['maxWidth'], $v['maxHeight']));
240: if (!$success) {
241: self::setError($id, $rule, $v, $v['maxWidth'], $v['maxHeight']);
242: }
243: break;
244:
245: case 'fileExactDimension':
246: # Required property: width, height
247: if (!isset($v['width']) || !isset($v['height'])) {
248: break;
249: }
250: $success = call_user_func_array($func, array($value, $v['width'], $v['height']));
251: if (!$success) {
252: self::setError($id, $rule, $v, $v['width'], $v['height']);
253: }
254: break;
255:
256: case 'fileExtension':
257: # Required property: extensions
258: if (!isset($v['extensions'])) {
259: break;
260: }
261: $success = call_user_func_array($func, array($value, $v['extensions']));
262: if (!$success) {
263: self::setError($id, $rule, $v, implode(', ', $v['extensions']));
264: }
265: break;
266:
267: case 'date':
268: # Optional property: dateFormat
269: if (!isset($v['dateFormat']) || (isset($v['dateFormat']) && empty($v['dateFormat']))) {
270: $v['dateFormat'] = 'y-m-d';
271: }
272: $success = call_user_func_array($func, array($value, $v['dateFormat']));
273: if (!$success) {
274: self::setError($id, $rule, $v, $v['dateFormat']);
275: }
276: break;
277:
278: case 'time':
279: # Optional property: $timeFormat
280: if (!isset($v['timeFormat']) || (isset($v['timeFormat']) && empty($v['timeFormat']))) {
281: $v['timeFormat'] = 'both';
282: }
283: $success = call_user_func_array($func, array($value, $v['timeFormat']));
284: if (!$success) {
285: self::setError($id, $rule, $v, ($v['timeFormat'] === 'both') ? '12/24-hour' : $v['timeFormat'] . '-hour');
286: }
287: break;
288:
289: case 'datetime':
290: # Optional property: dateFormat, timeFormat
291: if (!isset($v['dateFormat']) || (isset($v['dateFormat']) && empty($v['dateFormat']))) {
292: $v['dateFormat'] = 'y-m-d';
293: }
294: if (!isset($v['timeFormat']) || (isset($v['timeFormat']) && empty($v['timeFormat']))) {
295: $v['timeFormat'] = 'both';
296: }
297: $success = call_user_func_array($func, array($value, $v['dateFormat'], $v['timeFormat']));
298: if (!$success) {
299: self::setError($id, $rule, $v, $v['dateFormat'], ($v['timeFormat'] === 'both') ? '12/24-hour' : $v['timeFormat'] . '-hour');
300: }
301: break;
302:
303: case 'unique':
304: # Required property: table, field
305: if (!isset($v['table']) || !isset($v['field'])) {
306: break;
307: }
308:
309: $v['id'] = isset($v['id']) ? $v['id'] : 0;
310:
311: $success = call_user_func_array($func, array($value, $v['table'], $v['field'], $v['id']));
312: if (!$success) {
313: self::setError($id, $rule, $v, $v['table'], $v['field'], $v['id']);
314: }
315: break;
316:
317: default:
318: $success = call_user_func_array($func, array($value));
319: if (!$success) {
320: self::setError($id, $rule, $v);
321: }
322: }
323: if (!$success) {
324: if ($type == 'single') {
325: break 3;
326: }
327: continue 3;
328: }
329: } # if (function_exists($func))
330: } # if (strstr($rule, 'validate_'))
331: } # foreach ($values as $value)
332: } # foreach ($v['rules'] as $rule)
333: } # if (is_array($v['rules']) )
334: } # foreach ($validations as $id => $v)
335:
336: return !count(self::$errors);
337: }
338:
339: /**
340: * @internal
341: * @param string $id
342: * @param string $rule
343: * @param array $element
344: */
345: private static function setError($id, $rule, $element)
346: {
347: $caption = $element['caption'];
348: $msg = isset(self::$messages[$rule]) ? self::$messages[$rule] : self::$messages['default'];
349: $msg = isset($element['messages'][$rule]) ? $element['messages'][$rule] : $msg;
350: $args = func_get_args();
351:
352: if (count($args) > 3) {
353: $args = array_slice($args, 3);
354: array_unshift($args, $caption);
355: self::addError($id, vsprintf($msg, $args));
356: } else {
357: self::addError($id, sprintf($msg, $caption));
358: }
359: }
360:
361: /**
362: * Add an external error message
363: *
364: * @param string $id HTML ID
365: * @param string $msg The error message to show
366: *
367: * @return void
368: */
369: public static function addError($id, $msg)
370: {
371: self::$errors[] = array(
372: 'field' => $id,
373: 'message' => $msg,
374: );
375: }
376: }
377: