PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/system/cms/libraries/MY_Form_validation.php

https://github.com/abenzakour/pyrocms
PHP | 751 lines | 422 code | 94 blank | 235 comment | 77 complexity | c47eff37f68b8d0cec62a208e082b51c MD5 | raw file
Possible License(s): CC-BY-3.0, BSD-3-Clause
  1. <?php if (!defined('BASEPATH')) exit('No direct script access allowed.');
  2. /**
  3. * MY_Form_validation
  4. *
  5. * Extending the Form Validation class to add extra rules and model validation
  6. *
  7. * @package PyroCMS\Core\Libraries
  8. * @author PyroCMS Dev Team
  9. * @copyright Copyright (c) 2012, PyroCMS LLC
  10. */
  11. class MY_Form_validation extends CI_Form_validation
  12. {
  13. /**
  14. * The model class to call with callbacks
  15. */
  16. private $_model;
  17. public function __construct($rules = array())
  18. {
  19. parent::__construct($rules);
  20. $this->CI->load->language('extra_validation');
  21. }
  22. // --------------------------------------------------------------------
  23. /**
  24. * Alpha-numeric with underscores dots and dashes
  25. *
  26. * @param string
  27. * @return bool
  28. */
  29. public function alpha_dot_dash($str)
  30. {
  31. return (bool) preg_match("/^([-a-z0-9_\-\.])+$/i", $str);
  32. }
  33. // --------------------------------------------------------------------
  34. /**
  35. * Sneaky function to get field data from
  36. * the form validation library
  37. *
  38. * @param string
  39. * @return bool
  40. */
  41. public function field_data($field)
  42. {
  43. return (isset($this->_field_data[$field])) ? $this->_field_data[$field] : null;
  44. }
  45. // --------------------------------------------------------------------
  46. /**
  47. * Formats an UTF-8 string and removes potential harmful characters
  48. *
  49. * @param string
  50. * @return string
  51. * @todo Find decent regex to check utf-8 strings for harmful characters
  52. */
  53. public function utf8($str)
  54. {
  55. // If they don't have mbstring enabled (suckers) then we'll have to do with what we got
  56. if ( ! function_exists('mb_convert_encoding'))
  57. {
  58. return $str;
  59. }
  60. $str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
  61. return htmlentities($str, ENT_QUOTES, 'UTF-8');
  62. }
  63. // --------------------------------------------------------------------
  64. /**
  65. * Sets the model to be used for validation callbacks. It's set dynamically in MY_Model
  66. *
  67. * @param string The model class name
  68. * @return void
  69. */
  70. public function set_model($model)
  71. {
  72. if ($model)
  73. {
  74. $this->_model = strtolower($model);
  75. }
  76. }
  77. // --------------------------------------------------------------------
  78. /**
  79. * Format an error in the set error delimiters
  80. *
  81. * @param string
  82. * @return void
  83. */
  84. public function format_error($error)
  85. {
  86. return $this->_error_prefix.$error.$this->_error_suffix;
  87. }
  88. // --------------------------------------------------------------------
  89. /**
  90. * Valid URL
  91. *
  92. * @param string
  93. * @return void
  94. */
  95. public function valid_url($str)
  96. {
  97. if (filter_var($str, FILTER_VALIDATE_URL))
  98. {
  99. return true;
  100. }
  101. else
  102. {
  103. $this->set_message('valid_url', $this->CI->lang->line('valid_url'));
  104. return false;
  105. }
  106. }
  107. // --------------------------------------------------------------------
  108. /**
  109. * Executes the Validation routines
  110. *
  111. * Modified to work with HMVC -- Phil Sturgeon
  112. * Modified to work with callbacks in the calling model -- Jerel Unruh
  113. *
  114. * @param array
  115. * @param array
  116. * @param mixed
  117. * @param integer
  118. * @return mixed
  119. */
  120. protected function _execute($row, $rules, $postdata = null, $cycles = 0)
  121. {
  122. // If the $_POST data is an array we will run a recursive call
  123. if (is_array($postdata))
  124. {
  125. foreach ($postdata as $key => $val)
  126. {
  127. $this->_execute($row, $rules, $val, $cycles);
  128. $cycles++;
  129. }
  130. return;
  131. }
  132. // --------------------------------------------------------------------
  133. // If the field is blank, but NOT required, no further tests are necessary
  134. $callback = false;
  135. if ( ! in_array('required', $rules) and is_null($postdata))
  136. {
  137. // Before we bail out, does the rule contain a callback?
  138. if (preg_match("/(callback_\w+(\[.*?\])?)/", implode(' ', $rules), $match))
  139. {
  140. $callback = true;
  141. $rules = (array('1' => $match[1]));
  142. }
  143. else
  144. {
  145. return;
  146. }
  147. }
  148. // --------------------------------------------------------------------
  149. // Isset Test. Typically this rule will only apply to checkboxes.
  150. if (is_null($postdata) and $callback == false)
  151. {
  152. if (in_array('isset', $rules, true) or in_array('required', $rules))
  153. {
  154. // Set the message type
  155. $type = (in_array('required', $rules)) ? 'required' : 'isset';
  156. if ( ! isset($this->_error_messages[$type]))
  157. {
  158. if (false === ($line = $this->CI->lang->line($type)))
  159. {
  160. $line = 'The field was not set';
  161. }
  162. }
  163. else
  164. {
  165. $line = $this->_error_messages[$type];
  166. }
  167. // Build the error message
  168. $message = sprintf($line, $this->_translate_fieldname($row['label']));
  169. // Save the error message
  170. $this->_field_data[$row['field']]['error'] = $message;
  171. if ( ! isset($this->_error_array[$row['field']]))
  172. {
  173. $this->_error_array[$row['field']] = $message;
  174. }
  175. }
  176. return;
  177. }
  178. // --------------------------------------------------------------------
  179. // Cycle through each rule and run it
  180. foreach ($rules as $rule)
  181. {
  182. $_in_array = false;
  183. // We set the $postdata variable with the current data in our master array so that
  184. // each cycle of the loop is dealing with the processed data from the last cycle
  185. if ($row['is_array'] == true and is_array($this->_field_data[$row['field']]['postdata']))
  186. {
  187. // We shouldn't need this safety, but just in case there isn't an array index
  188. // associated with this cycle we'll bail out
  189. if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles]))
  190. {
  191. continue;
  192. }
  193. $postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
  194. $_in_array = true;
  195. }
  196. else
  197. {
  198. $postdata = $this->_field_data[$row['field']]['postdata'];
  199. }
  200. // --------------------------------------------------------------------
  201. // Is the rule a callback?
  202. $callback = false;
  203. if (substr($rule, 0, 9) == 'callback_')
  204. {
  205. $rule = substr($rule, 9);
  206. $callback = true;
  207. }
  208. // Strip the parameter (if exists) from the rule
  209. // Rules can contain a parameter: max_length[5]
  210. $param = false;
  211. if (preg_match("/(.*?)\[(.*)\]/", $rule, $match))
  212. {
  213. $rule = $match[1];
  214. $param = $match[2];
  215. }
  216. // Call the function that corresponds to the rule
  217. if ($callback === true)
  218. {
  219. // first check in the controller scope
  220. if (method_exists(CI::$APP->controller, $rule))
  221. {
  222. $result = call_user_func(array(new CI::$APP->controller, $rule), $postdata, $param);
  223. }
  224. // it wasn't in the controller. Did MY_Model specify a valid model in use?
  225. elseif ($this->_model)
  226. {
  227. // moment of truth. Does the callback itself exist?
  228. if (method_exists(CI::$APP->{$this->_model}, $rule))
  229. {
  230. $result = call_user_func(array(CI::$APP->{$this->_model}, $rule), $postdata, $param);
  231. }
  232. else
  233. {
  234. throw new Exception('Undefined callback '.$rule.' Not found in '.$this->_model);
  235. }
  236. }
  237. else
  238. {
  239. throw new Exception('Undefined callback "'.$rule.'" in '.CI::$APP->controller);
  240. }
  241. // Re-assign the result to the master data array
  242. if ($_in_array == true)
  243. {
  244. $this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
  245. }
  246. else
  247. {
  248. $this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
  249. }
  250. // If the field isn't required and we just processed a callback we'll move on...
  251. if ( ! in_array('required', $rules, true) and $result !== false)
  252. {
  253. continue;
  254. }
  255. }
  256. else
  257. {
  258. if ( ! method_exists($this, $rule))
  259. {
  260. // If our own wrapper function doesn't exist we see if a native PHP function does.
  261. // Users can use any native PHP function call that has one param.
  262. if (function_exists($rule))
  263. {
  264. $result = $rule($postdata);
  265. if ($_in_array == true)
  266. {
  267. $this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
  268. }
  269. else
  270. {
  271. $this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
  272. }
  273. }
  274. else
  275. {
  276. log_message('debug', "Unable to find validation rule: ".$rule);
  277. }
  278. continue;
  279. }
  280. $result = $this->$rule($postdata, $param);
  281. if ($_in_array == true)
  282. {
  283. $this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
  284. }
  285. else
  286. {
  287. $this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
  288. }
  289. }
  290. // Did the rule test negatively? If so, grab the error.
  291. if ($result === false)
  292. {
  293. if ( ! isset($this->_error_messages[$rule]))
  294. {
  295. if (false === ($line = $this->CI->lang->line($rule)))
  296. {
  297. $line = 'Unable to access an error message corresponding to your field name.'.$rule;
  298. }
  299. }
  300. else
  301. {
  302. $line = $this->_error_messages[$rule];
  303. }
  304. // Is the parameter we are inserting into the error message the name
  305. // of another field? If so we need to grab its "field label"
  306. if (isset($this->_field_data[$param]) and isset($this->_field_data[$param]['label']))
  307. {
  308. $param = $this->_translate_fieldname($this->_field_data[$param]['label']);
  309. }
  310. // Build the error message
  311. $message = sprintf($line, $this->_translate_fieldname($row['label']), $param);
  312. // Save the error message
  313. $this->_field_data[$row['field']]['error'] = $message;
  314. if ( ! isset($this->_error_array[$row['field']]))
  315. {
  316. $this->_error_array[$row['field']] = $message;
  317. }
  318. return;
  319. }
  320. }
  321. }
  322. // --------------------------------------------------------------------------
  323. /**
  324. * Check Recaptcha callback
  325. *
  326. * Used for streams but can be used in other
  327. * recaptcha situations.
  328. *
  329. * @param string
  330. * @return bool
  331. */
  332. public function check_recaptcha($val)
  333. {
  334. if ($this->CI->recaptcha->check_answer(
  335. $this->CI->input->ip_address(),
  336. $this->CI->input->post('recaptcha_challenge_field'),
  337. $val))
  338. {
  339. return true;
  340. }
  341. else
  342. {
  343. $this->set_message(
  344. 'check_captcha',
  345. $this->CI->lang->line('recaptcha_incorrect_response'));
  346. return false;
  347. }
  348. }
  349. // --------------------------------------------------------------------------
  350. /**
  351. * Is unique
  352. *
  353. * Used by streams_core.
  354. *
  355. * @param string
  356. * @param string
  357. * @param obj
  358. * @return bool
  359. */
  360. public function streams_unique($string, $data)
  361. {
  362. // Split the data
  363. $items = explode(":", $data);
  364. $column = trim($items[0]);
  365. if ( ! isset($items[0]) or ! isset($items[1]))
  366. {
  367. return true;
  368. }
  369. $mode = $items[1];
  370. $stream_id = $items[2];
  371. if ($mode == 'edit' and isset($items[3]) and is_numeric($items[3]))
  372. {
  373. $row_id = $items[3];
  374. }
  375. elseif ($mode == 'edit' and $this->CI->input->post('row_edit_id'))
  376. {
  377. $row_id = $this->CI->input->post('row_edit_id');
  378. }
  379. else
  380. {
  381. $row_id = null;
  382. }
  383. // Get the stream
  384. $stream = $this->CI->streams_m->get_stream($stream_id);
  385. $obj = $this->CI->db
  386. ->select('id')
  387. ->where(trim($column), trim($string))
  388. ->get($stream->stream_prefix.$stream->stream_slug);
  389. // If this is new, we just need to make sure the
  390. // value doesn't exist already.
  391. if ($mode == 'new')
  392. {
  393. if ($obj->num_rows() == 0)
  394. {
  395. return true;
  396. }
  397. else
  398. {
  399. $this->set_message('streams_unique', lang('streams:field_unique'));
  400. return false;
  401. }
  402. }
  403. else
  404. {
  405. if ( ! $row_id) return true;
  406. // Is this new value the same as what we had before?
  407. // If it is, then we're cool
  408. $existing = $this->CI->db
  409. ->select($column)
  410. ->limit(1)
  411. ->where('id', $row_id)
  412. ->get($stream->stream_prefix.$stream->stream_slug)
  413. ->row();
  414. // Is this the same value? If so, we are
  415. // all in the clear. They did not change the value
  416. // so we don't need to worry.
  417. if ($existing->$column == $string)
  418. {
  419. return true;
  420. }
  421. // Now we know there was a change. We treat it as new now.
  422. // and do the regular old routine.
  423. if ($obj->num_rows() == 0)
  424. {
  425. return true;
  426. }
  427. else
  428. {
  429. // Looks like the end of the road.
  430. $this->set_message('streams_unique', lang('streams:field_unique'));
  431. return false;
  432. }
  433. }
  434. return true;
  435. }
  436. // --------------------------------------------------------------------------
  437. /**
  438. * Streams Field Type Validation Callback
  439. *
  440. * Used by streams as conduit to call custom
  441. * callback functions.
  442. *
  443. * @param string
  444. * @param string
  445. * @return bool
  446. */
  447. public function streams_field_validation($value, $data)
  448. {
  449. // Data is in the form of field_id|mode
  450. // Mode is edit or new.
  451. $pieces = explode(':', $data);
  452. if (count($pieces) != 2) return false;
  453. $field_id = $pieces[0];
  454. $mode = $pieces[1];
  455. // Lets get the field
  456. $field = $this->CI->fields_m->get_field($field_id);
  457. // Check for the type
  458. if ( ! isset($this->CI->type->types->{$field->field_type}) or
  459. ! method_exists($this->CI->type->types->{$field->field_type}, 'validate'))
  460. {
  461. return false;
  462. }
  463. // Call the type. It will either return a string or true
  464. if (($result = $this->CI->type->types->{$field->field_type}->validate($value, $mode, $field)) === true)
  465. {
  466. return true;
  467. }
  468. else
  469. {
  470. $this->set_message('streams_field_validation', $result);
  471. return false;
  472. }
  473. }
  474. // --------------------------------------------------------------------------
  475. /**
  476. * File is Required
  477. *
  478. * Checks various inputs needed for files
  479. * to see if one is indeed added.
  480. *
  481. * Used by Streams.
  482. *
  483. * @param string
  484. * @param string
  485. * @return bool
  486. */
  487. public function streams_file_required($string, $field)
  488. {
  489. // Do we already have something? If we are editing the row,
  490. // the file may already be there. We know that if the ID has a
  491. // numerical value, since it is hooked up with the PyroCMS
  492. // file system.
  493. if (is_numeric($this->CI->input->post($field)))
  494. {
  495. return true;
  496. }
  497. // OK. Now we really need to make sure there is a file here.
  498. // The method of choice here is checking for a file name
  499. if (isset($_FILES[$field.'_file']['name']) and $_FILES[$field.'_file']['name'] != '')
  500. {
  501. // Don't do shit.
  502. }
  503. else
  504. {
  505. $this->set_message('streams_file_required', lang('streams:field_is_required'));
  506. return false;
  507. }
  508. return null;
  509. }
  510. // --------------------------------------------------------------------------
  511. /**
  512. * Unique field slug
  513. *
  514. * Checks to see if the slug is unique based on the
  515. * circumstances
  516. *
  517. * Used by Streams.
  518. *
  519. * @param string
  520. * @param string
  521. * @return void
  522. */
  523. public function streams_unique_field_slug($field_slug, $data)
  524. {
  525. // Get our mode and namespace
  526. $items = explode(':', $data);
  527. if (count($items) != 2)
  528. {
  529. // @todo: Do we need an error message here?
  530. return false;
  531. }
  532. // If the mode is not 'new', it is the current
  533. // field slug so that we can check to see if a field
  534. // Slug has changed - pretty tricky, eh?
  535. $mode = $items[0];
  536. // We check by namespace, because you can have
  537. // fields with the same slug in multiple namespaces.
  538. $namespace = $items[1];
  539. $existing = $this->CI->db
  540. ->where('field_namespace', $namespace)
  541. ->where('field_slug', trim($field_slug))
  542. ->from(FIELDS_TABLE)
  543. ->count_all_results();
  544. if ($mode == 'new')
  545. {
  546. if ($existing > 0)
  547. {
  548. $this->set_message('streams_unique_field_slug', lang('streams:field_slug_not_unique'));
  549. return false;
  550. }
  551. }
  552. else
  553. {
  554. // Mode should be the existing slug
  555. if ($field_slug != $mode)
  556. {
  557. // We're changing the slug?
  558. // Better make sure it doesn't exist.
  559. if ($existing != 0)
  560. {
  561. $this->set_message('streams_unique_field_slug', lang('streams:field_slug_not_unique'));
  562. return false;
  563. }
  564. }
  565. }
  566. return true;
  567. }
  568. // --------------------------------------------------------------------------
  569. /**
  570. * Unique Stream Slug
  571. *
  572. * Checks to see if the stream is unique based on the
  573. * stream_slug
  574. *
  575. * @param string
  576. * @param string
  577. * @return bool
  578. */
  579. public function stream_unique($stream_slug, $mode)
  580. {
  581. $this->CI->db->select('id')->where('stream_slug', trim($stream_slug));
  582. $db_obj = $this->CI->db->get(STREAMS_TABLE);
  583. if ($mode == 'new')
  584. {
  585. if ($db_obj->num_rows() > 0)
  586. {
  587. $this->set_message('stream_unique', lang('streams:stream_slug_not_unique'));
  588. return false;
  589. }
  590. }
  591. else
  592. {
  593. // Mode should be the existing slug
  594. // We check the two to see if the slug is changing.
  595. // If it is changing we of course need to make sure
  596. // it is unique.
  597. if ($stream_slug != $mode)
  598. {
  599. if ($db_obj->num_rows() != 0)
  600. {
  601. $this->set_message('stream_unique', lang('streams:stream_slug_not_unique'));
  602. return false;
  603. }
  604. }
  605. }
  606. return true;
  607. }
  608. // --------------------------------------------------------------------------
  609. /**
  610. * Streams Slug Safe
  611. *
  612. * 1. Sees if a word is safe for the DB. Used for
  613. * stream_fields, etc. Basically, we are checking to see if
  614. * a word is going to conflict with MySQL
  615. *
  616. * 2. Sees if a word is safe the Lex parser to
  617. * be used as a variable. The same variable rules for
  618. * PHP variables apply to Lex.
  619. *
  620. * @param string
  621. * @return bool
  622. */
  623. public function streams_slug_safe($string)
  624. {
  625. // See if word is MySQL Reserved Word
  626. if (in_array(strtoupper($string), $this->CI->config->item('streams:reserved')))
  627. {
  628. $this->set_message('streams_slug_safe', lang('streams:not_mysql_safe_word'));
  629. return false;
  630. }
  631. // See if there are no-no characters. We are basically validating
  632. // the string to make sure it can be used as a PHP/Lex variable.
  633. if ( ! preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $string))
  634. {
  635. $this->set_message('streams_slug_safe', lang('streams:not_mysql_safe_characters'));
  636. return false;
  637. }
  638. return true;
  639. }
  640. // --------------------------------------------------------------------------
  641. /**
  642. * Make sure a type is valid
  643. *
  644. * @param string
  645. * @return bool
  646. */
  647. public function streams_type_valid($string)
  648. {
  649. if ($string == '-')
  650. {
  651. $this->set_message('type_valid', lang('streams:type_not_valid'));
  652. return false;
  653. }
  654. return true;
  655. }
  656. }
  657. /* End of file MY_Form_validation.php */