<?php
/**
* @version	$Id: event_manager.php 14357 2011-06-01 07:40:40Z 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!');

	define('hBEFORE',	1);
	define('hAFTER',	2);

	define('reBEFORE',	1);
	define('reAFTER',	2);

	class kEventManager extends kBase {

		/**
		* Connection to database
		*
		* @var kDBConnection
		* @access public
		*/
		var $Conn;

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

		/**
		 * Build events registred for
		 * pseudo classes. key - pseudo class
		 * value - event name
		 *
		 * @var Array
		 * @access private
		 */
		var $buildEvents=Array();


		/**
		 * Events, that should be run before parser initialization
		 *
		 * @var Array
		 */
		var $beforeRegularEvents = Array();

		/**
		 * Events, that should be run after parser initialization
		 *
		 * @var Array
		 */
		var $afterRegularEvents = Array();

		/**
		 * Holds before hooks
		 * key - prefix.event (to link to)
		 * value - hooked event info
		 *
		 * @var Array
		 * @access private
		 */
		var $beforeHooks=Array();

		/**
		 * Holds after hooks
		 * key - prefix.event (to link to)
		 * value - hooked event info
		 *
		 * @var Array
		 * @access private
		 */
		var $afterHooks = Array();


		var $recursionStack = Array();

		function kEventManager()
		{
			parent::kBase();
			$this->Conn =& $this->Application->GetADODBConnection();
		}


		/**
		 * Returns information about registered regular events
		 *
		 * @param bool $from_cache
		 *
		 * @return Array
		 */
		function getRegularEvents($from_cache = false)
		{
			static $agents = null;

			if ($from_cache) {
				return Array (
					reBEFORE => $this->beforeRegularEvents,
					reAFTER => $this->afterRegularEvents,
				);
			}
			else {
				if (!isset($agents)) {
					$sql = 'SELECT *
							FROM ' . $this->Application->getUnitOption('agent', 'TableName') . '
							WHERE Status = ' . STATUS_ACTIVE . ' AND LastRunStatus <> ' . AGENT_LAST_RUN_RUNNING;
					$all_agents = $this->Conn->Query($sql);

					$agents = Array (
						reBEFORE => Array (),
						reAFTER => Array (),
					);

					foreach ($all_agents as $agent_data) {
						$agents[ $agent_data['RunMode'] ][ $agent_data['AgentName'] ] = Array (
							'EventName' => $agent_data['Event'],
							'RunInterval' => (int)$agent_data['RunInterval'],
							'LastRunOn' => (int)$agent_data['LastRunOn'],
							'NextRunOn' => (int)$agent_data['NextRunOn'],
							'Status' => $agent_data['Status'],
						);
					}
				}

				return $agents;
			}
		}


		/**
		 * Set's new enviroment parameter mappings
		 * between their names as application vars
		 *
		 * @param Array $new_query_maps
		 * @access public
		 */
		/*function setQueryMaps($new_query_maps)
		{
			$this->queryMaps = $new_query_maps;
		}*/

		/**
		 * Adds new query map to already parsed query maps
		 *
		 * @param string $prefix
		 */
		function setQueryMap($prefix_special)
		{
			list($prefix) = explode('.', $prefix_special);

			$query_map = $this->getQueryMap($prefix);
			if ($query_map) {
				$this->queryMaps[$prefix_special] = $query_map;
			}
			else {
				unset($this->queryMaps[$prefix]);
			}

//			'passed' is set later in ProcessRequest - do we really need it here? (it breakes HTTPQuery initialization...)
//			$this->Application->SetVar('passed', implode(',', array_keys($this->queryMaps)) );
			return $query_map;
		}

		/**
		 * 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
		 */
		function getQueryMap($prefix)
		{
			$query_map = $this->Application->getUnitOption($prefix, 'QueryString');
			if (!$query_map) {
				if (preg_match('/(.*?)-(.*)/', $prefix, $regs)) {
					// #prefix - format for configs, that are only cloned & optionally used for hooking (without # is old format)
					$check_prefixes = Array ('#'.$regs[2], $regs[2]);
					foreach ($check_prefixes as $check_prefix) {
						if ($this->Application->UnitConfigReader->prefixRegistred($check_prefix)) {
							return $this->Application->getUnitOption($check_prefix, 'QueryString');
						}
					}
				}
			}

			return $query_map;
		}

		/**
		 * Registers new regular event
		 *
		 * @param string $short_name name to be used to store last maintenace run info
		 * @param string $event_name
		 * @param int $run_interval run interval in seconds
		 * @param int $type before or after regular event
		 */
		function registerRegularEvent($short_name, $event_name, $run_interval, $type = reBEFORE, $status = STATUS_ACTIVE)
		{
			if($type == reBEFORE)
			{
				$this->beforeRegularEvents[$short_name] = Array('EventName' => $event_name, 'RunInterval' => $run_interval, 'Status' => $status);
			}
			else
			{
				$this->afterRegularEvents[$short_name] = Array('EventName' => $event_name, 'RunInterval' => $run_interval, 'Status' => $status);
			}
		}

		function registerBuildEvent($pseudo_class,$build_event_name)
		{
			$this->buildEvents[$pseudo_class]=$build_event_name;
		}

		/**
		 * Returns build event by pseudo class
		 * name if any defined in config
		 *
		 * @param string $pseudo_class
		 * @return kEvent
		 * @access public
		 */
		function &getBuildEvent($pseudo_class)
		{
			$false = false;
			if( !isset($this->buildEvents[$pseudo_class]) ) return $false;

			$event = new kEvent();
			$event->Name=$this->buildEvents[$pseudo_class];
			$event->MasterEvent=null;
			return $event;
		}

		/**
		 * Check if event is called twice, that causes recursion
		 *
		 * @param kEvent $event
		 */
		function isRecursion(&$event)
		{
			$event_key = $event->getPrefixSpecial().':'.$event->Name;
			return in_array($event_key, $this->recursionStack) ? true : false;
		}

		function pushEvent(&$event)
		{
			$event_key = $event->getPrefixSpecial().':'.$event->Name;
			array_push($this->recursionStack, $event_key);
		}

		function popEvent()
		{
			array_pop($this->recursionStack);
		}

		/**
		 * Allows to process any type of event
		 *
		 * @param kEvent $event
		 * @access public
		 */
		function HandleEvent(&$event)
		{
			if ($this->isRecursion($event)) {
				return true;
			}

			$this->pushEvent($event);

			if( !$this->Application->prefixRegistred($event->Prefix) )
			{
				$unit_config_reader =& $this->Application->recallObject('kUnitConfigReader');
				$unit_config_reader->loadConfig($event->Prefix);
				if( !$this->Application->prefixRegistred($event->Prefix) )
				{
					trigger_error('Prefix <b>'.$event->Prefix.'</b> not registred (requested event <b>'.$event->Name.'</b>)', E_USER_WARNING);
					return false;
				}
			}

			if (!$event->SkipBeforeHooks) {
				$this->processHooks($event, hBEFORE);
				if ($event->status == erFATAL) return true;
			}

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

			if ($event->status == erFATAL) return true;
			if (!$event->SkipAfterHooks) {
				$this->processHooks($event, hAFTER);
			}

			$this->popEvent();
			return true;
		}

		/**
		 * Returns event names given in POST
		 *
		 * @return Array
		 */
		function _getEventsFromPost()
		{
			$events = $this->Application->GetVar('events');
			if ($events === false) {
				return Array ();
			}

			$ret = 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;
		}

		function ProcessRequest()
		{
			$this->processOpener();

			// 1. get events from $_POST
			$events = $this->_getEventsFromPost();

			// 2. if nothing there, then try to find them in $_GET
			if ($this->queryMaps && !$events) {
				// if we got $_GET type submit (links, not javascript)
				foreach ($this->queryMaps as $prefix_special => $query_map) {
					$query_map = array_flip($query_map);
					if (isset($query_map['event'])) {
						$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;
						}
					}
				}

				// used ?
				$actions = $this->Application->GetVar('do');
				if ($actions) {
					list ($prefix, $event_name) = explode('_', $actions);
					if ($event_name) {
						$events[$prefix] = $event_name;
					}
				}
			}

			// 3. store all prefixes passed before event processing, because they are used by GetTopmostPrefix
			$all_passed = explode(',', $this->Application->GetVar('passed'));
			foreach ($events as $prefix_special => $event_name) {
				if (!$event_name) {
					continue;
				}

				if ($this->Application->isAdmin) {
					array_push($all_passed, $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($all_passed, $prefix_special[0]);
				}
			}
			$this->Application->SetVar('all_passed', implode(',', $all_passed));

			foreach ($events as $prefix_special => $event_name) {
				$event = new kEvent();
				$event->Name = $event_name;
				$event->Prefix_Special = $prefix_special;

				$prefix_special = explode('.',$prefix_special);
				$event->Prefix = $prefix_special[0];
//				array_push($passed, $prefix_special[0]);
				$event->Special = isset($prefix_special[1]) ? $prefix_special[1] : '';

				$event->redirect_params = Array('opener' => 's', 'pass' => 'all');
				$event->redirect = true;

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

				if (preg_match('/(.*?)-(.*)/', $event->Prefix, $regs) && $this->Application->UnitConfigReader->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]);
				}

				$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
				if (($this->Application->RecallVar('user_id') == USER_ROOT) || $event_handler->CheckPermission($event)) {
					$this->HandleEvent($event);
				}

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

				if ($event->status == erPERM_FAIL) {
					// should do redirect but to no_permissions template
					$event->redirect = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
					$event->redirect_params['pass'] = 'm';

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

					$event->redirect_params['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);
				}

				if ( ($event->status == erSUCCESS || $event->status == erPERM_FAIL) && ($event->redirect === true || strlen($event->redirect) > 0)) {
					// we need to pass category if the action was submitted to self-template, with the category passed
					// and it has not explicly set redirect template or pass_cateogry param
					if ($event->redirect === true && !array_key_exists('pass_category', $event->redirect_params) && $this->Application->GetVar('m_cat_id')) {
						$event->redirect_params['pass_category'] = 1;
					}

					$wid = $this->Application->GetVar('m_wid');
					if ($wid && $event->redirect_params['opener'] == 'u') {
						$event->redirect_params['opener'] = 's'; // because Application->HREF will react differently when 'opener' = 'u'
						$event->redirect = defined('CLOSE_POPUP_TPL') ? CLOSE_POPUP_TPL : 'incs/close_popup';
					}

					$this->Application->Redirect($event->redirect, $event->redirect_params, null, $event->redirect_script);
				}
			}

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

		function processOpener()
		{
			$wid = $this->Application->GetVar('m_wid');

			$opener_stack = $this->Application->RecallVar(rtrim('opener_stack_'.$wid, '_'));
			$opener_stack = $opener_stack ? unserialize($opener_stack) : Array();

			$opener_action = $this->Application->GetVar('m_opener');
			switch ($opener_action) {
				case 'r':	// "reset" opener stack
					$opener_stack = Array();
					break;

				case 'd':	// "down/push" new template to opener stack, deeplevel++
					if ($this->Application->GetVar('front')) {
						$front_session =& $this->Application->recallObject('Session.front');
						array_push($opener_stack, '../'.$front_session->RecallVar('last_template') );
					}
					else {
						array_push($opener_stack, $this->Application->RecallVar('last_template') );
					}
					break;

				case 'u':	// "up/pop" last template from opener stack, deeplevel--
					array_pop($opener_stack);
					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);

					if ($this->Application->GetVar('front')) {
						$front_session =& $this->Application->recallObject('Session.front');

						$last_template = $front_session->RecallVar(rtrim('last_template_popup_'.$parent_wid, '_'));
						$last_template = '../'.$last_template;
					}
					else {
						if ($this->Application->GetVar('merge_opener_stack')) {
							// get last template from parent (that was closed) window opener stack
							$parent_opener_stack_name = rtrim('opener_stack_' . $parent_wid, '_');
							$parent_opener_stack = unserialize( $this->Application->RecallVar($parent_opener_stack_name) );
							$last_template = array_pop($parent_opener_stack);

							if ($parent_opener_stack) {
								$this->Application->StoreVar($parent_opener_stack_name, serialize($parent_opener_stack));
							}
							else {
								$this->Application->RemoveVar($parent_opener_stack_name);
							}
						}
						else {
							$last_template = $this->Application->RecallVar(rtrim('last_template_popup_'.$parent_wid, '_'));
						}
					}

					$opener_stack = Array ( $last_template );
					$this->Application->SetVar('m_opener', 's');
					$wid = $popup_wid;

					/*// 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));*/
					break;

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


			$this->Application->SetVar('m_opener', 's');
			$this->Application->StoreVar(rtrim('opener_stack_'.$wid, '_'), serialize($opener_stack), !$opener_stack); // empty stack is optional
		}

		/**
		 * Allows to add new element to opener stack
		 *
		 * @param string $template
		 * @param Array $params
		 * @param string $pass
		 */
		function openerStackPush($template, $params, $pass = 'all', $wid = null)
		{
			if (!isset($wid)) {
				$wid = $this->Application->GetVar('m_wid');
			}

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

			// get opener stack
			$stack_name = rtrim('opener_stack_' . $wid, '_');
			$opener_stack = $this->Application->RecallVar($stack_name);
			$opener_stack = $opener_stack ? unserialize($opener_stack) : Array ();

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

			if (!$this->Application->ConfigValue('UsePopups') && $wid) {
				// remove wid to show combined header block in editing window
				$default_params['m_wid'] = '';

				// move last popup's opener stack elemenent to main window's opener stack
				if ($opener_stack) {
					list ($index_file, $env) = explode('|', $opener_stack[ count($opener_stack) - 1 ], 2);
					$main_params = $this->Application->HttpQuery->processQueryString($env, 'pass');
					$main_template = $main_params['t'];
					unset($main_params['t']);

					$main_params = array_merge($main_params, $default_params);
					$this->openerStackPush($main_template, $main_params, $main_params['pass'], '');
				}
			}

			$redirect_params = array_merge_recursive2($default_params, $params);
			$new_level = $this->Application->BuildEnv($template, $redirect_params, $pass, true);
			array_push($opener_stack, 'index.php|' . ltrim($new_level, ENV_VAR_NAME . '=') );
			$this->Application->StoreVar($stack_name, serialize($opener_stack));
		}

		/**
		 * Allows to change last element in opener stack
		 *
		 * @param string $template
		 * @param Array $params
		 * @param string $pass
		 */
		function openerStackChange($params = Array(), $pass_events = true, $wid = null)
		{
			if (!isset($wid)) {
				$wid = $this->Application->GetVar('m_wid');
			}

			// get opener stack
			$stack_name = rtrim('opener_stack_' . $wid, '_');
			$opener_stack = $this->Application->RecallVar($stack_name);
			$opener_stack = $opener_stack ? unserialize($opener_stack) : Array ();

			// change opener stack
			list ($index_file, $env) = explode('|', $opener_stack[ count($opener_stack) - 1 ], 2);
			$vars = $this->Application->HttpQuery->processQueryString($env, 'pass');
			$vars = array_merge_recursive2($vars, $params);

			// save opener stack
			$new_level = $this->Application->BuildEnv($vars['t'], $vars, $vars['pass'], $pass_events, false);
			$opener_stack[ count($opener_stack) - 1 ] = $index_file . '|' . $new_level;
			$this->Application->StoreVar($stack_name, serialize($opener_stack));
		}

		function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional)
		{
			if ( !$this->Application->prefixRegistred($hookto_prefix) && $hookto_prefix != '*' ) {
				if ($this->Application->isDebugMode()) {
					trigger_error('Prefix <b>'.$hookto_prefix.'</b> doesn\'t exist when trying to hook from <b>'.$do_prefix.':'.$do_event.'</b>', E_USER_WARNING);
				}
				return;
			}
			$hookto_prefix_special = rtrim($hookto_prefix.'.'.$hookto_special, '.');
			if ($mode == hBEFORE) {
				$this->beforeHooks[strtolower($hookto_prefix_special.'.'.$hookto_event)][] = Array(
						'DoPrefix' => $do_prefix,
						'DoSpecial' => $do_special,
						'DoEvent' => $do_event,
						'Conditional' => $conditional,
					);
			}
			elseif ($mode == hAFTER) {
				$this->afterHooks[strtolower($hookto_prefix_special.'.'.$hookto_event)][] = Array(
						'DoPrefix' => $do_prefix,
						'DoSpecial' => $do_special,
						'DoEvent' => $do_event,
						'Conditional' => $conditional,
					);
			}
		}

		/**
		 * Enter description here...
		 *
		 * @param kEvent $event
		 * @param int $mode hBEFORE or hAFTER
		 * @param string $event_key
		 * @return Array
		 */
		function &_getHooks(&$event, $mode, $event_key = null)
		{
			$event_key = isset($event_key) ? $event_key : $event->Prefix_Special;

			if ($mode == hBEFORE) {
				$mode_hooks =& $this->beforeHooks;
			}
			else {
				$mode_hooks =& $this->afterHooks;
			}

			if (!isset($mode_hooks[strtolower($event_key.'.'.$event->Name)])) {
				$hooks = array();
				return $hooks;
			}

			return $mode_hooks[strtolower($event_key.'.'.$event->Name)];
		}

		/**
		 * Enter description here...
		 *
		 * @param kEvent $event
		 * @param int $mode hBEFORE or hAFTER
		 */
		function processHooks(&$event, $mode)
		{
			// * - get hooks that are valid with any special of given prefix
			$hooks = array_merge(
				// given prefix, any special
				$this->_getHooks($event, $mode, $event->Prefix.'.*'),

				// given special, given special
				$this->_getHooks($event, $mode),

				// any prefix, any special
				$this->_getHooks($event, $mode, '*.*'),

				// any prefix, given special
				$this->_getHooks($event, $mode, rtrim('*.' . $event->Special, '.'))
			);

			if ($hooks) {
				foreach ($hooks as $hook) {
					if ($hook['DoSpecial'] == '*') {
						// use same special as master event
						$hook['DoSpecial'] = $event->Special;
					}

					$prefix_special = rtrim($hook['DoPrefix'].'_'.$hook['DoSpecial'], '_');
					if ( $hook['Conditional'] && !$this->Application->GetVar($prefix_special) ) {
						continue;
					}

					if ($this->Application->prefixRegistred($hook['DoPrefix'])) {
						// run hook only, when it's config is found
						$hook_event = new kEvent( Array('name'=>$hook['DoEvent'],'prefix'=>$hook['DoPrefix'],'special'=>$hook['DoSpecial']) );
						$hook_event->MasterEvent =& $event;
						$this->HandleEvent($hook_event);
					}
				}
			}
		}

		/**
		 * Set's new event for $prefix_special
		 * passed
		 *
		 * @param string $prefix_special
		 * @param string $event_name
		 * @access public
		 */
		function setEvent($prefix_special,$event_name)
		{
			$actions =& $this->Application->recallObject('kActions');
			$actions->Set('events['.$prefix_special.']',$event_name);
		}

		/**
		 * Run registred regular events with specified event type
		 *
		 * @param int $event_type
		 */
		function RunRegularEvents($event_type = reBEFORE, $from_cron = false)
		{
			if ( defined('IS_INSTALL') ) {
				return ;
			}

			if ( !$from_cron && $this->Application->ConfigValue('UseCronForRegularEvent') ) {
				// if RegularEvents are set to run from cron
				return ;
			}

			$agents = $this->getRegularEvents();
			$events_source = $agents[$event_type];

			$user_id = $this->Application->RecallVar('user_id');
			$this->Application->StoreVar('user_id', USER_ROOT, true); // to prevent permission checking inside events, true for optional storage

			foreach ($events_source as $short_name => $event_data) {
				$next_run = $event_data['NextRunOn'];

				if ($next_run && ($next_run > adodb_mktime())) {
					continue;
				}

				$event_data['AgentName'] = $short_name;
				$this->runAgent($event_data);
			}

			$this->Application->StoreVar('user_id', $user_id, $user_id == USER_GUEST);
		}

		function runAgent($agent_data)
		{
			$event = new kEvent($agent_data['EventName']);

			if (!$this->Application->prefixRegistred($event->Prefix)) {
				// don't process agents, left from disabled modules
				return false;
			}

			$start_time = adodb_mktime();

			// remember, when agent execution started
			$fields_hash = Array (
				'LastRunOn' => $start_time,
				'LastRunStatus' => AGENT_LAST_RUN_RUNNING,
				'NextRunOn' => $start_time + $agent_data['RunInterval'],
			);

			$this->Conn->doUpdate(
				$fields_hash,
				$this->Application->getUnitOption('agent', 'TableName'),
				'AgentName = ' . $this->Conn->qstr($agent_data['AgentName'])
			);

			$event->redirect = false;
			$this->Application->HandleEvent($event);

			$now = adodb_mktime();
			$next_run = $agent_data['RunInterval'] ? $start_time + $agent_data['RunInterval'] : $now;

			while ($next_run < $now) {
				// in case event execution took longer, then RunInterval (don't use <=, because RunInterval can be 0)
				$next_run += $agent_data['RunInterval'];
			}

			// remember, when agent execution ended
			$fields_hash = Array (
				'NextRunOn' => $next_run,
				'RunTime' => round(($now - $start_time) / 60),
				'LastRunStatus' => $event->status == erSUCCESS ? AGENT_LAST_RUN_SUCCEDED : AGENT_LAST_RUN_FAILED,
			);

			$this->Conn->doUpdate(
				$fields_hash,
				$this->Application->getUnitOption('agent', 'TableName'),
				'AgentName = ' . $this->Conn->qstr($agent_data['AgentName'])
			);

			return true;
		}

		/**
		 * Allows to determine, that required event is beeing processed right now
		 *
		 * @param string $event_key Event name in format prefix[.special]:event_name
		 * @return bool
		 */
		function eventRunning($event_key)
		{
			return array_search($event_key, $this->recursionStack) !== false;
		}

	}