PageRenderTime 38ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 1ms

/system/libraries/Validation.php

https://github.com/Toushi/flow
PHP | 773 lines | 436 code | 109 blank | 228 comment | 59 complexity | c1a3d3d487b3994e4447487720e248d0 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Validation library.
  4. *
  5. * $Id: Validation.php 3127 2008-07-16 14:43:52Z Shadowhand $
  6. *
  7. * @package Validation
  8. * @author Kohana Team
  9. * @copyright (c) 2007-2008 Kohana Team
  10. * @license http://kohanaphp.com/license.html
  11. */
  12. class Validation_Core extends ArrayObject {
  13. // Unique "any field" key
  14. protected $any_field;
  15. // Array fields
  16. protected $array_fields = array();
  17. // Filters
  18. protected $pre_filters = array();
  19. protected $post_filters = array();
  20. // Rules and callbacks
  21. protected $rules = array();
  22. protected $callbacks = array();
  23. // Rules that are allowed to run on empty fields
  24. protected $empty_rules = array('required', 'matches');
  25. // Errors
  26. protected $errors = array();
  27. protected $messages = array();
  28. // Checks if there is data to validate.
  29. protected $submitted;
  30. /**
  31. * Creates a new Validation instance.
  32. *
  33. * @param array array to use for validation
  34. * @return object
  35. */
  36. public static function factory($array = NULL)
  37. {
  38. return new Validation( ! is_array($array) ? $_POST : $array);
  39. }
  40. /**
  41. * Sets the unique "any field" key and creates an ArrayObject from the
  42. * passed array.
  43. *
  44. * @param array array to validate
  45. * @return void
  46. */
  47. public function __construct(array $array)
  48. {
  49. // Set a dynamic, unique "any field" key
  50. $this->any_field = uniqid(NULL, TRUE);
  51. // Test if there is any actual data
  52. $this->submitted = (count($array) > 0);
  53. parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
  54. }
  55. /**
  56. * Test if the data has been submitted.
  57. *
  58. * @return boolean
  59. */
  60. public function submitted($value = NULL)
  61. {
  62. if (is_bool($value))
  63. {
  64. $this->submitted = $value;
  65. }
  66. return $this->submitted;
  67. }
  68. /**
  69. * Returns the ArrayObject values.
  70. *
  71. * @return array
  72. */
  73. public function as_array()
  74. {
  75. return $this->getArrayCopy();
  76. }
  77. /**
  78. * Returns the ArrayObject values, removing all inputs without rules.
  79. * To choose specific inputs, list the field name as arguments.
  80. *
  81. * @return array
  82. */
  83. public function safe_array()
  84. {
  85. // All the fields that are being validated
  86. $all_fields = array_unique(array_merge
  87. (
  88. array_keys($this->pre_filters),
  89. array_keys($this->rules),
  90. array_keys($this->callbacks),
  91. array_keys($this->post_filters)
  92. ));
  93. // Load choices
  94. $choices = func_get_args();
  95. $choices = empty($choices) ? NULL : array_combine($choices, $choices);
  96. $safe = array();
  97. foreach ($all_fields as $i => $field)
  98. {
  99. // Ignore "any field" key
  100. if ($field === $this->any_field) continue;
  101. if (isset($this->array_fields[$field]))
  102. {
  103. // Use the key field
  104. $field = $this->array_fields[$field];
  105. }
  106. if ($choices === NULL OR isset($choices[$field]))
  107. {
  108. // Make sure all fields are defined
  109. $safe[$field] = isset($this[$field]) ? $this[$field] : NULL;
  110. }
  111. }
  112. return $safe;
  113. }
  114. /**
  115. * Add additional rules that will forced, even for empty fields. All arguments
  116. * passed will be appended to the list.
  117. *
  118. * @chainable
  119. * @param string rule name
  120. * @return object
  121. */
  122. public function allow_empty_rules($rules)
  123. {
  124. // Any number of args are supported
  125. $rules = func_get_args();
  126. // Merge the allowed rules
  127. $this->empty_rules = array_merge($this->empty_rules, $rules);
  128. return $this;
  129. }
  130. /**
  131. * Add a pre-filter to one or more inputs.
  132. *
  133. * @chainable
  134. * @param callback filter
  135. * @param string fields to apply filter to, use TRUE for all fields
  136. * @return object
  137. */
  138. public function pre_filter($filter, $field = TRUE)
  139. {
  140. if ( ! is_callable($filter))
  141. throw new Kohana_Exception('validation.filter_not_callable');
  142. $filter = (is_string($filter) AND strpos($filter, '::') !== FALSE) ? explode('::', $filter) : $filter;
  143. if ($field === TRUE)
  144. {
  145. // Handle "any field" filters
  146. $fields = array($this->any_field);
  147. }
  148. else
  149. {
  150. // Add the filter to specific inputs
  151. $fields = func_get_args();
  152. $fields = array_slice($fields, 1);
  153. }
  154. foreach ($fields as $field)
  155. {
  156. if (strpos($field, '.') > 0)
  157. {
  158. // Field keys
  159. $keys = explode('.', $field);
  160. // Add to array fields
  161. $this->array_fields[$field] = $keys[0];
  162. }
  163. // Add the filter to specified field
  164. $this->pre_filters[$field][] = $filter;
  165. }
  166. return $this;
  167. }
  168. /**
  169. * Add a post-filter to one or more inputs.
  170. *
  171. * @chainable
  172. * @param callback filter
  173. * @param string fields to apply filter to, use TRUE for all fields
  174. * @return object
  175. */
  176. public function post_filter($filter, $field = TRUE)
  177. {
  178. if ( ! is_callable($filter, TRUE))
  179. throw new Kohana_Exception('validation.filter_not_callable');
  180. $filter = (is_string($filter) AND strpos($filter, '::') !== FALSE) ? explode('::', $filter) : $filter;
  181. if ($field === TRUE)
  182. {
  183. // Handle "any field" filters
  184. $fields = array($this->any_field);
  185. }
  186. else
  187. {
  188. // Add the filter to specific inputs
  189. $fields = func_get_args();
  190. $fields = array_slice($fields, 1);
  191. }
  192. foreach ($fields as $field)
  193. {
  194. if (strpos($field, '.') > 0)
  195. {
  196. // Field keys
  197. $keys = explode('.', $field);
  198. // Add to array fields
  199. $this->array_fields[$field] = $keys[0];
  200. }
  201. // Add the filter to specified field
  202. $this->post_filters[$field][] = $filter;
  203. }
  204. return $this;
  205. }
  206. /**
  207. * Add rules to a field. Rules are callbacks or validation methods. Rules can
  208. * only return TRUE or FALSE.
  209. *
  210. * @chainable
  211. * @param string field name
  212. * @param callback rules (unlimited number)
  213. * @return object
  214. */
  215. public function add_rules($field, $rules)
  216. {
  217. // Handle "any field" filters
  218. ($field === TRUE) and $field = $this->any_field;
  219. // Get the rules
  220. $rules = func_get_args();
  221. $rules = array_slice($rules, 1);
  222. foreach ($rules as $rule)
  223. {
  224. // Rule arguments
  225. $args = NULL;
  226. if (is_string($rule))
  227. {
  228. if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
  229. {
  230. // Split the rule into the function and args
  231. $rule = $matches[1];
  232. $args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
  233. // Replace escaped comma with comma
  234. $args = str_replace('\,', ',', $args);
  235. }
  236. if (method_exists($this, $rule))
  237. {
  238. // Make the rule a valid callback
  239. $rule = array($this, $rule);
  240. }
  241. elseif (method_exists('valid', $rule))
  242. {
  243. // Make the rule a callback for the valid:: helper
  244. $rule = array('valid', $rule);
  245. }
  246. }
  247. if ( ! is_callable($rule, TRUE))
  248. throw new Kohana_Exception('validation.rule_not_callable');
  249. $rule = (is_string($rule) AND strpos($rule, '::') !== FALSE) ? explode('::', $rule) : $rule;
  250. if (strpos($field, '.') > 0)
  251. {
  252. // Field keys
  253. $keys = explode('.', $field);
  254. // Add to array fields
  255. $this->array_fields[$field] = $keys[0];
  256. }
  257. // Add the rule to specified field
  258. $this->rules[$field][] = array($rule, $args);
  259. }
  260. return $this;
  261. }
  262. /**
  263. * Add callbacks to a field. Callbacks must accept the Validation object
  264. * and the input name. Callback returns are not processed.
  265. *
  266. * @chainable
  267. * @param string field name
  268. * @param callbacks callbacks (unlimited number)
  269. * @return object
  270. */
  271. public function add_callbacks($field, $callbacks)
  272. {
  273. // Handle "any field" filters
  274. ($field === TRUE) and $field = $this->any_field;
  275. if (func_get_args() > 2)
  276. {
  277. // Multiple callback
  278. $callbacks = array_slice(func_get_args(), 1);
  279. }
  280. else
  281. {
  282. // Only one callback
  283. $callbacks = array($callbacks);
  284. }
  285. foreach ($callbacks as $callback)
  286. {
  287. if ( ! is_callable($callback, TRUE))
  288. throw new Kohana_Exception('validation.callback_not_callable');
  289. $callback = (is_string($callback) AND strpos($callback, '::') !== FALSE) ? explode('::', $callback) : $callback;
  290. if (strpos($field, '.') > 0)
  291. {
  292. // Field keys
  293. $keys = explode('.', $field);
  294. // Add to array fields
  295. $this->array_fields[$field] = $keys[0];
  296. }
  297. // Add the callback to specified field
  298. $this->callbacks[$field][] = $callback;
  299. }
  300. return $this;
  301. }
  302. /**
  303. * Validate by processing pre-filters, rules, callbacks, and post-filters.
  304. * All fields that have filters, rules, or callbacks will be initialized if
  305. * they are undefined. Validation will only be run if there is data already
  306. * in the array.
  307. *
  308. * @return bool
  309. */
  310. public function validate()
  311. {
  312. // All the fields that are being validated
  313. $all_fields = array_unique(array_merge
  314. (
  315. array_keys($this->pre_filters),
  316. array_keys($this->rules),
  317. array_keys($this->callbacks),
  318. array_keys($this->post_filters)
  319. ));
  320. // Copy the array from the object, to optimize multiple sets
  321. $object_array = $this->getArrayCopy();
  322. foreach ($all_fields as $i => $field)
  323. {
  324. if ($field === $this->any_field)
  325. {
  326. // Remove "any field" from the list of fields
  327. unset($all_fields[$i]);
  328. continue;
  329. }
  330. if (substr($field, -2) === '.*')
  331. {
  332. // Set the key to be an array
  333. Kohana::key_string_set($object_array, substr($field, 0, -2), array());
  334. }
  335. else
  336. {
  337. // Set the key to be NULL
  338. Kohana::key_string_set($object_array, $field, NULL);
  339. }
  340. }
  341. // Swap the array back into the object
  342. $this->exchangeArray($object_array);
  343. // Reset all fields to ALL defined fields
  344. $all_fields = array_keys($this->getArrayCopy());
  345. foreach ($this->pre_filters as $field => $calls)
  346. {
  347. foreach ($calls as $func)
  348. {
  349. if ($field === $this->any_field)
  350. {
  351. foreach ($all_fields as $f)
  352. {
  353. // Process each filter
  354. $this[$f] = is_array($this[$f]) ? arr::map_recursive($func, $this[$f]) : call_user_func($func, $this[$f]);
  355. }
  356. }
  357. else
  358. {
  359. // Process each filter
  360. $this[$field] = is_array($this[$field]) ? arr::map_recursive($func, $this[$field]) : call_user_func($func, $this[$field]);
  361. }
  362. }
  363. }
  364. if ($this->submitted === FALSE)
  365. return FALSE;
  366. foreach ($this->rules as $field => $calls)
  367. {
  368. foreach ($calls as $call)
  369. {
  370. // Split the rule into function and args
  371. list($func, $args) = $call;
  372. if ($field === $this->any_field)
  373. {
  374. foreach ($all_fields as $f)
  375. {
  376. if (isset($this->array_fields[$f]))
  377. {
  378. // Use the field key
  379. $f_key = $this->array_fields[$f];
  380. // Prevent other rules from running when this field already has errors
  381. if ( ! empty($this->errors[$f_key])) break;
  382. // Don't process rules on empty fields
  383. if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$f_key] == NULL)
  384. continue;
  385. foreach ($this[$f_key] as $k => $v)
  386. {
  387. if ( ! call_user_func($func, $this[$f_key][$k], $args))
  388. {
  389. // Run each rule
  390. $this->errors[$f_key] = is_array($func) ? $func[1] : $func;
  391. }
  392. }
  393. }
  394. else
  395. {
  396. // Prevent other rules from running when this field already has errors
  397. if ( ! empty($this->errors[$f])) break;
  398. // Don't process rules on empty fields
  399. if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$f] == NULL)
  400. continue;
  401. if ( ! call_user_func($func, $this[$f], $args))
  402. {
  403. // Run each rule
  404. $this->errors[$f] = is_array($func) ? $func[1] : $func;
  405. }
  406. }
  407. }
  408. }
  409. else
  410. {
  411. if (isset($this->array_fields[$field]))
  412. {
  413. // Use the field key
  414. $field_key = $this->array_fields[$field];
  415. // Prevent other rules from running when this field already has errors
  416. if ( ! empty($this->errors[$field_key])) break;
  417. // Don't process rules on empty fields
  418. if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$field_key] == NULL)
  419. continue;
  420. foreach ($this[$field_key] as $k => $val)
  421. {
  422. if ( ! call_user_func($func, $this[$field_key][$k], $args))
  423. {
  424. // Run each rule
  425. $this->errors[$field_key] = is_array($func) ? $func[1] : $func;
  426. // Stop after an error is found
  427. break 2;
  428. }
  429. }
  430. }
  431. else
  432. {
  433. // Prevent other rules from running when this field already has errors
  434. if ( ! empty($this->errors[$field])) break;
  435. // Don't process rules on empty fields
  436. if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$field] == NULL)
  437. continue;
  438. if ( ! call_user_func($func, $this[$field], $args))
  439. {
  440. // Run each rule
  441. $this->errors[$field] = is_array($func) ? $func[1] : $func;
  442. // Stop after an error is found
  443. break;
  444. }
  445. }
  446. }
  447. }
  448. }
  449. foreach ($this->callbacks as $field => $calls)
  450. {
  451. foreach ($calls as $func)
  452. {
  453. if ($field === $this->any_field)
  454. {
  455. foreach ($all_fields as $f)
  456. {
  457. // Execute the callback
  458. call_user_func($func, $this, $f);
  459. // Stop after an error is found
  460. if ( ! empty($errors[$f])) break 2;
  461. }
  462. }
  463. else
  464. {
  465. // Execute the callback
  466. call_user_func($func, $this, $field);
  467. // Stop after an error is found
  468. if ( ! empty($errors[$field])) break;
  469. }
  470. }
  471. }
  472. foreach ($this->post_filters as $field => $calls)
  473. {
  474. foreach ($calls as $func)
  475. {
  476. if ($field === $this->any_field)
  477. {
  478. foreach ($all_fields as $f)
  479. {
  480. if (isset($this->array_fields[$f]))
  481. {
  482. // Use the field key
  483. $f = $this->array_fields[$f];
  484. }
  485. // Process each filter
  486. $this[$f] = is_array($this[$f]) ? array_map($func, $this[$f]) : call_user_func($func, $this[$f]);
  487. }
  488. }
  489. else
  490. {
  491. if (isset($this->array_fields[$field]))
  492. {
  493. // Use the field key
  494. $field = $this->array_fields[$field];
  495. }
  496. // Process each filter
  497. $this[$field] = is_array($this[$field]) ? array_map($func, $this[$field]) : call_user_func($func, $this[$field]);
  498. }
  499. }
  500. }
  501. // Return TRUE if there are no errors
  502. return (count($this->errors) === 0);
  503. }
  504. /**
  505. * Add an error to an input.
  506. *
  507. * @chainable
  508. * @param string input name
  509. * @param string unique error name
  510. * @return object
  511. */
  512. public function add_error($field, $name)
  513. {
  514. if (isset($this[$field]))
  515. {
  516. $this->errors[$field] = $name;
  517. }
  518. return $this;
  519. }
  520. /**
  521. * Sets or returns the message for an input.
  522. *
  523. * @chainable
  524. * @param string input key
  525. * @param string message to set
  526. * @return string|object
  527. */
  528. public function message($input = NULL, $message = NULL)
  529. {
  530. if ($message === NULL)
  531. {
  532. if ($input === NULL)
  533. {
  534. $messages = array();
  535. $keys = array_keys($this->messages);
  536. foreach ($keys as $input)
  537. {
  538. $messages[] = $this->message($input);
  539. }
  540. return implode("\n", $messages);
  541. }
  542. // Return nothing if no message exists
  543. if (empty($this->messages[$input]))
  544. return '';
  545. // Return the HTML message string
  546. return $this->messages[$input];
  547. }
  548. else
  549. {
  550. $this->messages[$input] = $message;
  551. }
  552. return $this;
  553. }
  554. /**
  555. * Return the errors array.
  556. *
  557. * @param boolean load errors from a lang file
  558. * @return array
  559. */
  560. public function errors($file = NULL)
  561. {
  562. if ($file === NULL)
  563. {
  564. return $this->errors;
  565. }
  566. else
  567. {
  568. $errors = array();
  569. foreach ($this->errors as $input => $error)
  570. {
  571. // Key for this input error
  572. $key = "$file.$input.$error";
  573. if (($errors[$input] = Kohana::lang($key)) === $key)
  574. {
  575. // Get the default error message
  576. $errors[$input] = Kohana::lang("$file.$input.default");
  577. }
  578. }
  579. return $errors;
  580. }
  581. }
  582. /**
  583. * Rule: required. Generates an error if the field has an empty value.
  584. *
  585. * @param mixed input value
  586. * @return bool
  587. */
  588. public function required($str)
  589. {
  590. return ! ($str === '' OR $str === NULL OR $str === FALSE OR (is_array($str) AND empty($str)));
  591. }
  592. /**
  593. * Rule: matches. Generates an error if the field does not match one or more
  594. * other fields.
  595. *
  596. * @param mixed input value
  597. * @param array input names to match against
  598. * @return bool
  599. */
  600. public function matches($str, array $inputs)
  601. {
  602. foreach ($inputs as $key)
  603. {
  604. if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
  605. return FALSE;
  606. }
  607. return TRUE;
  608. }
  609. /**
  610. * Rule: length. Generates an error if the field is too long or too short.
  611. *
  612. * @param mixed input value
  613. * @param array minimum, maximum, or exact length to match
  614. * @return bool
  615. */
  616. public function length($str, array $length)
  617. {
  618. if ( ! is_string($str))
  619. return FALSE;
  620. $size = utf8::strlen($str);
  621. $status = FALSE;
  622. if (count($length) > 1)
  623. {
  624. list ($min, $max) = $length;
  625. if ($size >= $min AND $size <= $max)
  626. {
  627. $status = TRUE;
  628. }
  629. }
  630. else
  631. {
  632. $status = ($size === (int) $length[0]);
  633. }
  634. return $status;
  635. }
  636. /**
  637. * Rule: depends_on. Generates an error if the field does not depend on one
  638. * or more other fields.
  639. *
  640. * @param mixed field name
  641. * @param array field names to check dependency
  642. * @return bool
  643. */
  644. public function depends_on($field, array $fields)
  645. {
  646. foreach ($fields as $depends_on)
  647. {
  648. if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
  649. return FALSE;
  650. }
  651. return TRUE;
  652. }
  653. /**
  654. * Rule: chars. Generates an error if the field contains characters outside of the list.
  655. *
  656. * @param string field value
  657. * @param array allowed characters
  658. * @return bool
  659. */
  660. public function chars($value, array $chars)
  661. {
  662. return ! preg_match('![^'.preg_quote(implode(',', $chars)).']!', $value);
  663. }
  664. } // End Validation