<?php
/**
 * This file implements the Request class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link http://sourceforge.net/projects/evocms/}.
 *
 * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}.
 *
 * {@internal License choice
 * - If you have received this file as part of a package, please find the license.txt file in
 *   the same folder or the closest folder above for complete license terms.
 * - If you have received this file individually (e-g: from http://cvs.sourceforge.net/viewcvs.py/evocms/)
 *   then you must choose one of the following licenses before using the file:
 *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
 *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
 * }}
 *
 * {@internal Open Source relicensing agreement:
 * Daniel HAHLER grants Francois PLANQUE the right to license
 * Daniel HAHLER's contributions to this file and the b2evolution project
 * under any OSI approved OSS license (http://www.opensource.org/licenses/).
 *
 * PROGIDISTRI S.A.S. grants Francois PLANQUE the right to license
 * PROGIDISTRI S.A.S.'s contributions to this file and the b2evolution project
 * under any OSI approved OSS license (http://www.opensource.org/licenses/).
 * }}
 *
 * @package evocore
 *
 * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
 * @author blueyed: Daniel HAHLER
 * @author fplanque: Francois PLANQUE.
 * @author mbruneau: Marc BRUNEAU / PROGIDISTRI
 *
 * @version $Id: _request.class.php,v 1.19.2.5.2.3 2007/05/14 20:16:00 blueyed Exp $
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


/**
 * Request class: handles the current HTTP request
 *
 * @todo (fplanque)
 */
class Request
{
	/**
	 * @var Array of values, indexed by param name
	 */
	var $params = array();

	/**
	 * @var Array of strings, indexed by param name
	 */
	var $err_messages = array();

	/**
	 * @var Log Reference to a Log object that gets used to add (error) messages to.
	 */
	var $Messages;

	/**
	 * If true, the internal function {@link _add_message_to_Log()} will create links to IDs
	 * of the fields where the error occurred.
	 *
	 * Useful if your forms are WAAAAYYYY too long!
	 *
	 * @var boolean
	 */
	var $link_log_messages_to_field_IDs = false;	// fp> leave this OFF for b2evo.


	/**
	 * Constructor.
	 */
	function Request( & $Messages )
	{
		$this->Messages = & $Messages;
	}


	/**
	 * Sets a parameter with values from the request or to provided default,
	 * except if param is already set!
	 *
	 * Also removes magic quotes if they are set automatically by PHP.
	 * Also forces type.
	 * Priority order: POST, GET, COOKIE, DEFAULT.
	 *
	 * @param string Variable to set
	 * @param string Force value type to one of:
	 * - integer
	 * - float, double
	 * - string (strips (HTML-)Tags, trims whitespace)
	 * - array	(TODO:  array/integer  , array/array/string )
	 * - html (does nothing)
	 * - '' (does nothing)
	 * - '/^...$/' check regexp pattern match (string)
	 * - boolean (will force type to boolean, but you can't use 'true' as a default since it has special meaning. There is no real reason to pass booleans on a URL though. Passing 0 and 1 as integers seems to be best practice).
	 * Value type will be forced only if resulting value (probably from default then) is !== NULL
	 * @param mixed Default value or TRUE if user input required
	 * @param boolean Do we need to memorize this to regenerate the URL for this page?
	 * @param boolean Override if variable already set
	 * @param boolean Force setting of variable to default if no param is sent and var wasn't set before
	 * @param mixed true will refuse illegal values,
	 *              false will try to convert illegal to legal values,
	 *              'allow_empty' will refuse illegal values but will always accept empty values (This helps blocking dirty spambots or borked index bots. Saves a lot of processor time by killing invalid requests)
	 * @return mixed Final value of Variable, or false if we don't force setting and did not set
	 */
	function param( $var, $type = '', $default = '', $memorize = false,
									$override = false, $use_default = true, $strict_typing = 'allow_empty' )
	{
		return $this->params[$var] = param( $var, $type, $default, $memorize, $override, $use_default, $strict_typing );
	}


	/**
	 * Sets several similar parameters at once.
	 *
	 * @param array
	 */
	function params( $vars, $type = '', $default = '', $memorize = false, $override = false, $forceset = true )
	{
		foreach( $vars as $var )
		{
			$this->param( $var, $type, $default, $memorize, $override, $forceset );
		}
	}


	/**
	 * Set the value of a param (by force! :P)
	 */
	function set_param( $var, $value )
	{
		$this->params[$var] = $value;
		$GLOBALS[$var] = $value;
	}


	/**
	 * Memorize a parameter for automatic future use in regenerate_url()
	 */
	function memorize_param( $var, $type, $default, $value = NULL )
	{
		memorize_param( $var, $type, $default, $value ); // note: will also set the global $$var if $valie is not NULL
		if( !is_null($value) )
		{
			$this->params[$var] = $value;
		}
	}


	/**
	 * Extend a parameter with an array of params.
	 *
	 * Will be used for author/authorsel[], etc.
	 * Note: cannot be used for catsel[], because catsel is NON-recursive
	 *
	 * @param string Variable to extend
	 * @param string Name of array Variable to use as an extension
	 * @param boolean Save non numeric prefix?
	 */
	function param_extend( $var, $var_ext_array, $save_prefix = true )
	{
		// Make sure original var exists:
		if( !isset($this->params[$var]) )
		{
			debug_die( 'Cannot extend non existing param : '.$var );
		}
		$original_val = $this->params[$var];

		// Get extension array:
		$ext_values_array = $this->param( $var_ext_array, 'array', array(), false );
		if( empty($ext_values_array) )
		{	// No extension required:
			return $original_val;
		}

		// Handle prefix:
		$prefix = '';
		if( $save_prefix )
		{	// We might want to save a prefix:
			$prefix = substr( $original_val, 0, 1 );
			if( is_numeric( $prefix ) )
			{	// The prefix is numeric, so it's NOT a prefix
				$prefix = '';
			}
			else
			{	// We save the prefix, we must crop if off from the values:
				$original_val = substr( $original_val, 1 );
			}
		}

		// Merge values:
		if( empty($original_val) )
		{
			$original_values_array = array();
		}
		else
		{
			$original_values_array = explode( ',', $original_val );
		}
		$new_values = array_merge( $original_values_array, $ext_values_array );
		$new_values = array_unique( $new_values );
		$this->params[$var] = $prefix.implode( ',', $new_values );

		// Save into global var:
		global $$var;
		$$var = $this->params[$var];

		return $this->params[$var];
	}


	/**
	 * Sets a date parameter by converting locale date (if valid) to ISO date.
	 *
	 * If the date is not valid, it is set to the param unchanged (unconverted).
	 */
	function param_date( $var, $err_msg, $required, $default = '' )
	{
		$this->param( $var, 'string', $default );

		$iso_date = $this->param_check_date( $var, $err_msg, $required );

		if( $iso_date )
		{
			$this->set_param( $var, $iso_date );
		}

		return $this->params[$var];
	}


	/**
	 * Sets a time parameter with the value from the request of the var argument or of the concat of the var argument_h: var argument_mn: var argument_s ,
	 * except if param is already set!
	 *
	 * @param string Variable to set
	 * @param mixed Default value or TRUE if user input required
	 * @param boolean Do we need to memorize this to regenerate the URL for this page?
	 * @param boolean Override if variable already set
	 * @param boolean Force setting of variable to default?
	 * @return mixed Final value of Variable, or false if we don't force setting and did not set
	 */
	function param_time( $var, $default = '', $memorize = false,	$override = false, $forceset = true )
	{
		global $$var;

		$got_time = false;

		if( $this->param( $var, 'string', $default, $memorize, $override, $forceset ) )
		{ // Got a time from text field:
			if( preg_match( '/^(\d\d):(\d\d)(:(\d\d))?$/', $$var, $matches ) )
			{
				$time_h = $matches[1];
				$time_mn = $matches[2];
				$time_s = empty( $matches[4] ) ? 0 : $matches[4];
				$got_time = true;
			}
		}
		elseif( ( $time_h = param( $var.'_h', 'integer', -1 ) ) != -1
					&& ( $time_mn = param( $var.'_mn', 'integer', -1 ) ) != -1 )
		{	// Got a time from selects:
			$time_s = param( $var.'_s', 'integer', 0 );
			$$var = substr('0'.$time_h,-2).':'.substr('0'.$time_mn,-2).':'.substr('0'.$time_s,-2);
			$this->params[$var] = $$var;
			$got_time = true;
		}

		if( $got_time )
		{ // We got a time...
			// Check if ranges are correct:
			if( $time_h >= 0 && $time_h <= 23
				&& $time_mn >= 0 && $time_mn <= 59
				&& $time_s >= 0 && $time_s <= 59 )
			{
				// Time is correct
				return $$var;
			}
		}


		$this->param_error( $var, T_('Please enter a valid time.') );

		return false;
	}


	/**
	 * set a parameter with the second part(X2) of the value from request ( X1-X2 )
	 *
	 * @param string Variable to set
	 *
	 */
	function param_child_select_value( $var )
	{
		global $$var;

		if( $val = param( $var, 'string' ) )
		{ // keep only the second part of val
			preg_match( '/^[0-9]+-([0-9]+)$/', $val, $res );

			if( isset( $res[1] ) )
			{ //set to the var the second part of val
				$$var = $res[1];
				$this->params[$var] = $res[1];
				return $$var;
			}
		}
		return '';
	}


	/**
	 * Check if the value is a file name
	 *
	 * @param string param name
	 * @param string error message
	 * @return boolean true if OK
	 */
	function param_isFilename( $var, $err_msg )
	{
		if( $error_filename = validate_filename( $this->params[$var] ) )
		{
			$this->param_error( $var, $error_filename );
			return false;
		}
		return true;
	}


	/**
	 * Get the action from params.
	 *
	 * If we got no "action" param, we'll check for an "actionArray" param
	 * ( <input type="submit" name="actionArray[real_action]" ...> ).
	 * And the real $action will be found in the first key...
	 * When there are multiple submit buttons, this is smarter than checking the value which is a translated string.
	 * When there is an image button, this allows to work around IE not sending the value (it only sends X & Y coords of the click).
	 *
	 * @param mixed Default to use.
	 * @return string
	 */
	function param_action( $default = '', $memorize = false )
	{
		#$this->param( 'action', 'string', NULL, true ); // blueyed>> is there a reason to remember? (taken from files.php)
		$action = $this->param( 'action', 'string', NULL, $memorize );

		if( is_null($action) )
		{ // Check $actionArray
			$action = $this->param_arrayindex( 'actionArray', $default );

			$this->set_param( 'action', $action ); // always set "action"
		}

		return $action;
	}


	/**
	 * Get the param from an array param's first index.
	 *
	 * E.g., for "param[value]" as a submit button you can get the value with
	 *       <code>Request::param_arrayindex( 'param' )</code>.
	 *
	 * @see Request::param_action()
	 * @param string Param name
	 * @param mixed Default to use
	 * @return string
	 */
	function param_arrayindex( $param_name, $default = '' )
	{
		$array = array_keys( $this->param( $param_name, 'array', array() ) );
		$action = array_pop($array);
		if( is_string($action) )
		{
			$action = substr( strip_tags($action), 0, 50 );  // sanitize it
		}
		elseif( !empty($action) )
		{ // this is probably a numeric index from '<input name="array[]" .. />'
			debug_die( 'Invalid action!' );
		}
		else
		{
			$action = $default;
		}

		return $action;
	}


	/**
	 * Get the value of a param.
	 *
	 * @return NULL|mixed The value of the param, if set. NULL otherwise.
	 */
	function get( $var )
	{
		return isset($this->params[$var]) ? $this->params[$var] : NULL;
	}


	/**
	 * @param string param name
	 * @param string error message
	 * @param string|NULL error message for form field ($err_msg gets used if === NULL).
	 * @return boolean true if OK
	 */
	function param_string_not_empty( $var, $err_msg, $field_err_msg = NULL )
	{
		$this->param( $var, 'string', true );
		return $this->param_check_not_empty( $var, $err_msg, $field_err_msg );
	}


	/**
	 * @param string param name
	 * @param string error message
	 * @param string|NULL error message for form field ($err_msg gets used if === NULL).
	 * @return boolean true if OK
	 */
	function param_check_not_empty( $var, $err_msg, $field_err_msg = NULL )
	{
		if( empty( $this->params[$var] ) )
		{
			$this->param_error( $var, $err_msg, $field_err_msg );
			return false;
		}
		return true;
	}


	/**
	 * Checks if the param is a decimal number (no float, e.g. 3.14).
	 *
	 * @param string param name
	 * @param string error message
	 * @return boolean true if OK
	 */
	function param_check_number( $var, $err_msg, $required = false )
	{
		if( empty( $this->params[$var] ) && ! $required )
		{ // empty is OK:
			return true;
		}

		if( ! preg_match( '#^[0-9]+$#', $this->params[$var] ) )
		{
			$this->param_error( $var, $err_msg );
			return false;
		}
		return true;
	}


	/**
	 * Gets a param and makes sure it's a decimal number (no float, e.g. 3.14) in a given range.
	 *
	 * @param string param name
	 * @param integer min value
	 * @param integer max value
	 * @param string error message
	 * @return boolean true if OK
	 */
	function param_integer_range( $var, $min, $max, $err_msg, $required = true )
	{
		$this->param( $var, 'integer', $required ? true : '' );
		return $this->param_check_range( $var, $min, $max, $err_msg, $required );
	}


	/**
	 * Checks if the param is a decimal number (no float, e.g. 3.14) in a given range.
	 *
	 * @param string param name
	 * @param integer min value
	 * @param integer max value
	 * @param string error message
	 * @return boolean true if OK
	 */
	function param_check_range( $var, $min, $max, $err_msg, $required = true )
	{
		if( empty( $this->params[$var] ) && ! $required )
		{ // empty is OK:
			return true;
		}

		if( ! preg_match( '~^[-+]?\d+$~', $this->params[$var] ) || $this->params[$var] < $min || $this->params[$var] > $max )
		{
			$this->param_error( $var, sprintf( $err_msg, $min, $max ) );
			return false;
		}
		return true;
	}


	/**
	 * @param string param name
	 * @return boolean true if OK
	 */
	function param_check_email( $var, $required = false )
	{
		if( empty( $this->params[$var] ) && ! $required )
		{ // empty is OK:
			return true;
		}

		if( !is_email( $this->params[$var] ) )
		{
			$this->param_error( $var, T_('The email address is invalid.') );
			return false;
		}
		return true;
	}


	/**
	 * @param string param name
	 * @return boolean true if OK
	 */
	function param_check_phone( $var, $required = false )
	{
		global $$var;

		if( empty( $this->params[$var] ) && ! $required )
		{ // empty is OK:
			return true;
		}

		if( ! preg_match( '|^\+?[\-*#/(). 0-9]+$|', $this->params[$var] ) )
		{
			$this->param_error( $var, T_('The phone number is invalid.') );
			return false;
		}
		else
		{ // Keep only 0123456789+ caracters
			$this->params[$var] = preg_replace( '#[^0-9+]#', '', $this->params[$var]);
			$$var = $this->params[$var];
		}
		return true;
	}


	/**
	 * @param string param name
	 * @param string error message
	 * @return boolean true if OK
	 */
	function param_check_url( $var, & $uri_scheme )
	{
		if( $error_detail = validate_url( $this->params[$var], $uri_scheme ) )
		{
			$this->param_error( $var, sprintf( T_('Supplied URL is invalid. (%s)'), $error_detail ) );
			return false;
		}
		return true;
	}


	/**
	 * Check if param is an ISO date
	 *
	 * @param string param name
	 * @param string error message
	 * @param boolean Is a non-empty date required?
	 * @param string date format (php format)
	 * @return boolean|string false if not OK, ISO date if OK
	 */
	function param_check_date( $var, $err_msg, $required = false, $date_format = NULL )
	{

		if( empty( $this->params[$var] ) )
		{ // empty is OK if not required:
			if( $required )
			{
				$this->param_error( $var, $err_msg );
				return false;
			}

			return '';
		}

		if( empty( $date_format ) )
		{	// Use locale date format:
			$date_format = locale_datefmt();
		}

		// Convert PHP date format to regexp pattern:
		$date_regexp = '~'.preg_replace_callback( '~(\\\)?(\w)~', create_function( '$m', '
			if( $m[1] == "\\\" ) return $m[2]; // escaped
			switch( $m[2] )
			{
				case "d": return "([0-3]\\d)"; // day, 01-31
				case "j": return "([1-3]?\\d)"; // day, 1-31
				case "l": return "(".str_replace("~", "\~", implode("|", array_map("trim", array_map("T_", $GLOBALS["weekday"])))).")";
				case "D": return "(".str_replace("~", "\~", implode("|", array_map("trim", array_map("T_", $GLOBALS["weekday_abbrev"])))).")";
				case "e": // b2evo extension!
					return "(".str_replace("~", "\~", implode("|", array_map("trim", array_map("T_", $GLOBALS["weekday_letter"])))).")";
				case "S": return "(st|nd|rd|th)"; // english suffix for day

				case "m": return "([0-1]\\d)"; // month, 01-12
				case "n": return "(1?\\d)"; // month, 1-12
				case "F": return "(".str_replace("~", "\~", implode("|", array_map("trim", array_map("T_", $GLOBALS["month"])))).")"; //  A full textual representation of a month, such as January or March
				case "M": return "(".str_replace("~", "\~", implode("|", array_map("trim", array_map("T_", $GLOBALS["month_abbrev"])))).")";

				case "y": return "(\\d\\d)"; // year, 00-99
				case "Y": return "(\\d{4})"; // year, XXXX
				default:
					return $m[0];
			}' ), $date_format ).'~i'; // case-insensitive?
		// Allow additional spaces, e.g. "03  May 2007" when format is "d F Y":
		$date_regexp = preg_replace( '~ +~', '\s+', $date_regexp );
		//pre_dump( $date_format, $this->params[$var], $date_regexp );

		// Check that the numbers match the date pattern:
		if( preg_match( $date_regexp, $this->params[$var], $numbers ) )
		{	// Date does match pattern:
			//pre_dump( $numbers );

			// Get all date pattern parts. We should get 3 parts!:
			preg_match_all( '/(?<!\\\\)[A-Za-z]/', $date_format, $parts ); // "(?<!\\\\)" means that the letter is not escaped with "\"
			//pre_dump( $parts );

			foreach( $parts[0] as $position => $part )
			{
				switch( $part )
				{
					case 'd':
					case 'j':
						$day = $numbers[$position+1];
						break;

					case 'm':
					case 'n':
						$month = $numbers[$position+1];
						break;
					case 'F': // full month name
						$month = array_search( strtolower($numbers[$position+1]), array_map('strtolower', array_map('trim', array_map('T_', $GLOBALS['month']))) );
						break;
					case 'M':
						$month = array_search( strtolower($numbers[$position+1]), array_map('strtolower', array_map('trim', array_map('T_', $GLOBALS['month_abbrev']))) );
						break;

					case 'y':
					case 'Y':
						$year = $numbers[$position+1];
						if( $year < 50 )
						{
							$year = 2000 + $year;
						}
						elseif( $year < 100 )
						{
							$year = 1900 + $year;
						}
						break;
				}
			}

			if( checkdate( $month, $day, $year ) )
			{ // all clean! :)

				// We convert the value to ISO:
				$iso_date = substr( '0'.$year, -4 ).'-'.substr( '0'.$month, -2 ).'-'.substr( '0'.$day, -2 );

				return $iso_date;
			}
		}

		// Date did not pass all tests:

		$this->param_error( $var, $err_msg );

		return false;
	}


	/**
	 * Check if param is a valid date (format wise).
	 *
	 * @deprecated won't handle locale date formats on PHP < 4.3.3
	 *
	 * @param string param name
	 * @param string error message
	 * @param array|boolean Additional params:
	 *        - 'required': Is non-empty date required? Default: true.
	 *        - 'date_pattern': Pattern for the date, using named capturing groups ('day', 'month', 'year').
	 *           Default: '#^(\d\d\d\d)-?(\d\d)-?(\d\d)$#' (accepts both ISO and "compact")
	 *         	 		You can also use "Named Capturing Groups" and write it like this:
	 *         	  	'#^(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)$#' (ISO).
	 *           		This allows to use for example '#^(?P<day>\d\d)-(?P<month>\d\d)-(?P<year>\d\d\d\d)$#'
	 *           		NOTE: "Named Capturing Groups" require PHP 4.3.3! If no named groups are given we'll use
	 *           		(1 == year, 2 == month, 3 == day).
	 *        - 'field_err_msg': Error for the form field
	 * @return boolean|array true if empty, but OK. False if not valid. Matching array if ok.
	 */
	function param_check_date_format( $var, $err_msg, $func_params = array() )
	{
		$required = ( !isset($func_params['required']) || $func_params['required'] );

		if( empty( $this->params[$var] ) && ! $required )
		{ // empty is OK:
			return true;
		}

		if( !isset($func_params['date_pattern']) )
		{ // ISO by default
			//$func_params['date_pattern'] = '#^(?P<year>\d\d\d\d)-(?P<month>[01]?\d)-(?P<day>[0123]?\d)$#'; // This may be useful, when taking care of leading zeros afterwards again, eg date('Y-m-d').
			//$func_params['date_pattern'] = '#^(?P<year>\d\d\d\d)-(?P<month>[01]\d)-(?P<day>[0123]\d)$#';
			//$func_params['date_pattern'] = '#^(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d)$#';
			$func_params['date_pattern'] = '#^(\d\d\d\d)-?(\d\d)-?(\d\d)$#';
		}

		if( preg_match( $func_params['date_pattern'], $this->params[$var], $match ) )
		{
			if( isset( $match['month'], $match['day'], $match['year'] ) )
			{ // extended/preferred format (PHP >= 4.3.3)
				if( checkdate( $match['month'], $match['day'], $match['year'] ) )
				{ // all clean! :)
					return $match;
				}
			}
			elseif( isset( $match[1], $match[2], $match[3] ) )
			{ // Fallback to numeric format ( 1 == year, 2 == month, 3 == day )
				if( checkdate( $match[2], $match[3], $match[1] ) )
				{ // all clean! :)
					return $match;
				}
			}
		}

		if( !isset($func_params['field_err_msg']) )
		{
			$func_params['field_err_msg'] = NULL;
		}

		$this->param_error( $var, $err_msg, $func_params['field_err_msg'] );
		return false;
	}


	/**
	 * Sets a date parameter with values from the request or to provided default,
	 * And check if param is a valid date (format wise).
	 *
	 * @param string Variable to set
	 * @param mixed Default value or TRUE if user input required
	 * @param boolean memorize ( see {@link param()} )
	 * @param string error message
	 * @param boolean 'required': Is non-empty date required? Default: true.
	 *
	 * @return string the compact date value ( yyyymmdd )
	 */
	function param_compact_date( $var, $default = '', $memorize = false, $err_msg, $required = false )
	{
		global $$var;

		$this->params[$var] = param( $var, 'string', $default, $memorize );

		if( preg_match( '#^[0-9]{4,}$#', $this->params[$var] ) )
		{	// We have a compact date ( used for URL filtering )
			// TODO: check that this still works
			if( $this->param_check_date_format( $var, $err_msg, array( 'required' => $required ) ) )
			{	// Valid DATE input format!
				// Convert to output format:
				$this->set_param( $var, compact_date( $this->params[$var] ) );
				return $this->params[$var];
			}
			// Invalid compact date for some reason:
			return '';
		}

		// We do not have a compact date, try normal date matching:
		$iso_date = $this->param_check_date( $var, $err_msg, $required );

		if( $iso_date )
		{
			$this->set_param( $var, compact_date( $iso_date ) );
			return $this->params[$var];
		}

		// Nothing valid found....
		return '';
	}


	/**
	 * Check if the value of a param is a regular expression (syntax).
	 *
	 * @param string param name
	 * @param string error message
	 * @param string|NULL error message for form field ($err_msg gets used if === NULL).
	 * @return boolean true if OK
	 */
	function param_check_regexp( $var, $err_msg, $field_err_msg = NULL )
	{
		if( ! is_regexp( $this->params[$var] ) )
		{
			$this->param_error( $var, $field_err_msg );
			return false;
		}
		return true;
	}


	/**
	 * @param string param name
	 * @param string param name
	 * @param boolean Is a password required? (non-empty)
	 * @return boolean true if OK
	 */
	function param_check_passwords( $var1, $var2, $required = false )
	{
		global $Settings;

		$pass1 = $this->params[$var1];
		$pass2 = $this->params[$var2];

		if( empty($pass1) && empty($pass2) && ! $required )
		{ // empty is OK:
			return true;
		}

		if( empty($pass1) )
		{
			$this->param_error( $var1, T_('Please enter your password twice.') );
			return false;
		}
		if( empty($pass2) )
		{
			$this->param_error( $var2, T_('Please enter your password twice.') );
			return false;
		}

		// checking the password has been typed twice the same:
		if( $pass1 != $pass2 )
		{
			$this->param_error_multiple( array( $var1, $var2), T_('You typed two different passwords.') );
			return false;
		}

		if( strlen($pass1) < $Settings->get('user_minpwdlen') )
		{
			$this->param_error_multiple( array( $var1, $var2), sprintf( T_('The minimum password length is %d characters.'), $Settings->get('user_minpwdlen') ) );
			return false;
		}

		return true;
	}


	/**
	 * @param array of param names
	 * @param string error message
	 * @param string|NULL error message for form field ($err_msg gets used if === NULL).
	 * @return boolean true if OK
	 */
	function params_check_at_least_one( $vars, $err_msg, $field_err_msg = NULL )
	{
		foreach( $vars as $var )
		{
			if( !empty( $this->params[$var] ) )
			{ // Okay, we got at least one:
				return true;
			}
		}

		// Error!
		$this->param_error_multiple( $vars, $err_msg, $field_err_msg );
		return false;
	}


	/**
	 * Sets a combo parameter with values from the request,
	 * => the value of the select option and the input text value if new is selected
	 * Display an error if the new value is selected that the input text has a value
	 *
	 * @param string Variable to set
 	 * @param mixed Default value or TRUE if user input required
	 * @param boolean true: allows to select new without entring a value in the input combo text
	 * @param string error message
	 *
	 * @return string position status ID or 'new' or '' if new is seleted but not input text value
	 *
	 */
	function param_combo( $var, $default, $allow_none, $err_msg = ''  )
	{
		$this->params[$var] = param( $var, 'string', $default );

		if( $this->params[$var] == 'new' )
		{	// The new option is selected in the combo select, so we need to check if we have a value in the combo input text:
			$this->params[$var.'_combo'] = param( $var.'_combo', 'string' );

			if( empty( $this->params[$var.'_combo'] ) )
			{ // We have no value in the combo input text

				// Set request param to null
				$this->params[$var] = NULL;

				if( !$allow_none )
				{ // it's not allowed, so display error:
					$this->param_error( $var, $err_msg );
				}
			}
		}

		return $this->params[$var];
	}


	/**
	 * Check if there have been validation errors
	 *
	 * We play it safe here and check for all kind of errors, not just those from this particlar class.
	 *
	 * @return integer
	 */
	function validation_errors()
	{
		return $this->Messages->count('error');
	}


	/**
	 * Add an error for a variable, either to the Form's field and/or the global {@link $Messages} object.
	 *
	 * @param string param name
	 * @param string|NULL error message (by using NULL you can only add an error to the field, but not the $Message object)
	 * @param string|NULL error message for form field ($err_msg gets used if === NULL).
	 */
	function param_error( $var, $err_msg, $field_err_msg = NULL )
	{
		if( ! isset( $this->err_messages[$var] ) )
		{ // We haven't already recorded an error for this field:
			if( $field_err_msg === NULL )
			{
				$field_err_msg = $err_msg;
			}
			$this->err_messages[$var] = $field_err_msg;

			if( isset($err_msg) )
			{
				$this->_add_message_to_Log( $var, $err_msg, 'error' );
			}
		}
	}


	/**
	 * Add an error for multiple variables, either to the Form's field and/or the global {@link $Messages} object.
	 *
	 * @param array of param names
	 * @param string|NULL error message (by using NULL you can only add an error to the field, but not the $Message object)
	 * @param string|NULL error message for form fields ($err_msg gets used if === NULL).
	 */
	function param_error_multiple( $vars, $err_msg, $field_err_msg = NULL )
	{
		if( $field_err_msg === NULL )
		{
			$field_err_msg = $err_msg;
		}

		foreach( $vars as $var )
		{
			if( ! isset( $this->err_messages[$var] ) )
			{ // We haven't already recorded an error for this field:
				$this->err_messages[$var] = $field_err_msg;
			}
		}

		if( isset($err_msg) )
		{
			$this->_add_message_to_Log( $var, $err_msg, 'error' );
		}
	}


	/**
	 * This function is used by {@link param_error()} and {@link param_error_multiple()}.
	 *
	 * If {@link $link_log_messages_to_field_IDs} is true, it will link those parts of the
	 * error message that are not already links, to the html IDs of the fields with errors.
	 *
	 * @access protected
	 *
	 * @param string param name
	 * @param string error message
	 */
	function _add_message_to_Log( $var, $err_msg, $log_category = 'error' )
	{
		if( $this->link_log_messages_to_field_IDs )
		{
			$var_id = Form::get_valid_id($var);
			$start_link = '<a href="#'.$var_id.'" onclick="var form_elem = document.getElementById(\''.$var_id.'\'); if( form_elem ) { form_elem.select(); }">';

			if( strpos( $err_msg, '<a' ) !== false )
			{ // there is at least one link in $err_msg, link those parts that are no links
				$err_msg = preg_replace( '~(\s*)(<a\s+[^>]+>[^<]*</a>\s*)~i', '</a>$1&raquo;$2'.$start_link, $err_msg );
			}

			if( substr($err_msg, 0, 4) == '</a>' )
			{ // There was a link at the beginning of $err_msg: we do not prepend an emtpy link before it
				$this->Messages->add( substr( $err_msg, 4 ).'</a>', $log_category );
			}
			else
			{
				$this->Messages->add( $start_link.$err_msg.'</a>', $log_category );
			}
		}
		else
		{
			$this->Messages->add( $err_msg, $log_category );
		}
	}


	/**
	 * Compiles the cat array from $cat (recursive + optional modifiers) and $catsel[] (non recursive)
	 * and keeps those values available for future reference
	 */
	function compile_cat_array( $restrict_to_blog = 0, $cat_default = NULL, $catsel_default = array() )
	{
		// For now, we'll also need those as globals!
		global $cat_array, $cat_modifier;

		$cat = $this->param( 'cat', '/^[*\-]?([0-9]+(,[0-9]+)*)?$/', $cat_default, true ); // List of cats to restrict to
		$catsel = $this->param( 'catsel', 'array', $catsel_default, true );  // Array of cats to restrict to

		$cat_array = array();
		$cat_modifier = '';

		compile_cat_array( $cat, $catsel, /* by ref */ $cat_array, /* by ref */ $cat_modifier, $restrict_to_blog );

		// Also memorize inside of Request:
		$this->params['cat_array'] = $cat_array;
		$this->params['cat_modifier'] = $cat_modifier;
	}

}


?>
