1<?php
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP
6 *
7 * This content is released under the MIT License (MIT)
8 *
9 * Copyright (c) 2014 - 2019, British Columbia Institute of Technology
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 * THE SOFTWARE.
28 *
29 * @package	CodeIgniter
30 * @author	EllisLab Dev Team
31 * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
32 * @copyright	Copyright (c) 2014 - 2019, British Columbia Institute of Technology (https://bcit.ca/)
33 * @license	https://opensource.org/licenses/MIT	MIT License
34 * @link	https://codeigniter.com
35 * @since	Version 1.0.0
36 * @filesource
37 */
38defined('BASEPATH') OR exit('No direct script access allowed');
39
40/**
41 * Form Validation Class
42 *
43 * @package		CodeIgniter
44 * @subpackage	Libraries
45 * @category	Validation
46 * @author		EllisLab Dev Team
47 * @link		https://codeigniter.com/user_guide/libraries/form_validation.html
48 */
49class CI_Form_validation {
50
51	/**
52	 * Reference to the CodeIgniter instance
53	 *
54	 * @var object
55	 */
56	protected $CI;
57
58	/**
59	 * Validation data for the current form submission
60	 *
61	 * @var array
62	 */
63	protected $_field_data		= array();
64
65	/**
66	 * Validation rules for the current form
67	 *
68	 * @var array
69	 */
70	protected $_config_rules	= array();
71
72	/**
73	 * Array of validation errors
74	 *
75	 * @var array
76	 */
77	protected $_error_array		= array();
78
79	/**
80	 * Array of custom error messages
81	 *
82	 * @var array
83	 */
84	protected $_error_messages	= array();
85
86	/**
87	 * Start tag for error wrapping
88	 *
89	 * @var string
90	 */
91	protected $_error_prefix	= '<p>';
92
93	/**
94	 * End tag for error wrapping
95	 *
96	 * @var string
97	 */
98	protected $_error_suffix	= '</p>';
99
100	/**
101	 * Custom error message
102	 *
103	 * @var string
104	 */
105	protected $error_string		= '';
106
107	/**
108	 * Whether the form data has been validated as safe
109	 *
110	 * @var bool
111	 */
112	protected $_safe_form_data	= FALSE;
113
114	/**
115	 * Custom data to validate
116	 *
117	 * @var array
118	 */
119	public $validation_data	= array();
120
121	/**
122	 * Initialize Form_Validation class
123	 *
124	 * @param	array	$rules
125	 * @return	void
126	 */
127	public function __construct($rules = array())
128	{
129		$this->CI =& get_instance();
130
131		// applies delimiters set in config file.
132		if (isset($rules['error_prefix']))
133		{
134			$this->_error_prefix = $rules['error_prefix'];
135			unset($rules['error_prefix']);
136		}
137		if (isset($rules['error_suffix']))
138		{
139			$this->_error_suffix = $rules['error_suffix'];
140			unset($rules['error_suffix']);
141		}
142
143		// Validation rules can be stored in a config file.
144		$this->_config_rules = $rules;
145
146		// Automatically load the form helper
147		$this->CI->load->helper('form');
148
149		log_message('info', 'Form Validation Class Initialized');
150	}
151
152	// --------------------------------------------------------------------
153
154	/**
155	 * Set Rules
156	 *
157	 * This function takes an array of field names and validation
158	 * rules as input, any custom error messages, validates the info,
159	 * and stores it
160	 *
161	 * @param	mixed	$field
162	 * @param	string	$label
163	 * @param	mixed	$rules
164	 * @param	array	$errors
165	 * @return	CI_Form_validation
166	 */
167	public function set_rules($field, $label = '', $rules = array(), $errors = array())
168	{
169		// No reason to set rules if we have no POST data
170		// or a validation array has not been specified
171		if ($this->CI->input->method() !== 'post' && empty($this->validation_data))
172		{
173			return $this;
174		}
175
176		// If an array was passed via the first parameter instead of individual string
177		// values we cycle through it and recursively call this function.
178		if (is_array($field))
179		{
180			foreach ($field as $row)
181			{
182				// Houston, we have a problem...
183				if ( ! isset($row['field'], $row['rules']))
184				{
185					continue;
186				}
187
188				// If the field label wasn't passed we use the field name
189				$label = isset($row['label']) ? $row['label'] : $row['field'];
190
191				// Add the custom error message array
192				$errors = (isset($row['errors']) && is_array($row['errors'])) ? $row['errors'] : array();
193
194				// Here we go!
195				$this->set_rules($row['field'], $label, $row['rules'], $errors);
196			}
197
198			return $this;
199		}
200
201		// No fields or no rules? Nothing to do...
202		if ( ! is_string($field) OR $field === '' OR empty($rules))
203		{
204			return $this;
205		}
206		elseif ( ! is_array($rules))
207		{
208			// BC: Convert pipe-separated rules string to an array
209			if ( ! is_string($rules))
210			{
211				return $this;
212			}
213
214			$rules = preg_split('/\|(?![^\[]*\])/', $rules);
215		}
216
217		// If the field label wasn't passed we use the field name
218		$label = ($label === '') ? $field : $label;
219
220		$indexes = array();
221
222		// Is the field name an array? If it is an array, we break it apart
223		// into its components so that we can fetch the corresponding POST data later
224		if (($is_array = (bool) preg_match_all('/\[(.*?)\]/', $field, $matches)) === TRUE)
225		{
226			sscanf($field, '%[^[][', $indexes[0]);
227
228			for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
229			{
230				if ($matches[1][$i] !== '')
231				{
232					$indexes[] = $matches[1][$i];
233				}
234			}
235		}
236
237		// Build our master array
238		$this->_field_data[$field] = array(
239			'field'		=> $field,
240			'label'		=> $label,
241			'rules'		=> $rules,
242			'errors'	=> $errors,
243			'is_array'	=> $is_array,
244			'keys'		=> $indexes,
245			'postdata'	=> NULL,
246			'error'		=> ''
247		);
248
249		return $this;
250	}
251
252	// --------------------------------------------------------------------
253
254	/**
255	 * By default, form validation uses the $_POST array to validate
256	 *
257	 * If an array is set through this method, then this array will
258	 * be used instead of the $_POST array
259	 *
260	 * Note that if you are validating multiple arrays, then the
261	 * reset_validation() function should be called after validating
262	 * each array due to the limitations of CI's singleton
263	 *
264	 * @param	array	$data
265	 * @return	CI_Form_validation
266	 */
267	public function set_data(array $data)
268	{
269		if ( ! empty($data))
270		{
271			$this->validation_data = $data;
272		}
273
274		return $this;
275	}
276
277	// --------------------------------------------------------------------
278
279	/**
280	 * Set Error Message
281	 *
282	 * Lets users set their own error messages on the fly. Note:
283	 * The key name has to match the function name that it corresponds to.
284	 *
285	 * @param	array
286	 * @param	string
287	 * @return	CI_Form_validation
288	 */
289	public function set_message($lang, $val = '')
290	{
291		if ( ! is_array($lang))
292		{
293			$lang = array($lang => $val);
294		}
295
296		$this->_error_messages = array_merge($this->_error_messages, $lang);
297		return $this;
298	}
299
300	// --------------------------------------------------------------------
301
302	/**
303	 * Set The Error Delimiter
304	 *
305	 * Permits a prefix/suffix to be added to each error message
306	 *
307	 * @param	string
308	 * @param	string
309	 * @return	CI_Form_validation
310	 */
311	public function set_error_delimiters($prefix = '<p>', $suffix = '</p>')
312	{
313		$this->_error_prefix = $prefix;
314		$this->_error_suffix = $suffix;
315		return $this;
316	}
317
318	// --------------------------------------------------------------------
319
320	/**
321	 * Get Error Message
322	 *
323	 * Gets the error message associated with a particular field
324	 *
325	 * @param	string	$field	Field name
326	 * @param	string	$prefix	HTML start tag
327	 * @param 	string	$suffix	HTML end tag
328	 * @return	string
329	 */
330	public function error($field, $prefix = '', $suffix = '')
331	{
332		if (empty($this->_field_data[$field]['error']))
333		{
334			return '';
335		}
336
337		if ($prefix === '')
338		{
339			$prefix = $this->_error_prefix;
340		}
341
342		if ($suffix === '')
343		{
344			$suffix = $this->_error_suffix;
345		}
346
347		return $prefix.$this->_field_data[$field]['error'].$suffix;
348	}
349
350	// --------------------------------------------------------------------
351
352	/**
353	 * Get Array of Error Messages
354	 *
355	 * Returns the error messages as an array
356	 *
357	 * @return	array
358	 */
359	public function error_array()
360	{
361		return $this->_error_array;
362	}
363
364	// --------------------------------------------------------------------
365
366	/**
367	 * Error String
368	 *
369	 * Returns the error messages as a string, wrapped in the error delimiters
370	 *
371	 * @param	string
372	 * @param	string
373	 * @return	string
374	 */
375	public function error_string($prefix = '', $suffix = '')
376	{
377		// No errors, validation passes!
378		if (count($this->_error_array) === 0)
379		{
380			return '';
381		}
382
383		if ($prefix === '')
384		{
385			$prefix = $this->_error_prefix;
386		}
387
388		if ($suffix === '')
389		{
390			$suffix = $this->_error_suffix;
391		}
392
393		// Generate the error string
394		$str = '';
395		foreach ($this->_error_array as $val)
396		{
397			if ($val !== '')
398			{
399				$str .= $prefix.$val.$suffix."\n";
400			}
401		}
402
403		return $str;
404	}
405
406	// --------------------------------------------------------------------
407
408	/**
409	 * Run the Validator
410	 *
411	 * This function does all the work.
412	 *
413	 * @param	string	$group
414	 * @return	bool
415	 */
416	public function run($group = '')
417	{
418		$validation_array = empty($this->validation_data)
419			? $_POST
420			: $this->validation_data;
421
422		// Does the _field_data array containing the validation rules exist?
423		// If not, we look to see if they were assigned via a config file
424		if (count($this->_field_data) === 0)
425		{
426			// No validation rules?  We're done...
427			if (count($this->_config_rules) === 0)
428			{
429				return FALSE;
430			}
431
432			if (empty($group))
433			{
434				// Is there a validation rule for the particular URI being accessed?
435				$group = trim($this->CI->uri->ruri_string(), '/');
436				isset($this->_config_rules[$group]) OR $group = $this->CI->router->class.'/'.$this->CI->router->method;
437			}
438
439			$this->set_rules(isset($this->_config_rules[$group]) ? $this->_config_rules[$group] : $this->_config_rules);
440
441			// Were we able to set the rules correctly?
442			if (count($this->_field_data) === 0)
443			{
444				log_message('debug', 'Unable to find validation rules');
445				return FALSE;
446			}
447		}
448
449		// Load the language file containing error messages
450		$this->CI->lang->load('form_validation');
451
452		// Cycle through the rules for each field and match the corresponding $validation_data item
453		foreach ($this->_field_data as $field => &$row)
454		{
455			// Fetch the data from the validation_data array item and cache it in the _field_data array.
456			// Depending on whether the field name is an array or a string will determine where we get it from.
457			if ($row['is_array'] === TRUE)
458			{
459				$this->_field_data[$field]['postdata'] = $this->_reduce_array($validation_array, $row['keys']);
460			}
461			elseif (isset($validation_array[$field]))
462			{
463				$this->_field_data[$field]['postdata'] = $validation_array[$field];
464			}
465		}
466
467		// Execute validation rules
468		// Note: A second foreach (for now) is required in order to avoid false-positives
469		//	 for rules like 'matches', which correlate to other validation fields.
470		foreach ($this->_field_data as $field => &$row)
471		{
472			// Don't try to validate if we have no rules set
473			if (empty($row['rules']))
474			{
475				continue;
476			}
477
478			$this->_execute($row, $row['rules'], $row['postdata']);
479		}
480
481		// Did we end up with any errors?
482		$total_errors = count($this->_error_array);
483		if ($total_errors > 0)
484		{
485			$this->_safe_form_data = TRUE;
486		}
487
488		// Now we need to re-set the POST data with the new, processed data
489		empty($this->validation_data) && $this->_reset_post_array();
490
491		return ($total_errors === 0);
492	}
493
494	// --------------------------------------------------------------------
495
496	/**
497	 * Prepare rules
498	 *
499	 * Re-orders the provided rules in order of importance, so that
500	 * they can easily be executed later without weird checks ...
501	 *
502	 * "Callbacks" are given the highest priority (always called),
503	 * followed by 'required' (called if callbacks didn't fail),
504	 * and then every next rule depends on the previous one passing.
505	 *
506	 * @param	array	$rules
507	 * @return	array
508	 */
509	protected function _prepare_rules($rules)
510	{
511		$new_rules = array();
512		$callbacks = array();
513
514		foreach ($rules as &$rule)
515		{
516			// Let 'required' always be the first (non-callback) rule
517			if ($rule === 'required')
518			{
519				array_unshift($new_rules, 'required');
520			}
521			// 'isset' is a kind of a weird alias for 'required' ...
522			elseif ($rule === 'isset' && (empty($new_rules) OR $new_rules[0] !== 'required'))
523			{
524				array_unshift($new_rules, 'isset');
525			}
526			// The old/classic 'callback_'-prefixed rules
527			elseif (is_string($rule) && strncmp('callback_', $rule, 9) === 0)
528			{
529				$callbacks[] = $rule;
530			}
531			// Proper callables
532			elseif (is_callable($rule))
533			{
534				$callbacks[] = $rule;
535			}
536			// "Named" callables; i.e. array('name' => $callable)
537			elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1]))
538			{
539				$callbacks[] = $rule;
540			}
541			// Everything else goes at the end of the queue
542			else
543			{
544				$new_rules[] = $rule;
545			}
546		}
547
548		return array_merge($callbacks, $new_rules);
549	}
550
551	// --------------------------------------------------------------------
552
553	/**
554	 * Traverse a multidimensional $_POST array index until the data is found
555	 *
556	 * @param	array
557	 * @param	array
558	 * @param	int
559	 * @return	mixed
560	 */
561	protected function _reduce_array($array, $keys, $i = 0)
562	{
563		if (is_array($array) && isset($keys[$i]))
564		{
565			return isset($array[$keys[$i]]) ? $this->_reduce_array($array[$keys[$i]], $keys, ($i+1)) : NULL;
566		}
567
568		// NULL must be returned for empty fields
569		return ($array === '') ? NULL : $array;
570	}
571
572	// --------------------------------------------------------------------
573
574	/**
575	 * Re-populate the _POST array with our finalized and processed data
576	 *
577	 * @return	void
578	 */
579	protected function _reset_post_array()
580	{
581		foreach ($this->_field_data as $field => $row)
582		{
583			if ($row['postdata'] !== NULL)
584			{
585				if ($row['is_array'] === FALSE)
586				{
587					isset($_POST[$field]) && $_POST[$field] = is_array($row['postdata']) ? NULL : $row['postdata'];
588				}
589				else
590				{
591					// start with a reference
592					$post_ref =& $_POST;
593
594					// before we assign values, make a reference to the right POST key
595					if (count($row['keys']) === 1)
596					{
597						$post_ref =& $post_ref[current($row['keys'])];
598					}
599					else
600					{
601						foreach ($row['keys'] as $val)
602						{
603							$post_ref =& $post_ref[$val];
604						}
605					}
606
607					$post_ref = $row['postdata'];
608				}
609			}
610		}
611	}
612
613	// --------------------------------------------------------------------
614
615	/**
616	 * Executes the Validation routines
617	 *
618	 * @param	array
619	 * @param	array
620	 * @param	mixed
621	 * @param	int
622	 * @return	mixed
623	 */
624	protected function _execute($row, $rules, $postdata = NULL, $cycles = 0)
625	{
626		// If the $_POST data is an array we will run a recursive call
627		//
628		// Note: We MUST check if the array is empty or not!
629		//       Otherwise empty arrays will always pass validation.
630		if (is_array($postdata) && ! empty($postdata))
631		{
632			foreach ($postdata as $key => $val)
633			{
634				$this->_execute($row, $rules, $val, $key);
635			}
636
637			return;
638		}
639
640		$rules = $this->_prepare_rules($rules);
641		foreach ($rules as $rule)
642		{
643			$_in_array = FALSE;
644
645			// We set the $postdata variable with the current data in our master array so that
646			// each cycle of the loop is dealing with the processed data from the last cycle
647			if ($row['is_array'] === TRUE && is_array($this->_field_data[$row['field']]['postdata']))
648			{
649				// We shouldn't need this safety, but just in case there isn't an array index
650				// associated with this cycle we'll bail out
651				if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles]))
652				{
653					continue;
654				}
655
656				$postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
657				$_in_array = TRUE;
658			}
659			else
660			{
661				// If we get an array field, but it's not expected - then it is most likely
662				// somebody messing with the form on the client side, so we'll just consider
663				// it an empty field
664				$postdata = is_array($this->_field_data[$row['field']]['postdata'])
665					? NULL
666					: $this->_field_data[$row['field']]['postdata'];
667			}
668
669			// Is the rule a callback?
670			$callback = $callable = FALSE;
671			if (is_string($rule))
672			{
673				if (strpos($rule, 'callback_') === 0)
674				{
675					$rule = substr($rule, 9);
676					$callback = TRUE;
677				}
678			}
679			elseif (is_callable($rule))
680			{
681				$callable = TRUE;
682			}
683			elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1]))
684			{
685				// We have a "named" callable, so save the name
686				$callable = $rule[0];
687				$rule = $rule[1];
688			}
689
690			// Strip the parameter (if exists) from the rule
691			// Rules can contain a parameter: max_length[5]
692			$param = FALSE;
693			if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match))
694			{
695				$rule = $match[1];
696				$param = $match[2];
697			}
698
699			// Ignore empty, non-required inputs with a few exceptions ...
700			if (
701				($postdata === NULL OR $postdata === '')
702				&& $callback === FALSE
703				&& $callable === FALSE
704				&& ! in_array($rule, array('required', 'isset', 'matches'), TRUE)
705			)
706			{
707				continue;
708			}
709
710			// Call the function that corresponds to the rule
711			if ($callback OR $callable !== FALSE)
712			{
713				if ($callback)
714				{
715					if ( ! method_exists($this->CI, $rule))
716					{
717						log_message('debug', 'Unable to find callback validation rule: '.$rule);
718						$result = FALSE;
719					}
720					else
721					{
722						// Run the function and grab the result
723						$result = $this->CI->$rule($postdata, $param);
724					}
725				}
726				else
727				{
728					$result = is_array($rule)
729						? $rule[0]->{$rule[1]}($postdata)
730						: $rule($postdata);
731
732					// Is $callable set to a rule name?
733					if ($callable !== FALSE)
734					{
735						$rule = $callable;
736					}
737				}
738
739				// Re-assign the result to the master data array
740				if ($_in_array === TRUE)
741				{
742					$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
743				}
744				else
745				{
746					$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
747				}
748			}
749			elseif ( ! method_exists($this, $rule))
750			{
751				// If our own wrapper function doesn't exist we see if a native PHP function does.
752				// Users can use any native PHP function call that has one param.
753				if (function_exists($rule))
754				{
755					// Native PHP functions issue warnings if you pass them more parameters than they use
756					$result = ($param !== FALSE) ? $rule($postdata, $param) : $rule($postdata);
757
758					if ($_in_array === TRUE)
759					{
760						$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
761					}
762					else
763					{
764						$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
765					}
766				}
767				else
768				{
769					log_message('debug', 'Unable to find validation rule: '.$rule);
770					$result = FALSE;
771				}
772			}
773			else
774			{
775				$result = $this->$rule($postdata, $param);
776
777				if ($_in_array === TRUE)
778				{
779					$this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
780				}
781				else
782				{
783					$this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
784				}
785			}
786
787			// Did the rule test negatively? If so, grab the error.
788			if ($result === FALSE)
789			{
790				// Callable rules might not have named error messages
791				if ( ! is_string($rule))
792				{
793					$line = $this->CI->lang->line('form_validation_error_message_not_set').'(Anonymous function)';
794				}
795				else
796				{
797					$line = $this->_get_error_message($rule, $row['field']);
798				}
799
800				// Is the parameter we are inserting into the error message the name
801				// of another field? If so we need to grab its "field label"
802				if (isset($this->_field_data[$param], $this->_field_data[$param]['label']))
803				{
804					$param = $this->_translate_fieldname($this->_field_data[$param]['label']);
805				}
806
807				// Build the error message
808				$message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']), $param);
809
810				// Save the error message
811				$this->_field_data[$row['field']]['error'] = $message;
812
813				if ( ! isset($this->_error_array[$row['field']]))
814				{
815					$this->_error_array[$row['field']] = $message;
816				}
817
818				return;
819			}
820		}
821	}
822
823	// --------------------------------------------------------------------
824
825	/**
826	 * Get the error message for the rule
827	 *
828	 * @param 	string $rule 	The rule name
829	 * @param 	string $field	The field name
830	 * @return 	string
831	 */
832	protected function _get_error_message($rule, $field)
833	{
834		// check if a custom message is defined through validation config row.
835		if (isset($this->_field_data[$field]['errors'][$rule]))
836		{
837			return $this->_field_data[$field]['errors'][$rule];
838		}
839		// check if a custom message has been set using the set_message() function
840		elseif (isset($this->_error_messages[$rule]))
841		{
842			return $this->_error_messages[$rule];
843		}
844		elseif (FALSE !== ($line = $this->CI->lang->line('form_validation_'.$rule)))
845		{
846			return $line;
847		}
848		// DEPRECATED support for non-prefixed keys, lang file again
849		elseif (FALSE !== ($line = $this->CI->lang->line($rule, FALSE)))
850		{
851			return $line;
852		}
853
854		return $this->CI->lang->line('form_validation_error_message_not_set').'('.$rule.')';
855	}
856
857	// --------------------------------------------------------------------
858
859	/**
860	 * Translate a field name
861	 *
862	 * @param	string	the field name
863	 * @return	string
864	 */
865	protected function _translate_fieldname($fieldname)
866	{
867		// Do we need to translate the field name? We look for the prefix 'lang:' to determine this
868		// If we find one, but there's no translation for the string - just return it
869		if (sscanf($fieldname, 'lang:%s', $line) === 1 && FALSE === ($fieldname = $this->CI->lang->line($line, FALSE)))
870		{
871			return $line;
872		}
873
874		return $fieldname;
875	}
876
877	// --------------------------------------------------------------------
878
879	/**
880	 * Build an error message using the field and param.
881	 *
882	 * @param	string	The error message line
883	 * @param	string	A field's human name
884	 * @param	mixed	A rule's optional parameter
885	 * @return	string
886	 */
887	protected function _build_error_msg($line, $field = '', $param = '')
888	{
889		// Check for %s in the string for legacy support.
890		if (strpos($line, '%s') !== FALSE)
891		{
892			return sprintf($line, $field, $param);
893		}
894
895		return str_replace(array('{field}', '{param}'), array($field, $param), $line);
896	}
897
898	// --------------------------------------------------------------------
899
900	/**
901	 * Checks if the rule is present within the validator
902	 *
903	 * Permits you to check if a rule is present within the validator
904	 *
905	 * @param	string	the field name
906	 * @return	bool
907	 */
908	public function has_rule($field)
909	{
910		return isset($this->_field_data[$field]);
911	}
912
913	// --------------------------------------------------------------------
914
915	/**
916	 * Get the value from a form
917	 *
918	 * Permits you to repopulate a form field with the value it was submitted
919	 * with, or, if that value doesn't exist, with the default
920	 *
921	 * @param	string	the field name
922	 * @param	string
923	 * @return	string
924	 */
925	public function set_value($field = '', $default = '')
926	{
927		if ( ! isset($this->_field_data[$field], $this->_field_data[$field]['postdata']))
928		{
929			return $default;
930		}
931
932		// If the data is an array output them one at a time.
933		//	E.g: form_input('name[]', set_value('name[]');
934		if (is_array($this->_field_data[$field]['postdata']))
935		{
936			return array_shift($this->_field_data[$field]['postdata']);
937		}
938
939		return $this->_field_data[$field]['postdata'];
940	}
941
942	// --------------------------------------------------------------------
943
944	/**
945	 * Set Select
946	 *
947	 * Enables pull-down lists to be set to the value the user
948	 * selected in the event of an error
949	 *
950	 * @param	string
951	 * @param	string
952	 * @param	bool
953	 * @return	string
954	 */
955	public function set_select($field = '', $value = '', $default = FALSE)
956	{
957		if ( ! isset($this->_field_data[$field], $this->_field_data[$field]['postdata']))
958		{
959			return ($default === TRUE && count($this->_field_data) === 0) ? ' selected="selected"' : '';
960		}
961
962		$field = $this->_field_data[$field]['postdata'];
963		$value = (string) $value;
964		if (is_array($field))
965		{
966			// Note: in_array('', array(0)) returns TRUE, do not use it
967			foreach ($field as &$v)
968			{
969				if ($value === $v)
970				{
971					return ' selected="selected"';
972				}
973			}
974
975			return '';
976		}
977		elseif (($field === '' OR $value === '') OR ($field !== $value))
978		{
979			return '';
980		}
981
982		return ' selected="selected"';
983	}
984
985	// --------------------------------------------------------------------
986
987	/**
988	 * Set Radio
989	 *
990	 * Enables radio buttons to be set to the value the user
991	 * selected in the event of an error
992	 *
993	 * @param	string
994	 * @param	string
995	 * @param	bool
996	 * @return	string
997	 */
998	public function set_radio($field = '', $value = '', $default = FALSE)
999	{
1000		if ( ! isset($this->_field_data[$field], $this->_field_data[$field]['postdata']))
1001		{
1002			return ($default === TRUE && count($this->_field_data) === 0) ? ' checked="checked"' : '';
1003		}
1004
1005		$field = $this->_field_data[$field]['postdata'];
1006		$value = (string) $value;
1007		if (is_array($field))
1008		{
1009			// Note: in_array('', array(0)) returns TRUE, do not use it
1010			foreach ($field as &$v)
1011			{
1012				if ($value === $v)
1013				{
1014					return ' checked="checked"';
1015				}
1016			}
1017
1018			return '';
1019		}
1020		elseif (($field === '' OR $value === '') OR ($field !== $value))
1021		{
1022			return '';
1023		}
1024
1025		return ' checked="checked"';
1026	}
1027
1028	// --------------------------------------------------------------------
1029
1030	/**
1031	 * Set Checkbox
1032	 *
1033	 * Enables checkboxes to be set to the value the user
1034	 * selected in the event of an error
1035	 *
1036	 * @param	string
1037	 * @param	string
1038	 * @param	bool
1039	 * @return	string
1040	 */
1041	public function set_checkbox($field = '', $value = '', $default = FALSE)
1042	{
1043		// Logic is exactly the same as for radio fields
1044		return $this->set_radio($field, $value, $default);
1045	}
1046
1047	// --------------------------------------------------------------------
1048
1049	/**
1050	 * Required
1051	 *
1052	 * @param	string
1053	 * @return	bool
1054	 */
1055	public function required($str)
1056	{
1057		return is_array($str)
1058			? (empty($str) === FALSE)
1059			: (trim($str) !== '');
1060	}
1061
1062	// --------------------------------------------------------------------
1063
1064	/**
1065	 * Performs a Regular Expression match test.
1066	 *
1067	 * @param	string
1068	 * @param	string	regex
1069	 * @return	bool
1070	 */
1071	public function regex_match($str, $regex)
1072	{
1073		return (bool) preg_match($regex, $str);
1074	}
1075
1076	// --------------------------------------------------------------------
1077
1078	/**
1079	 * Match one field to another
1080	 *
1081	 * @param	string	$str	string to compare against
1082	 * @param	string	$field
1083	 * @return	bool
1084	 */
1085	public function matches($str, $field)
1086	{
1087		return isset($this->_field_data[$field], $this->_field_data[$field]['postdata'])
1088			? ($str === $this->_field_data[$field]['postdata'])
1089			: FALSE;
1090	}
1091
1092	// --------------------------------------------------------------------
1093
1094	/**
1095	 * Differs from another field
1096	 *
1097	 * @param	string
1098	 * @param	string	field
1099	 * @return	bool
1100	 */
1101	public function differs($str, $field)
1102	{
1103		return ! (isset($this->_field_data[$field]) && $this->_field_data[$field]['postdata'] === $str);
1104	}
1105
1106	// --------------------------------------------------------------------
1107
1108	/**
1109	 * Is Unique
1110	 *
1111	 * Check if the input value doesn't already exist
1112	 * in the specified database field.
1113	 *
1114	 * @param	string	$str
1115	 * @param	string	$field
1116	 * @return	bool
1117	 */
1118	public function is_unique($str, $field)
1119	{
1120		sscanf($field, '%[^.].%[^.]', $table, $field);
1121		return isset($this->CI->db)
1122			? ($this->CI->db->limit(1)->get_where($table, array($field => $str))->num_rows() === 0)
1123			: FALSE;
1124	}
1125
1126	// --------------------------------------------------------------------
1127
1128	/**
1129	 * Minimum Length
1130	 *
1131	 * @param	string
1132	 * @param	string
1133	 * @return	bool
1134	 */
1135	public function min_length($str, $val)
1136	{
1137		if ( ! is_numeric($val))
1138		{
1139			return FALSE;
1140		}
1141
1142		return ($val <= mb_strlen($str));
1143	}
1144
1145	// --------------------------------------------------------------------
1146
1147	/**
1148	 * Max Length
1149	 *
1150	 * @param	string
1151	 * @param	string
1152	 * @return	bool
1153	 */
1154	public function max_length($str, $val)
1155	{
1156		if ( ! is_numeric($val))
1157		{
1158			return FALSE;
1159		}
1160
1161		return ($val >= mb_strlen($str));
1162	}
1163
1164	// --------------------------------------------------------------------
1165
1166	/**
1167	 * Exact Length
1168	 *
1169	 * @param	string
1170	 * @param	string
1171	 * @return	bool
1172	 */
1173	public function exact_length($str, $val)
1174	{
1175		if ( ! is_numeric($val))
1176		{
1177			return FALSE;
1178		}
1179
1180		return (mb_strlen($str) === (int) $val);
1181	}
1182
1183	// --------------------------------------------------------------------
1184
1185	/**
1186	 * Valid URL
1187	 *
1188	 * @param	string	$str
1189	 * @return	bool
1190	 */
1191	public function valid_url($str)
1192	{
1193		if (empty($str))
1194		{
1195			return FALSE;
1196		}
1197		elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches))
1198		{
1199			if (empty($matches[2]))
1200			{
1201				return FALSE;
1202			}
1203			elseif ( ! in_array(strtolower($matches[1]), array('http', 'https'), TRUE))
1204			{
1205				return FALSE;
1206			}
1207
1208			$str = $matches[2];
1209		}
1210
1211		// Apparently, FILTER_VALIDATE_URL doesn't reject digit-only names for some reason ...
1212		// See https://github.com/bcit-ci/CodeIgniter/issues/5755
1213		if (ctype_digit($str))
1214		{
1215			return FALSE;
1216		}
1217
1218		// PHP 7 accepts IPv6 addresses within square brackets as hostnames,
1219		// but it appears that the PR that came in with https://bugs.php.net/bug.php?id=68039
1220		// was never merged into a PHP 5 branch ... https://3v4l.org/8PsSN
1221		if (preg_match('/^\[([^\]]+)\]/', $str, $matches) && ! is_php('7') && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== FALSE)
1222		{
1223			$str = 'ipv6.host'.substr($str, strlen($matches[1]) + 2);
1224		}
1225
1226		return (filter_var('http://'.$str, FILTER_VALIDATE_URL) !== FALSE);
1227	}
1228
1229	// --------------------------------------------------------------------
1230
1231	/**
1232	 * Valid Email
1233	 *
1234	 * @param	string
1235	 * @return	bool
1236	 */
1237	public function valid_email($str)
1238	{
1239		if (function_exists('idn_to_ascii') && preg_match('#\A([^@]+)@(.+)\z#', $str, $matches))
1240		{
1241			$domain = defined('INTL_IDNA_VARIANT_UTS46')
1242				? idn_to_ascii($matches[2], 0, INTL_IDNA_VARIANT_UTS46)
1243				: idn_to_ascii($matches[2]);
1244
1245			if ($domain !== FALSE)
1246			{
1247				$str = $matches[1].'@'.$domain;
1248			}
1249		}
1250
1251		return (bool) filter_var($str, FILTER_VALIDATE_EMAIL);
1252	}
1253
1254	// --------------------------------------------------------------------
1255
1256	/**
1257	 * Valid Emails
1258	 *
1259	 * @param	string
1260	 * @return	bool
1261	 */
1262	public function valid_emails($str)
1263	{
1264		if (strpos($str, ',') === FALSE)
1265		{
1266			return $this->valid_email(trim($str));
1267		}
1268
1269		foreach (explode(',', $str) as $email)
1270		{
1271			if (trim($email) !== '' && $this->valid_email(trim($email)) === FALSE)
1272			{
1273				return FALSE;
1274			}
1275		}
1276
1277		return TRUE;
1278	}
1279
1280	// --------------------------------------------------------------------
1281
1282	/**
1283	 * Validate IP Address
1284	 *
1285	 * @param	string
1286	 * @param	string	'ipv4' or 'ipv6' to validate a specific IP format
1287	 * @return	bool
1288	 */
1289	public function valid_ip($ip, $which = '')
1290	{
1291		return $this->CI->input->valid_ip($ip, $which);
1292	}
1293
1294	// --------------------------------------------------------------------
1295
1296	/**
1297	 * Alpha
1298	 *
1299	 * @param	string
1300	 * @return	bool
1301	 */
1302	public function alpha($str)
1303	{
1304		return ctype_alpha($str);
1305	}
1306
1307	// --------------------------------------------------------------------
1308
1309	/**
1310	 * Alpha-numeric
1311	 *
1312	 * @param	string
1313	 * @return	bool
1314	 */
1315	public function alpha_numeric($str)
1316	{
1317		return ctype_alnum((string) $str);
1318	}
1319
1320	// --------------------------------------------------------------------
1321
1322	/**
1323	 * Alpha-numeric w/ spaces
1324	 *
1325	 * @param	string
1326	 * @return	bool
1327	 */
1328	public function alpha_numeric_spaces($str)
1329	{
1330		return (bool) preg_match('/^[A-Z0-9 ]+$/i', $str);
1331	}
1332
1333	// --------------------------------------------------------------------
1334
1335	/**
1336	 * Alpha-numeric with underscores and dashes
1337	 *
1338	 * @param	string
1339	 * @return	bool
1340	 */
1341	public function alpha_dash($str)
1342	{
1343		return (bool) preg_match('/^[a-z0-9_-]+$/i', $str);
1344	}
1345
1346	// --------------------------------------------------------------------
1347
1348	/**
1349	 * Numeric
1350	 *
1351	 * @param	string
1352	 * @return	bool
1353	 */
1354	public function numeric($str)
1355	{
1356		return (bool) preg_match('/^[\-+]?[0-9]*\.?[0-9]+$/', $str);
1357
1358	}
1359
1360	// --------------------------------------------------------------------
1361
1362	/**
1363	 * Integer
1364	 *
1365	 * @param	string
1366	 * @return	bool
1367	 */
1368	public function integer($str)
1369	{
1370		return (bool) preg_match('/^[\-+]?[0-9]+$/', $str);
1371	}
1372
1373	// --------------------------------------------------------------------
1374
1375	/**
1376	 * Decimal number
1377	 *
1378	 * @param	string
1379	 * @return	bool
1380	 */
1381	public function decimal($str)
1382	{
1383		return (bool) preg_match('/^[\-+]?[0-9]+\.[0-9]+$/', $str);
1384	}
1385
1386	// --------------------------------------------------------------------
1387
1388	/**
1389	 * Greater than
1390	 *
1391	 * @param	string
1392	 * @param	int
1393	 * @return	bool
1394	 */
1395	public function greater_than($str, $min)
1396	{
1397		return is_numeric($str) ? ($str > $min) : FALSE;
1398	}
1399
1400	// --------------------------------------------------------------------
1401
1402	/**
1403	 * Equal to or Greater than
1404	 *
1405	 * @param	string
1406	 * @param	int
1407	 * @return	bool
1408	 */
1409	public function greater_than_equal_to($str, $min)
1410	{
1411		return is_numeric($str) ? ($str >= $min) : FALSE;
1412	}
1413
1414	// --------------------------------------------------------------------
1415
1416	/**
1417	 * Less than
1418	 *
1419	 * @param	string
1420	 * @param	int
1421	 * @return	bool
1422	 */
1423	public function less_than($str, $max)
1424	{
1425		return is_numeric($str) ? ($str < $max) : FALSE;
1426	}
1427
1428	// --------------------------------------------------------------------
1429
1430	/**
1431	 * Equal to or Less than
1432	 *
1433	 * @param	string
1434	 * @param	int
1435	 * @return	bool
1436	 */
1437	public function less_than_equal_to($str, $max)
1438	{
1439		return is_numeric($str) ? ($str <= $max) : FALSE;
1440	}
1441
1442	// --------------------------------------------------------------------
1443
1444	/**
1445	 * Value should be within an array of values
1446	 *
1447	 * @param	string
1448	 * @param	string
1449	 * @return	bool
1450	 */
1451	public function in_list($value, $list)
1452	{
1453		return in_array($value, explode(',', $list), TRUE);
1454	}
1455
1456	// --------------------------------------------------------------------
1457
1458	/**
1459	 * Is a Natural number  (0,1,2,3, etc.)
1460	 *
1461	 * @param	string
1462	 * @return	bool
1463	 */
1464	public function is_natural($str)
1465	{
1466		return ctype_digit((string) $str);
1467	}
1468
1469	// --------------------------------------------------------------------
1470
1471	/**
1472	 * Is a Natural number, but not a zero  (1,2,3, etc.)
1473	 *
1474	 * @param	string
1475	 * @return	bool
1476	 */
1477	public function is_natural_no_zero($str)
1478	{
1479		return ($str != 0 && ctype_digit((string) $str));
1480	}
1481
1482	// --------------------------------------------------------------------
1483
1484	/**
1485	 * Valid Base64
1486	 *
1487	 * Tests a string for characters outside of the Base64 alphabet
1488	 * as defined by RFC 2045 http://www.faqs.org/rfcs/rfc2045
1489	 *
1490	 * @param	string
1491	 * @return	bool
1492	 */
1493	public function valid_base64($str)
1494	{
1495		return (base64_encode(base64_decode($str)) === $str);
1496	}
1497
1498	// --------------------------------------------------------------------
1499
1500	/**
1501	 * Prep data for form
1502	 *
1503	 * This function allows HTML to be safely shown in a form.
1504	 * Special characters are converted.
1505	 *
1506	 * @deprecated	3.0.6	Not used anywhere within the framework and pretty much useless
1507	 * @param	mixed	$data	Input data
1508	 * @return	mixed
1509	 */
1510	public function prep_for_form($data)
1511	{
1512		if ($this->_safe_form_data === FALSE OR empty($data))
1513		{
1514			return $data;
1515		}
1516
1517		if (is_array($data))
1518		{
1519			foreach ($data as $key => $val)
1520			{
1521				$data[$key] = $this->prep_for_form($val);
1522			}
1523
1524			return $data;
1525		}
1526
1527		return str_replace(array("'", '"', '<', '>'), array('&#39;', '&quot;', '&lt;', '&gt;'), stripslashes($data));
1528	}
1529
1530	// --------------------------------------------------------------------
1531
1532	/**
1533	 * Prep URL
1534	 *
1535	 * @param	string
1536	 * @return	string
1537	 */
1538	public function prep_url($str = '')
1539	{
1540		if ($str === 'http://' OR $str === '')
1541		{
1542			return '';
1543		}
1544
1545		if (strpos($str, 'http://') !== 0 && strpos($str, 'https://') !== 0)
1546		{
1547			return 'http://'.$str;
1548		}
1549
1550		return $str;
1551	}
1552
1553	// --------------------------------------------------------------------
1554
1555	/**
1556	 * Strip Image Tags
1557	 *
1558	 * @param	string
1559	 * @return	string
1560	 */
1561	public function strip_image_tags($str)
1562	{
1563		return $this->CI->security->strip_image_tags($str);
1564	}
1565
1566	// --------------------------------------------------------------------
1567
1568	/**
1569	 * Convert PHP tags to entities
1570	 *
1571	 * @param	string
1572	 * @return	string
1573	 */
1574	public function encode_php_tags($str)
1575	{
1576		return str_replace(array('<?', '?>'), array('&lt;?', '?&gt;'), $str);
1577	}
1578
1579	// --------------------------------------------------------------------
1580
1581	/**
1582	 * Reset validation vars
1583	 *
1584	 * Prevents subsequent validation routines from being affected by the
1585	 * results of any previous validation routine due to the CI singleton.
1586	 *
1587	 * @return	CI_Form_validation
1588	 */
1589	public function reset_validation()
1590	{
1591		$this->_field_data = array();
1592		$this->_error_array = array();
1593		$this->_error_messages = array();
1594		$this->error_string = '';
1595		return $this;
1596	}
1597
1598}
1599