<?php
/**
* @version	$Id: scheduled_task_manager.php 15137 2012-03-04 08:06:21Z 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 kScheduledTaskManager extends kBase implements kiCacheable {

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

	/**
	 * Sets data from cache to object
	 *
	 * @param Array $data
	 * @access public
	 */
	public function setFromCache(&$data)
	{
		$this->tasks = $data['EventManager.scheduledTasks'];
	}

	/**
	 * Gets object data for caching
	 *
	 * @return Array
	 * @access public
	 */
	public function getToCache()
	{
		return Array (
			'EventManager.scheduledTasks' => $this->tasks,
		);
	}

	/**
	 * Returns information about registered scheduled tasks
	 *
	 * @param bool $from_cache
	 * @return Array
	 * @access public
	 */
	public function getAll($from_cache = false)
	{
		static $scheduled_tasks = null;

		if ( $from_cache ) {
			return $this->tasks;
		}

		if ( !isset($scheduled_tasks) ) {
			$timeout_clause = 'LastRunStatus = ' . ScheduledTask::LAST_RUN_RUNNING . ' AND Timeout > 0 AND ' . adodb_mktime() . ' - LastRunOn > Timeout';

			$sql = 'SELECT *
					FROM ' . $this->Application->getUnitOption('scheduled-task', 'TableName') . '
					WHERE (Status = ' . STATUS_ACTIVE . ') AND ((LastRunStatus != ' . ScheduledTask::LAST_RUN_RUNNING . ') OR (' . $timeout_clause . '))';
			$scheduled_tasks = $this->Conn->Query($sql, 'Name');
		}

		return $scheduled_tasks;
	}

	/**
	 * Add new scheduled task
	 *
	 * @param string $short_name name to be used to store last maintenance run info
	 * @param string $event_string
	 * @param int $run_interval run interval in seconds
	 * @param int $status
	 * @access public
	 */
	public function add($short_name, $event_string, $run_interval, $status = STATUS_ACTIVE)
	{
		$this->tasks[$short_name] = Array (
			'Event' => $event_string, 'RunInterval' => $run_interval, 'Status' => $status
		);
	}

	/**
	 * Run registered scheduled tasks with specified event type
	 *
	 * @param bool $from_cron
	 * @access public
	 */
	public function runAll($from_cron = false)
	{
		if ( defined('IS_INSTALL') ) {
			return ;
		}

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

		$events_source = $this->getAll();

		$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

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

		$site_domain_id = $site_helper->getDomainByName('DomainName', DOMAIN);

		foreach ($events_source as $short_name => $event_data) {
			if ( $site_domain_id && $event_data['SiteDomainLimitation'] != '' ) {
				$site_domains = explode('|', substr($event_data['SiteDomainLimitation'], 1, -1));

				if ( !in_array($site_domain_id, $site_domains) ) {
					// scheduled task isn't allowed on this site domain
					continue;
				}
			}

			// remember LastTimeoutOn only for events that are still running and will be reset
			if ( $event_data['LastRunStatus'] == ScheduledTask::LAST_RUN_RUNNING ) {
				$this->update($short_name, Array ('LastTimeoutOn' => adodb_mktime()));
			}

			$next_run = (int)$event_data['NextRunOn'];

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

			$event_data['Name'] = $short_name;
			$this->run($event_data);
		}

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

	/**
	 * Runs scheduled task based on given data
	 *
	 * @param Array $scheduled_task_data
	 * @return bool
	 * @access public
	 */
	public function run($scheduled_task_data)
	{
		$event = new kEvent($scheduled_task_data['Event']);

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

		$start_time = adodb_mktime();

		// remember, when scheduled task execution started
		$fields_hash = Array (
			'LastRunOn' => $start_time,
			'LastRunStatus' => ScheduledTask::LAST_RUN_RUNNING,
			'NextRunOn' => $start_time + $scheduled_task_data['RunInterval'],
		);

		$this->update($scheduled_task_data['Name'], $fields_hash);

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

		$now = adodb_mktime();
		$next_run = $scheduled_task_data['RunInterval'] ? $start_time + $scheduled_task_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 += $scheduled_task_data['RunInterval'];
		}

		// remember, when scheduled task execution ended
		$fields_hash = Array (
			'NextRunOn' => $next_run,
			'RunTime' => round(($now - $start_time) / 60),
			'LastRunStatus' => $event->status == kEvent::erSUCCESS ? ScheduledTask::LAST_RUN_SUCCEEDED : ScheduledTask::LAST_RUN_FAILED,
		);

		$this->update($scheduled_task_data['Name'], $fields_hash);

		return true;
	}

	/**
	 * Updates scheduled task record with latest changes about it's invocation progress
	 *
	 * @param string $scheduled_task_name
	 * @param Array $fields_hash
	 * @return void
	 * @access protected
	 */
	protected function update($scheduled_task_name, $fields_hash)
	{
		$this->Conn->doUpdate(
			$fields_hash,
			$this->Application->getUnitOption('scheduled-task', 'TableName'),
			'Name = ' . $this->Conn->qstr($scheduled_task_name)
		);
	}
}