PageRenderTime 24ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/application/libraries/Validation.php

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