<?php
/**
* @version	$Id: request_manager.php 15252 2012-03-30 17:49:37Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2010 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 kRequestManager extends kBase {

	/**
	 * Prevents request from being proceeded twice
	 *
	 * @var bool
	 * @access protected
	 */
	protected $processed = false;

	/**
	 * Processes request
	 *
	 * @access public
	 */
	public function process()
	{
		if ( $this->processed ) {
			return;
		}

		$this->dumpRequest();
		$this->processOpener();

		$events = $this->getEvents();
		$all_passed = $this->getAllPassed($events);

		// set "all_passed" before kApplication::GetTopmostPrefix method call !
		$this->Application->SetVar('all_passed', implode(',', $all_passed));

		foreach ($events as $prefix_special => $event_name) {
			$event =& $this->runEvent($prefix_special, $event_name);

			if ( $event->status == kEvent::erSTOP ) {
				// event requested to stop processing at this point
				kUtil::safeDefine('DBG_SKIP_REPORTING', 1);
				$this->Application->Session->SaveData();
				exit;
			}

			if ( $event->status == kEvent::erPERM_FAIL ) {
				$this->processPermissionError($event);
			}

			if ( ($event->status == kEvent::erSUCCESS || $event->status == kEvent::erPERM_FAIL) && $this->canRedirect($event) ) {
				$this->performRedirect($event);
			}
		}

		$this->Application->SetVar('events', $events);
		$this->Application->SetVar('passed', implode(',', $all_passed));

		$this->processed = true;
	}

	/**
	 * Dumps user request to debugger (only when enabled)
	 *
	 * @return void
	 * @access protected
	 */
	protected function dumpRequest()
	{
		if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_HTTPQUERY') ) {
			$this->Application->Debugger->appendHTML('HTTPQuery:');
			$this->Application->Debugger->dumpVars($this->Application->HttpQuery->_Params);
		}
	}

	/**
	 * Returns event names, given in request (post, get)
	 *
	 * @return Array
	 * @access protected
	 */
	protected function getEvents()
	{
		$post_events = $this->getEventsFromPost();

		return $post_events ? $post_events : $this->getEventsFromGet();
	}

	/**
	 * Get all passed prefixes
	 *
	 * @param Array $events
	 * @return Array
	 * @access protected
	 */
	protected function getAllPassed($events)
	{
		$ret = explode(',', $this->Application->GetVar('passed'));

		foreach ($events as $prefix_special => $event_name) {
			if (!$event_name) {
				continue;
			}

			if ($this->Application->isAdmin) {
				array_push($ret, $prefix_special);
			}
			else {
				// don't add special on Front-end because of category item list special is autogenerated
				$prefix_special = explode('.', $prefix_special);
				array_push($ret, $prefix_special[0]);
			}
		}

		return $ret;
	}

	/**
	 * Creates and runs event. Returns false, when prefix of given event isn't registered
	 *
	 * @param string $prefix_special
	 * @param string $event_name
	 *
	 * @return kEvent|false
	 * @access protected
	 */
	protected function &runEvent($prefix_special, $event_name)
	{
		$event = new kEvent($prefix_special . ':' . $event_name);

		if ( preg_match('/(.*?)-(.*)/', $event->Prefix, $regs) && $this->Application->prefixRegistred($regs[1]) ) {
			// this is event from cloned config -> load parent config to create valid clone
			$this->Application->UnitConfigReader->loadConfig($regs[1]);
			$this->Application->UnitConfigReader->runAfterConfigRead($regs[1]);
		}

		if ( !$this->Application->EventManager->verifyEventPrefix($event, true) ) {
			$false = false;

			return $false;
		}

		$event->SetRedirectParam('opener', 's'); // stay on same page after event is called
		$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));

		$event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
		/* @var $event_handler kEventHandler */

		if ( ($this->Application->RecallVar('user_id') == USER_ROOT) || $event_handler->CheckPermission($event) ) {
			$this->Application->HandleEvent($event);
			$this->Application->notifyEventSubscribers($event);
		}

		return $event;
	}

	/**
	 * Processes case, when event finished with permission error
	 *
	 * @param kEvent $event
	 * @access protected
	 */
	protected function processPermissionError($event)
	{
		// should do redirect but to no_permissions template
		$event->redirect = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
		$event->SetRedirectParam('pass', 'm');

		$themes_helper = $this->Application->recallObject('ThemesHelper');
		/* @var $themes_helper kThemesHelper */

		$event->SetRedirectParam( 'm_cat_id', $themes_helper->getPageByTemplate($event->redirect) );

		// restore stuff, that processOpener() changed
		$wid = $this->Application->GetVar('m_wid');
		$this->Application->RestoreVar( rtrim('opener_stack_' . $wid, '_') );

		// don't save last_template, because no_permission template does js history.back and could cause invalid opener_stack content
		$this->Application->SetVar('skip_last_template', 1);
	}

	/**
	 * Performs redirect after event execution
	 *
	 * @param kEvent $event
	 * @access protected
	 */
	protected function performRedirect($event)
	{
		// we need to pass category if the action was submitted to self-template, with the category passed
		// and it has not explicitly set redirect template or pass_category param
		if ( $this->samePageRedirect($event) && ($event->getEventParam('pass_category') === false) && $this->Application->GetVar('m_cat_id') ) {
			$event->SetRedirectParam('pass_category', 1);
		}

		$wid = $this->Application->GetVar('m_wid');
		$redirect_params = $event->getRedirectParams();

		if ( $wid && $event->getRedirectParam('opener') == 'u' ) {
			// update last element in current opener stack
			unset($redirect_params['opener']);
			$redirect_template = is_string($event->redirect) ? $event->redirect : null;
			$this->openerStackChange($redirect_template, $redirect_params);

			// reset opener, because kApplication::HREF will react differently when 'opener' => 'u'
			$event->SetRedirectParam('opener', 's');
			$event->redirect = defined('CLOSE_POPUP_TPL') ? CLOSE_POPUP_TPL : 'incs/close_popup';
		}

		if ( $event->getRedirectParam('pass') === false ) {
			// pass all discovered units to redirected page unless developer decided otherwise
			$event->SetRedirectParam('pass', 'all');
		}

		$this->Application->Redirect($event->redirect, $event->getRedirectParams(), '', $event->redirectScript);
	}

	/**
	 * Checks, if redirect can be made
	 *
	 * @param kEvent $event
	 * @return bool
	 * @access protected
	 */
	protected function canRedirect($event)
	{
		return $this->samePageRedirect($event) || strlen($event->redirect) > 0;
	}

	/**
	 * Checks, that current template will be displayed after redirect
	 *
	 * @param kEvent $event
	 * @return bool
	 * @access protected
	 */
	protected function samePageRedirect($event)
	{
		return $event->redirect === true || $event->redirect == $this->Application->GetVar('t');
	}

	/**
	 * Returns event names given in GET
	 *
	 * @return Array
	 * @access protected
	 */
	protected function getEventsFromGet()
	{
		$events = Array ();
		$discovered_units = $this->Application->HttpQuery->getDiscoveredUnits(false);

		foreach ($discovered_units as $prefix_special => $query_string) {
			$query_string = array_flip($query_string);

			if ( !isset($query_string['event']) ) {
				continue;
			}

			$event_name = $this->Application->GetVar($prefix_special . '_event');

			// we need to check for pre 5.1.0 url format, because of "PerPage"
			// query string part (that was added in place of "event" query
			// string part) is available only since 5.1.0 version
			if ($event_name && !is_numeric($event_name)) {
				$events[$prefix_special] = $event_name;
			}
		}

		return $events;
	}

	/**
	 * Returns event names given in POST
	 *
	 * @return Array
	 * @access protected
	 */
	protected function getEventsFromPost()
	{
		$ret = Array ();
		$events = $this->Application->GetVar('events');

		if (!$events) {
			return Array ();
		}

		foreach ($events as $prefix_special => $event_name) {
			if (!$event_name) {
				continue;
			}

			if ( is_array($event_name) ) {
				// HTML-input names like "events[prefix.special][event_name]", input value don't matter
				$event_name = key($event_name);
				$this->Application->SetVar($prefix_special . '_event', $event_name);
			}

			// HTML-input names like "events[prefix.special]", input value is event name
			$ret[$prefix_special] = $event_name;
		}

		return $ret;
	}

	/**
	 * Processes window opener stack
	 *
	 * @access protected
	 */
	protected function processOpener()
	{
		$opener_stack = $this->Application->makeClass('kOpenerStack');
		/* @var $opener_stack kOpenerStack */

		switch ( $this->Application->GetVar('m_opener') ) {
			case 'r':
				$opener_stack->reset();
				break;

			case 'd':
				// "down/push" new template to opener stack, deeplevel++
				if ( $this->Application->GetVar('front') ) {
					$front_session = $this->Application->recallObject('Session.front');
					/* @var $front_session Session */

					$opener_stack->pushRaw( '../' . $front_session->RecallVar('last_template') );
				}
				else {
					$opener_stack->pushRaw( $this->Application->RecallVar('last_template') );
				}
				break;

			case 'u':
				// "up/pop" last template from opener stack, deeplevel--
				$opener_stack->pop();
				break;

			case 'p':
				// pop-up - generate new wid
				$parent_wid = $this->Application->GetVar('m_wid'); // window_id of popup's parent window
				$popup_wid = (int)$this->Application->RecallVar('last_wid') + 1;
				$this->Application->StoreVar('last_wid', $popup_wid);
				$this->Application->SetVar('m_wid', $popup_wid);

				$popup_opener_stack = $this->Application->makeClass('kOpenerStack', Array ($popup_wid));
				/* @var $popup_opener_stack kOpenerStack */

				$popup_opener_stack->pushRaw( $this->getLastTemplate($parent_wid) );
				$popup_opener_stack->save();

				$this->Application->SetVar('m_opener', 's');

				/*// store window relations
				$window_relations = $this->Application->RecallVar('window_relations');
				$window_relations = $window_relations ? unserialize($window_relations) : Array ();
				$window_relations[$popup_wid] = $parent_wid;
				$this->Application->StoreVar('window_relations', serialize($window_relations));*/
				return;
				break;

			default:
				// "s/0," stay on same deep level
				break;
		}

		$this->Application->SetVar('m_opener', 's');
		$opener_stack->save();
	}

	/**
	 * Returns last template from window with given id
	 *
	 * @param int $window_id
	 * @return string
	 * @access protected
	 */
	protected function getLastTemplate($window_id)
	{
		if ( $this->Application->GetVar('front') ) {
			$front_session = $this->Application->recallObject('Session.front');
			/* @var $front_session Session */

			return '../' . $front_session->RecallVar( rtrim('last_template_popup_' . $window_id, '_') );
		}

		if ( $this->Application->GetVar('merge_opener_stack') ) {
			// get last template from parent (that was closed) window opener stack
			$parent_opener_stack = $this->Application->makeClass('kOpenerStack', Array ($window_id));
			/* @var $parent_opener_stack kOpenerStack */

			$last_template = $parent_opener_stack->pop(true);
			$parent_opener_stack->save(true);
		}
		else {
			$last_template = $this->Application->RecallVar( rtrim('last_template_popup_' . $window_id, '_') );
		}

		return $last_template;
	}

	/**
	 * Allows to add new element to opener stack
	 *
	 * @param string $template
	 * @param Array $params
	 * @param int $wid
	 * @access public
	 */
	public function openerStackPush($template = '', $params = Array (), $wid = null)
	{
		if ( !isset($params['pass']) ) {
			$params['pass'] = 'all';
		}

		/*// get parent window wid, when this was popup
		$window_relations = $this->Application->RecallVar('window_relations');
		$window_relations = $window_relations ? unserialize($window_relations) : Array ();
		$wid = isset($window_relations[$wid]) ? $window_relations[$wid] : false;*/

		$opener_stack = $this->Application->makeClass('kOpenerStack', Array ($wid));
		/* @var $opener_stack kOpenerStack */

		// change opener stack
		$default_params = Array ('m_opener' => 'u', '__URLENCODE__' => 1);

		if ( !$this->Application->ConfigValue('UsePopups') && $opener_stack->getWindowID() ) {
			// remove wid to show combined header block in editing window
			$default_params['m_wid'] = '';
			list ($last_template, $last_params, ) = $opener_stack->get(kOpenerStack::LAST_ELEMENT);

			// move last popup's opener stack element to main window's opener stack
			if ( $last_params ) {
				$last_params = array_merge($last_params, $default_params);
				$this->openerStackPush($last_template, $last_params, '');
			}
		}

		$params = array_merge($default_params, $params);
		$opener_stack->push($template, $params, 'index.php');
		$opener_stack->save();
	}

	/**
	 * Allows to change last element in opener stack
	 *
	 * @param string $new_template
	 * @param Array $new_params
	 * @access protected
	 */
	protected function openerStackChange($new_template = null, $new_params = null)
	{
		$opener_stack = $this->Application->makeClass('kOpenerStack');
		/* @var $opener_stack kOpenerStack */

		list ($template, $params, $index_file) = $opener_stack->pop();

		if ( isset($new_template) ) {
			$template = $new_template;
		}

		if ( isset($new_params) ) {
			$params = array_merge($params, $new_params);
		}

		if ( !isset($params['pass_events']) ) {
			// don't pass events, unless requested
			$params['pass_events'] = false;
		}

		$opener_stack->push($template, $params, $index_file);
		$opener_stack->save();
	}
}