<?php

/**
 * This file is part of LEPTON Core, released under the GNU GPL
 * Please see LICENSE and COPYING files in your package for details, specially for terms and warranties.
 * 
 * NOTICE:LEPTON CMS Package has several different licenses.
 * Please see the individual license in the header of each single file or info.php of modules and templates.
 *
 * @author          LEPTON Project
 * @copyright       2010-2021 LEPTON Project
 * @link            https://lepton-cms.org
 * @license         http://www.gnu.org/licenses/gpl.html
 * @license_terms   please see LICENSE and COPYING files in your package
 *
 */

// include class.secure.php to protect this file and the whole CMS!
if (defined('LEPTON_PATH')) {   
   include(LEPTON_PATH.'/framework/class.secure.php');
} else {
   $oneback = "../";
   $root = $oneback;
   $level = 1;
   while (($level < 10) && (!file_exists($root.'/framework/class.secure.php'))) {
      $root .= $oneback;
      $level += 1;
   }
   if (file_exists($root.'/framework/class.secure.php')) {
      include($root.'/framework/class.secure.php');
   } else {
      trigger_error(sprintf("[ <b>%s</b> ] Can't include class.secure.php!", $_SERVER['SCRIPT_NAME']), E_USER_ERROR);
   }
}
// end include class.secure.php

/**
 *	Class to handle values out the $_POST or $_GET
 */
 
class LEPTON_request extends LEPTON_abstract
{

	static $instance;
	
	/**
	 *	Public var to handle the way the class should look for the value;
	 *	supported types are 'post', 'get' or 'request'
	 *
	 *	@type		string
	 *	@default	"post"
	 *
	 */
	public $strict_looking_inside = "post";
	
	/**
	 *	By default we are not collecting any errors
	 *	
	 *	@since		0.1.0
	 *	@type		bool
	 *	@default	false
	 *
	 */
	public $error_mode = false;
	
	/**
	 *	Public var that holds the errors in an array.
	 *
	 *	@since		0.1.0
	 *	@type		array
	 *	@default	empty array
	 *
	 */
	public $errors = array();
	
	public function initialize()
	{
	
	}
	
	/**
	 *  Testing a list of values against the $_POST array and returns a linear list of the results.
	 *
	 *  @param  array   An array with an assoc. subarray for the 'keys' to test for.
     *
	 *  @code{.php}
	 *  // a simple list of values to test agains $_POST
	 *  $aLookUp = array(
     *       'page_id'      => array('type' => 'integer+', 'default' => NULL),
     *       'section_id'   => array('type' => 'integer+', 'default' => NULL),
     *       'hello_text'   => array('type' => 'string', 'default' => ''),
	 *		 'content'		=> array('type' => 'string_chars', 'default' => ""),
	 *		 'text'			=> array('type' => 'string_clean', 'default' => ""),
	 *		 'input'		=> array('type' => 'string_allowed', 'default' => "", 'range' => array('h1','h2','h3') )
     *   );
	 *
	 *  @endcode
	 *
     *  @return array   The results as an assoc. array.
	 *
	 */
	public function testPostValues( &$aValueList )
	{
	    $this->strict_looking_inside = "post";
	    
	    $aReturnList = array();
	    foreach($aValueList as $term => $options)
	    {
	        $aReturnList[ $term ] = $this->get_request( 
	            $term, 
	            $options['default'], 
	            $options['type'], 
	            (isset($options['range']) ? $options['range'] : "" )
	        );
	    }
	    
	    return $aReturnList;
	}
	
	/**
	 *  Testing a list of values against the $_GET array and returns a linear list of the results.
	 *  This method is similar to ::testPostValues
	 *
	 *  @param  array   An array with an assoc. subarray for the 'keys' to test for.
     *
	 *  @code{.php}
	 *  // a simple list of values to test agains $_GET
	 *  $aLookUp = array(
     *       'page_id'      => array('type' => 'integer+', 'default' => NULL),
     *       'section_id'   => array('type' => 'integer+', 'default' => NULL),
     *       'hello_text'   => array('type' => 'string', 'default' => ''),
	 *		 'content'		=> array('type' => 'string_chars', 'default' => ""),
	 *		 'text'			=> array('type' => 'string_clean', 'default' => ""),
	 *		 'input'		=> array('type' => 'string_allowed', 'default' => "", 'range' => array('h1','h2','h3') )
     *   );
	 *
	 *  @endcode
	 *
     *  @return array   The results as an assoc. array.
	 *
	 */
	public function testGetValues( &$aValueList )
	{
	    $this->strict_looking_inside = "get";
	    
	    $aReturnList = array();
	    foreach($aValueList as $term => $options)
	    {
	        $aReturnList[ $term ] = $this->get_request(
	            $term,
	            $options['default'],
	            $options['type'],
	            (isset($options['range']) ? $options['range'] : "" )
	        );
	    }
	    
	    return $aReturnList;
	}
	
	/**
	 *
	 *	Public function that looks for a value in the $_POST or $_GET super-variables.
	 *	If it is found and the type match it is returned. If not, the given default
	 *	value is returned. There are also optional settings for the situation, the
	 *	value has to be in a given range, and/or the string has to be a given size.
	 *
	 *	@param	string	name	Name of the value/key
	 *	@param	mixed	default	(optional) The default of this value
	 *	@param	string	type	(optional) The type of the value, see list below
	 *	@param	array	range	(optional) The settings for testing if the value is inside a range.
	 *
	 *	@retuns	mixed	the value
	 *
	 *	NOTICE:	the range-settings have no effekt if the default-value is set to NULL.
	 *
	 *
	 *	Supported types are (quick-, short-, long-notation)
	 *	
	 *	integer		Any integer
	 *	integer+	Any positive integer + 0
	 *	integer-	Any negative integer +0
	 *
	 *	string		Any string
	 *  string_clean    Any string - HTML tags will be removed!
	 *  string_chars    Any String - but tags are converted via htmlspecialchars!
	 *  string_allowed  Any String - HTML tags are removed, exept the given one in "range"!
	 *
	 *	email		Any mail adress
	 *	array		Any array (e.g. from a multible select)
	 *
	 *	Range got following keys/options
	 *
	 *	min		the minmium, as for integers the lowest number, as for strings the min. number of chars.
	 *			default: 0;
	 *
	 *	max		the maximum, as for integers the heights number, as for strings the max. number of chars.
	 *			default: 255;
	 *
	 *	use		what kind of use if the value is not inside the range, supported types are
	 *			- default	use the given default value (integer, string, NULL)
	 *			- min		however (value is less or more out of the range) use the min-value.
	 *			- max		however (value is less or more out of the range) use the max-value.
	 *			- near		if the value is less, use the min-value, if it is more, use the max-value.
	 *			- fill		only for strings.
	 *			- cut		only for strings.
	 *			default: 'default'
	 *
	 *	char	default: ' 'if #fill is used and the number of chars is less than the min-value,
	 *			the string is 'filled' up with this char.
	 *			default: ' ' (space|blank)
	 *
	 *	If one of the keys/options is missing, the default-settings are used.
	 *	NOTICE:	the range-settings have no effekt if the default-value is set to NULL.
	 *
	 */
	 
	public function get_request( $aName="", $aDefault=NULL, $type="", $range="") {
		
		if ($aName == "") return NULL;
		
		if ( false === $this->strict_looking_inside ) {
		
			if ( strtoupper($_SERVER['REQUEST_METHOD']) == "POST") {
				$return_value = (true === array_key_exists ($aName , $_POST) ) ? $_POST[$aName] : $aDefault;
			} else {
				$return_value = (true === array_key_exists ($aName , $_GET) ) ? $_GET[$aName] : $aDefault;
			}
		} else {
			switch (strtolower($this->strict_looking_inside)) {
				case 'post':
					$return_value = (true === array_key_exists($aName , $_POST) ) ? $_POST[$aName] : $aDefault;
					break;
					
				case 'get':
					$return_value = (true === array_key_exists($aName , $_GET) ) ? $_GET[$aName] : $aDefault;
					break;
					
				case 'request':
					$return_value = (true === array_key_exists($aName , $_REQUEST) ) ? $_REQUEST[$aName] : $aDefault;
					break;
					
				default:
					$return_value = NULL;
					break;
			}
		}
		
		if ($type != "") {
			switch (strtolower($type)) {
				//  [1] Integers
				//  1.1
				case 'integer':
					$return_value = intval($return_value);
					if (!is_integer($return_value)) {
						$return_value = $aDefault;
					} else {
						if ( true === is_array($range) )
						{
						    $this->__check_range($type, $return_value, $aDefault, $range);
						}
					}
					break;
				
				//  1.2 Only positive integers (without 0)
				case 'integer+':
					$return_value = intval($return_value);
					if (!is_integer($return_value))
					{
					    $return_value = $aDefault;
					}
					
					if ($return_value < 0)
					{
					    $return_value = $aDefault;
					}
					
					if (true === is_array($range) )
					{
					    $this->__check_range($type, $return_value, $aDefault, $range);
					}
					break;
					
				//  1.3 Only negative integers (without 0)
				case 'integer-':
					$return_value = intval($return_value);
					if (!is_integer($return_value))
					{
					    $return_value = $aDefault;
					}
					
					if ( $return_value > 0)
					{
					    $return_value = $aDefault;
					}
					
					if ( true === is_array($range) )
					{
					    $this->__check_range($type, $return_value, $aDefault, $range);
					}
					break;
            
                /*  1.4 decimal
                 *      Keep in mind that we only replace the "," (Comma) with "." (decimal-dot)!  
                 *      to save a valid value in the database.
                 *
                 *  e.g. "Win 1.234.300 Euro" will become "1234300"
                 *       "1.300.000,34" will become "1300000.34"
                 *       "1.300.000.27" will become "1300000.27"
                 *       "-23,99" will become "-23.99"
                 */ 
				case 'float':
				case 'double':
                case 'decimal': // -12.350,78 
                    //  1.4.1 remove all NONE numbers (but keep '.', ',' and '-')
                    $sPattern = "/[^0-9-,\.]/i";
                    $return_value = preg_replace (
                        $sPattern , 
                        "" ,
                        $return_value
                    );
                    
                    //  1.4.2 replace existing "," with "."
                    $return_value = str_replace(",", ".", $return_value);
                    
                    //  1.4.3 force to keep at last ONE dot (.)
                    $aTemp = explode(".", $return_value);
                    
                    //  1.4.3.1 more than one dot found!
                    if(count($aTemp) > 2)
                    {
                        $sPrefix = array_pop($aTemp);
                        $return_value = implode("", $aTemp).".".$sPrefix;    
                    }
                    
                    //  1.4.4 the range
                    if ( true === is_array($range) )
                    {
					    $this->__check_range($type, $return_value, $aDefault, $range);
                    }
					break;
                
                         
				//  [2] Strings
				//  2.1
				case 'string':
				case 'str':  // for backward compatibility only
					//	keep in mind that pdo add slashes automatically	via prepare and execute
					if (!is_string($return_value))
					{
					    $return_value = $aDefault;
					}
					
					if ( true === is_array($range) )
					{
					    $this->__check_range($type, $return_value, $aDefault, $range);
					}
					break;
				
				//  2.2 string without any html tags
				case 'string_clean':
					
					if (!is_string($return_value))
					{
					    $return_value = $aDefault;
					}
					else
					{
						$return_value = htmlspecialchars(strip_tags($return_value));
					}				
					break;	

                //  2.3 string with all html-tags converted to htmlspecialchars
				case 'string_chars':
					
					if (!is_string($return_value))
					{
					    $return_value = $aDefault;
					}
					else
					{
					    $return_value = lib_lepton::getToolInstance("htmlpurifier")->purify($return_value);
						$return_value = htmlspecialchars($return_value);
					}				
					break;
                
                //  2.4 string without tags but allowed html-tags
				case 'string_allowed':
				
					if (!is_string($return_value))
					{
					    $return_value = $aDefault;
					}
					else
					{
						if ( true === is_array($range) )
						{
							// range contains all allowed html-tags
							//  see: https://www.php.net/manual/de/function.strip-tags.php
							if (version_compare(PHP_VERSION, '7.4.0', '<'))
							{
							    $range = "<".implode("><", $range).">";
							}
							$return_value = strip_tags($return_value, $range);
						}
						else
						{
							$return_value = strip_tags($return_value, $range);
						}						
					}				
					break;						
				
				//  [3] EAN	
				case 'ean':
					if (false === $this->validate_ean13($return_value))
					{
					    $return_value = $aDefault;
					}
					break;
				
				//  [4] E-Mail	
				case 'email':
					if ( false === LEPTON_handle::checkEmailChars($return_value ))
					{
						$return_value = '';
					}
					break;

                //  [5] Date(-s)
                //  5.1
				case 'date':
					$format = 'Y-m-d';
					$d = DateTime::createFromFormat($format, $return_value);
					$result = $d && $d->format($format) == $return_value;
					if ($result == false) {
						$return_value = $aDefault;
					}
					break;	
                
                //  5.2
				case 'datetime':
					$format = 'Y-m-d H:i:s';
					$d = DateTime::createFromFormat($format, $return_value);
					$result = $d && $d->format($format) == $return_value;
					if ($result == false) {
						$return_value = $aDefault;
					}
					break;					

                //  [6] Arrays
				case 'array':
					if(!is_array($return_value))
					{
					    $return_value = $aDefault;
					}
					break;
			}
		}
		return $return_value;
		
	}
	
	static public function add_slash( &$sText="" ) {
		
		if (substr($sText, 0,1) != "/")
		{
		    $sText = "/".$sText;
		}
		
		if (substr($sText, -1) != "/")
		{
		    $sText .= "/";
		}
	}
	
	private function __check_range($type, &$value, &$default, &$range) {
		
		if ($value === NULL) return true;
		
		if ( !array_key_exists('use', $range))
		{
		    $range['use'] = 'default';
		}
		
		if ( !array_key_exists('min', $range))
		{
		    $range['min'] = 0;
		}
		
		if ( !array_key_exists('max', $range))
		{
		    $range['max'] = 255;
		}
		
		if ( !array_key_exists('char', $range))
		{
		    $range['char'] = " ";
		}
		
		switch (strtolower ($type) ) {
			case 'integer':
			case 'integer+':
			case 'integer-':
			case 'float':
				if ( ($value < $range['min']) OR ($value > $range['max']) )
				{
					switch (strtolower($range['use']))
					{
						case 'default' : $value = $default; break;
						case 'min': $value = $range['min']; break;
						case 'max': $value = $range['max']; break;
						case 'near':
							if ($value < $range['min'])
							{
							    $value = $range['min'];
							}
							
							if ($value > $range['max'])
							{
							    $value = $range['max'];
							}
							break;
					}
				}
				break;
				
			case 'string':
				$nc = strlen($value);
				
				if (($nc < $range['min']) OR ($nc > $range['max'])) {
					
					switch(strtolower($range['use'])) {
						case 'default': $value = $default; break;
						case 'fill':
							for ($i=$nc; $i<=$range['min'];$i++)
							{
							    $value .= $range['char'];
							}
							break;
							
						case 'cut':
							$value = substr(
							    $value,
							    0,
							    ( isset($range['max'])
							        ? intval($range['max'])
							        : $nc 
							    )
							);
							break;
					}
				}
				break;
		}
		return true;
	}
}
