AnonSec Team
Server IP : 10.2.73.233  /  Your IP : 216.73.216.223
Web Server : Apache/2.4.59 (Debian)
System : Linux polon 4.19.0-27-amd64 #1 SMP Debian 4.19.316-1 (2024-06-25) x86_64
User : www-data ( 33)
PHP Version : 5.6.40-64+0~20230107.71+debian10~1.gbp673146
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
MySQL : ON  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON
Directory (0755) :  /home/ifk/web/assets/../prado4.3.2/Util/Cron/

[  Home  ][  C0mmand  ][  Upload File  ]

Current File : /home/ifk/web/assets/../prado4.3.2/Util/Cron/TDbCronModule.php
<?php
/**
 * TDbCronModule class file.
 *
 * @author Brad Anderson <belisoful@icloud.com>
 * @link https://github.com/pradosoft/prado
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
 */

namespace Prado\Util\Cron;

use Exception;
use PDO;
use Prado\Security\Permissions\TPermissionEvent;
use Prado\Security\Permissions\TUserOwnerRule;
use Prado\Data\TDataSourceConfig;
use Prado\Data\TDbConnection;
use Prado\Exceptions\TConfigurationException;
use Prado\Exceptions\TInvalidDataValueException;
use Prado\Exceptions\TInvalidOperationException;
use Prado\Prado;
use Prado\TPropertyValue;
use Prado\Util\TLogger;

/**
 * TDbCronModule class.
 *
 * TDbCronModule does everything that TCronModule does but stores the tasks and
 * persistent data in its own database table.
 *
 * The TDbCronModule allows for adding, updating, and removing tasks from the
 * application and shell.  It can log executing tasks to the table as well.
 *
 * There are log maintenance methods and {@link TDbCronCleanLogTask} for cleaning
 * the cron logs.
 *
 * Runtime Tasks can be added for execution onEndRequest.  Singleton tasks can
 * be added to TDbCronModule, and scheduled to execute during Runtime at
 * onEndRequest.  Then if it does not execute onEndRequest, then the next
 * shell cron will execute the task.  This could occur if the user presses stop
 * before the page completes.
 *
 * @author Brad Anderson <belisoful@icloud.com>
 * @since 4.2.0
 * @method bool dyClearCronLog(bool $return, int $seconds)
 * @method bool dyGetCronLog(bool $return, string $name, int $pageSize, int $offset, string $sortingDesc)
 * @method bool dyGetCronLogCount(bool $return, string $name)
 * @method bool dyRemoveCronLogItem(bool $return, int $taskUID)
 * @method bool dyAddTask(bool $return,\Prado\Util\Cron\TCronTask $task, bool $runtime)
 * @method bool dyUpdateTask(bool $return, \Prado\Util\Cron\TCronTask $task, array $extraData)
 * @method bool dyRemoveTask(bool $return, \Prado\Util\Cron\TCronTask|string $untask, array $extraData)
 */
class TDbCronModule extends TCronModule implements \Prado\Util\IDbModule
{
	/** Name Regular Expression, no spaces, single or double quotes, less than or greater than, no percent, and cannot start with star */
	public const NAME_VALIDATOR_REGEX = '/^[^\s`\'\"\\*<>%][^\s`\'\"<>%]*$/i';

	public const PERM_CRON_LOG_READ = 'cron_log_read';

	public const PERM_CRON_LOG_DELETE = 'cron_log_delete';

	public const PERM_CRON_ADD_TASK = 'cron_add_task';

	public const PERM_CRON_UPDATE_TASK = 'cron_update_task';

	public const PERM_CRON_REMOVE_TASK = 'cron_remove_task';

	/** @var string name of the db table for cron tasks, default 'crontabs' */
	private $_tableName = 'crontabs';

	/** @var bool auto create the db table for cron, default true */
	private $_autoCreate = true;

	/** @var bool has the table been verified to be in the DB */
	private $_tableEnsured = false;

	/** @var bool log the cron tasks, in the table, as they run, default true  */
	private $_logCronTasks = true;

	/** @var array[]|TCronTask[] the tasks created from the (parent) application configuration */
	private $_configTasks;

	/** @var bool are the tasks Initialized */
	private $_tasksInitialized = false;

	/** @var array[]|TCronTask[] the tasks manually added to the database */
	private $_tasks = [];

	/** @var array[] the row data from the database */
	private $_taskRows;

	/** @var string the ID of TDataSourceConfig module  */
	private $_connID = '';

	/**  @var TDbConnection the DB connection instance  */
	private $_conn;

	/** @var TCronTask[] */
	private $_runtimeTasks;

	/**
	 * constructs the instances, sets the _shellClass.
	 */
	public function __construct()
	{
		$this->_shellClass = 'Prado\\Util\\Cron\\TShellDbCronAction';
		parent::__construct();
	}
	/**
	 * Initializes the module. Keeps track of the configured tasks different than db tasks.
	 * @param array|\Prado\Xml\TXmlElement $config
	 */
	public function init($config)
	{
		parent::init($config);

		$this->_configTasks = parent::getRawTasks();
	}

	/**
	 * the global event handling requests for cron task info
	 * @param TDbCronModule $cron
	 * @param null $param
	 */
	public function fxGetCronTaskInfos($cron, $param)
	{
		return new TCronTaskInfo('cronclean', 'Prado\\Util\\Cron\\TDbCronCleanLogTask', $this->getId(), Prado::localize('DbCron Clean Log Task'), Prado::localize('Clears the database of cron log items before the specified time period.'));
	}

	/**
	 * @param \Prado\Security\Permissions\TPermissionsManager $manager
	 * @return \Prado\Security\Permissions\TPermissionEvent[]
	 */
	public function getPermissions($manager)
	{
		$userIsOwnerAllowedRule = new TUserOwnerRule();
		return array_merge([
			new TPermissionEvent(static::PERM_CRON_LOG_READ, 'Cron read Db log.', ['dyGetCronLog', 'dyGetCronLogCount']),
			new TPermissionEvent(static::PERM_CRON_LOG_DELETE, 'Cron delete Db log.', ['dyClearCronLog', 'dyRemoveCronLogItem']),
			new TPermissionEvent(static::PERM_CRON_ADD_TASK, 'Cron add Db Task.', ['dyAddTask']),
			new TPermissionEvent(static::PERM_CRON_UPDATE_TASK, 'Cron update Db task.', ['dyUpdateTask'], $userIsOwnerAllowedRule),
			new TPermissionEvent(static::PERM_CRON_REMOVE_TASK, 'Cron remove Db task.', ['dyRemoveTask'], $userIsOwnerAllowedRule)
		], parent::getPermissions($manager));
	}

	/**
	 * This checks for "name".  The name cannot by '*' or have spaces, `, ', ", <, or >t characters.
	 * @param array $properties the task as an array of properties
	 * @throws TConfigurationException when the name is invalid.
	 */
	public function validateTask($properties)
	{
		$name = $properties[parent::NAME_KEY] ?? '';
		if (!preg_match(TDbCronModule::NAME_VALIDATOR_REGEX, $name)) {
			throw new TConfigurationException('dbcron_invalid_name', $name);
		}
		parent::validateTask($properties);
	}

	/**
	 * @param string $name name of the task to get Persistent Data from
	 * @param object $task
	 * @return bool is the persistent data set or not
	 */
	protected function setPersistentData($name, $task)
	{
		if (isset($this->_taskRows[$name])) {
			$time = $task->getLastExecTime();
			$count = $task->getProcessCount();
			$task->setLastExecTime((int) $this->_taskRows[$name]['lastexectime']);
			$task->setProcessCount((int) $this->_taskRows[$name]['processcount']);
			if (serialize($task) !== $this->_taskRows[$name]['options']) {
				$task->setLastExecTime($time);
				$task->setProcessCount($count);
				$this->updateTaskInternal($task);
				return false;
			}
			return true;
		} else {
			$this->addTaskInternal($task);
		}
		return false;
	}

	/**
	 * reads in all the tasks from the db, instances them if they are active db tasks.
	 * otherwise the rows are kept for persistent data.
	 * @param bool $initConfigTasks initialize the configuration
	 * @return \Prado\Util\Cron\TCronTask[]
	 */
	protected function ensureTasks($initConfigTasks = true)
	{
		if ($this->_tasksInitialized !== true) {
			$this->ensureTable();
			$this->_taskRows = $this->_tasks = [];
			$cmd = $this->getDbConnection()->createCommand(
				"SELECT * FROM {$this->_tableName} WHERE active IS NOT NULL"
			);
			$results = $cmd->query();

			Prado::log('Reading DB Cron Configuration', TLogger::NOTICE, 'Prado.Cron.TDbCronModule');
			foreach ($results->readAll() as $data) {
				if ($data['active']) {
					$task = $this->_tasks[$data['name']] = @unserialize($data['options']);
				}
				$this->_taskRows[$data['name']] = $data;
			}
			$this->_tasksInitialized = true;
		}
		if ($initConfigTasks) {
			$this->_configTasks = parent::ensureTasks();
		}
		return array_merge($this->_tasks, $this->_configTasks ?? []);
	}

	/**
	 * @throws TConfigurationException when the configuration task names interfere with the db tasks names.
	 * @return array[TCronTask] combines the active configuration and db cron tasks
	 */
	public function getTasks()
	{
		$this->ensureTasks();
		if ($colliding = array_intersect_key($this->_tasks, $this->_configTasks)) {
			throw new TConfigurationException('dbcron_conflicting_task_names', implode(', ', array_keys($colliding)));
		}
		return array_merge($this->_tasks, $this->_configTasks);
	}

	/**
	 * checks for the table, and if not there and autoCreate, then creates the table else throw error.
	 * @throws TConfigurationException if the table does not exist and cannot autoCreate
	 */
	protected function ensureTable()
	{
		if ($this->_tableEnsured) {
			return;
		}
		$db = $this->getDbConnection();
		$sql = 'SELECT * FROM ' . $this->_tableName . ' WHERE 0=1';
		try {
			$db->createCommand($sql)->query()->close();
		} catch (Exception $e) {
			// DB table not exists
			if ($this->_autoCreate) {
				$this->createDbTable();
			} else {
				throw new TConfigurationException('dbcron_table_nonexistent', $this->_tableName);
			}
		}
		$this->_tableEnsured = true;
	}


	/**
	 * creates the module table
	 */
	protected function createDbTable()
	{
		$db = $this->getDbConnection();
		$driver = $db->getDriverName();
		$autotype = 'INTEGER';
		$autoidAttributes = '';
		if ($driver === 'mysql') {
			$autoidAttributes = ' AUTO_INCREMENT';
		} elseif ($driver === 'sqlite') {
			$autoidAttributes = ' AUTOINCREMENT';
		} elseif ($driver === 'postgresql') {
			$autotype = 'SERIAL';
		}
		$postIndices = '; CREATE INDEX tname ON ' . $this->_tableName . '(`name`);' .
			'CREATE INDEX tclass ON ' . $this->_tableName . '(`task`);' .
			'CREATE INDEX tactive ON ' . $this->_tableName . '(`active`);';

		$sql = 'CREATE TABLE IF NOT EXISTS ' . $this->_tableName . ' (
			`tabuid` ' . $autotype . ' PRIMARY KEY' . $autoidAttributes . ', 
			`name` VARCHAR (127) NOT NULL, 
			`schedule` VARCHAR (127) NOT NULL, 
			`task` VARCHAR (256) NOT NULL, 
			`moduleid` VARCHAR (127) NULL, 
			`username` VARCHAR (127) NULL, 
			`options` MEDIUMTEXT NULL, 
			`processcount` INT NOT NULL DEFAULT 0, 
			`lastexectime` VARCHAR (20) NULL DEFAULT `0`, 
			`active` BOOLEAN NULL
			)' . $postIndices;

		//`lastexectime` DECIMAL(12,8) NULL DEFAULT 0,

		$cmd = $this->getDbConnection()->createCommand($sql);

		$cmd->execute();
	}

	/**
	 * logCronTask adds a task log to the table.
	 * @param TCronTask $task
	 * @param string $username
	 */
	protected function logCronTask($task, $username)
	{
		parent::logCronTask($task, $username);

		$app = $this->getApplication();

		$logid = null;
		if ($this->getLogCronTasks()) {
			$this->ensureTable();

			$cmd = $this->getDbConnection()->createCommand(
				"INSERT INTO {$this->_tableName} " .
					"(name, schedule, task, moduleid, username, options, processcount, lastexectime, active)" .
					" VALUES (:name, :schedule, :task, :mid, :username, :options, :count, :time, NULL)"
			);
			$cmd->bindValue(":name", $task->getName(), PDO::PARAM_STR);
			$cmd->bindValue(":task", $task->getTask(), PDO::PARAM_STR);
			$cmd->bindValue(":schedule", $task->getSchedule(), PDO::PARAM_STR);
			$cmd->bindValue(":mid", $task->getModuleId(), PDO::PARAM_STR);
			$cmd->bindValue(":username", $username, PDO::PARAM_STR);
			$cmd->bindValue(":options", serialize($task), PDO::PARAM_STR);
			$cmd->bindValue(":count", $task->getProcessCount(), PDO::PARAM_INT);
			$cmd->bindValue(":time", (int) microtime(true), PDO::PARAM_STR);
			$cmd->execute();
			$logid = $this->getDbConnection()->getLastInsertID();
		}
		return $logid;
	}

	/**
	 * This updates the LastExecTime and ProcessCount in the database
	 * @param TCronTask $task
	 */
	protected function updateTaskInfo($task)
	{
		$task->setLastExecTime($time = (int) microtime(true));
		$task->setProcessCount($count = ($task->getProcessCount() + 1));

		$cmd = $this->getDbConnection()->createCommand(
			"UPDATE {$this->_tableName} SET processcount=:count, lastexectime=:time, options=:task WHERE name=:name AND active IS NOT NULL"
		);
		$cmd->bindValue(":count", $count, PDO::PARAM_STR);
		$cmd->bindValue(":time", $time, PDO::PARAM_STR);
		$cmd->bindValue(":task", serialize($task), PDO::PARAM_STR);
		$cmd->bindValue(":name", $task->getName(), PDO::PARAM_STR);
		$cmd->execute();

		Prado::log('Ending cron task (' . $task->getName() . ', ' . get_class($task) . ')', TLogger::INFO, 'Prado.Cron.TCronModule');
		$this->dyUpdateTaskInfo($task);
	}

	/**
	 * this removes any stale database rows from changing configTasks
	 */
	protected function filterStaleTasks()
	{
		$configTasks = $this->_taskRows;

		//remove active tasks
		foreach ($this->_taskRows as $name => $data) {
			if ($data['active']) {
				unset($configTasks[$name]);
			}
		}

		//remove configuration tasks
		foreach ($this->_configTasks as $name => $data) {
			unset($configTasks[$name]);
		}

		//remaining are stale
		if (count($configTasks)) {
			foreach ($configTasks as $name => $task) {
				$this->removeTaskInternal($name);
			}
		}
	}

	/**
	 * This executes the Run Time Tasks, this method is automatically added
	 * to TApplication::onEndRequest when there are RuntimeTasks via {@link addRuntimeTask}.
	 * @param null|\Prado\TApplication $sender
	 * @param null|mixed $param
	 * @return int number of tasks run
	 */
	public function executeRuntimeTasks($sender = null, $param = null)
	{
		$runtimeTasks = $this->getRuntimeTasks();
		if (!$runtimeTasks) {
			return;
		}
		$numtasks = count($runtimeTasks);
		$this->logCron($numtasks);
		foreach ($runtimeTasks as $key => $task) {
			$this->runTask($task);
		}
		$this->filterStaleTasks();
		return $numtasks;
	}

	/**
	 * Adds a task to being run time.  If this is the first runtime task this
	 * method adds {@link executeRuntimeTasks} to TApplication::onEndRequest.
	 * @param TCronTask $task
	 */
	public function addRuntimeTask($task)
	{
		if ($this->_runtimeTasks === null) {
			Prado::getApplication()->attachEventHandler('onEndRequest', [$this, 'executeRuntimeTasks']);
			$this->_runtimeTasks = [];
		}
		$this->_runtimeTasks[$task->getName()] = $task;
	}

	/**
	 * Gets the runtime tasks.
	 * @return \Prado\Util\Cron\TCronTask the tasks to run on {@link executeRuntimeTasks}
	 */
	public function getRuntimeTasks()
	{
		return $this->_runtimeTasks;
	}

	/**
	 * Removes a task from being run time.  If there are no runtime tasks left
	 * then it removes {@link executeRuntimeTasks} from TApplication::onEndRequest.
	 * @param TCronTask $untask
	 */
	public function removeRuntimeTask($untask)
	{
		if ($this->_runtimeTasks === null) {
			return;
		}
		$name = is_string($untask) ? $untask : $untask->getName();
		unset($this->_runtimeTasks[$name]);
		if (!$this->_runtimeTasks) {
			$this->_runtimeTasks = null;
			Prado::getApplication()->detachEventHandler('onEndRequest', [$this, 'executeRuntimeTasks']);
		}
	}

	/**
	 * Clears all tasks from being run time, and removes the handler from onEndRequest.
	 */
	public function clearRuntimeTasks()
	{
		if ($this->_runtimeTasks === null) {
			return;
		}
		$this->_runtimeTasks = null;
		Prado::getApplication()->detachEventHandler('onEndRequest', [$this, 'executeRuntimeTasks']);
	}

	/**
	 *
	 * @param string $taskName
	 * @param bool $checkExisting
	 * @param bool $asObject returns the database row if false.
	 */
	public function getTask($taskName, $checkExisting = true, $asObject = true)
	{
		$this->ensureTable();

		if ($checkExisting) {
			$this->ensureTasks();
			if ($asObject) {
				if (isset($this->_tasks[$taskName])) {
					return $this->_tasks[$taskName];
				}
				if (isset($this->_configTasks[$taskName])) {
					return $this->_configTasks[$taskName];
				}
			} else {
				if (isset($this->_taskRows[$taskName])) {
					return $this->_taskRows[$taskName];
				}
			}
		}


		$cmd = $this->getDbConnection()->createCommand(
			"SELECT * FROM {$this->_tableName} WHERE name=:name AND active IS NOT NULL LIMIT 1"
		);
		$cmd->bindValue(":name", $taskName, PDO::PARAM_STR);

		$result = $cmd->queryRow();

		if (!$result) {
			return null;
		}

		if ($asObject) {
			return @unserialize($result['options']);
		}

		return $result;
	}

	/**
	 * Adds a task to the database.  Validates the name and cannot add a task with an existing name.
	 * This updates the table row data as well.
	 * @param TCronTask $task
	 * @param bool $runtime should the task be added to the Run Time Task after being added
	 * @return bool was the task added
	 */
	public function addTask($task, $runtime = false)
	{
		if ($this->dyAddTask(false, $task, $runtime) === true) {
			return false;
		}
		return $this->addTaskInternal($task, $runtime);
	}

	/**
	 * Adds a task to the database.  Validates the name and cannot add a task with an existing name.
	 * This updates the table row data as well.
	 * @param \Prado\Util\Cron\TCronTask $task
	 * @param bool $runtime should the task be added to the Run Time Task after being added
	 * @return bool was the task added
	 */
	protected function addTaskInternal($task, $runtime = false)
	{
		$this->ensureTable();
		$this->ensureTasks(false);
		$name = $task->getName();
		if (!preg_match(TDbCronModule::NAME_VALIDATOR_REGEX, $name)) {
			return false;
		}
		if (isset($this->_tasks[$name])) {
			return false;
		}
		try {
			$task->getScheduler();
		} catch (TInvalidDataValueException $e) {
			return false;
		}

		$cmd = $this->getDbConnection()->createCommand(
			"INSERT INTO {$this->_tableName} " .
				"(name, schedule, task, moduleid, username, options, lastexectime, processcount, active)" .
				" VALUES (:name, :schedule, :task, :mid, :username, :options, :time, :count, :active)"
		);
		$cmd->bindValue(":name", $name, PDO::PARAM_STR);
		$cmd->bindValue(":schedule", $schedule = $task->getSchedule(), PDO::PARAM_STR);
		$cmd->bindValue(":task", $taskExec = $task->getTask(), PDO::PARAM_STR);
		$cmd->bindValue(":mid", $mid = $task->getModuleId(), PDO::PARAM_STR);
		$cmd->bindValue(":username", $username = $task->getUserName(), PDO::PARAM_STR);
		$cmd->bindValue(":options", $serial = serialize($task), PDO::PARAM_STR);
		$cmd->bindValue(":time", $time = $task->getLastExecTime(), PDO::PARAM_STR);
		$cmd->bindValue(":count", $count = $task->getProcessCount(), PDO::PARAM_INT);
		$cmd->bindValue(":active", $active = (isset($this->_configTasks[$name]) ? '0' : '1'), PDO::PARAM_INT);
		$cmd->execute();

		if ($this->_tasks !== null && !isset($this->_configTasks[$name])) {
			$this->_tasks[$name] = $task;
			$this->_taskRows[$name] = [];
			$this->_taskRows[$name]['name'] = $name;
			$this->_taskRows[$name]['schedule'] = $schedule;
			$this->_taskRows[$name]['task'] = $taskExec;
			$this->_taskRows[$name]['moduleid'] = $mid;
			$this->_taskRows[$name]['username'] = $username;
			$this->_taskRows[$name]['options'] = $serial;
			$this->_taskRows[$name]['processcount'] = $count;
			$this->_taskRows[$name]['lastexectime'] = $time;
			$this->_taskRows[$name]['active'] = $active;
		}
		if ($runtime) {
			$this->addRuntimeTask($task);
		}
		return true;
	}

	/**
	 * Updates a task from its unique name.  If the Task is not in the DB it returns false
	 * @param TCronTask $task
	 * @return bool was the task updated
	 */
	public function updateTask($task)
	{
		if ($this->dyUpdateTask(false, $task, ['extra' => ['username' => $task->getUserName()]]) === true) {
			return false;
		}
		return $this->updateTaskInternal($task);
	}

	/**
	 * Updates a task from its unique name.  If the Task is not in the DB it returns false
	 * @param \Prado\Util\Cron\TCronTask $task
	 * @return bool was the task updated
	 */
	protected function updateTaskInternal($task)
	{
		$this->ensureTable();
		$this->ensureTasks(false);
		$name = $task->getName();
		if (!$this->taskExists($name)) {
			return false;
		}
		try {
			$task->getScheduler();
		} catch (TInvalidDataValueException $e) {
			return false;
		}

		$cmd = $this->getDbConnection()->createCommand(
			"UPDATE {$this->_tableName} SET schedule=:schedule, task=:task, moduleid=:mid, username=:username, options=:options, processcount=:count, lastexectime=:time WHERE name=:name AND active IS NOT NULL"
		);
		$cmd->bindValue(":schedule", $schedule = $task->getSchedule(), PDO::PARAM_STR);
		$cmd->bindValue(":task", $taskExec = $task->getTask(), PDO::PARAM_STR);
		$cmd->bindValue(":mid", $mid = $task->getModuleId(), PDO::PARAM_STR);
		$cmd->bindValue(":username", $username = $task->getUserName(), PDO::PARAM_STR);
		$cmd->bindValue(":options", $serial = serialize($task), PDO::PARAM_STR);
		$cmd->bindValue(":count", $count = $task->getProcessCount(), PDO::PARAM_STR);
		$cmd->bindValue(":time", $time = $task->getLastExecTime(), PDO::PARAM_STR);
		$cmd->bindValue(":name", $name, PDO::PARAM_STR);
		$cmd->execute();

		if ($this->_tasks !== null) {
			$this->_taskRows[$name]['schedule'] = $schedule;
			$this->_taskRows[$name]['task'] = $taskExec;
			$this->_taskRows[$name]['moduleid'] = $mid;
			$this->_taskRows[$name]['username'] = $username;
			$this->_taskRows[$name]['options'] = $serial;
			$this->_taskRows[$name]['processcount'] = $count;
			$this->_taskRows[$name]['lastexectime'] = $time;
		}
		return true;
	}

	/**
	 * Removes a task from the database table.
	 * This also removes the task from the current tasks, the taskRow, and runtime Tasks.
	 *
	 * This cannot remove tasks that are current configuration tasks.  Only tasks
	 * that exist can be removed.
	 * @param string|TCronTask $untask the task to remove from the DB
	 * @return bool was the task removed
	 */
	public function removeTask($untask)
	{
		$task = null;
		if (is_string($untask)) {
			$task = $this->getTask($untask);
			if (!$task) {
				return false;
			}
		}
		if ($this->dyRemoveTask(false, $untask, ['extra' => ['username' => ($task ?? $untask)->getUserName()]]) === true) {
			return false;
		}
		return $this->removeTaskInternal($untask);
	}

	/**
	 * Removes a task from the database table.
	 * This also removes the task from the current tasks, the taskRow, and runtime Tasks.
	 *
	 * This cannot remove tasks that are current configuration tasks.  Only tasks
	 * that exist can be removed.
	 * @param \Prado\Util\Cron\TCronTask|string $untask the task to remove from the DB
	 * @return bool was the task removed
	 */
	protected function removeTaskInternal($untask)
	{
		$this->ensureTable();
		$this->ensureTasks(false);

		$name = is_string($untask) ? $untask : $untask->getName();
		if (isset($this->_configTasks[$name])) {
			return false;
		}
		if (!$this->taskExists($name)) {
			return false;
		}

		$cmd = $this->getDbConnection()->createCommand(
			"DELETE FROM {$this->_tableName} WHERE name=:name AND active IS NOT NULL"
		);
		$cmd->bindValue(":name", $name, PDO::PARAM_STR);
		$cmd->execute();

		// Remove task to list of tasks
		unset($this->_tasks[$name]);
		unset($this->_taskRows[$name]);
		$this->removeRuntimeTask($name);
		return true;
	}

	/**
	 * taskExists checks for a task or task name in the database
	 * @param string $name task to check in the database
	 * @throws \Prado\Exceptions\TDbException if the Fields and table is not correct
	 * @return bool whether the task name exists in the database table
	 */
	public function taskExists($name)
	{
		$this->ensureTable();

		$db = $this->getDbConnection();
		$cmd = $db->createCommand(
			"SELECT COUNT(*) AS count FROM {$this->_tableName} WHERE name=:name AND active IS NOT NULL"
		);
		$cmd->bindParameter(":name", $name, PDO::PARAM_STR);
		return $cmd->queryScalar() > 0;
	}

	/**
	 * deletes the cron log items before time minus $seconds.
	 * @param int $seconds the number of seconds before Now
	 */
	public function clearCronLog($seconds)
	{
		if ($this->dyClearCronLog(false, $seconds) === true) {
			return false;
		}
		$this->ensureTable();

		$seconds = (int) $seconds;
		$cmd = $this->getDbConnection()->createCommand(
			"SELECT COUNT(*) FROM {$this->_tableName} WHERE active IS NULL AND lastexectime <= :time"
		);
		$time = time() - $seconds;
		$cmd->bindParameter(":time", $time, PDO::PARAM_STR);
		$count = $cmd->queryScalar();
		$cmd = $this->getDbConnection()->createCommand(
			"DELETE FROM {$this->_tableName} WHERE active IS NULL AND lastexectime <= :time"
		);
		$cmd->bindParameter(":time", $time, PDO::PARAM_STR);
		$cmd->execute();

		return $count;
	}

	/**
	 * Deletes one cron log item from the database
	 * @param int $taskUID
	 */
	public function removeCronLogItem($taskUID)
	{
		if ($this->dyRemoveCronLogItem(false, $taskUID) === true) {
			return false;
		}
		$this->ensureTable();
		$taskUID = (int) $taskUID;

		$cmd = $this->getDbConnection()->createCommand(
			"DELETE FROM {$this->_tableName} WHERE active IS NULL AND tabuid = :uid"
		);
		$cmd->bindParameter(":uid", $taskUID, PDO::PARAM_INT);
		$cmd->execute();
	}

	/**
	 * @param null|string $name name of the logs to look for, or null for all
	 * @return int the number of log items of all or of $name
	 */
	public function getCronLogCount($name = null)
	{
		if ($this->dyGetCronLogCount(false, $name) === true) {
			return false;
		}
		$this->ensureTable();

		$db = $this->getDbConnection();
		$where = '';
		if (is_string($name)) {
			$where = 'name=:name AND ';
		}
		$cmd = $db->createCommand(
			"SELECT COUNT(*) AS count FROM {$this->_tableName} WHERE {$where}active IS NULL"
		);
		if (is_string($name)) {
			$cmd->bindParameter(":name", $name, PDO::PARAM_STR);
		}
		return (int) $cmd->queryScalar();
	}

	/**
	 * Gets the cron log table of specific named or all tasks.
	 * @param null|string $name name of the tasks to get from the log, or null for all
	 * @param int $pageSize
	 * @param int $offset
	 * @param null|bool $sortingDesc sort by descending execution time.
	 */
	public function getCronLog($name, $pageSize, $offset, $sortingDesc = null)
	{
		if ($this->dyGetCronLog(false, $name, $pageSize, $offset, $sortingDesc) === true) {
			return false;
		}
		$this->ensureTable();

		$db = $this->getDbConnection();
		$driver = $db->getDriverName();

		$limit = $orderby = $where = '';
		if (is_string($name)) {
			$where = 'name=:name AND ';
		}
		$pageSize = (int) $pageSize;
		$offset = (int) $offset;
		if ($pageSize !== 0) {
			if ($offset !== 0) {
				if ($driver === 'postgresql') {
					$limit = " LIMIT {$pageSize} OFFSET {$offset}";
				} else {
					$limit = " LIMIT {$offset}, {$pageSize}";
				}
			} else {
				$limit = " LIMIT {$pageSize}";
			}
			$sortingDesc = $sortingDesc ?? true;
		}
		if ($sortingDesc !== null) {
			$sortingDesc = TPropertyValue::ensureBoolean($sortingDesc) ? "DESC" : "ASC";
			$orderby = " ORDER BY lastExecTime $sortingDesc, processCount $sortingDesc";
		}
		$cmd = $db->createCommand(
			"SELECT * FROM {$this->_tableName} WHERE {$where}active IS NULL{$orderby}{$limit}"
		);
		if (is_string($name)) {
			$cmd->bindParameter(":name", $name, PDO::PARAM_STR);
		}
		$results = $cmd->query();
		return $results->readAll();
	}

	/**
	 * Creates the DB connection. If no ConnectionId is provided, then this
	 * creates a sqlite database in runtime named 'cron.jobs'.
	 * @throws TConfigurationException if module ID is invalid or empty
	 * @return \Prado\Data\TDbConnection the created DB connection
	 */
	protected function createDbConnection()
	{
		if ($this->_connID !== '') {
			$config = $this->getApplication()->getModule($this->_connID);
			if ($config instanceof TDataSourceConfig) {
				return $config->getDbConnection();
			} else {
				throw new TConfigurationException('dbcron_connectionid_invalid', $this->_connID);
			}
		} else {
			$db = new TDbConnection();
			// default to SQLite3 database
			$dbFile = $this->getApplication()->getRuntimePath() . DIRECTORY_SEPARATOR . 'cron.jobs';
			$db->setConnectionString('sqlite:' . $dbFile);
			return $db;
		}
	}

	/**
	 * @return \Prado\Data\TDbConnection the DB connection instance
	 */
	public function getDbConnection()
	{
		if ($this->_conn === null) {
			$this->_conn = $this->createDbConnection();
			$this->_conn->setActive(true);
		}
		return $this->_conn;
	}

	/**
	 * @return null|string the ID of a {@link TDataSourceConfig} module. Defaults to empty string, meaning not set.
	 */
	public function getConnectionID()
	{
		return $this->_connID;
	}

	/**
	 * Sets the ID of a TDataSourceConfig module.
	 * The datasource module will be used to establish the DB connection for this cron module.
	 * @param string $value ID of the {@link TDataSourceConfig} module
	 * @throws TInvalidOperationException when trying to set this property but the module is already initialized.
	 */
	public function setConnectionID($value)
	{
		if ($this->_initialized) {
			throw new TInvalidOperationException('dbcron_property_unchangeable', 'ConnectionID');
		}
		$this->_connID = $value;
	}

	/**
	 * @return bool should tasks that run be logged, default true
	 */
	public function getLogCronTasks()
	{
		return $this->_logCronTasks;
	}

	/**
	 * @param bool $log should tasks that run be logged
	 */
	public function setLogCronTasks($log)
	{
		$this->_logCronTasks = TPropertyValue::ensureBoolean($log);
	}

	/**
	 * @return string table in the database for cron tasks and logs. Defaults to 'crontabs'
	 */
	public function getTableName()
	{
		return $this->_tableName;
	}

	/**
	 * @param string $table table in the database for cron tasks and logs
	 * @throws TInvalidOperationException when trying to set this property but the module is already initialized.
	 */
	public function setTableName($table)
	{
		if ($this->_initialized) {
			throw new TInvalidOperationException('dbcron_property_unchangeable', 'TableName');
		}
		$this->_tableName = TPropertyValue::ensureString($table);
	}

	/**
	 * @return bool whether the cron DB table should be automatically created if not exists. Defaults to true.
	 * @see setTableName
	 */
	public function getAutoCreateCronTable()
	{
		return $this->_autoCreate;
	}

	/**
	 * @param bool $value whether the cron DB table should be automatically created if not exists.
	 * @throws TInvalidOperationException when trying to set this property but the module is already initialized.
	 * @see setTableName
	 */
	public function setAutoCreateCronTable($value)
	{
		if ($this->_initialized) {
			throw new TInvalidOperationException('dbcron_property_unchangeable', 'AutoCreateCronTable');
		}
		$this->_autoCreate = TPropertyValue::ensureBoolean($value);
	}
}

AnonSec - 2021