PageRenderTime 53ms CodeModel.GetById 2ms RepoModel.GetById 0ms app.codeStats 0ms

/system/libraries/Validation.php

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