Passed
Push — master ( 57d36e...277087 )
by Fabio
05:57
created

TDbCronModule::removeCronLogItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 10
rs 10
1
<?php
2
/**
3
 * TDbCronModule class file.
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Util\Cron;
11
12
use Exception;
13
use PDO;
14
use Prado\Data\TDataSourceConfig;
15
use Prado\Data\TDbConnection;
16
use Prado\Exceptions\TConfigurationException;
17
use Prado\Exceptions\TInvalidDataValueException;
18
use Prado\Exceptions\TInvalidOperationException;
19
use Prado\Prado;
20
use Prado\TPropertyValue;
21
use Prado\Util\TLogger;
22
23
/**
24
 * TDbCronModule class.
25
 *
26
 * TDbCronModule does everything that TCronModule does but stores the tasks and
27
 * persistent data in its own database table.
28
 *
29
 * The TDbCronModule allows for adding, updating, and removing tasks from the
30
 * application and shell.  It can log executing tasks to the table as well.
31
 *
32
 * There are log maintenance methods and {@link TDbCronCleanLogTask} for cleaning
33
 * the cron logs.
34
 *
35
 * Runtime Tasks can be added for execution onEndRequest.  Singleton tasks can
36
 * be added to TDbCronModule, and scheduled to execute during Runtime at
37
 * onEndRequest.  Then if it does not execute onEndRequest, then the next
38
 * shell cron will execute the task.  This could occur if the user presses stop
39
 * before the page completes.
40
 *
41
 * @author Brad Anderson <[email protected]>
42
 * @package Prado\Util\Cron
43
 * @since 4.2.0
44
 */
45
46
class TDbCronModule extends TCronModule implements \Prado\Util\IDbModule
47
{
48
	/** Name Regular Expression, no spaces, single or double quotes, less than or greater than and cannot start with star */
49
	public const NAME_VALIDATOR_REGEX = '/^[^\s`\'\"\\*<>][^\s`\'\"<>]*$/i';
50
	
51
	/** @var string name of the db table for cron tasks, default 'crontabs' */
52
	private $_tableName = 'crontabs';
53
	
54
	/** @var bool auto create the db table for cron, default true */
55
	private $_autoCreate = true;
56
	
57
	/** @var bool has the table been verified to be in the DB */
58
	private $_tableEnsured = false;
59
	
60
	/** @var bool log the cron tasks, in the table, as they run, default true  */
61
	private $_logCronTasks = true;
62
	
63
	/** @var array[]|Prado\Util\Cron\TCronTask[] the tasks created from the (parent) application configuration */
64
	private $_configTasks;
65
	
66
	/** @var bool are the tasks Initialized */
67
	private $_tasksInitialized = false;
68
	
69
	/** @var array[]|Prado\Util\Cron\TCronTask[] the tasks manually added to the database */
70
	private $_tasks = [];
71
	
72
	/** @var array[] the row data from the database */
73
	private $_taskRows;
74
	
75
	/** @var string the ID of TDataSourceConfig module  */
76
	private $_connID = '';
77
	
78
	/**  @var TDbConnection the DB connection instance  */
79
	private $_conn;
80
	
81
	/** @var Prado\Util\Cron\TCronTask[] */
82
	private $_runtimeTasks;
83
	
84
	/**
85
	 * constructs the instances, sets the _shellClass.
86
	 */
87
	public function __construct()
88
	{
89
		$this->_shellClass = 'Prado\\Util\\Cron\\TShellDbCronAction';
90
		parent::__construct();
91
	}
92
	/**
93
	 * Initializes the module. Keeps track of the configured tasks different than db tasks.
94
	 * @param array|Prado\Xml\TXmlElement $config
0 ignored issues
show
Bug introduced by
The type Prado\Prado\Xml\TXmlElement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
95
	 */
96
	public function init($config)
97
	{
98
		parent::init($config);
99
		
100
		$this->_configTasks = parent::getRawTasks();
101
	}
102
	
103
	/**
104
	 * the global event handling requests for cron task info
105
	 * @param TDbCronModule $cron
106
	 * @param null $param
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $param is correct as it would always require null to be passed?
Loading history...
107
	 */
108
	public function fxGetCronTaskInfos($cron, $param)
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

108
	public function fxGetCronTaskInfos($cron, /** @scrutinizer ignore-unused */ $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $cron is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

108
	public function fxGetCronTaskInfos(/** @scrutinizer ignore-unused */ $cron, $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
109
	{
110
		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.'));
111
	}
112
	
113
	/**
114
	 * This checks for "name".  The name cannot by '*' or have spaces, `, ', ", <, or >t characters.
115
	 * @param array $properties the task as an array of properties
116
	 * @throws TConfigurationException when the name is invalid.
117
	 */
118
	public function validateTask($properties)
119
	{
120
		$name = $properties[parent::NAME_KEY] ?? '';
121
		if (!preg_match(TDbCronModule::NAME_VALIDATOR_REGEX, $name)) {
122
			throw new TConfigurationException('dbcron_invalid_name', $name);
123
		}
124
		parent::validateTask($properties);
125
	}
126
	
127
	/**
128
	 * @param string $name name of the task to get Persistent Data from
129
	 * @param object $task
130
	 * @return bool is the persistent data set or not
131
	 */
132
	protected function setPersistentData($name, $task)
133
	{
134
		if (isset($this->_taskRows[$name])) {
135
			$time = $task->getLastExecTime();
136
			$count = $task->getProcessCount();
137
			$task->setLastExecTime((int) $this->_taskRows[$name]['lastexectime']);
138
			$task->setProcessCount((int) $this->_taskRows[$name]['processcount']);
139
			if (serialize($task) !== $this->_taskRows[$name]['options']) {
140
				$task->setLastExecTime($time);
141
				$task->setProcessCount($count);
142
				$this->updateTask($task);
143
				return false;
144
			}
145
			return true;
146
		} else {
147
			$this->addTask($task);
148
		}
149
		return false;
150
	}
151
	
152
	/**
153
	 * reads in all the tasks from the db, instances them if they are active db tasks.
154
	 * otherwise the rows are kept for persistent data.
155
	 * @param bool $initConfigTasks initialize the configuration
156
	 */
157
	protected function ensureTasks($initConfigTasks = true)
158
	{
159
		if ($this->_tasksInitialized !== true) {
160
			$this->ensureTable();
161
			$this->_taskRows = $this->_tasks = [];
162
			$cmd = $this->getDbConnection()->createCommand(
163
				"SELECT * FROM {$this->_tableName} WHERE active IS NOT NULL"
164
			);
165
			$results = $cmd->query();
166
			
167
			Prado::log('Reading DB Cron Configuration', TLogger::NOTICE, 'Prado.Cron.TDbCronModule');
168
			foreach ($results->readAll() as $data) {
169
				if ($data['active']) {
170
					$task = $this->_tasks[$data['name']] = @unserialize($data['options']);
0 ignored issues
show
Unused Code introduced by
The assignment to $task is dead and can be removed.
Loading history...
171
				}
172
				$this->_taskRows[$data['name']] = $data;
173
			}
174
			$this->_tasksInitialized = true;
175
		}
176
		if ($initConfigTasks) {
177
			$this->_configTasks = parent::ensureTasks();
178
		}
179
	}
180
	
181
	/**
182
	 * @throws TConfigurationException when the configuration task names interfere with the db tasks names.
183
	 * @return array[TCronTask] combines the active configuration and db cron tasks
0 ignored issues
show
Documentation Bug introduced by
The doc comment array[TCronTask] at position 1 could not be parsed: Expected ']' at position 1, but found '['.
Loading history...
184
	 */
185
	public function getTasks()
186
	{
187
		$this->ensureTasks();
188
		if ($colliding = array_intersect_key($this->_tasks, $this->_configTasks)) {
189
			throw new TConfigurationException('dbcron_conflicting_task_names', implode(', ', array_keys($colliding)));
190
		}
191
		return array_merge($this->_tasks, $this->_configTasks);
192
	}
193
	
194
	/**
195
	 * checks for the table, and if not there and autoCreate, then creates the table else throw error.
196
	 * @throws TConfigurationException if the table does not exist and cannot autoCreate
197
	 */
198
	protected function ensureTable()
199
	{
200
		if ($this->_tableEnsured) {
201
			return;
202
		}
203
		$db = $this->getDbConnection();
204
		$sql = 'SELECT * FROM ' . $this->_tableName . ' WHERE 0=1';
205
		try {
206
			$db->createCommand($sql)->query()->close();
207
		} catch (Exception $e) {
208
			// DB table not exists
209
			if ($this->_autoCreate) {
210
				$this->createDbTable();
211
			} else {
212
				throw new TConfigurationException('dbcron_table_nonexistent', $this->_tableName);
213
			}
214
		}
215
		$this->_tableEnsured = true;
216
	}
217
	
218
	
219
	/**
220
	 * creates the module table
221
	 */
222
	protected function createDbTable()
223
	{
224
		$db = $this->getDbConnection();
225
		$driver = $db->getDriverName();
226
		$autotype = 'INTEGER';
227
		$autoidAttributes = '';
228
		if ($driver === 'mysql') {
229
			$autoidAttributes = ' AUTO_INCREMENT';
230
		} elseif ($driver === 'sqlite') {
231
			$autoidAttributes = ' AUTOINCREMENT';
232
		} elseif ($driver === 'postgresql') {
233
			$autotype = 'SERIAL';
234
		}
235
		$postIndices = '; CREATE INDEX tname ON ' . $this->_tableName . '(`name`);' .
236
			'CREATE INDEX tclass ON ' . $this->_tableName . '(`task`);' .
237
			'CREATE INDEX tactive ON ' . $this->_tableName . '(`active`);';
238
			
239
		$sql = 'CREATE TABLE IF NOT EXISTS ' . $this->_tableName . ' (
240
			`tabuid` ' . $autotype . ' PRIMARY KEY' . $autoidAttributes . ', 
241
			`name` VARCHAR (127) NOT NULL, 
242
			`schedule` VARCHAR (127) NOT NULL, 
243
			`task` VARCHAR (256) NOT NULL, 
244
			`moduleid` VARCHAR (127) NULL, 
245
			`userid` VARCHAR (127) NULL, 
246
			`options` MEDIUMTEXT NULL, 
247
			`processcount` INT NOT NULL DEFAULT 0, 
248
			`lastexectime` VARCHAR (20) NULL DEFAULT `0`, 
249
			`active` BOOLEAN NULL
250
			)' . $postIndices;
251
			
252
		//`lastexectime` DECIMAL(12,8) NULL DEFAULT 0,
253
		
254
		$cmd = $this->getDbConnection()->createCommand($sql);
255
		
256
		$cmd->execute();
257
	}
258
	
259
	/**
260
	 * logCronTask adds a task log to the table.
261
	 * @param Prado\Util\Cron\TCronTask $task
0 ignored issues
show
Bug introduced by
The type Prado\Prado\Util\Cron\TCronTask was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
262
	 * @param mixed $user
263
	 * @param mixed $username
264
	 */
265
	protected function logCronTask($task, $username)
266
	{
267
		parent::logCronTask($task, $username);
268
		
269
		$app = $this->getApplication();
0 ignored issues
show
Unused Code introduced by
The assignment to $app is dead and can be removed.
Loading history...
270
		
271
		$logid = null;
272
		if ($this->getLogCronTasks()) {
273
			$this->ensureTable();
274
			
275
			$cmd = $this->getDbConnection()->createCommand(
276
				"INSERT INTO {$this->_tableName} " .
277
					"(name, schedule, task, moduleid, userid, options, processcount, lastexectime, active)" .
278
					" VALUES (:name, :schedule, :task, :mid, :userid, :options, :count, :time, NULL)"
279
			);
280
			$cmd->bindValue(":name", $task->getName(), PDO::PARAM_STR);
281
			$cmd->bindValue(":task", $task->getTask(), PDO::PARAM_STR);
282
			$cmd->bindValue(":schedule", $task->getSchedule(), PDO::PARAM_STR);
283
			$cmd->bindValue(":mid", $task->getModuleId(), PDO::PARAM_STR);
284
			$cmd->bindValue(":userid", $username, PDO::PARAM_STR);
285
			$cmd->bindValue(":options", serialize($task), PDO::PARAM_STR);
286
			$cmd->bindValue(":count", $task->getProcessCount(), PDO::PARAM_INT);
287
			$cmd->bindValue(":time", (int) microtime(true), PDO::PARAM_STR);
288
			$cmd->execute();
289
			$logid = $this->getDbConnection()->getLastInsertID();
290
		}
291
		return $logid;
292
	}
293
	
294
	/**
295
	 * This updates the LastExecTime and ProcessCount in the database
296
	 * @param Prado\Util\Cron\TCronTask $task
297
	 */
298
	protected function updateTaskInfo($task)
299
	{
300
		$task->setLastExecTime($time = (int) microtime(true));
301
		$task->setProcessCount($count = ($task->getProcessCount() + 1));
302
		
303
		$cmd = $this->getDbConnection()->createCommand(
304
			"UPDATE {$this->_tableName} SET processcount=:count, lastexectime=:time, options=:task WHERE name=:name AND active IS NOT NULL"
305
		);
306
		$cmd->bindValue(":count", $count, PDO::PARAM_STR);
307
		$cmd->bindValue(":time", $time, PDO::PARAM_STR);
308
		$cmd->bindValue(":task", serialize($task), PDO::PARAM_STR);
309
		$cmd->bindValue(":name", $task->getName(), PDO::PARAM_STR);
310
		$cmd->execute();
311
		
312
		Prado::log('Ending cron task (' . $task->getName() . ', ' . get_class($task) . ')', TLogger::INFO, 'Prado.Cron.TCronModule');
313
		$this->dyUpdateTaskInfo($task);
314
	}
315
	
316
	/**
317
	 * this removes any stale database rows from changing configTasks
318
	 */
319
	protected function filterStaleTasks()
320
	{
321
		$configTasks = $this->_taskRows;
322
		
323
		//remove active tasks
324
		foreach ($this->_taskRows as $name => $data) {
325
			if ($data['active']) {
326
				unset($configTasks[$name]);
327
			}
328
		}
329
		
330
		//remove configuration tasks
331
		foreach ($this->_configTasks as $name => $data) {
332
			unset($configTasks[$name]);
333
		}
334
		
335
		//remaining are stale
336
		if (count($configTasks)) {
337
			foreach ($configTasks as $name => $task) {
338
				$this->removeTask($name);
339
			}
340
		}
341
	}
342
	
343
	/**
344
	 * This executes the Run Time Tasks, this method is automatically added
345
	 * to TApplication::onEndRequest when there are RuntimeTasks via {@link addRuntimeTask}.
346
	 * @param null|Prado\TApplication $sender
0 ignored issues
show
Bug introduced by
The type Prado\Prado\TApplication was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
347
	 * @param null|mixed $param
348
	 * @return int number of tasks run
349
	 */
350
	public function executeRuntimeTasks($sender = null, $param = null)
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

350
	public function executeRuntimeTasks($sender = null, /** @scrutinizer ignore-unused */ $param = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $sender is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

350
	public function executeRuntimeTasks(/** @scrutinizer ignore-unused */ $sender = null, $param = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
351
	{
352
		$runtimeTasks = $this->getRuntimeTasks();
353
		if (!$runtimeTasks) {
0 ignored issues
show
introduced by
$runtimeTasks is of type Prado\Prado\Util\Cron\TCronTask, thus it always evaluated to true.
Loading history...
354
			return;
355
		}
356
		$numtasks = count($runtimeTasks);
357
		$this->logCron($numtasks);
358
		foreach ($runtimeTasks as $key => $task) {
359
			$this->runTask($task);
360
		}
361
		$this->filterStaleTasks();
362
		return $numtasks;
363
	}
364
	
365
	/**
366
	 * Adds a task to being run time.  If this is the first runtime task this
367
	 * method adds {@link executeRuntimeTasks} to TApplication::onEndRequest.
368
	 * @param Prado\Util\Cron\TCronTask $task
369
	 */
370
	public function addRuntimeTask($task)
371
	{
372
		if ($this->_runtimeTasks === null) {
373
			Prado::getApplication()->onEndRequest[] = [$this, 'executeRuntimeTasks'];
0 ignored issues
show
Bug Best Practice introduced by
The property onEndRequest does not exist on Prado\TApplication. Since you implemented __get, consider adding a @property annotation.
Loading history...
374
			$this->_runtimeTasks = [];
375
		}
376
		$this->_runtimeTasks[$task->getName()] = $task;
377
	}
378
	
379
	/**
380
	 * Gets the runtime tasks.
381
	 * @return Prado\Util\Cron\TCronTask the tasks to run on {@link executeRuntimeTasks}
382
	 */
383
	public function getRuntimeTasks()
384
	{
385
		return $this->_runtimeTasks;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_runtimeTasks returns the type Prado\Prado\Util\Cron\TCronTask[] which is incompatible with the documented return type Prado\Prado\Util\Cron\TCronTask.
Loading history...
386
	}
387
	
388
	/**
389
	 * Removes a task from being run time.  If there are no runtime tasks left
390
	 * then it removes {@link executeRuntimeTasks} from TApplication::onEndRequest.
391
	 * @param Prado\Util\Cron\TCronTask $untask
392
	 */
393
	public function removeRuntimeTask($untask)
394
	{
395
		if ($this->_runtimeTasks === null) {
396
			return;
397
		}
398
		$name = is_string($untask) ? $untask : $untask->getName();
0 ignored issues
show
introduced by
The condition is_string($untask) is always false.
Loading history...
399
		unset($this->_runtimeTasks[$name]);
400
		if (!$this->_runtimeTasks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_runtimeTasks of type Prado\Prado\Util\Cron\TCronTask[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
401
			$this->_runtimeTasks = null;
402
			Prado::getApplication()->detachEventHandler('onEndRequest', [$this, 'executeRuntimeTasks']);
403
		}
404
	}
405
	
406
	/**
407
	 * Clears all tasks from being run time, and removes the handler from onEndRequest.
408
	 * @param Prado\Util\Cron\TCronTask $untask
409
	 */
410
	public function clearRuntimeTasks()
411
	{
412
		if ($this->_runtimeTasks === null) {
413
			return;
414
		}
415
		$this->_runtimeTasks = null;
416
		Prado::getApplication()->detachEventHandler('onEndRequest', [$this, 'executeRuntimeTasks']);
417
	}
418
	
419
	/**
420
	 *
421
	 * @param string $taskName
422
	 * @param bool $checkExisting
423
	 * @param bool $asObject returns the database row if false.
424
	 */
425
	public function getTask($taskName, $checkExisting = true, $asObject = true)
426
	{
427
		$this->ensureTable();
428
		
429
		if ($checkExisting) {
430
			$this->ensureTasks();
431
			if ($asObject) {
432
				if (isset($this->_tasks[$taskName])) {
433
					return $this->_tasks[$taskName];
434
				}
435
				if (isset($this->_configTasks[$taskName])) {
436
					return $this->_configTasks[$taskName];
437
				}
438
			} else {
439
				if (isset($this->_taskRows[$taskName])) {
440
					return $this->_taskRows[$taskName];
441
				}
442
			}
443
		}
444
		
445
		
446
		$cmd = $this->getDbConnection()->createCommand(
447
			"SELECT * FROM {$this->_tableName} WHERE name=:name AND active IS NOT NULL LIMIT 1"
448
		);
449
		$cmd->bindValue(":name", $taskName, PDO::PARAM_STR);
450
		
451
		$result = $cmd->queryRow();
452
		
453
		if (!$result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
454
			return null;
455
		}
456
		
457
		if ($asObject) {
458
			return @unserialize($result['options']);
459
		}
460
		
461
		return $result;
462
	}
463
	
464
	/**
465
	 * Adds a task to the database.  Validates the name and cannot add a task with an existing name.
466
	 * This updates the table row data as well.
467
	 * @param Prado\Util\Cron\TCronTask $task
468
	 * @param bool $runtime should the task be added to the Run Time Task after being added
469
	 * @return bool was the task added
470
	 */
471
	public function addTask($task, $runtime = false)
472
	{
473
		$this->ensureTable();
474
		$this->ensureTasks(false);
475
		$name = $task->getName();
476
		if (!preg_match(TDbCronModule::NAME_VALIDATOR_REGEX, $name)) {
477
			return false;
478
		}
479
		if (isset($this->_tasks[$name])) {
480
			return false;
481
		}
482
		try {
483
			$task->getScheduler();
484
		} catch (TInvalidDataValueException $e) {
485
			return false;
486
		}
487
		
488
		$cmd = $this->getDbConnection()->createCommand(
489
			"INSERT INTO {$this->_tableName} " .
490
				"(name, schedule, task, moduleid, userid, options, lastexectime, processcount, active)" .
491
				" VALUES (:name, :schedule, :task, :mid, :userid, :options, :time, :count, :active)"
492
		);
493
		$cmd->bindValue(":name", $name, PDO::PARAM_STR);
494
		$cmd->bindValue(":schedule", $schedule = $task->getSchedule(), PDO::PARAM_STR);
495
		$cmd->bindValue(":task", $taskExec = $task->getTask(), PDO::PARAM_STR);
496
		$cmd->bindValue(":mid", $mid = $task->getModuleId(), PDO::PARAM_STR);
497
		$cmd->bindValue(":userid", $userid = $task->getUserId(), PDO::PARAM_STR);
498
		$cmd->bindValue(":options", $serial = serialize($task), PDO::PARAM_STR);
499
		$cmd->bindValue(":time", $time = $task->getLastExecTime(), PDO::PARAM_STR);
500
		$cmd->bindValue(":count", $count = $task->getProcessCount(), PDO::PARAM_INT);
501
		$cmd->bindValue(":active", $active = (isset($this->_configTasks[$name]) ? '0' : '1'), PDO::PARAM_INT);
502
		$cmd->execute();
503
		
504
		if ($this->_tasks !== null && !isset($this->_configTasks[$name])) {
505
			$this->_tasks[$name] = $task;
506
			$this->_taskRows[$name] = [];
507
			$this->_taskRows[$name]['name'] = $name;
508
			$this->_taskRows[$name]['schedule'] = $schedule;
509
			$this->_taskRows[$name]['task'] = $taskExec;
510
			$this->_taskRows[$name]['moduleid'] = $mid;
511
			$this->_taskRows[$name]['userid'] = $userid;
512
			$this->_taskRows[$name]['options'] = $serial;
513
			$this->_taskRows[$name]['processcount'] = $count;
514
			$this->_taskRows[$name]['lastexectime'] = $time;
515
			$this->_taskRows[$name]['active'] = $active;
516
		}
517
		if ($runtime) {
518
			$this->addRuntimeTask($task);
519
		}
520
		return true;
521
	}
522
	
523
	/**
524
	 * Updates a task from its unique name.  If the Task is not in the DB it returns false
525
	 * @param Prado\Util\Cron\TCronTask $task
526
	 * @return bool was the task updated
527
	 */
528
	public function updateTask($task)
529
	{
530
		$this->ensureTable();
531
		$this->ensureTasks(false);
532
		$name = $task->getName();
533
		if (!$this->taskExists($name)) {
534
			return false;
535
		}
536
		try {
537
			$task->getScheduler();
538
		} catch (TInvalidDataValueException $e) {
539
			return false;
540
		}
541
		
542
		$cmd = $this->getDbConnection()->createCommand(
543
			"UPDATE {$this->_tableName} SET schedule=:schedule, task=:task, moduleid=:mid, userid=:userid, options=:options, processcount=:count, lastexectime=:time WHERE name=:name AND active IS NOT NULL"
544
		);
545
		$cmd->bindValue(":schedule", $schedule = $task->getSchedule(), PDO::PARAM_STR);
546
		$cmd->bindValue(":task", $taskExec = $task->getTask(), PDO::PARAM_STR);
547
		$cmd->bindValue(":mid", $mid = $task->getModuleId(), PDO::PARAM_STR);
548
		$cmd->bindValue(":userid", $userid = $task->getUserid(), PDO::PARAM_STR);
549
		$cmd->bindValue(":options", $serial = serialize($task), PDO::PARAM_STR);
550
		$cmd->bindValue(":count", $count = $task->getProcessCount(), PDO::PARAM_STR);
551
		$cmd->bindValue(":time", $time = $task->getLastExecTime(), PDO::PARAM_STR);
552
		$cmd->bindValue(":name", $name, PDO::PARAM_STR);
553
		$cmd->execute();
554
		
555
		if ($this->_tasks !== null) {
556
			$this->_taskRows[$name]['schedule'] = $schedule;
557
			$this->_taskRows[$name]['task'] = $taskExec;
558
			$this->_taskRows[$name]['moduleid'] = $mid;
559
			$this->_taskRows[$name]['userid'] = $userid;
560
			$this->_taskRows[$name]['options'] = $serial;
561
			$this->_taskRows[$name]['processcount'] = $count;
562
			$this->_taskRows[$name]['lastexectime'] = $time;
563
		}
564
		return true;
565
	}
566
	
567
	/**
568
	 * Removes a task from the database table.
569
	 * This also removes the task from the current tasks, the taskRow, and runtime Tasks.
570
	 *
571
	 * This cannot remove tasks that are current configuration tasks.  Only tasks
572
	 * that exist can be removed.
573
	 * @param Prado\Util\Cron\TCronTask|string $untask the task to remove from the DB
574
	 * @return bool was the task removed
575
	 */
576
	public function removeTask($untask)
577
	{
578
		$this->ensureTable();
579
		$this->ensureTasks(false);
580
		
581
		$name = is_string($untask) ? $untask : $untask->getName();
582
		if (isset($this->_configTasks[$name])) {
583
			return false;
584
		}
585
		if (!$this->taskExists($name)) {
586
			return false;
587
		}
588
		
589
		$cmd = $this->getDbConnection()->createCommand(
590
			"DELETE FROM {$this->_tableName} WHERE name=:name AND active IS NOT NULL"
591
		);
592
		$cmd->bindValue(":name", $name, PDO::PARAM_STR);
593
		$cmd->execute();
594
		
595
		// Remove task to list of tasks
596
		unset($this->_tasks[$name]);
597
		unset($this->_taskRows[$name]);
598
		$this->removeRuntimeTask($name);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type string; however, parameter $untask of Prado\Util\Cron\TDbCronModule::removeRuntimeTask() does only seem to accept Prado\Prado\Util\Cron\TCronTask, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

598
		$this->removeRuntimeTask(/** @scrutinizer ignore-type */ $name);
Loading history...
599
		return true;
600
	}
601
		
602
	/**
603
	 * taskExists checks for a task or task name in the database
604
	 * @param string $name task to check in the database
605
	 * @throws TDbException if the Fields and table is not correct
606
	 * @return bool whether the task name exists in the database table
607
	 */
608
	public function taskExists($name)
609
	{
610
		$this->ensureTable();
611
		
612
		$db = $this->getDbConnection();
613
		$cmd = $db->createCommand(
614
			"SELECT COUNT(*) AS count FROM {$this->_tableName} WHERE name=:name AND active IS NOT NULL"
615
		);
616
		$cmd->bindParameter(":name", $name, PDO::PARAM_STR);
617
		return $cmd->queryScalar() > 0;
618
	}
619
	
620
	/**
621
	 * deletes the cron log items before time minus $seconds.
622
	 * @param int $seconds the number of seconds before Now
623
	 */
624
	public function clearCronLog($seconds)
625
	{
626
		$this->ensureTable();
627
		
628
		$seconds = (int) $seconds;
629
		$cmd = $this->getDbConnection()->createCommand(
630
			"SELECT COUNT(*) FROM {$this->_tableName} WHERE active IS NULL AND lastexectime <= :time"
631
		);
632
		$time = time() - $seconds;
633
		$cmd->bindParameter(":time", $time, PDO::PARAM_STR);
634
		$count = $cmd->queryScalar();
635
		$cmd = $this->getDbConnection()->createCommand(
636
			"DELETE FROM {$this->_tableName} WHERE active IS NULL AND lastexectime <= :time"
637
		);
638
		$cmd->bindParameter(":time", $time, PDO::PARAM_STR);
639
		$cmd->execute();
640
		
641
		return $count;
642
	}
643
	
644
	/**
645
	 * Deletes one cron log item from the database
646
	 * @param int $taskUID
647
	 */
648
	public function removeCronLogItem($taskUID)
649
	{
650
		$this->ensureTable();
651
		$taskUID = (int) $taskUID;
652
		
653
		$cmd = $this->getDbConnection()->createCommand(
654
			"DELETE FROM {$this->_tableName} WHERE active IS NULL AND tabuid = :uid"
655
		);
656
		$cmd->bindParameter(":uid", $taskUID, PDO::PARAM_INT);
657
		$cmd->execute();
658
	}
659
	
660
	/**
661
	 * @param null|string $name name of the logs to look for, or null for all
662
	 * @return int the number of log items of all or of $name
663
	 */
664
	public function getCronLogCount($name = null)
665
	{
666
		$this->ensureTable();
667
		
668
		$db = $this->getDbConnection();
669
		$where = '';
670
		if (is_string($name)) {
671
			$where = 'name=:name AND ';
672
		}
673
		$cmd = $db->createCommand(
674
			"SELECT COUNT(*) AS count FROM {$this->_tableName} WHERE {$where}active IS NULL"
675
		);
676
		if (is_string($name)) {
677
			$cmd->bindParameter(":name", $name, PDO::PARAM_STR);
678
		}
679
		return (int) $cmd->queryScalar();
680
	}
681
	
682
	/**
683
	 * Gets the cron log table of specific named or all tasks.
684
	 * @param null|string $name name of the tasks to get from the log, or null for all
685
	 * @param int $pageSize
686
	 * @param int $offset
687
	 * @param null|bool $sortingDesc sort by descending execution time.
688
	 */
689
	public function getCronLog($name, $pageSize, $offset, $sortingDesc = null)
690
	{
691
		$this->ensureTable();
692
		
693
		$db = $this->getDbConnection();
694
		$driver = $db->getDriverName();
695
		
696
		$limit = $orderby = $where = '';
697
		if (is_string($name)) {
698
			$where = 'name=:name AND ';
699
		}
700
		$pageSize = (int) $pageSize;
701
		$offset = (int) $offset;
702
		if ($pageSize !== 0) {
703
			if ($offset !== 0) {
704
				if ($driver === 'postgresql') {
705
					$limit = " LIMIT {$pageSize} OFFSET {$offset}";
706
				} else {
707
					$limit = " LIMIT {$offset}, {$pageSize}";
708
				}
709
			} else {
710
				$limit = " LIMIT {$pageSize}";
711
			}
712
			$sortingDesc = $sortingDesc ?? true;
713
		}
714
		if ($sortingDesc !== null) {
715
			$sortingDesc = TPropertyValue::ensureBoolean($sortingDesc) ? "DESC" : "ASC";
716
			$orderby = " ORDER BY lastExecTime $sortingDesc, processCount $sortingDesc";
717
		}
718
		$cmd = $db->createCommand(
719
			"SELECT * FROM {$this->_tableName} WHERE {$where}active IS NULL{$orderby}{$limit}"
720
		);
721
		if (is_string($name)) {
722
			$cmd->bindParameter(":name", $name, PDO::PARAM_STR);
723
		}
724
		$results = $cmd->query();
725
		return $results->readAll();
726
	}
727
728
	/**
729
	 * Creates the DB connection. If no ConnectionId is provided, then this
730
	 * creates a sqlite database in runtime named 'cron.jobs'.
731
	 * @throws TConfigurationException if module ID is invalid or empty
732
	 * @return \Prado\Data\TDbConnection the created DB connection
733
	 */
734
	protected function createDbConnection()
735
	{
736
		if ($this->_connID !== '') {
737
			$config = $this->getApplication()->getModule($this->_connID);
738
			if ($config instanceof TDataSourceConfig) {
739
				return $config->getDbConnection();
740
			} else {
741
				throw new TConfigurationException('dbcron_connectionid_invalid', $this->_connID);
742
			}
743
		} else {
744
			$db = new TDbConnection;
745
			// default to SQLite3 database
746
			$dbFile = $this->getApplication()->getRuntimePath() . DIRECTORY_SEPARATOR . 'cron.jobs';
747
			$db->setConnectionString('sqlite:' . $dbFile);
748
			return $db;
749
		}
750
	}
751
752
	/**
753
	 * @return \Prado\Data\TDbConnection the DB connection instance
754
	 */
755
	public function getDbConnection()
756
	{
757
		if ($this->_conn === null) {
758
			$this->_conn = $this->createDbConnection();
759
			$this->_conn->setActive(true);
760
		}
761
		return $this->_conn;
762
	}
763
764
	/**
765
	 * @return null|string the ID of a {@link TDataSourceConfig} module. Defaults to empty string, meaning not set.
766
	 */
767
	public function getConnectionID()
768
	{
769
		return $this->_connID;
770
	}
771
772
	/**
773
	 * Sets the ID of a TDataSourceConfig module.
774
	 * The datasource module will be used to establish the DB connection for this cron module.
775
	 * @param string $value ID of the {@link TDataSourceConfig} module
776
	 * @throws TInvalidOperationException when trying to set this property but the module is already initialized.
777
	 */
778
	public function setConnectionID($value)
779
	{
780
		if ($this->_initialized) {
781
			throw new TInvalidOperationException('dbcron_property_unchangeable', 'ConnectionID');
782
		}
783
		$this->_connID = $value;
784
	}
785
	
786
	/**
787
	 * @return bool should tasks that run be logged, default true
788
	 */
789
	public function getLogCronTasks()
790
	{
791
		return $this->_logCronTasks;
792
	}
793
	
794
	/**
795
	 * @param bool $log should tasks that run be logged
796
	 */
797
	public function setLogCronTasks($log)
798
	{
799
		$this->_logCronTasks = TPropertyValue::ensureBoolean($log);
800
	}
801
	
802
	/**
803
	 * @return string table in the database for cron tasks and logs. Defaults to 'crontabs'
804
	 */
805
	public function getTableName()
806
	{
807
		return $this->_tableName;
808
	}
809
810
	/**
811
	 * @param string $table table in the database for cron tasks and logs
812
	 * @throws TInvalidOperationException when trying to set this property but the module is already initialized.
813
	 */
814
	public function setTableName($table)
815
	{
816
		if ($this->_initialized) {
817
			throw new TInvalidOperationException('dbcron_property_unchangeable', 'TableName');
818
		}
819
		$this->_tableName = TPropertyValue::ensureString($table);
820
	}
821
822
	/**
823
	 * @return bool whether the cron DB table should be automatically created if not exists. Defaults to true.
824
	 * @see setTableName
825
	 */
826
	public function getAutoCreateCronTable()
827
	{
828
		return $this->_autoCreate;
829
	}
830
831
	/**
832
	 * @param bool $value whether the cron DB table should be automatically created if not exists.
833
	 * @throws TInvalidOperationException when trying to set this property but the module is already initialized.
834
	 * @see setTableName
835
	 */
836
	public function setAutoCreateCronTable($value)
837
	{
838
		if ($this->_initialized) {
839
			throw new TInvalidOperationException('dbcron_property_unchangeable', 'AutoCreateCronTable');
840
		}
841
		$this->_autoCreate = TPropertyValue::ensureBoolean($value);
842
	}
843
}
844