<?php
/**
* @version	$Id: http_query.php 15601 2012-11-02 14:18:43Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

class kHTTPQuery extends Params {

	/**
	 * Cache of QueryString parameters
	 * from config, that are represented
	 * in environment variable
	 *
	 * @var Array
	 */
	protected $discoveredUnits = Array ();

	/**
	 * $_POST vars
	 *
	 * @var Array
	 * @access private
	 */
	var $Post;

	/**
	 * $_GET vars
	 *
	 * @var Array
	 * @access private
	 */
	var $Get;

	/**
	 * $_COOKIE vars
	 *
	 * @var Array
	 * @access private
	 */
	var $Cookie;

	/**
	 * $_SERVER vars
	 *
	 * @var Array
	 * @access private
	 */
	var $Server;

	/**
	 * $_ENV vars
	 *
	 * @var Array
	 * @access private
	 */
	var $Env;

	/**
	 * Order in what write
	 * all vars together in
	 * the same array
	 *
	 * @var string
	 */
	var $Order;

	/**
	 * Uploaded files info
	 *
	 * @var Array
	 * @access private
	 */
	var $Files;

	var $specialsToRemove = Array();

	/**
	 * SessionID is given via "sid" variable in query string
	 *
	 * @var bool
	 */
	var $_sidInQueryString = false;

	/**
	 * Trust information, provided by proxy
	 *
	 * @var bool
	 */
	protected $_trustProxy = false;

	/**
	 * Loads info from $_POST, $_GET and
	 * related arrays into common place
	 *
	 * @param string $order
	 * @access public
	 */
	public function __construct($order = 'CGPF')
	{
		parent::__construct();

		$this->Order = $order;

		if ( isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
			// when AJAX request is made from jQuery, then create ajax variable,
			// so any logic based in it (like redirects) will not break down
			$_GET['ajax'] = 'yes';
		}

		$vars = kUtil::getConfigVars();

		$this->_trustProxy = isset($vars['TrustProxy']) ? (bool)$vars['TrustProxy'] : false;
	}

	/**
	 * Discovers unit form request and returns it's QueryString option on success
	 *
	 * @param string $prefix_special
	 *
	 * @return Array|bool
	 * @access public
	 */
	public function discoverUnit($prefix_special)
	{
		list($prefix) = explode('.', $prefix_special);

		$query_string = $this->getQueryString($prefix);

		if ($query_string) {
			// only units with QueryString option can be discovered
			$this->discoveredUnits[$prefix_special] = $query_string;

			return $query_string;
		}

		unset( $this->discoveredUnits[$prefix] );

		return false;
	}

	/**
	 * Returns units, passed in request
	 *
	 * @param bool $prefix_special_only
	 * @return Array
	 * @access protected
	 */
	public function getDiscoveredUnits($prefix_special_only = true)
	{
		return $prefix_special_only ? array_keys( $this->discoveredUnits ) : $this->discoveredUnits;
	}

	/**
	 * Returns QueryMap for requested unit config.
	 * In case if unit config is a clone, then get parent item's (from prefix) config to create clone
	 *
	 * @param string $prefix
	 * @return Array
	 * @access protected
	 */
	protected function getQueryString($prefix)
	{
		$ret = $this->Application->getUnitOption($prefix, 'QueryString', Array ());

		if ( !$ret && preg_match('/(.*?)-(.*)/', $prefix, $regs) ) {
			// "#prefix" (new format), "prefix" (old format)
			return $this->_getQueryString('#' . $regs[2]);
		}

		return $ret;
	}

	/**
	 * Returns query string (with safety check against missing prefixes)
	 *
	 * @param string $prefix
	 * @return Array
	 */
	private function _getQueryString($prefix)
	{
		if ( $this->Application->prefixRegistred($prefix) ) {
			return $this->Application->getUnitOption($prefix, 'QueryString');
		}

		return substr($prefix, 0, 1) == '#' ? $this->_getQueryString( substr($prefix, 1) ) : Array ();
	}

	/**
	 * Removes specials from request
	 *
	 * @param Array $array
	 * @return Array
	 * @access protected
	 */
	protected function _removeSpecials($array)
	{
		$ret = Array ();
		$removed = false;

		foreach ($this->specialsToRemove as $prefix_special => $flag) {
			if ( $flag ) {
				$removed = true;
				list ($prefix, $special) = explode('.', $prefix_special, 2);

				foreach ($array as $key => $val) {
					$new_key = preg_match("/^" . $prefix . "[._]{1}" . $special . "(.*)/", $key, $regs) ? $prefix . $regs[1] : $key;
					$ret[$new_key] = is_array($val) ? $this->_removeSpecials($val) : $val;
				}
			}
		}

		return $removed ? $ret : $array;
	}

	public function process()
	{
		$this->AddAllVars();
		$this->removeSpecials();
		ini_set('magic_quotes_gpc', 0);

		$this->Application->UrlManager->LoadStructureTemplateMapping();

		$this->AfterInit();
	}

	/**
	 * All all requested vars to
	 * common storage place
	 *
	 * @return void
	 * @access protected
	 */
	protected function AddAllVars()
	{
		for ($i = 0; $i < strlen($this->Order); $i++) {
			switch ($this->Order[$i]) {
				case 'G':
					$this->Get = $this->AddVars($_GET);
					if ( array_key_exists('sid', $_GET) ) {
						$this->_sidInQueryString = true;
					}

					$vars = $this->Application->processQueryString($this->Get(ENV_VAR_NAME));

					if ( array_key_exists('sid', $vars) ) {
						// used by Session::GetPassedSIDValue
						$this->Get['sid'] = $vars['sid'];
					}

					$this->AddParams($vars);
					break;

				case 'P':
					$this->Post = $this->AddVars($_POST);
					$this->convertPostEvents();
					$this->_processPostEnvVariables();
					break;

				case 'C':
					$this->Cookie = $this->AddVars($_COOKIE);
					break;

				/*case 'E';
					$this->Env = $this->AddVars($_ENV, false); //do not strip slashes!
					break;

				case 'S';
					$this->Server = $this->AddVars($_SERVER, false); //do not strip slashes!
					break;*/

				case 'F';
					$this->convertFiles();
					$this->Files = $this->MergeVars($_FILES); // , false); //do not strip slashes!
					break;
			}
		}
	}

	/**
	 * Allow POST variables, that names were transformed by PHP ("." replaced with "_") to
	 * override variables, that were virtually created through environment variable parsing
	 *
	 */
	function _processPostEnvVariables()
	{
		$passed = $this->Get('passed');
		if ( !$passed ) {
			return;
		}

		$passed = explode(',', $passed);
		foreach ($passed as $prefix_special) {
			if ( strpos($prefix_special, '.') === false ) {
				continue;
			}

			list ($prefix, $special) = explode('.', $prefix_special);
			$query_map = $this->getQueryString($prefix);
			$post_prefix_special = $prefix . '_' . $special;

			foreach ($query_map as $var_name) {
				if ( array_key_exists($post_prefix_special . '_' . $var_name, $this->Post) ) {
					$this->Set($prefix_special . '_' . $var_name, $this->Post[$post_prefix_special . '_' . $var_name]);
				}
			}
		}
	}

	/**
	 * Removes requested specials from all request variables
	 *
	 * @return void
	 * @access protected
	 */
	protected function removeSpecials()
	{
		$this->specialsToRemove = $this->Get('remove_specials');

		if ( $this->specialsToRemove ) {
			foreach ($this->specialsToRemove as $prefix_special => $flag) {
				if ( $flag && strpos($prefix_special, '.') === false ) {
					unset($this->specialsToRemove[$prefix_special]);
					trigger_error('Incorrect usage of "<strong>remove_specials[' . $prefix_special . ']</strong>" field (no special found)', E_USER_NOTICE);
				}
			}

			$this->_Params = $this->_removeSpecials($this->_Params);
		}
	}

	/**
	 * Finishes initialization of kHTTPQuery class
	 *
	 * @return void
	 * @access protected
	 * @todo: only uses build-in rewrite listeners, when cache is build for the first time
	 */
	protected function AfterInit()
	{
		$rewrite_url = $this->Get('_mod_rw_url_');

		if ( $this->Application->RewriteURLs() || $rewrite_url ) {
			// maybe call onafterconfigread here

			$this->Application->UrlManager->initRewrite();

			if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() ) {
				$this->Application->Debugger->profileStart('url_parsing', 'Parsing <b>MOD_REWRITE</b> url');
				$this->Application->UrlManager->rewrite->parseRewriteURL();
				$description = 'Parsing <b>MOD_REWRITE</b> url (template: <b>' . $this->Get('t') . '</b>)';
				$this->Application->Debugger->profileFinish('url_parsing', $description);
			}
			else {
				$this->Application->UrlManager->rewrite->parseRewriteURL();
			}

			if ( !$rewrite_url && $this->rewriteRedirectRequired() ) {
				// rewrite url is missing (e.g. not a script from tools folder)
				$url_params = $this->getRedirectParams();

				// no idea about how to check, that given template require category to be passed with it, so pass anyway
				$url_params['pass_category'] = 1;
				$url_params['response_code'] = 301; // Moved Permanently

				trigger_error('Non mod-rewrite url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" used', E_USER_NOTICE);
				$this->Application->Redirect('', $url_params);
			}
		}
		else {
			$this->Application->VerifyThemeId();
			$this->Application->VerifyLanguageId();
		}
	}

	/**
	 * Checks, that non-rewrite url was visited and it's automatic rewrite is required
	 *
	 * @return bool
	 */
	function rewriteRedirectRequired()
	{
		$redirect_conditions = Array (
			!$this->IsHTTPSRedirect(), // not https <-> http redirect
			!$this->refererIsOurSite(), // referer doesn't match ssl path or non-ssl domain (same for site domains)
			!defined('GW_NOTIFY'), // not in payment gateway notification script
			preg_match('/[\/]{0,1}index.php[\/]{0,1}/', $_SERVER['PHP_SELF']), // "index.php" was visited
			$this->Get('t') != 'index', // not on index page
		);

		$perform_redirect = true;

		foreach ($redirect_conditions as $redirect_condition) {
			$perform_redirect = $perform_redirect && $redirect_condition;

			if (!$perform_redirect) {
				return false;
			}
		}

		return true;
	}

	/**
	 * This is redirect from https to http or via versa
	 *
	 * @return bool
	 */
	function IsHTTPSRedirect()
	{
		$http_referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : false;

		return (
			( PROTOCOL == 'https://' && preg_match('#http:\/\/#', $http_referer) )
			||
			( PROTOCOL == 'http://' && preg_match('#https:\/\/#', $http_referer) )
		);
	}

	/**
	 * Checks, that referer is out site
	 *
	 * @return bool
	 */
	function refererIsOurSite()
	{
		if ( !array_key_exists('HTTP_REFERER', $_SERVER) ) {
			// no referer -> don't care what happens
			return false;
		}

		$site_helper = $this->Application->recallObject('SiteHelper');
		/* @var $site_helper SiteHelper */

		$found = false;
		$http_referer = $_SERVER['HTTP_REFERER'];
		preg_match('/^(.*?):\/\/(.*?)(\/|$)/', $http_referer, $regs); // 1 - protocol, 2 - domain

		if ($regs[1] == 'https') {
			$found = $site_helper->getDomainByName('SSLUrl', $http_referer) > 0;

			if (!$found) {
				// check if referer starts with our ssl url
				$ssl_url = $this->Application->ConfigValue('SSL_URL');
				$found = $ssl_url && preg_match('/^' . preg_quote($ssl_url, '/') . '/', $http_referer);
			}
		}
		else {
			$found = $site_helper->getDomainByName('DomainName', $regs[2]) > 0;

			if (!$found) {
				$found = $regs[2] == DOMAIN;
			}
		}

		return $found;
	}

	function convertFiles()
	{
		if ( !$_FILES ) {
			return ;
		}

		$tmp = Array ();
		$file_keys = Array ('error', 'name', 'size', 'tmp_name', 'type');

		foreach ($_FILES as $file_name => $file_info) {
			if ( is_array($file_info['error']) ) {
				$tmp[$file_name] = $this->getArrayLevel($file_info['error'], $file_name);
			}
			else {
				$normal_files[$file_name] = $file_info;
			}
		}

		if ( !$tmp ) {
			return ;
		}

		$files = $_FILES;
		$_FILES = Array ();

		foreach ($tmp as $prefix => $prefix_files) {
			$anchor =& $_FILES;
			foreach ($prefix_files['keys'] as $key) {
				$anchor =& $anchor[$key];
			}

			foreach ($prefix_files['value'] as $field_name) {
				unset($inner_anchor, $copy);
				$work_copy = $prefix_files['keys'];

				foreach ($file_keys as $file_key) {
					$inner_anchor =& $files[$prefix][$file_key];

					if ( isset($copy) ) {
						$work_copy = $copy;
					}
					else {
						$copy = $work_copy;
					}

					array_shift($work_copy);
					foreach ($work_copy as $prefix_file_key) {
						$inner_anchor =& $inner_anchor[$prefix_file_key];
					}

					$anchor[$field_name][$file_key] = $inner_anchor[$field_name];
				}
			}
		}
		// keys: img_temp, 0, values: LocalPath, ThumbPath
	}

	function getArrayLevel(&$level, $prefix='')
	{
		$ret['keys'] = $prefix ? Array($prefix) : Array();
		$ret['value'] = Array();

		foreach($level as $level_key => $level_value)
		{
			if( is_array($level_value) )
			{
				$ret['keys'][] = $level_key;
				$tmp = $this->getArrayLevel($level_value);

				$ret['keys'] = array_merge($ret['keys'], $tmp['keys']);
				$ret['value'] = array_merge($ret['value'], $tmp['value']);
			}
			else
			{
				$ret['value'][] = $level_key;
			}
		}

		return $ret;
	}

	/**
	 * Overwrites GET events with POST events in case if they are set and not empty
	 *
	 * @return void
	 * @access protected
	 */
	protected function convertPostEvents()
	{
		$events = $this->Get('events', Array ());
		/* @var $events Array */

		if ( is_array($events) ) {
			$events = array_filter($events);

			foreach ($events as $prefix_special => $event_name) {
				$this->Set($prefix_special . '_event', $event_name);
			}
		}
	}

	function finalizeParsing($passed = Array())
	{
		if (!$passed) {
			return;
		}

		foreach ($passed as $passed_prefix) {
			$this->discoverUnit($passed_prefix); // from mod-rewrite url parsing
		}

		$this->Set('passed', implode(',', $this->getDiscoveredUnits()));
	}

	/**
	 * Saves variables from array specified
	 * into common variable storage place
	 *
	 * @param Array $array
	 * @param bool $strip_slashes
	 * @return Array
	 * @access private
	 */
	function AddVars($array, $strip_slashes = true)
	{
		if ( $strip_slashes ) {
			$array = $this->StripSlashes($array);
		}

		foreach ($array as $key => $value) {
			$this->Set($key, $value);
		}

		return $array;
	}

	function MergeVars($array, $strip_slashes = true)
	{
		if ( $strip_slashes ) {
			$array = $this->StripSlashes($array);
		}

		foreach ($array as $key => $value_array) {
			// $value_array is an array too
			$this->_Params = kUtil::array_merge_recursive($this->_Params, Array ($key => $value_array));
		}

		return $array;
	}

	function StripSlashes($array)
	{
		static $magic_quotes = null;

		if (!isset($magic_quotes)) {
			$magic_quotes = get_magic_quotes_gpc();
		}

		foreach ($array as $key => $value) {
			if (is_array($value)) {
				$array[$key] = $this->StripSlashes($value);
			}
			else {
				if ($magic_quotes) {
					$value = stripslashes($value);
				}

				if (!$this->Application->isAdmin) {
					$value = htmlspecialchars($value, null, CHARSET);
				}

				$array[$key] = $value;
			}
		}

		return $array;
	}

	/**
	 * Returns all $_GET array excluding system parameters, that are not allowed to be passed through generated urls
	 *
	 * @param bool $access_error Method is called during no_permission, require login, session expiration link preparation
	 * @return Array
	 */
	function getRedirectParams($access_error = false)
	{
		$vars = $this->Get;
		$unset_vars = Array (ENV_VAR_NAME, 'rewrite', '_mod_rw_url_', 'Action');

		if (!$this->_sidInQueryString) {
			$unset_vars[] = 'sid';
		}

		// remove system variables
		foreach ($unset_vars as $var_name) {
			if (array_key_exists($var_name, $vars)) {
				unset($vars[$var_name]);
			}
		}

		if ($access_error) {
			// place 1 of 2 (also in UsersEventHandler::OnSessionExpire)
			$vars = $this->_removePassThroughVariables($vars);
		}

		// transform arrays
		return $this->_transformArrays($vars);
	}

	/**
	 * Removes all pass_though variables from redirect params
	 *
	 * @param Array $url_params
	 * @return Array
	 */
	function _removePassThroughVariables($url_params)
	{
		$pass_through = array_key_exists('pass_through', $url_params) ? $url_params['pass_through'] : '';
		if (!$pass_through) {
			return $url_params;
		}

		$pass_through = explode(',', $pass_through . ',pass_through');
		foreach ($pass_through as $pass_through_var) {
			unset($url_params[$pass_through_var]);
		}

		$url_params['no_pass_through'] = 1; // this way kApplication::HREF won't add them again

		return $url_params;
	}

	function _transformArrays($array, $level_prefix = '')
	{
		$ret = Array ();
		foreach ($array as $var_name => $var_value) {
			$new_var_name = $level_prefix ? $level_prefix . '[' . $var_name . ']' : $var_name;

			if (is_array($var_value)) {
				$ret = array_merge($ret, $this->_transformArrays($var_value, $new_var_name));
			}
			else {
				$ret[$new_var_name] = $var_value;
			}
		}

		return $ret;
	}

	function writeRequestLog($filename)
	{
		$log_file = (defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/' . $filename;

		if ( is_writable(dirname($log_file)) ) {
			$fp = fopen($log_file, 'a');

			if ( $fp ) {
				$session = $this->Application->recallObject('Session');
				/* @var $session Session */

				$user_id = $session->GetField('PortalUserId');
				$admin_mark = $this->Application->isAdmin ? 'ADMIN' : 'FRONT';

				$data = '[' . date('D M d H:i:s Y') . '] ' . $admin_mark . '; ip: ' . $this->getClientIp() . '; user_id: ' . $user_id . '; sid: ' . $this->Application->GetSID() . '; request: ' . "\n";
				if ( $this->Get ) {
					$data .= "_GET:\n" . print_r($this->Get, true);
				}

				if ( $this->Post ) {
					$data .= "_POST:\n" . print_r($this->Post, true);
				}

				if ( $this->Cookie ) {
					$data .= "_COOKIE:\n" . print_r($this->Cookie, true);
				}
				$data .= str_repeat('=', 100) . "\n";

				fwrite($fp, $data);
				fclose($fp);
			}
			else {
				trigger_error('Request Log directory not writable', E_USER_WARNING);
			}
		}
		else {
			trigger_error('Request Log directory not writable', E_USER_WARNING);
		}
	}

	/**
	 * Checks, that url is empty
	 *
	 * @return bool
	 * @access public
	 */
	public function isEmptyUrl()
	{
		if ( $this->Application->RewriteURLs() ) {
			return !$this->Get('_mod_rw_url_');
		}

		return !count($this->Get);
	}

	/**
	 * Returns the client IP address.
	 *
	 * @return string The client IP address
	 * @access public
	 */
	public function getClientIp()
	{
		if ( $this->_trustProxy ) {
			if ( array_key_exists('HTTP_CLIENT_IP', $_SERVER) ) {
				return $_SERVER['HTTP_CLIENT_IP'];
			}

			if ( array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) ) {
				$client_ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

				foreach ($client_ip as $ip_address) {
					$clean_ip_address = trim($ip_address);

					if ( false !== filter_var($clean_ip_address, FILTER_VALIDATE_IP) ) {
						return $clean_ip_address;
					}
				}

				return '';
			}
		}

		return $_SERVER['REMOTE_ADDR'];
	}
}