1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8use Psr\Log\LoggerInterface; 9 10class Scheduler_Item 11{ 12 13 public $id; 14 public $name; 15 public $description; 16 public $task; 17 public $params; 18 public $run_time; 19 public $status; 20 public $re_run; 21 public $run_only_once; 22 public $creation_date; 23 private $logger; 24 25 const STATUS_ACTIVE = 'active'; 26 const STATUS_INACTIVE = 'inactive'; 27 28 public static $availableTasks = [ 29 'ConsoleCommandTask' => 'ConsoleCommand', 30 'ShellCommandTask' => 'ShellCommand', 31 'HTTPGetCommandTask' => 'HTTPGetCommand', 32 'TikiCheckerCommandTask' => 'TikiCheckerCommand', 33 ]; 34 35 public function __construct($id, $name, $description, $task, $params, $run_time, $status, $re_run, $run_only_once, LoggerInterface $logger) 36 { 37 $this->id = $id; 38 $this->name = $name; 39 $this->description = $description; 40 $this->task = $task; 41 $this->params = $params; 42 $this->run_time = $run_time; 43 $this->status = $status; 44 $this->re_run = $re_run; 45 $this->run_only_once = $run_only_once; 46 $this->logger = $logger; 47 } 48 49 public static function getAvailableTasks() 50 { 51 return self::$availableTasks; 52 } 53 54 /** 55 * Save Scheduler 56 */ 57 public function save() 58 { 59 $schedLib = TikiLib::lib('scheduler'); 60 $id = $schedLib->set_scheduler( 61 $this->name, 62 $this->description, 63 $this->task, 64 $this->params, 65 $this->run_time, 66 $this->status, 67 $this->re_run, 68 $this->run_only_once, 69 $this->id, 70 $this->creation_date 71 ); 72 73 if ($id) { 74 $this->id = $id; 75 } 76 } 77 78 /** 79 * check if the scheduler is running 80 * 81 * @return int return scheduler id if it is matched and return -1 otherwise 82 */ 83 public function isRunning() 84 { 85 $lastRun = $this->getLastRun(); 86 return ! empty($lastRun) ? $lastRun['status'] == 'running' : false; 87 } 88 89 /** 90 * Check if scheduler is stalled (running for long time) 91 * 92 * @param bool|null $notify Send tiki admins an email notification if scheduler is marked as stalled, if null uses tiki stored preferences 93 * 94 * @return int|bool The scheduler run id if is stalled, false otherwise 95 */ 96 public function isStalled($notify = null) 97 { 98 global $tikilib; 99 100 $threshold = $tikilib->get_preference('scheduler_stalled_timeout', 15); 101 102 if ($threshold == 0) { 103 return false; 104 } 105 106 $lastRun = $this->getLastRun(); 107 108 if (empty($lastRun) || $lastRun['status'] != 'running') { 109 return false; 110 } 111 112 if ($lastRun['stalled']) { 113 return true; 114 } 115 116 $startTime = $lastRun['start_time']; 117 $now = time(); 118 119 if ($now < ($startTime + $threshold * 60)) { 120 return false; 121 } 122 123 $this->setStalled($lastRun['id'], $notify); 124 125 return $lastRun['id']; 126 } 127 128 /** 129 * Sets last run as stalled 130 * 131 * @param int $runId The run id to mark as stalled 132 * @param bool|null $notify Send tiki admins an email notification, if null uses tiki stored preferences 133 */ 134 protected function setStalled($runId, $notify = null) 135 { 136 global $tikilib; 137 138 $schedLib = TikiLib::lib('scheduler'); 139 $schedLib->setSchedulerRunStalled($this->id, $runId); 140 141 if (is_null($notify)) { 142 $notify = $tikilib->get_preference('scheduler_notify_on_stalled', 'y') === 'y'; 143 } 144 145 if (! $notify) { 146 return; 147 } 148 149 $users = Scheduler_Utils::getSchedulerNotificationUsers('scheduler_users_to_notify_on_stalled'); 150 151 Tiki\Notifications\Email::sendSchedulerNotification('scheduler_stalled_notification_subject.tpl', 'scheduler_stalled_notification.tpl', $this, $users); 152 } 153 154 /** 155 * Mark last run as healed 156 * 157 * @param string $message The output message when healed 158 * @param bool|null $notify Send email notification to tiki admins when scheduler is marked as healed, if null uses tiki stored preferences 159 * @param bool $force Force heal even if not in the timeframe 160 * 161 * @return bool True if healed, false otherwise. 162 */ 163 public function heal($message, $notify = null, $force = false) 164 { 165 global $tikilib; 166 167 $threshold = $tikilib->get_preference('scheduler_healing_timeout', 30); 168 169 if ($threshold == 0 && ! $force) { 170 return false; 171 } 172 173 $lastRun = $this->getLastRun(); 174 175 if (empty($lastRun) || $lastRun['status'] != 'running' || $lastRun['healed']) { 176 return false; 177 } 178 179 $startTime = $lastRun['start_time']; 180 $now = time(); 181 182 if ($now < ($startTime + $threshold * 60) && ! $force) { 183 return false; 184 } 185 186 $schedLib = TikiLib::lib('scheduler'); 187 $schedLib->setSchedulerRunHealed($this->id, $lastRun['id'], $message); 188 189 if (is_null($notify)) { 190 $notify = $tikilib->get_preference('scheduler_notify_on_healing', 'y') === 'y'; 191 } 192 193 if ($notify) { 194 $users = Scheduler_Utils::getSchedulerNotificationUsers('scheduler_users_to_notify_on_healed'); 195 196 Tiki\Notifications\Email::sendSchedulerNotification('scheduler_healed_notification_subject.tpl', 'scheduler_healed_notification.tpl', $this, $users); 197 } 198 199 $this->reduceLogs(); 200 201 return true; 202 } 203 204 /** 205 * Remove old logs 206 * 207 * @param int|null $numberLogs THe number of logs to keep 208 */ 209 public function reduceLogs($numberLogs = null) 210 { 211 global $tikilib; 212 213 if (is_null($numberLogs) || ! is_numeric($numberLogs)) { 214 $numberLogs = $tikilib->get_preference('scheduler_keep_logs'); 215 } 216 217 if (empty($numberLogs)) { 218 return; 219 } 220 221 $schedLib = TikiLib::lib('scheduler'); 222 $count = $schedLib->countRuns($this->id); 223 224 if ($count > $numberLogs) { 225 // Get the older run to keep 226 $schedulers = $schedLib->get_scheduler_runs($this->id, 1, $numberLogs - 1); 227 $runId = $schedulers[0]['id']; 228 229 $schedLib->removeLogs($this->id, $runId); 230 } 231 } 232 233 /** 234 * @param bool $userTriggered True if user triggered the execution, false otherwise. 235 * 236 * @return array The execution status and output message 237 */ 238 public function execute($userTriggered = false) 239 { 240 global $user, $prefs; 241 242 $schedlib = TikiLib::lib('scheduler'); 243 $status = $schedlib->get_run_status($this->id); 244 245 $this->logger->info('Scheduler last run status: ' . $status); 246 247 if ($status == 'running') { 248 if ($this->isStalled()) { 249 return [ 250 'status' => 'failed', 251 'message' => tr('Scheduler task is stalled.') 252 ]; 253 } 254 255 return [ 256 'status' => 'failed', 257 'message' => tr('Scheduler task is already running.') 258 ]; 259 } 260 261 $this->logger->info('Task: ' . $this->task); 262 263 $class = 'Scheduler_Task_' . $this->task; 264 if (! class_exists($class)) { 265 return [ 266 'status' => 'failed', 267 'message' => $class . ' not found.', 268 ]; 269 } 270 271 if($this->run_only_once) { 272 $schedlib->setInactive($this->id); 273 } 274 275 list('run_id' => $runId, 'start_time' => $startTime) = $schedlib->start_scheduler_run($this->id); 276 $this->logger->debug("Start time: " . $startTime); 277 278 $params = json_decode($this->params, true); 279 $this->logger->debug("Task params: " . $this->params); 280 281 if ($params === null && ! empty($this->params)) { 282 return [ 283 'status' => 'failed', 284 'message' => tr('Unable to decode task params.') 285 ]; 286 } 287 288 $task = new $class($this->logger); 289 $result = $task->execute($params); 290 291 $executionStatus = $result ? 'done' : 'failed'; 292 $outputMessage = $task->getOutput(); 293 294 if ($userTriggered) { 295 $userlib = TikiLib::lib('user'); 296 $email = $userlib->get_user_email($user); 297 $outputMessage = sprintf('Run triggered by %s - %s.' . PHP_EOL, $user, $email) . (empty($outputMessage) ? '' : '<hr>') . $outputMessage; 298 } 299 300 $endTime = $schedlib->end_scheduler_run($this->id, $runId, $executionStatus, $outputMessage, null, 0); 301 $this->logger->debug("End time: " . $endTime); 302 303 $this->reduceLogs(); 304 305 return [ 306 'status' => $executionStatus, 307 'message' => $outputMessage, 308 ]; 309 } 310 311 /** 312 * Return scheduler last run 313 * 314 * @return array|null An array with last run details or null if not found 315 */ 316 public function getLastRun() 317 { 318 $schedlib = TikiLib::lib('scheduler'); 319 $runs = $schedlib->get_scheduler_runs($this->id, 1); 320 321 if (empty($runs)) { 322 return null; 323 } 324 325 return $runs[0]; 326 } 327 328 /** 329 * Transforms an array (from schedulers lib) to a Scheduler_Item object 330 * 331 * @param array $scheduler The scheduler details 332 * @param LoggerInterface $logger Logger 333 * 334 * @return Scheduler_Item 335 */ 336 public static function fromArray(array $scheduler, $logger) 337 { 338 return new self( 339 $scheduler['id'], 340 $scheduler['name'], 341 $scheduler['description'], 342 $scheduler['task'], 343 $scheduler['params'], 344 $scheduler['run_time'], 345 $scheduler['status'], 346 $scheduler['re_run'], 347 $scheduler['run_only_once'], 348 $logger 349 ); 350 } 351} 352