PageRenderTime 27ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Controller/Validator/Basic.php

http://github.com/atk4/atk4
PHP | 1123 lines | 832 code | 117 blank | 174 comment | 58 complexity | c320e8cb096d1f967f9c3bb1e7b2bb8b MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1. <?php
  2. class Controller_Validator_Basic extends Controller_Validator_Abstract
  3. {
  4. public function init()
  5. {
  6. parent::init();
  7. $this->alias = array_merge(
  8. $this->alias,
  9. array(
  10. 'same' => 'eq',
  11. 'different' => 'ne',
  12. )
  13. );
  14. }
  15. /* \section Value Test Rules: General */
  16. public function rule_required($a)
  17. {
  18. if ($a === '' || $a === false || $a === null) {
  19. return $this->fail('must not be empty');
  20. }
  21. }
  22. public function rule_len($a)
  23. {
  24. if (!is_string($a)) {
  25. return $this->fail('must be a string');
  26. }
  27. $this->prefix = 'length of ';
  28. return strlen($a);
  29. }
  30. /**
  31. * Requires a regex pattern as the
  32. * next rule in the chain.
  33. *
  34. * Please give your rule a custom error message.
  35. */
  36. public function rule_regex($a)
  37. {
  38. $opt = array();
  39. $rule = $this->pullRule();
  40. if ($rule[0] != '/') {
  41. $rule = '/^'.$rule.'*$/';
  42. }
  43. $opt['regexp'] = $rule;
  44. if (!filter_var($a, FILTER_VALIDATE_REGEXP, ['options' => $opt])) {
  45. return $this->fail('does not match the pattern');
  46. }
  47. }
  48. /* \section Value Test Rules: Value Lists */
  49. /**
  50. * Checks that value is in the list provided.
  51. * Syntax: |in|foo,bar,foobar.
  52. */
  53. public function rule_in($a)
  54. {
  55. $vals = $this->prep_in_vals($a);
  56. if (!in_array($a, $vals)) {
  57. return $this->fail('has an invalid value');
  58. }
  59. }
  60. /**
  61. * Checks that value is not in the list provided.
  62. * Syntax: |not_in|foo,bar,foobar.
  63. */
  64. public function rule_not_in($a)
  65. {
  66. $vals = $this->prep_in_vals($a);
  67. if (in_array($a, $vals)) {
  68. return $this->fail('has an invalid value');
  69. }
  70. }
  71. /* \section Value Comparison Rules: absolute value */
  72. /**
  73. * Inclusive range check.
  74. *
  75. * Overloaded: checks value for numbers,
  76. * string-length for other values.
  77. *
  78. * Next 2 rules must specify the min and max
  79. */
  80. public function rule_between($a)
  81. {
  82. $min = $this->pullRule(true);
  83. $max = $this->pullRule(true);
  84. if (is_numeric($a)) {
  85. if ($a < $min || $a > $max) {
  86. return $this->fail('must be between {{arg1}} and {{arg2}}', $min, $max);
  87. }
  88. } else {
  89. return $this->fail('Must be a numeric value');
  90. }
  91. }
  92. public function rule_gt($a)
  93. {
  94. $target = $this->pullRule(true);
  95. if ($a <= $target) {
  96. return $this->fail('must be greater than {{arg1}}', $target);
  97. }
  98. return $a;
  99. }
  100. public function rule_lt($a)
  101. {
  102. $target = $this->pullRule(true);
  103. if ($a >= $target) {
  104. return $this->fail('must be less than {{arg1}}', $target);
  105. }
  106. return $a;
  107. }
  108. public function rule_gte($a)
  109. {
  110. $target = $this->pullRule(true);
  111. if ($a < $target) {
  112. return $this->fail('must be at least {{arg1}}', $target);
  113. }
  114. return $a;
  115. }
  116. public function rule_lte($a)
  117. {
  118. $target = $this->pullRule(true);
  119. if ($a > $target) {
  120. return $this->fail('must be not greater than {{arg1}}', $target);
  121. }
  122. return $a;
  123. }
  124. public function rule_eq($a)
  125. {
  126. $target = $this->pullRule();
  127. if ($a !== $target) {
  128. return $this->fail('must be exactly {{arg1}}', $target);
  129. }
  130. return $a;
  131. }
  132. public function rule_eqf($a)
  133. {
  134. $target=$this->pullRule(true);
  135. if ($a !== $this->get($target)) {
  136. return $this->fail('must be same as {{arg1}}', $target);
  137. }
  138. return $a;
  139. }
  140. /* \section Value Test Rules: Numeric */
  141. /**
  142. * Checks for int or float.
  143. */
  144. public function rule_number($a)
  145. {
  146. if (!is_numeric($a)) {
  147. return $this->fail('must be a number');
  148. }
  149. }
  150. public function rule_decimal($a)
  151. {
  152. if (!preg_match('/^[0-9]+\.[0-9]+$/', $a)) {
  153. return $this->fail('must be a decimal number: eg 12.34');
  154. }
  155. }
  156. public function rule_phone($a)
  157. {
  158. if (strlen(preg_replace('/[^0-9]/', '', $a)) < 6) {
  159. return $this->fail('must contain valid phone number');
  160. }
  161. }
  162. /**
  163. * Checks for a specific number of
  164. * decimal places.
  165. *
  166. * Arg: int- number of places
  167. */
  168. public function rule_decimal_places($a)
  169. {
  170. $places = $this->pullRule();
  171. $pattern = sprintf('/^[0-9]+\.[0-9]{%s}$/', $places);
  172. if (!preg_match($pattern, $a)) {
  173. return $this->fail('Must have {{arg1}} decimal places', $places);
  174. }
  175. }
  176. public function rule_int($a)
  177. {
  178. if (!preg_match('/^[0-9]*$/', $a)) {
  179. return $this->fail('Must be an integer: eg 1234');
  180. }
  181. }
  182. /* \section Value Test Rules: String */
  183. /**
  184. * Test for A-Za-z.
  185. */
  186. public function rule_alpha($a)
  187. {
  188. $msg = 'must contain only letters';
  189. if (!preg_match('/^([A-Za-z])*$/', $a)) {
  190. return $this->fail($msg);
  191. }
  192. }
  193. /**
  194. * Test for A-Za-z0-9.
  195. */
  196. public function rule_alphanum($a)
  197. {
  198. $msg = 'must contain only digits and letters';
  199. if (!preg_match('/^([a-zA-Z0-9])*$/', $a)) {
  200. return $this->fail($msg);
  201. }
  202. }
  203. public function rule_type($a)
  204. {
  205. $this->prefix = 'type of ';
  206. return gettype($a);
  207. }
  208. public function rule_class($a)
  209. {
  210. if (!is_object($a)) {
  211. return $this->fail('is not an object');
  212. }
  213. $this->prefix = 'class of ';
  214. return get_class($a);
  215. }
  216. /* \section Value Test Rules: Boolean */
  217. /**
  218. * Validate for true|false|t|f|1|0|yes|no|y|n.
  219. *
  220. * Normalizes to lower case
  221. */
  222. public function rule_bool($a)
  223. {
  224. // We don't use PHP inbuilt test - a bit restrictive
  225. // Changes PHP true/false to 1, 0
  226. $a = strtolower($a);
  227. $vals = array('true', 'false', 't', 'f', 1, 0, 'yes', 'no', 'y', 'n');
  228. if (!in_array($a, $vals)) {
  229. return $this->fail('Must be a boolean value');
  230. }
  231. }
  232. /**
  233. * Validate for true|t|1|yes|y.
  234. *
  235. * Normalizes to lower case
  236. *
  237. * Useful for 'if' rules
  238. */
  239. public function rule_true($a)
  240. {
  241. // Changes PHP true to 1
  242. $a = strtolower($a);
  243. $vals = array('true', 't', 1, 'yes', 'y');
  244. if (!in_array($a, $vals)) {
  245. return $this->fail('Must be true');
  246. }
  247. }
  248. /**
  249. * Validate for false|f|0|no|n.
  250. *
  251. * Normalizes to lower case
  252. *
  253. * Useful for 'if' rules
  254. */
  255. public function rule_false($a)
  256. {
  257. // Changes PHP false to 0
  258. $a = strtolower($a);
  259. $vals = array('false', 'f', 0, 'no', 'n');
  260. if (!in_array($a, $vals)) {
  261. return $this->fail('Must be false');
  262. }
  263. }
  264. /* \section Value Test Rules: Date & Time */
  265. /**
  266. * Validate for ISO date in format YYYY-MM-DD.
  267. *
  268. * Also checks for valid month and day values
  269. */
  270. public function validate_iso_date($a)
  271. {
  272. $date = explode('-', $a);
  273. $msg = 'Must be date in format: YYYY-MMM-DD';
  274. if (count($date) != 3) {
  275. return $this->fail($msg);
  276. }
  277. if (strlen($date[0]) !== 4 || strlen($date[1]) !== 2 || strlen($date[2]) !== 2) {
  278. return $this->fail($msg);
  279. }
  280. if (!@checkdate($date[1], $date[2], $date[0])) {
  281. return $this->fail($msg);
  282. }
  283. }
  284. /**
  285. * Validate for ISO time.
  286. *
  287. * Requires a complete hour:minute:second time with
  288. * the optional ':' separators.
  289. *
  290. * Checks for hh:mm[[:ss][.**..] where * = microseconds
  291. * Also checks for valid # of hours, mins, secs
  292. */
  293. public function rule_iso_time($a)
  294. {
  295. $pattern = "/^([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:(?:\.[0-9]{1,}))?)?$/";
  296. $msg = 'Must be a valid ISO time';
  297. if (preg_match($pattern, $a, $matches)) {
  298. if ($matches[1] > 24) {
  299. return $this->fail($msg);
  300. }
  301. if ($matches[2] > 59) {
  302. return $this->fail($msg);
  303. }
  304. if (isset($matches[3]) && $matches[3] > 59) {
  305. return $this->fail($msg);
  306. }
  307. } else {
  308. return $this->fail($msg);
  309. }
  310. }
  311. /**
  312. * Validate ISO datetime in the format:.
  313. *
  314. * YYYY-MM-DD hh:mm:ss with optional microseconds
  315. */
  316. public function rule_iso_datetime($a)
  317. {
  318. $parts = explode(' ', $a);
  319. $msg = 'Must be a valid ISO datetime';
  320. if (count($parts) != 2) {
  321. return $this->fail($msg);
  322. }
  323. try {
  324. $this->rule_iso_date($parts[0]);
  325. } catch (Exception $e) {
  326. return $this->fail($msg);
  327. }
  328. try {
  329. $this->rule_iso_time($parts[1]);
  330. } catch (Exception $e) {
  331. return $this->fail($msg);
  332. }
  333. }
  334. /**
  335. * Checks any PHP datetime format:
  336. * http://www.php.net/manual/en/datetime.formats.date.php.
  337. */
  338. public function rule_before($a)
  339. {
  340. $time = $this->pullRule();
  341. if (strtotime($a) >= strtotime($time)) {
  342. return $this->fail('Must be before {{arg1}}', $time);
  343. }
  344. }
  345. /**
  346. * Checks any PHP datetime format:
  347. * http://www.php.net/manual/en/datetime.formats.date.php.
  348. */
  349. public function rule_after($a)
  350. {
  351. $time = $this->pullRule();
  352. if (strtotime($a) <= strtotime($time)) {
  353. return $this->fail('Must be after {{arg1}}', $time);
  354. }
  355. }
  356. /* \section Value Test Rules: Postal, Email & Credit Card */
  357. public function rule_email($a)
  358. {
  359. if (!filter_var($a, FILTER_VALIDATE_EMAIL)) {
  360. return $this->fail('Must be a valid email address');
  361. }
  362. }
  363. /**
  364. * Checks for a 5 digit or extended US zip.
  365. */
  366. public function rule_zip($a)
  367. {
  368. if (!preg_match('/^\d{5}(-\d{4})?$/', $a)) {
  369. return $this->fail('Must be a valid ZIP code');
  370. }
  371. }
  372. /**
  373. * Validate for credit card number.
  374. *
  375. * Uses the Luhn Mod 10 check
  376. */
  377. public function rule_credit_card($a)
  378. {
  379. // Card formats keep changing and there is too high a risk
  380. // of false negatives if we get clever. So we just check it
  381. // with the Luhn Mod 10 formula
  382. // Calculate the Luhn check number
  383. $msg = 'Not a valid card number';
  384. $sum = 0;
  385. $alt = false;
  386. for ($i = strlen($a) - 1; $i >= 0; --$i) {
  387. $n = substr($a, $i, 1);
  388. if ($alt) {
  389. //square n
  390. $n *= 2;
  391. if ($n > 9) {
  392. //calculate remainder
  393. $n = ($n % 10) + 1;
  394. }
  395. }
  396. $sum += $n;
  397. $alt = !$alt;
  398. }
  399. // If $sum divides exactly by 10 it's valid
  400. if (!($sum % 10 == 0)) {
  401. return $this->fail($msg);
  402. } else {
  403. // Luhn check seems to return true for any string of 0s
  404. $stripped = str_replace('0', '', $a);
  405. if (strlen($stripped) == 0) {
  406. return $this->fail($msg);
  407. }
  408. }
  409. }
  410. /**
  411. * Validate a card "expires end" date.
  412. */
  413. public function rule_card_to_date($a)
  414. {
  415. $msg = 'Not a valid date';
  416. if (!$this->card_date_parser($a, 'to')) {
  417. return $this->fail($msg);
  418. }
  419. }
  420. /**
  421. * Validate a card "valid from" date.
  422. */
  423. public function rule_card_from_date($a)
  424. {
  425. $msg = 'Not a valid date';
  426. if (!$this->card_date_parser($a, 'from')) {
  427. return $this->fail($msg);
  428. }
  429. }
  430. /* \section Value Conversion Rules: General */
  431. /**
  432. * Strips out the characters matched by the
  433. * pattern in the argument.
  434. *
  435. * The pattern should include the pattern delimiter, eg:
  436. *
  437. * /my_pattern/
  438. */
  439. public function to_strip_regex($a)
  440. {
  441. $pattern = $this->pullRule();
  442. return preg_replace($pattern, '', $a);
  443. }
  444. /* \section Value Conversion Rules: Numeric */
  445. public function rule_to_int($a)
  446. {
  447. return (int) $a = preg_replace('/[^0-9]/', '', $a);
  448. }
  449. public function rule_to_number($a)
  450. {
  451. return (int) $a = preg_replace('/[^0-9\.]/', '', $a);
  452. }
  453. public function rule_to_float($a)
  454. {
  455. return (int) $a = preg_replace('/[^0-9\.]/', '', $a);
  456. }
  457. public function rule_to_digits_and_single_spaces($a)
  458. {
  459. $a = preg_replace("/[^\d ]/", '', $a);
  460. return $this->rule_strip_extra_space($a);
  461. }
  462. /* \section Value Conversion Rules: String */
  463. public function rule_to_trim($a)
  464. {
  465. return trim($a);
  466. }
  467. public function rule_to_ltrim($a)
  468. {
  469. return ltrim($a);
  470. }
  471. public function rule_to_rtrim($a)
  472. {
  473. return rtrim($a);
  474. }
  475. /**
  476. * Strip out all white space.
  477. */
  478. public function rule_to_strip_space($a)
  479. {
  480. return preg_replace("/\s/", '', $a);
  481. }
  482. /**
  483. * Reduce sequential whitespaces to a single space.
  484. */
  485. public function rule_to_strip_extra_space($a)
  486. {
  487. return $this->strip_excess_whitespace($a);
  488. }
  489. /**
  490. * Strip to A-Za-z0-9.
  491. */
  492. public function rule_to_alpha($a)
  493. {
  494. return preg_replace('/[^a-zA-Z]/', '', $a);
  495. }
  496. /**
  497. * Test for unicode letter characters.
  498. *
  499. * Should work even is PCRE compiled
  500. * without "--enable-unicode-properties".
  501. */
  502. public function rule_to_alpha_unicode($a)
  503. {
  504. return preg_replace('/(*UTF8)[^\p{L}]/u', '', $a);
  505. }
  506. public function rule_to_alpha_num($a)
  507. {
  508. return preg_replace('/[^a-zA-Z0-9]/', '', $a);
  509. }
  510. public function rule_to_lower($a)
  511. {
  512. return strtolower($a);
  513. }
  514. public function rule_to_upper($a)
  515. {
  516. return $this->mb_str_to_upper($a);
  517. }
  518. public function rule_to_upper_words($a)
  519. {
  520. return $this->mb_str_to_upper_words($a);
  521. }
  522. /* \section Value Conversion Rules: Alphanumeric */
  523. /**
  524. * Strip to unicode letter characters and 0-9.
  525. *
  526. * Should work even is PCRE compiled
  527. * without "--enable-unicode-properties".
  528. */
  529. public function rule_to_alpha_num_unicode($a)
  530. {
  531. return preg_replace('/(*UTF8)[^\p{L}0-9]/u', '', $a);
  532. }
  533. public function rule_to_alpha_num_dash($a)
  534. {
  535. return preg_replace('/[^a-zA-Z0-9_-]/', '', $a);
  536. }
  537. /**
  538. * Truncates, and adds '...' to
  539. * end of string.
  540. *
  541. * Requires parameter 'length'
  542. */
  543. public function rule_to_truncate($a)
  544. {
  545. $len = $this->pullRule();
  546. return $this->mb_truncate($a, $len);
  547. }
  548. /**
  549. * Requires parameters: length,
  550. * custom string to end of string.
  551. */
  552. public function rule_to_truncate_custom($a)
  553. {
  554. $len = $this->pullRule();
  555. $append = $this->pullRule();
  556. return $this->mb_truncate($a, $len, $append);
  557. }
  558. /**
  559. * Strip to unicode letter characters and 0-9, -, _.
  560. *
  561. * Requires PCRE compiled with "--enable-unicode-properties".
  562. * Most distros these days will offer this
  563. */
  564. public function rule_to_alpha_num_dash_unicode($a)
  565. {
  566. return preg_replace('/(*UTF8)[^\p{L}0-9_-]/u', '', $a);
  567. }
  568. /* \section Value Conversion Rules: Date & Time */
  569. public function rule_to_iso_date($a)
  570. {
  571. $a = preg_replace("/[^T0-9\/\-\(\): ]/", '', $a);
  572. return $this->rule_iso_date($a);
  573. }
  574. /* \section Value Conversion Rules: Data Sanitization */
  575. public function rule_to_strip_tags($a)
  576. {
  577. return strip_tags($a);
  578. }
  579. public function rule_to_quote_meta($a)
  580. {
  581. return quotemeta($a);
  582. }
  583. public function rule_to_add_slashes($a)
  584. {
  585. return addslashes($a);
  586. }
  587. public function rule_to_strip_slashes($a)
  588. {
  589. return stripslashes($a);
  590. }
  591. /**
  592. * Strip out attack characters from names & addresses
  593. * and other strings where they have no place.
  594. *
  595. * Strips: * ^ <> ? ! () | / \ [] + = % ; ~ `
  596. */
  597. public function rule_to_strip_nasties($a)
  598. {
  599. return preg_replace("|[\*\^<?>!\"\(\)\|\\\\/\[\]\+=#%;~`]|", '', $a);
  600. }
  601. /* \section Value Conversion Rules: Phone, Name, Address */
  602. /**
  603. * Normalizes, then validates.
  604. */
  605. public function rule_to_zip($a)
  606. {
  607. // Change 12345 1234 to 12345-1234
  608. $a = preg_replace('/[^0-9-]/', '', $a);
  609. $this->rule_zip($a);
  610. }
  611. // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  612. // PERSONAL NAMES (European style)
  613. // *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  614. /**
  615. * Useful for cleaning up input where you don't want to present
  616. * an error to the user - eg a checkout where ease of use is
  617. * more important than accuracy. Can save a lot of re-keying.
  618. *
  619. * Some libraries don't clean up names if already in mixed case.
  620. * Experience shows this isn't very useful, as many users
  621. * will type mAry, JOSepH etc.
  622. *
  623. * Can only be a best guess - but much better than nothing:
  624. * has been in production for years without any negative
  625. * customer feedback.
  626. *
  627. * Set $is_capitalise_prefix if you want prefixes in upper:
  628. * Von Trapp vs von Trapp. You would do this to format
  629. * a last name for use in salutations:
  630. *
  631. * Dear Mr Von Trapp
  632. *
  633. * Can handle full names, 1st only, middle only, last only.
  634. *
  635. * Cleans up extra whitespace.
  636. */
  637. public function rule_to_name($name, $is_capitalise_prefix = false)
  638. {
  639. /*
  640. A name can have up to 5 components, space delimited:
  641. Worst case:
  642. salutation | forenames | prefix(es) | main name | suffix
  643. Ms | Jo-Sue Ellen | de la | Mer-Savarin | III
  644. Rules for forenames
  645. 1) Capitalise 1st char and after a hyphen.
  646. Rules for special case prefixes: von, de etc
  647. 1) Set capitalisation at runtime.
  648. There seem to be no fixed rules, but
  649. lower case is commonly used as part of a
  650. whole name:
  651. John von Trapp
  652. While it is normally capitalised as part of
  653. a salutation:
  654. Dear Mr Von Trapp
  655. By default we store in the lower case form.
  656. Set the param $is_capitalise_prefix to true
  657. to capitalise.
  658. 2) In default mode, St is capitalised,
  659. other prefixes lower cased. We retain user's
  660. choice of punctuation with St./St
  661. Rules for main name:
  662. 1) Capitalise after a hyphen in the main name:
  663. Smythington-Fenwick
  664. 2) Capitalise after Mc at start of name -
  665. this is pretty much a universal rule
  666. MCDONALD => McDonald
  667. 3) Unless user has capitalised after the M,
  668. do NOT capitalise after Mac at start of name:
  669. - Many Scottish Mac names
  670. are not capitalised: Macaulay, Macdonald
  671. - Many non-Scottish names start with Mac:
  672. eg Macon - we want to avoid "MacOn";
  673. - The Cpan name modules and some style manuals
  674. force MacDonald, but this seems to
  675. create more problems than it solves.
  676. macrae => Macrae
  677. 4) Capitalise after O'
  678. o'grady => O'Grady
  679. */
  680. // If name string is empty, bail out
  681. if (empty($name)) {
  682. return '';
  683. }
  684. // Setup special case prefix lookup list.
  685. // These are prefixes that are not capitalised.
  686. // Prefixes which are capitalised such as "St"
  687. // can be omitted.
  688. // We omit prefixes that are also common names,
  689. // such as "Della", "Di" and "Ben"
  690. $prefixes = array(
  691. 'ap' => array('upper' => 'Ap', 'lower' => 'ap'),
  692. 'da' => array('upper' => 'Da', 'lower' => 'da'),
  693. 'de' => array('upper' => 'De', 'lower' => 'de'),
  694. 'del' => array('upper' => 'Del', 'lower' => 'del'),
  695. 'der' => array('upper' => 'Der', 'lower' => 'der'),
  696. 'du' => array('upper' => 'Du', 'lower' => 'du'),
  697. 'la' => array('upper' => 'La', 'lower' => 'la'),
  698. 'le' => array('upper' => 'Le', 'lower' => 'le'),
  699. 'lo' => array('upper' => 'Lo', 'lower' => 'lo'),
  700. 'van' => array('upper' => 'Van', 'lower' => 'van'),
  701. 'von' => array('upper' => 'Von', 'lower' => 'von'),
  702. );
  703. // Set up suffix lookup list
  704. // We preserve user's preferred punctuation: Sr./Sr
  705. $suffixes = array(
  706. 'i' => 'I',
  707. 'ii' => 'II',
  708. 'iii' => 'III',
  709. 'iv' => 'IV',
  710. 'v' => 'V',
  711. 'vi' => 'VI',
  712. 'vii' => 'VII',
  713. 'viii' => 'VIII',
  714. 'ix' => 'IX',
  715. 'x' => 'X',
  716. 'jr.' => 'Jr.',
  717. 'jr' => 'Jr',
  718. 'jnr.' => 'Jnr.',
  719. 'jnr' => 'Jnr',
  720. 'sr.' => 'Sr.',
  721. 'sr' => 'Sr',
  722. 'snr.' => 'Snr.',
  723. 'snr' => 'Snr',
  724. '1st' => '1st',
  725. '2nd' => '2nd',
  726. '3rd' => '3rd',
  727. '4th' => '4th',
  728. '5th' => '5th',
  729. '6th' => '6th',
  730. '7th' => '7th',
  731. '8th' => '8th',
  732. '9th' => '9th',
  733. '10th' => '10th',
  734. '1st.' => '1st.',
  735. '2nd.' => '2nd.',
  736. '3rd.' => '3rd.',
  737. '4th.' => '4th.',
  738. '5th.' => '5th.',
  739. '6th.' => '6th.',
  740. '7th.' => '7th.',
  741. '8th.' => '8th.',
  742. '9th.' => '9th.',
  743. '10th.' => '10th.',
  744. );
  745. // Clean out extra whitespace
  746. $name = $this->strip_excess_whitespace(trim($name));
  747. // Try to parse into forenames, main name, suffix
  748. $parts = explode(' ', $name);
  749. if (count($parts) == 1) {
  750. // Must be the main name
  751. $name_main = array_pop($parts);
  752. $name_fname = false;
  753. $name_suffix = false;
  754. } else {
  755. // We have more than one part to parse
  756. // Is the last part a suffix?
  757. // We assume name can have only one suffix
  758. $part = array_pop($parts);
  759. $normalised_part = strtolower($part);
  760. if (array_key_exists($normalised_part, $suffixes)) {
  761. // Last part is a suffix
  762. $name_main = array_pop($parts);
  763. $name_suffix = $suffixes[$normalised_part];
  764. } else {
  765. // Last part is the main name
  766. $name_main = $part;
  767. $name_suffix = false;
  768. }
  769. }
  770. // Anything left is a salutation, initial or forname
  771. if (count($parts) > 0) {
  772. $name_fnames = $parts;
  773. } else {
  774. $name_fnames = false;
  775. }
  776. // We build the name from first to last:
  777. $new_name = array();
  778. // Set case for the forenames
  779. if ($name_fnames) {
  780. foreach ($name_fnames as $fname) {
  781. $parts = array();
  782. $fname = strtolower($fname);
  783. // Do hypenated parts separately
  784. $exploded_fname = explode('-', $fname);
  785. foreach ($exploded_fname as $part) {
  786. // If it is one of our special case prefixes
  787. // we use the appropriate value
  788. // Else, we capitalise
  789. if (array_key_exists($part, $prefixes)) {
  790. if ($is_capitalise_prefix !== false) {
  791. $parts[] = $prefixes[$part]['upper'];
  792. } else {
  793. $parts[] = $prefixes[$part]['lower'];
  794. }
  795. } else {
  796. // It is a normal forename, salutation or initial
  797. // We capitalise it.
  798. $parts[] = ucfirst($part);
  799. }
  800. }
  801. $new_name[] = implode('-', $parts);
  802. }
  803. }
  804. // Set case for the main name
  805. $name_main_original = $name_main;
  806. $name_main = strtolower($name_main);
  807. // Do hypenated parts separately
  808. $exploded_main_original = explode('-', $name_main_original);
  809. $exploded_main = explode('-', $name_main);
  810. $parts = array();
  811. foreach ($exploded_main as $key => $part) {
  812. $part_original = $exploded_main_original[$key];
  813. if (substr($part, 0, 2) == 'mc') {
  814. // Do "Mc"
  815. // Uppercase the 3rd character
  816. $a = substr($part, 2);
  817. $parts[] = 'Mc'.ucfirst($a);
  818. } elseif (substr($part, 0, 3) == 'mac') {
  819. // Do "Mac"
  820. // Lowercase the 3rd character
  821. // unless user has submitted
  822. // a correct looking name
  823. if (preg_match('|^Mac[A-Z][a-z]*$|', $part_original)) {
  824. $parts[] = $part_original;
  825. } else {
  826. $parts[] = ucfirst($part);
  827. }
  828. } elseif (substr($part, 0, 2) == "o'") {
  829. // Do O'
  830. // Uppercase the 3rd character
  831. $a = substr($part, 2);
  832. $parts[] = "O'".ucwords($a);
  833. } else {
  834. // It is a plain-jane name
  835. $parts[] = ucfirst($part);
  836. }
  837. }
  838. $new_name[] = implode('-', $parts);
  839. if ($name_suffix !== false) {
  840. $new_name[] = $name_suffix;
  841. }
  842. // Assemble the new name
  843. $output = implode(' ', $new_name);
  844. return $output;
  845. }
  846. /* \section Helper Functions */
  847. /**
  848. * Reduce sequential whitespaces to a single space.
  849. */
  850. protected function strip_excess_whitespace($a)
  851. {
  852. return preg_replace('/\s\s+/', ' ', $a);
  853. }
  854. /**
  855. * Helper for validating card to and from dates.
  856. */
  857. protected function card_date_parser($a, $type)
  858. {
  859. // Strip out any slash
  860. $date = str_replace('/', '', $a);
  861. // Check that we have 4 digits
  862. if (!preg_match('|^[0-9]{4}$|', $date)) {
  863. return false;
  864. }
  865. $month = substr($date, 0, 2);
  866. $year = substr($date, 2, 2);
  867. // Check month is logical
  868. if ($month > 12) {
  869. return false;
  870. }
  871. $parts = array(date('Y'), date('m'), 1);
  872. $now_datetime = new DateTime(implode('-', $parts));
  873. $parts = array('20'.$year, $month, '1');
  874. $card_datetime = new DateTime(implode('-', $parts));
  875. $interval = $now_datetime->diff($card_datetime);
  876. $days = $interval->format('%R%a days');
  877. if ($type == 'from') {
  878. // Check from date is older or equal to current month
  879. if ($days <= 0 && $days > -3650) {
  880. return true;
  881. } else {
  882. return false;
  883. }
  884. } elseif ($type == 'to') {
  885. // Check to date is newer or equal to current month
  886. if ($days >= 0 && $days < 3650) {
  887. return true;
  888. } else {
  889. return false;
  890. }
  891. } else {
  892. $msg = "Bad date type '$type' in card-date validation";
  893. throw new Error($msg);
  894. }
  895. }
  896. /**
  897. * Explode and trim a comma-delmited list
  898. * for the in and not_in rules.
  899. */
  900. protected function prep_in_vals($a)
  901. {
  902. $vals = $this->pullRule();
  903. if (is_array($vals)) {
  904. return $vals;
  905. }
  906. $vals = explode(',', $vals);
  907. array_walk($vals, function ($val) {
  908. return trim($val);
  909. });
  910. // create_function('&$val', '$val = trim($val);'));
  911. return $vals;
  912. }
  913. }