PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/system/classes/kohana/validate.php

https://bitbucket.org/alvinpd/monsterninja
PHP | 1084 lines | 525 code | 146 blank | 413 comment | 56 complexity | 5a7630a13911db1dcd7bd3d4bdff8d0b MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Array and variable validation.
  4. *
  5. * @package Kohana
  6. * @category Security
  7. * @author Kohana Team
  8. * @copyright (c) 2008-2009 Kohana Team
  9. * @license http://kohanaphp.com/license
  10. */
  11. class Kohana_Validate extends ArrayObject {
  12. /**
  13. * Creates a new Validation instance.
  14. *
  15. * @param array array to use for validation
  16. * @return object
  17. */
  18. public static function factory(array $array)
  19. {
  20. return new Validate($array);
  21. }
  22. /**
  23. * Checks if a field is not empty.
  24. *
  25. * @return boolean
  26. */
  27. public static function not_empty($value)
  28. {
  29. if (is_object($value) AND $value instanceof ArrayObject)
  30. {
  31. // Get the array from the ArrayObject
  32. $value = $value->getArrayCopy();
  33. }
  34. return ($value === '0' OR ! empty($value));
  35. }
  36. /**
  37. * Checks a field against a regular expression.
  38. *
  39. * @param string value
  40. * @param string regular expression to match (including delimiters)
  41. * @return boolean
  42. */
  43. public static function regex($value, $expression)
  44. {
  45. return (bool) preg_match($expression, (string) $value);
  46. }
  47. /**
  48. * Checks that a field is long enough.
  49. *
  50. * @param string value
  51. * @param integer minimum length required
  52. * @return boolean
  53. */
  54. public static function min_length($value, $length)
  55. {
  56. return UTF8::strlen($value) >= $length;
  57. }
  58. /**
  59. * Checks that a field is short enough.
  60. *
  61. * @param string value
  62. * @param integer maximum length required
  63. * @return boolean
  64. */
  65. public static function max_length($value, $length)
  66. {
  67. return UTF8::strlen($value) <= $length;
  68. }
  69. /**
  70. * Checks that a field is exactly the right length.
  71. *
  72. * @param string value
  73. * @param integer exact length required
  74. * @return boolean
  75. */
  76. public static function exact_length($value, $length)
  77. {
  78. return UTF8::strlen($value) === $length;
  79. }
  80. /**
  81. * Check an email address for correct format.
  82. *
  83. * @link http://www.iamcal.com/publish/articles/php/parsing_email/
  84. * @link http://www.w3.org/Protocols/rfc822/
  85. *
  86. * @param string email address
  87. * @param boolean strict RFC compatibility
  88. * @return boolean
  89. */
  90. public static function email($email, $strict = FALSE)
  91. {
  92. if ($strict === TRUE)
  93. {
  94. $qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
  95. $dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
  96. $atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
  97. $pair = '\\x5c[\\x00-\\x7f]';
  98. $domain_literal = "\\x5b($dtext|$pair)*\\x5d";
  99. $quoted_string = "\\x22($qtext|$pair)*\\x22";
  100. $sub_domain = "($atom|$domain_literal)";
  101. $word = "($atom|$quoted_string)";
  102. $domain = "$sub_domain(\\x2e$sub_domain)*";
  103. $local_part = "$word(\\x2e$word)*";
  104. $expression = "/^$local_part\\x40$domain$/D";
  105. }
  106. else
  107. {
  108. $expression = '/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD';
  109. }
  110. return (bool) preg_match($expression, (string) $email);
  111. }
  112. /**
  113. * Validate the domain of an email address by checking if the domain has a
  114. * valid MX record.
  115. *
  116. * @link http://php.net/checkdnsrr not added to Windows until PHP 5.3.0
  117. *
  118. * @param string email address
  119. * @return boolean
  120. */
  121. public static function email_domain($email)
  122. {
  123. // Check if the email domain has a valid MX record
  124. return (bool) checkdnsrr(preg_replace('/^[^@]++@/', '', $email), 'MX');
  125. }
  126. /**
  127. * Validate a URL.
  128. *
  129. * @param string URL
  130. * @return boolean
  131. */
  132. public static function url($url)
  133. {
  134. return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
  135. }
  136. /**
  137. * Validate an IP.
  138. *
  139. * @param string IP address
  140. * @param boolean allow private IP networks
  141. * @return boolean
  142. */
  143. public static function ip($ip, $allow_private = TRUE)
  144. {
  145. // Do not allow reserved addresses
  146. $flags = FILTER_FLAG_NO_RES_RANGE;
  147. if ($allow_private === FALSE)
  148. {
  149. // Do not allow private or reserved addresses
  150. $flags = $flags | FILTER_FLAG_NO_PRIV_RANGE;
  151. }
  152. return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
  153. }
  154. /**
  155. * Validates a credit card number using the Luhn (mod10) formula.
  156. *
  157. * @link http://en.wikipedia.org/wiki/Luhn_algorithm
  158. *
  159. * @param integer credit card number
  160. * @param string|array card type, or an array of card types
  161. * @return boolean
  162. */
  163. public static function credit_card($number, $type = NULL)
  164. {
  165. // Remove all non-digit characters from the number
  166. if (($number = preg_replace('/\D+/', '', $number)) === '')
  167. return FALSE;
  168. if ($type == NULL)
  169. {
  170. // Use the default type
  171. $type = 'default';
  172. }
  173. elseif (is_array($type))
  174. {
  175. foreach ($type as $t)
  176. {
  177. // Test each type for validity
  178. if (Validate::credit_card($number, $t))
  179. return TRUE;
  180. }
  181. return FALSE;
  182. }
  183. $cards = Kohana::config('credit_cards');
  184. // Check card type
  185. $type = strtolower($type);
  186. if ( ! isset($cards[$type]))
  187. return FALSE;
  188. // Check card number length
  189. $length = strlen($number);
  190. // Validate the card length by the card type
  191. if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
  192. return FALSE;
  193. // Check card number prefix
  194. if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number))
  195. return FALSE;
  196. // No Luhn check required
  197. if ($cards[$type]['luhn'] == FALSE)
  198. return TRUE;
  199. // Checksum of the card number
  200. $checksum = 0;
  201. for ($i = $length - 1; $i >= 0; $i -= 2)
  202. {
  203. // Add up every 2nd digit, starting from the right
  204. $checksum += substr($number, $i, 1);
  205. }
  206. for ($i = $length - 2; $i >= 0; $i -= 2)
  207. {
  208. // Add up every 2nd digit doubled, starting from the right
  209. $double = substr($number, $i, 1) * 2;
  210. // Subtract 9 from the double where value is greater than 10
  211. $checksum += ($double >= 10) ? $double - 9 : $double;
  212. }
  213. // If the checksum is a multiple of 10, the number is valid
  214. return ($checksum % 10 === 0);
  215. }
  216. /**
  217. * Checks if a phone number is valid.
  218. *
  219. * @param string phone number to check
  220. * @return boolean
  221. */
  222. public static function phone($number, $lengths = NULL)
  223. {
  224. if ( ! is_array($lengths))
  225. {
  226. $lengths = array(7,10,11);
  227. }
  228. // Remove all non-digit characters from the number
  229. $number = preg_replace('/\D+/', '', $number);
  230. // Check if the number is within range
  231. return in_array(strlen($number), $lengths);
  232. }
  233. /**
  234. * Tests if a string is a valid date string.
  235. *
  236. * @param string date to check
  237. * @return boolean
  238. */
  239. public static function date($str)
  240. {
  241. return (strtotime($str) !== FALSE);
  242. }
  243. /**
  244. * Checks whether a string consists of alphabetical characters only.
  245. *
  246. * @param string input string
  247. * @param boolean trigger UTF-8 compatibility
  248. * @return boolean
  249. */
  250. public static function alpha($str, $utf8 = FALSE)
  251. {
  252. $str = (string) $str;
  253. if ($utf8 === TRUE)
  254. {
  255. return (bool) preg_match('/^\pL++$/uD', $str);
  256. }
  257. else
  258. {
  259. return ctype_alpha($str);
  260. }
  261. }
  262. /**
  263. * Checks whether a string consists of alphabetical characters and numbers only.
  264. *
  265. * @param string input string
  266. * @param boolean trigger UTF-8 compatibility
  267. * @return boolean
  268. */
  269. public static function alpha_numeric($str, $utf8 = FALSE)
  270. {
  271. if ($utf8 === TRUE)
  272. {
  273. return (bool) preg_match('/^[\pL\pN]++$/uD', $str);
  274. }
  275. else
  276. {
  277. return ctype_alnum($str);
  278. }
  279. }
  280. /**
  281. * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
  282. *
  283. * @param string input string
  284. * @param boolean trigger UTF-8 compatibility
  285. * @return boolean
  286. */
  287. public static function alpha_dash($str, $utf8 = FALSE)
  288. {
  289. if ($utf8 === TRUE)
  290. {
  291. $regex = '/^[-\pL\pN_]++$/uD';
  292. }
  293. else
  294. {
  295. $regex = '/^[-a-z0-9_]++$/iD';
  296. }
  297. return (bool) preg_match($regex, $str);
  298. }
  299. /**
  300. * Checks whether a string consists of digits only (no dots or dashes).
  301. *
  302. * @param string input string
  303. * @param boolean trigger UTF-8 compatibility
  304. * @return boolean
  305. */
  306. public static function digit($str, $utf8 = FALSE)
  307. {
  308. if ($utf8 === TRUE)
  309. {
  310. return (bool) preg_match('/^\pN++$/uD', $str);
  311. }
  312. else
  313. {
  314. return is_int($str) OR ctype_digit($str);
  315. }
  316. }
  317. /**
  318. * Checks whether a string is a valid number (negative and decimal numbers allowed).
  319. *
  320. * Uses {@link http://www.php.net/manual/en/function.localeconv.php locale conversion}
  321. * to allow decimal point to be locale specific.
  322. *
  323. * @param string input string
  324. * @return boolean
  325. */
  326. public static function numeric($str)
  327. {
  328. // Get the decimal point for the current locale
  329. list($decimal) = array_values(localeconv());
  330. return (bool) preg_match('/^-?[0-9'.$decimal.']++$/D', (string) $str);
  331. }
  332. /**
  333. * Tests if a number is within a range.
  334. *
  335. * @param string number to check
  336. * @param integer minimum value
  337. * @param integer maximum value
  338. * @return boolean
  339. */
  340. public static function range($number, $min, $max)
  341. {
  342. return ($number >= $min AND $number <= $max);
  343. }
  344. /**
  345. * Checks if a string is a proper decimal format. Optionally, a specific
  346. * number of digits can be checked too.
  347. *
  348. * @param string number to check
  349. * @param integer number of decimal places
  350. * @param integer number of digits
  351. * @return boolean
  352. */
  353. public static function decimal($str, $places = 2, $digits = NULL)
  354. {
  355. if ($digits > 0)
  356. {
  357. // Specific number of digits
  358. $digits = '{'.(int) $digits.'}';
  359. }
  360. else
  361. {
  362. // Any number of digits
  363. $digits = '+';
  364. }
  365. // Get the decimal point for the current locale
  366. list($decimal) = array_values(localeconv());
  367. return (bool) preg_match('/^[0-9]'.$digits.preg_quote($decimal).'[0-9]{'.(int) $places.'}$/D', $str);
  368. }
  369. /**
  370. * Checks if a string is a proper hexadecimal HTML color value. The validation
  371. * is quite flexible as it does not require an initial "#" and also allows for
  372. * the short notation using only three instead of six hexadecimal characters.
  373. *
  374. * @param string input string
  375. * @return boolean
  376. */
  377. public static function color($str)
  378. {
  379. return (bool) preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/iD', $str);
  380. }
  381. // Field filters
  382. protected $_filters = array();
  383. // Field rules
  384. protected $_rules = array();
  385. // Field callbacks
  386. protected $_callbacks = array();
  387. // Field labels
  388. protected $_labels = array();
  389. // Rules that are executed even when the value is empty
  390. protected $_empty_rules = array('not_empty', 'matches');
  391. // Error list, field => rule
  392. protected $_errors = array();
  393. /**
  394. * Sets the unique "any field" key and creates an ArrayObject from the
  395. * passed array.
  396. *
  397. * @param array array to validate
  398. * @return void
  399. */
  400. public function __construct(array $array)
  401. {
  402. parent::__construct($array, ArrayObject::STD_PROP_LIST);
  403. }
  404. /**
  405. * Copies the current filter/rule/callback to a new array.
  406. *
  407. * $copy = $array->copy($new_data);
  408. *
  409. * @param array new data set
  410. * @return Validation
  411. * @since 3.0.5
  412. */
  413. public function copy(array $array)
  414. {
  415. // Create a copy of the current validation set
  416. $copy = clone $this;
  417. // Replace the data set
  418. $copy->exchangeArray($array);
  419. return $copy;
  420. }
  421. /**
  422. * Returns the array representation of the current object.
  423. *
  424. * @return array
  425. */
  426. public function as_array()
  427. {
  428. return $this->getArrayCopy();
  429. }
  430. /**
  431. * Sets or overwrites the label name for a field.
  432. *
  433. * @param string field name
  434. * @param string label
  435. * @return $this
  436. */
  437. public function label($field, $label)
  438. {
  439. // Set the label for this field
  440. $this->_labels[$field] = $label;
  441. return $this;
  442. }
  443. /**
  444. * Sets labels using an array.
  445. *
  446. * @param array list of field => label names
  447. * @return $this
  448. */
  449. public function labels(array $labels)
  450. {
  451. $this->_labels = $labels + $this->_labels;
  452. return $this;
  453. }
  454. /**
  455. * Overwrites or appends filters to a field. Each filter will be executed once.
  456. * All rules must be valid callbacks.
  457. *
  458. * // Run trim() on all fields
  459. * $validation->filter(TRUE, 'trim');
  460. *
  461. * @param string field name
  462. * @param mixed valid PHP callback
  463. * @param array extra parameters for the callback
  464. * @return $this
  465. */
  466. public function filter($field, $filter, array $params = NULL)
  467. {
  468. if ($field !== TRUE AND ! isset($this->_labels[$field]))
  469. {
  470. // Set the field label to the field name
  471. $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
  472. }
  473. // Store the filter and params for this rule
  474. $this->_filters[$field][$filter] = (array) $params;
  475. return $this;
  476. }
  477. /**
  478. * Add filters using an array.
  479. *
  480. * @param string field name
  481. * @param array list of functions or static method name
  482. * @return $this
  483. */
  484. public function filters($field, array $filters)
  485. {
  486. foreach ($filters as $filter => $params)
  487. {
  488. $this->filter($field, $filter, $params);
  489. }
  490. return $this;
  491. }
  492. /**
  493. * Overwrites or appends rules to a field. Each rule will be executed once.
  494. * All rules must be string names of functions method names.
  495. *
  496. * // The "username" must not be empty and have a minimum length of 4
  497. * $validation->rule('username', 'not_empty')
  498. * ->rule('username', 'min_length', array(4));
  499. *
  500. * @param string field name
  501. * @param string function or static method name
  502. * @param array extra parameters for the callback
  503. * @return $this
  504. */
  505. public function rule($field, $rule, array $params = NULL)
  506. {
  507. if ($field !== TRUE AND ! isset($this->_labels[$field]))
  508. {
  509. // Set the field label to the field name
  510. $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
  511. }
  512. // Store the rule and params for this rule
  513. $this->_rules[$field][$rule] = (array) $params;
  514. return $this;
  515. }
  516. /**
  517. * Add rules using an array.
  518. *
  519. * @param string field name
  520. * @param array list of functions or static method name
  521. * @return $this
  522. */
  523. public function rules($field, array $rules)
  524. {
  525. foreach ($rules as $rule => $params)
  526. {
  527. $this->rule($field, $rule, $params);
  528. }
  529. return $this;
  530. }
  531. /**
  532. * Adds a callback to a field. Each callback will be executed only once.
  533. * No extra parameters can be passed as the format for callbacks is
  534. * predefined as (Validate $array, $field, array $errors).
  535. *
  536. * // The "username" must be checked with a custom method
  537. * $validation->callback('username', array($this, 'check_username'));
  538. *
  539. * To add a callback to every field already set, use TRUE for the field name.
  540. *
  541. * @param string field name
  542. * @param mixed callback to add
  543. * @return $this
  544. */
  545. public function callback($field, $callback)
  546. {
  547. if ( ! isset($this->_callbacks[$field]))
  548. {
  549. // Create the list for this field
  550. $this->_callbacks[$field] = array();
  551. }
  552. if ($field !== TRUE AND ! isset($this->_labels[$field]))
  553. {
  554. // Set the field label to the field name
  555. $this->_labels[$field] = preg_replace('/[^\pL]+/u', ' ', $field);
  556. }
  557. if ( ! in_array($callback, $this->_callbacks[$field], TRUE))
  558. {
  559. // Store the callback
  560. $this->_callbacks[$field][] = $callback;
  561. }
  562. return $this;
  563. }
  564. /**
  565. * Add callbacks using an array.
  566. *
  567. * @param string field name
  568. * @param array list of callbacks
  569. * @return $this
  570. */
  571. public function callbacks($field, array $callbacks)
  572. {
  573. foreach ($callbacks as $callback)
  574. {
  575. $this->callback($field, $callback);
  576. }
  577. return $this;
  578. }
  579. /**
  580. * Executes all validation filters, rules, and callbacks. This should
  581. * typically be called within an if/else block.
  582. *
  583. * if ($validation->check())
  584. * {
  585. * // The data is valid, do something here
  586. * }
  587. *
  588. * @return boolean
  589. */
  590. public function check()
  591. {
  592. if (Kohana::$profiling === TRUE)
  593. {
  594. // Start a new benchmark
  595. $benchmark = Profiler::start('Validation', __FUNCTION__);
  596. }
  597. // New data set
  598. $data = $this->_errors = array();
  599. // Assume nothing has been submitted
  600. $submitted = FALSE;
  601. // Get a list of the expected fields
  602. $expected = array_keys($this->_labels);
  603. // Import the filters, rules, and callbacks locally
  604. $filters = $this->_filters;
  605. $rules = $this->_rules;
  606. $callbacks = $this->_callbacks;
  607. foreach ($expected as $field)
  608. {
  609. if (isset($this[$field]))
  610. {
  611. // Some data has been submitted, continue validation
  612. $submitted = TRUE;
  613. // Use the submitted value
  614. $data[$field] = $this[$field];
  615. }
  616. else
  617. {
  618. // No data exists for this field
  619. $data[$field] = NULL;
  620. }
  621. if (isset($filters[TRUE]))
  622. {
  623. if ( ! isset($filters[$field]))
  624. {
  625. // Initialize the filters for this field
  626. $filters[$field] = array();
  627. }
  628. // Append the filters
  629. $filters[$field] += $filters[TRUE];
  630. }
  631. if (isset($rules[TRUE]))
  632. {
  633. if ( ! isset($rules[$field]))
  634. {
  635. // Initialize the rules for this field
  636. $rules[$field] = array();
  637. }
  638. // Append the rules
  639. $rules[$field] += $rules[TRUE];
  640. }
  641. if (isset($callbacks[TRUE]))
  642. {
  643. if ( ! isset($callbacks[$field]))
  644. {
  645. // Initialize the callbacks for this field
  646. $callbacks[$field] = array();
  647. }
  648. // Append the callbacks
  649. $callbacks[$field] += $callbacks[TRUE];
  650. }
  651. }
  652. // Overload the current array with the new one
  653. $this->exchangeArray($data);
  654. if ($submitted === FALSE)
  655. {
  656. // Because no data was submitted, validation will not be forced
  657. return FALSE;
  658. }
  659. // Remove the filters, rules, and callbacks that apply to every field
  660. unset($filters[TRUE], $rules[TRUE], $callbacks[TRUE]);
  661. // Execute the filters
  662. foreach ($filters as $field => $set)
  663. {
  664. // Get the field value
  665. $value = $this[$field];
  666. foreach ($set as $filter => $params)
  667. {
  668. // Add the field value to the parameters
  669. array_unshift($params, $value);
  670. if (strpos($filter, '::') === FALSE)
  671. {
  672. // Use a function call
  673. $function = new ReflectionFunction($filter);
  674. // Call $function($this[$field], $param, ...) with Reflection
  675. $value = $function->invokeArgs($params);
  676. }
  677. else
  678. {
  679. // Split the class and method of the rule
  680. list($class, $method) = explode('::', $filter, 2);
  681. // Use a static method call
  682. $method = new ReflectionMethod($class, $method);
  683. // Call $Class::$method($this[$field], $param, ...) with Reflection
  684. $value = $method->invokeArgs(NULL, $params);
  685. }
  686. }
  687. // Set the filtered value
  688. $this[$field] = $value;
  689. }
  690. // Execute the rules
  691. foreach ($rules as $field => $set)
  692. {
  693. // Get the field value
  694. $value = $this[$field];
  695. foreach ($set as $rule => $params)
  696. {
  697. if ( ! in_array($rule, $this->_empty_rules) AND ! Validate::not_empty($value))
  698. {
  699. // Skip this rule for empty fields
  700. continue;
  701. }
  702. // Add the field value to the parameters
  703. array_unshift($params, $value);
  704. if (method_exists($this, $rule))
  705. {
  706. // Use a method in this object
  707. $method = new ReflectionMethod($this, $rule);
  708. if ($method->isStatic())
  709. {
  710. // Call static::$rule($this[$field], $param, ...) with Reflection
  711. $passed = $method->invokeArgs(NULL, $params);
  712. }
  713. else
  714. {
  715. // Do not use Reflection here, the method may be protected
  716. $passed = call_user_func_array(array($this, $rule), $params);
  717. }
  718. }
  719. elseif (strpos($rule, '::') === FALSE)
  720. {
  721. // Use a function call
  722. $function = new ReflectionFunction($rule);
  723. // Call $function($this[$field], $param, ...) with Reflection
  724. $passed = $function->invokeArgs($params);
  725. }
  726. else
  727. {
  728. // Split the class and method of the rule
  729. list($class, $method) = explode('::', $rule, 2);
  730. // Use a static method call
  731. $method = new ReflectionMethod($class, $method);
  732. // Call $Class::$method($this[$field], $param, ...) with Reflection
  733. $passed = $method->invokeArgs(NULL, $params);
  734. }
  735. if ($passed === FALSE)
  736. {
  737. // Remove the field name from the parameters
  738. array_shift($params);
  739. // Add the rule to the errors
  740. $this->error($field, $rule, $params);
  741. // This field has an error, stop executing rules
  742. break;
  743. }
  744. }
  745. }
  746. // Execute the callbacks
  747. foreach ($callbacks as $field => $set)
  748. {
  749. if (isset($this->_errors[$field]))
  750. {
  751. // Skip any field that already has an error
  752. continue;
  753. }
  754. foreach ($set as $callback)
  755. {
  756. if (is_string($callback) AND strpos($callback, '::') !== FALSE)
  757. {
  758. // Make the static callback into an array
  759. $callback = explode('::', $callback, 2);
  760. }
  761. if (is_array($callback))
  762. {
  763. // Separate the object and method
  764. list ($object, $method) = $callback;
  765. // Use a method in the given object
  766. $method = new ReflectionMethod($object, $method);
  767. if ( ! is_object($object))
  768. {
  769. // The object must be NULL for static calls
  770. $object = NULL;
  771. }
  772. // Call $object->$method($this, $field, $errors) with Reflection
  773. $method->invoke($object, $this, $field);
  774. }
  775. else
  776. {
  777. // Use a function call
  778. $function = new ReflectionFunction($callback);
  779. // Call $function($this, $field, $errors) with Reflection
  780. $function->invoke($this, $field);
  781. }
  782. if (isset($this->_errors[$field]))
  783. {
  784. // An error was added, stop processing callbacks
  785. break;
  786. }
  787. }
  788. }
  789. if (isset($benchmark))
  790. {
  791. // Stop benchmarking
  792. Profiler::stop($benchmark);
  793. }
  794. return empty($this->_errors);
  795. }
  796. /**
  797. * Add an error to a field.
  798. *
  799. * @param string field name
  800. * @param string error message
  801. * @return $this
  802. */
  803. public function error($field, $error, array $params = NULL)
  804. {
  805. $this->_errors[$field] = array($error, $params);
  806. return $this;
  807. }
  808. /**
  809. * Returns the error messages. If no file is specified, the error message
  810. * will be the name of the rule that failed. When a file is specified, the
  811. * message will be loaded from "field/rule", or if no rule-specific message
  812. * exists, "field/default" will be used. If neither is set, the returned
  813. * message will be "file/field/rule".
  814. *
  815. * By default all messages are translated using the default language.
  816. * A string can be used as the second parameter to specified the language
  817. * that the message was written in.
  818. *
  819. * // Get errors from messages/forms/login.php
  820. * $errors = $validate->errors('forms/login');
  821. *
  822. * @uses Kohana::message
  823. * @param string file to load error messages from
  824. * @param mixed translate the message
  825. * @return array
  826. */
  827. public function errors($file = NULL, $translate = TRUE)
  828. {
  829. if ($file === NULL)
  830. {
  831. // Return the error list
  832. return $this->_errors;
  833. }
  834. // Create a new message list
  835. $messages = array();
  836. foreach ($this->_errors as $field => $set)
  837. {
  838. list($error, $params) = $set;
  839. // Get the label for this field
  840. $label = $this->_labels[$field];
  841. if ($translate)
  842. {
  843. // Translate the label
  844. $label = __($label);
  845. }
  846. // Start the translation values list
  847. $values = array(':field' => $label);
  848. if ($params)
  849. {
  850. // Value passed to the callback
  851. $values[':value'] = array_shift($params);
  852. if (is_array($values[':value']))
  853. {
  854. // All values must be strings
  855. $values[':value'] = implode(', ', Arr::flatten($values[':value']));
  856. }
  857. foreach ($params as $key => $value)
  858. {
  859. if (is_array($value))
  860. {
  861. // All values must be strings
  862. $value = implode(', ', Arr::flatten($value));
  863. }
  864. // Check if a label for this parameter exists
  865. if (isset($this->_labels[$value]))
  866. {
  867. $value = $this->_labels[$value];
  868. if ($translate)
  869. {
  870. // Translate the label
  871. $value = __($value);
  872. }
  873. }
  874. // Add each parameter as a numbered value, starting from 1
  875. $values[':param'.($key + 1)] = $value;
  876. }
  877. }
  878. else
  879. {
  880. // No value is present
  881. $values[':value'] = NULL;
  882. }
  883. if ($message = Kohana::message($file, "{$field}.{$error}"))
  884. {
  885. // Found a message for this field and error
  886. }
  887. elseif ($message = Kohana::message($file, "{$field}.default"))
  888. {
  889. // Found a default message for this field
  890. }
  891. elseif ($message = Kohana::message($file, $error))
  892. {
  893. // Found a default message for this error
  894. }
  895. elseif ($message = Kohana::message('validate', $error))
  896. {
  897. // Found a default message for this error
  898. }
  899. else
  900. {
  901. // No message exists, display the path expected
  902. $message = "{$file}.{$field}.{$error}";
  903. }
  904. if ($translate == TRUE)
  905. {
  906. if (is_string($translate))
  907. {
  908. // Translate the message using specified language
  909. $message = __($message, $values, $translate);
  910. }
  911. else
  912. {
  913. // Translate the message using the default language
  914. $message = __($message, $values);
  915. }
  916. }
  917. else
  918. {
  919. // Do not translate, just replace the values
  920. $message = strtr($message, $values);
  921. }
  922. // Set the message for this field
  923. $messages[$field] = $message;
  924. }
  925. return $messages;
  926. }
  927. /**
  928. * Checks if a field matches the value of another field.
  929. *
  930. * @param string field value
  931. * @param string field name to match
  932. * @return boolean
  933. */
  934. protected function matches($value, $match)
  935. {
  936. return ($value === $this[$match]);
  937. }
  938. } // End Validation