Passed
Push — master ( 46bc20...476c9e )
by Fabio
07:16
created

TCronModule::setInCronShell()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 1
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 3
rs 10
1
<?php
2
/**
3
 * TCronModule 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 Prado\Exceptions\TConfigurationException;
14
use Prado\Exceptions\TInvalidDataTypeException;
15
use Prado\Exceptions\TInvalidOperationException;
16
use Prado\Prado;
17
use Prado\Security\IUserManager;
18
use Prado\Security\Permissions\IPermissions;
19
use Prado\Security\Permissions\TPermissionEvent;
20
use Prado\Shell\TShellApplication;
21
use Prado\TPropertyValue;
22
use Prado\Util\TLogger;
23
use Prado\Xml\TXmlElement;
24
use Prado\Xml\TXmlDocument;
25
26
/**
27
 * TCronModule class.
28
 *
29
 * TCronModule runs time based services for the application. This will run a
30
 * task at a given time.  A task can be a task class or a module Id followed by
31
 * '->' followed by a method with or without parameters. eg.
32
 *
33
 * <code>
34
 * 	<module id="cron" class="Prado\Util\Cron\TCronModule" DefaultUserName="admin">
35
 *		<job Name="cronclean" Schedule="0 0 1 * * *" Task="Prado\Util\Cron\TDbCronCleanLogTask" UserName="cron" />
36
 *		<job Name="dbcacheclean" Schedule="* * * * *" Task="dbcache->flushCacheExpired(true)" />
37
 *		<job Schedule="0 * * * *" Task="mymoduleid->taskmethod" />
38
 *	</module>
39
 * </code>
40
 *
41
 * The schedule is formatted like a linux crontab schedule expression.
42
 * {@link TTimeSchedule} parses the schedule and supports 8 different
43
 * languages.  Advanced options, like @daily, and @hourly, are supported.
44
 *
45
 * This module is designed to be run as a system Crontab prado-cli every
46
 * minute.  The application then decides what tasks to execute, or not, and
47
 * when.
48
 *
49
 * The following is an example for your system cron tab to run the PRADO
50
 * application cron.
51
 * <code>
52
 *		* * * * *  php /dir_to_/vendor/bin/prado-cli app /dir_to_app/ cron
53
 * </code>
54
 *
55
 * The default cron user can be set with {@link set$DefaultUserName} with its
56
 * default being 'cron' user.  The default user is used when no task specific
57
 * user is specifiedThe 'cron' user should exist in the TUserManager to
58
 * switched the application user properly.
59
 *
60
 * @author Brad Anderson <[email protected]>
61
 * @since 4.2.0
62
 * @method void dyLogCron($numtasks)
63
 * @method void dyLogCronTask($task, $username)
64
 * @method void dyLogCronTaskEnd($task)
65
 * @method bool dyRegisterShellAction($returnValue)
66
 * @method \Prado\Shell\TShellWriter getOutputWriter()
67
 * @see https://crontab.guru For more info on Crontab Schedule Expressions.
68
 */
69
class TCronModule extends \Prado\TModule implements IPermissions
70
{
71
	/** The behavior name for the Shell Log behavior */
72
	public const SHELL_LOG_BEHAVIOR = 'shellLog';
73
74
	public const TASK_KEY = 'task';
75
76
	public const SCHEDULE_KEY = 'schedule';
77
78
	public const NAME_KEY = 'name';
79
80
	public const USERNAME_KEY = 'username';
81
82
	/** Key to global state of the last time SystemCron was run */
83
	public const LAST_CRON_TIME = 'prado:cron:lastcron';
84
85
	/** Key to global state of each task, lastExecTime and ProcessCount */
86
	public const TASKS_INFO = 'prado:cron:tasksinfo';
87
88
	/** The name of the cron user */
89
	public const DEFAULT_CRON_USER = 'cron';
90
91
	/** The separator for TCronMethodTask */
92
	public const METHOD_SEPARATOR = '->';
93
94
	/** The permission for the cron shell */
95
	public const PERM_CRON_SHELL = 'cron_shell';
96
97
	/** @var bool if the module has been initialized */
98
	protected $_initialized = false;
99
100
	/** @var IUserManager user manager instance */
101
	private $_userManager;
102
103
	/** @var TCronTaskInfo[] The info for tasks in the system. */
104
	private $_tasksInfo;
105
106
	/** @var bool whether or not the $_tasks is properties or TCronTask  */
107
	private $_tasksInstanced = false;
108
109
	/** @var array[]|TCronTask[] the tasks */
110
	private $_tasks = [];
111
112
	/** @var string The user Id of the tasks without users */
113
	private $_defaultUserName = self::DEFAULT_CRON_USER;
114
115
	/** @var bool enable cron on requests, default false */
116
	private $_enableRequestCron = false;
117
118
	/** @var numeric probability that a request cron will trigger [0.0 to 100.0], default 1.0 (for 1%) */
0 ignored issues
show
Bug introduced by
The type Prado\Util\Cron\numeric 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...
119
	private $_requestCronProbability = 1.0;
120
121
	/** @var null|bool is the app running in cron shell mode or not, or null for auto-detect */
122
	private $_inCronShell;
123
124
	/** @var string the cli class to instance for CLI command line actions; this changes for TDbCronModule */
125
	protected $_shellClass = 'Prado\\Util\\Cron\\TShellCronAction';
126
127
	/** @var array[] any additional tasks to install from properties */
128
	private $_additionalCronTasks;
129
130
	/**
131
	 * Initializes the module.  Read the configuration, installs Shell Actions,
132
	 * should Request cron be activated.
133
	 * @param array|\Prado\Xml\TXmlElement $config
134
	 * @throws TConfigurationException when the user manage doesn't exist or is invalid
135
	 */
136
	public function init($config)
137
	{
138
		$app = $this->getApplication();
139
140
		//Get the IUserManager module from the string
141
		if (is_string($this->_userManager)) {
0 ignored issues
show
introduced by
The condition is_string($this->_userManager) is always false.
Loading history...
142
			if (($users = $app->getModule($this->_userManager)) === null) {
143
				throw new TConfigurationException('cron_usermanager_nonexistent', $this->_userManager);
144
			}
145
			if (!($users instanceof IUserManager)) {
146
				throw new TConfigurationException('cron_usermanager_invalid', $this->_userManager);
147
			}
148
			$this->_userManager = $users;
149
		}
150
151
		// Otherwise manually look for the IUserManager
152
		if ($this->_userManager === null) {
153
			$users = $app->getModulesByType('Prado\\Security\\IUserManager');
154
			foreach ($users as $id => $module) {
155
				$this->_userManager = $app->getModule($id);
0 ignored issues
show
Documentation Bug introduced by
It seems like $app->getModule($id) can also be of type Prado\TModule. However, the property $_userManager is declared as type Prado\Security\IUserManager. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
156
				break;
157
			}
158
		}
159
		$this->_tasksInstanced = false;
160
161
		//read config tasks and schedule
162
		$this->readConfiguration($config);
163
164
		//Read additional Config from Property
165
		$this->readConfiguration($this->_additionalCronTasks);
166
		$app->attachEventHandler('onAuthenticationComplete', [$this, 'registerShellAction']);
167
168
		if ($app instanceof \Prado\Shell\TShellApplication) {
169
			$app->registerOption('cron', [$this, 'setInCronShell'], 'If run in crontab, set this flag.  It limits tasks to the current minute, default auto-detect');
170
			$app->registerOptionAlias('c', 'cron');
171
		}
172
173
		if (php_sapi_name() !== 'cli' && $this->getEnableRequestCron()) {
174
			if (100.0 * ((float) (mt_rand()) / (float) (mt_getrandmax())) <= $this->getRequestCronProbability()) {
175
				$app->attachEventHandler('OnEndRequest', [$this, 'processPendingTasks'], 20);
0 ignored issues
show
Bug introduced by
20 of type integer is incompatible with the type Prado\numeric|null expected by parameter $priority of Prado\TComponent::attachEventHandler(). ( Ignorable by Annotation )

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

175
				$app->attachEventHandler('OnEndRequest', [$this, 'processPendingTasks'], /** @scrutinizer ignore-type */ 20);
Loading history...
176
			}
177
		}
178
		$this->_initialized = true;
179
		parent::init($config);
0 ignored issues
show
Bug introduced by
It seems like $config can also be of type array; however, parameter $config of Prado\TModule::init() does only seem to accept Prado\Xml\TXmlElement, 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

179
		parent::init(/** @scrutinizer ignore-type */ $config);
Loading history...
180
	}
181
182
	/**
183
	 * @param \Prado\Security\Permissions\TPermissionsManager $manager
184
	 * @return \Prado\Security\Permissions\TPermissionEvent[]
185
	 */
186
	public function getPermissions($manager)
187
	{
188
		return [
189
			new TPermissionEvent(static::PERM_CRON_SHELL, 'Activates cron shell commands.', 'dyRegisterShellAction')
190
		];
191
	}
192
193
	/**
194
	 * This reads the configuration and stores the specified tasks, for lazy loading, until needed.
195
	 * @param array|\Prado\Xml\TXmlElement $config the settings for cron
196
	 * @throws TConfigurationException when a PHP configuration is not an array or two jobs have the same name.
197
	 */
198
	protected function readConfiguration($config)
199
	{
200
		$isXml = false;
201
		if (!$config) {
202
			return;
203
		}
204
		if ($config instanceof \Prado\Xml\TXmlElement) {
205
			$isXml = true;
206
			$config = $config->getElementsByTagName('job');
207
		} elseif (is_array($config) && isset($config['jobs'])) {
208
			$config = $config['jobs'];
209
		}
210
		foreach ($config as $properties) {
211
			if ($isXml) {
212
				$properties = array_change_key_case($properties->getAttributes()->toArray());
213
			} else {
214
				if (!is_array($properties)) {
215
					throw new TConfigurationException('cron_task_as_array_required');
216
				}
217
			}
218
			if (!($properties[self::NAME_KEY] ?? null)) {
219
				$class = $properties[self::TASK_KEY] ?? '';
220
				$schedule = $properties[self::SCHEDULE_KEY] ?? '';
221
				$name = $properties[self::NAME_KEY] = substr(md5($schedule . $class), 0, 7);
222
			} else {
223
				$name = $properties[self::NAME_KEY];
224
			}
225
			if (isset($this->_tasks[$name])) {
226
				throw new TConfigurationException('cron_duplicate_task_name', $name);
227
			}
228
			$this->validateTask($properties);
229
			$this->_tasks[$name] = $properties;
230
		}
231
	}
232
233
	/**
234
	 * Validates that schedule and task are present.
235
	 * Subclasses overload this method to add their own validation.
236
	 * @param array $properties the task as an array of properties
237
	 * @throws TConfigurationException when the schedule or task doesn't exist
238
	 */
239
	public function validateTask($properties)
240
	{
241
		$schedule = $properties[self::SCHEDULE_KEY] ?? null;
242
		$task = $properties[self::TASK_KEY] ?? null;
243
		if (!$schedule) {
244
			throw new TConfigurationException('cron_schedule_required');
245
		}
246
		if (!$task) {
247
			throw new TConfigurationException('cron_task_required');
248
		}
249
	}
250
251
	/**
252
	 * @param object $sender sender of this event handler
253
	 * @param null|mixed $param parameter for the event
254
	 */
255
	public function registerShellAction($sender, $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

255
	public function registerShellAction($sender, /** @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 $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

255
	public function registerShellAction(/** @scrutinizer ignore-unused */ $sender, $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...
256
	{
257
		if ($this->dyRegisterShellAction(false) !== true && ($app = $this->getApplication()) instanceof \Prado\Shell\TShellApplication) {
258
			$app->addShellActionClass(['class' => $this->_shellClass, 'CronModule' => $this]);
0 ignored issues
show
Bug introduced by
array('class' => $this->... 'CronModule' => $this) of type array<string,Prado\Util\Cron\TCronModule|string> is incompatible with the type string expected by parameter $class of Prado\Shell\TShellApplic...::addShellActionClass(). ( Ignorable by Annotation )

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

258
			$app->addShellActionClass(/** @scrutinizer ignore-type */ ['class' => $this->_shellClass, 'CronModule' => $this]);
Loading history...
259
		}
260
	}
261
262
	/**
263
	 * makes the tasks from the configuration array into task objects.
264
	 * @return \Prado\Util\Cron\TCronTask[]
265
	 */
266
	protected function ensureTasks()
267
	{
268
		if (!$this->_tasksInstanced) {
269
			foreach ($this->_tasks as $properties) {
270
				$name = $properties[self::NAME_KEY];
271
				$task = $properties[self::TASK_KEY];
272
				unset($properties[self::NAME_KEY]);
273
				unset($properties[self::TASK_KEY]);
274
275
				$task = $this->instanceTask($task);
276
277
				$task->setName($name);
278
				foreach ($properties as $key => $value) {
279
					$task->$key = $value;
280
				}
281
				$this->setPersistentData($name, $task);
282
				$this->_tasks[$name] = $task;
283
			}
284
			$this->_tasksInstanced = true;
285
		}
286
		return $this->_tasks;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_tasks returns an array which contains values of type array which are incompatible with the documented value type Prado\Util\Cron\TCronTask.
Loading history...
287
	}
288
289
	/**
290
	 * This lazy loads the tasks from configuration array to instance.
291
	 * This calls {@link ensureTasks} to get the tasks and their persistent data.
292
	 * @return \Prado\Util\Cron\TCronTask[] currently active cron tasks
293
	 */
294
	public function getTasks()
295
	{
296
		$this->ensureTasks();
297
		return $this->_tasks;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_tasks returns an array which contains values of type array which are incompatible with the documented value type Prado\Util\Cron\TCronTask.
Loading history...
298
	}
299
300
	/**
301
	 * These are the tasks specified in the configuration and getAdditionalCronTasks
302
	 * until {@link ensureTasks} is called.
303
	 * @return array[]|TCronTask[] currently active cron tasks
304
	 */
305
	public function getRawTasks()
306
	{
307
		return $this->_tasks;
308
	}
309
310
	/**
311
	 * This lazy loads the tasks.
312
	 * @param string $name
313
	 * @return TCronTask cron tasks with a specific name
314
	 */
315
	public function getTask($name)
316
	{
317
		$tasks = $this->getTasks();
318
		return $tasks[$name] ?? null;
319
	}
320
321
	/**
322
	 * .
323
	 * @param string $taskExec the class name or "module->method('param1')" to place
324
	 * into a {@link TCronMethodTask}.
325
	 * @return TCronTask the instance of $taskExec
326
	 */
327
	public function instanceTask($taskExec)
328
	{
329
		if (($pos = strpos($taskExec, self::METHOD_SEPARATOR)) !== false) {
330
			$module = substr($taskExec, 0, $pos);
331
			$method = substr($taskExec, $pos + 2); //String Length of self::METHOD_SEPARATOR
332
			$task = new TCronMethodTask($module, $method);
333
		} else {
334
			$task = Prado::createComponent($taskExec);
335
			if (!$task instanceof \Prado\Util\Cron\TCronTask) {
336
				throw new TInvalidDataTypeException("cron_not_a_crontask", $taskExec);
337
			}
338
		}
339
		return $task;
340
	}
341
342
	/**
343
	 * when instancing and then loading the tasks, this sets the persisting data of
344
	 * the task from the global state.  When there is no instance in the global state,
345
	 * the lastExecTime is initialized.
346
	 * @param string $name name of the task.
347
	 * @param TCronTask $task the task object.
348
	 * @return bool updated the taskInfo with persistent data.
349
	 */
350
	protected function setPersistentData($name, $task)
351
	{
352
		$tasksInfo = $this->getApplication()->getGlobalState(self::TASKS_INFO, []);
353
		if (isset($tasksInfo[$name])) {
354
			$task->setLastExecTime($tasksInfo[$name]['lastExecTime']);
355
			$task->setProcessCount($tasksInfo[$name]['processCount']);
356
			return true;
357
		} else {
358
			$task->resetTaskLastExecTime();
359
			$task->setProcessCount(0);
360
			$tasksInfo[$name]['lastExecTime'] = $task->getLastExecTime();
361
			$tasksInfo[$name]['processCount'] = 0;
362
			$this->getApplication()->setGlobalState(self::TASKS_INFO, $tasksInfo, []);
363
		}
364
		return false;
365
	}
366
367
	/**
368
	 * @return TCronTask[] the tasks that are pending.
369
	 */
370
	public function getPendingTasks()
371
	{
372
		$pendingtasks = [];
373
		foreach ($this->getTasks() as $name => $task) {
374
			if ($task->getIsPending()) {
375
				$pendingtasks[$name] = $task;
376
			}
377
		}
378
		return $pendingtasks;
379
	}
380
381
	/**
382
	 * Filters the Tasks for a specific class.
383
	 * @param string $type
384
	 * @return TCronTask[] the tasks of $type
385
	 */
386
	public function getTasksByType($type)
387
	{
388
		$matches = [];
389
		foreach ($this->getTasks() as $name => $task) {
390
			if ($task->isa($type)) {
391
				$matches[$name] = $task;
392
			}
393
		}
394
		return $matches;
395
	}
396
397
	/**
398
	 * processPendingTasks executes pending tasks
399
	 * @return int number of tasks run that were pending
400
	 */
401
	public function processPendingTasks()
402
	{
403
		$inCronTab = (($inCron = $this->getInCronShell()) !== null) ? $inCron : TShellApplication::detectCronTabShell();
404
		$this->filterStaleTasks();
405
		$pendingTasks = $this->getPendingTasks();
406
		$numtasks = count($pendingTasks);
407
		$startMinute = floor(time() / 60);
408
409
		$this->logCron($numtasks);
410
		if ($numtasks) {
411
			foreach ($pendingTasks as $key => $task) {
412
				if ($inCronTab && $startMinute != floor(time() / 60)) {
413
					break;
414
				}
415
				$this->runTask($task);
416
			}
417
		}
418
419
		return $numtasks;
420
	}
421
422
	/**
423
	 * @param int $numtasks number of tasks being processed
424
	 */
425
	protected function logCron($numtasks)
426
	{
427
		Prado::log("Processing {$numtasks} Cron Tasks", TLogger::INFO, 'Prado.Cron.TCronModule');
428
		$this->dyLogCron($numtasks);
429
		$this->setLastCronTime(microtime(true));
0 ignored issues
show
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $time of Prado\Util\Cron\TCronModule::setLastCronTime() does only seem to accept double, 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

429
		$this->setLastCronTime(/** @scrutinizer ignore-type */ microtime(true));
Loading history...
430
	}
431
432
	/**
433
	 * This removes any stale tasks in the global state.
434
	 */
435
	protected function filterStaleTasks()
436
	{
437
		// Filter out any stale tasks in the global state that aren't in config
438
		$app = $this->getApplication();
439
		$tasksInfo = $app->getGlobalState(self::TASKS_INFO, []);
440
		$count = count($tasksInfo);
441
		$tasksInfo = array_intersect_key($tasksInfo, $this->_tasks);
442
		if ($count != count($tasksInfo)) {
443
			$app->setGlobalState(self::TASKS_INFO, $tasksInfo, []);
444
		}
445
	}
446
447
	/**
448
	 * Runs a specific task. Sets the user to the Task user or the cron module
449
	 * {@link getDefaultUserName}.
450
	 * @param \Prado\Util\Cron\TCronTask $task the task to run.
451
	 */
452
	public function runTask($task)
453
	{
454
		$app = $this->getApplication();
455
		$users = $this->getUserManager();
456
		$defaultUsername = $username = $this->getDefaultUserName();
457
		$restore_user = $app->getUser();
458
		$user = null;
459
460
		if ($users) {
0 ignored issues
show
introduced by
$users is of type Prado\Security\IUserManager, thus it always evaluated to true.
Loading history...
461
			if ($nusername = $task->getUserName()) {
462
				$username = $nusername;
463
			}
464
			$user = $users->getUser($username);
465
			if (!$user && $username !== $defaultUsername) {
0 ignored issues
show
introduced by
$user is of type Prado\Security\TUser, thus it always evaluated to true.
Loading history...
466
				$user = $users->getUser($username = $defaultUsername);
467
			}
468
469
			if ($user) {
0 ignored issues
show
introduced by
$user is of type Prado\Security\TUser, thus it always evaluated to true.
Loading history...
470
				$app->setUser($user);
471
			} elseif ($restore_user) {
472
				$user = clone $restore_user;
473
				$user->setIsGuest(true);
474
				$app->setUser($user);
475
			}
476
		}
477
478
		$this->logCronTask($task, $username);
479
		$this->updateTaskInfo($task);
480
		$task->execute($this);
481
		$this->logCronTaskEnd($task);
482
483
		if ($user) {
0 ignored issues
show
introduced by
$user is of type Prado\Security\TUser, thus it always evaluated to true.
Loading history...
484
			if ($restore_user) {
0 ignored issues
show
introduced by
$restore_user is of type Prado\Security\IUser, thus it always evaluated to true.
Loading history...
485
				$app->setUser($restore_user);
486
			} else {
487
				$user->setIsGuest(true);
488
			}
489
		}
490
	}
491
492
	/**
493
	 * Logs the cron task being run with the system log and output on cli
494
	 * @param TCronTask $task
495
	 * @param string $username the user the task is running under.
496
	 */
497
	protected function logCronTask($task, $username)
498
	{
499
		Prado::log('Running cron task (' . $task->getName() . ', ' . $username . ')', TLogger::INFO, 'Prado.Cron.TCronModule');
500
		$this->dyLogCronTask($task, $username);
501
	}
502
503
	/**
504
	 * sets the lastExecTime to now and increments the processCount.  This saves
505
	 * the new data to the global state.
506
	 * @param TCronTask $task
507
	 */
508
	protected function updateTaskInfo($task)
509
	{
510
		$tasksInfo = $this->getApplication()->getGlobalState(self::TASKS_INFO, []);
511
		$name = $task->getName();
512
		$time = $tasksInfo[$name]['lastExecTime'] = (int) microtime(true);
513
		$count = $tasksInfo[$name]['processCount'] = $task->getProcessCount() + 1;
514
		$task->setProcessCount($count);
515
		$task->setLastExecTime($time);
0 ignored issues
show
Bug introduced by
$time of type integer is incompatible with the type Prado\Util\Cron\numeric expected by parameter $v of Prado\Util\Cron\TCronTask::setLastExecTime(). ( Ignorable by Annotation )

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

515
		$task->setLastExecTime(/** @scrutinizer ignore-type */ $time);
Loading history...
516
517
		$this->getApplication()->setGlobalState(self::TASKS_INFO, $tasksInfo, [], true);
518
	}
519
520
	/**
521
	 * Logs the end of the task.
522
	 * @param TCronTask $task
523
	 */
524
	protected function logCronTaskEnd($task)
525
	{
526
		Prado::log('Ending cron task (' . $task->getName() . ', ' . $task->getTask() . ')', TLogger::INFO, 'Prado.Cron.TCronModule');
527
		$this->dyLogCronTaskEnd($task);
528
	}
529
530
	/**
531
	 * @return float time that cron last was last run
532
	 */
533
	public function getLastCronTime()
534
	{
535
		return $this->getApplication()->getGlobalState(self::LAST_CRON_TIME, 0);
536
	}
537
538
	/**
539
	 * @param float $time time that cron was last run
540
	 */
541
	public function setLastCronTime($time)
542
	{
543
		$this->getApplication()->setGlobalState(self::LAST_CRON_TIME, TPropertyValue::ensureFloat($time), 0, true);
544
	}
545
546
	/**
547
	 * Objects should handle fxGetCronTasks($sender, $param)
548
	 * @param bool $forceupdate if true, ignores the caching
549
	 * @return \Prado\Util\Cron\TCronTaskInfo[]
550
	 */
551
	public function getTaskInfos($forceupdate = false)
552
	{
553
		if (!$this->_tasksInfo || $forceupdate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_tasksInfo of type Prado\Util\Cron\TCronTaskInfo[] 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...
554
			$this->_tasksInfo = $this->raiseEvent('fxGetCronTaskInfos', $this, null);
555
		}
556
		return $this->_tasksInfo;
557
	}
558
559
	/**
560
	 * @return string the default user id of Tasks without users ids
561
	 */
562
	public function getDefaultUserName()
563
	{
564
		return $this->_defaultUserName;
565
	}
566
567
	/**
568
	 * @param string $id the default user id of Tasks without users ids
569
	 * @throws TInvalidOperationException if the module has been initialized
570
	 */
571
	public function setDefaultUserName($id)
572
	{
573
		if ($this->_initialized) {
574
			throw new TInvalidOperationException('cron_property_unchangeable', 'DefaultUserName');
575
		}
576
		$this->_defaultUserName = TPropertyValue::ensureString($id);
577
	}
578
579
	/**
580
	 * @return null|IUserManager user manager instance
581
	 */
582
	public function getUserManager()
583
	{
584
		return $this->_userManager;
585
	}
586
587
	/**
588
	 * @param null|IUserManager|string $provider the user manager module ID or the user manager object
589
	 * @throws TInvalidOperationException if the module has been initialized
590
	 * @throws TConfigurationException if the user manager is not a string, not an instance of IUserManager, and not null
591
	 */
592
	public function setUserManager($provider)
593
	{
594
		if ($this->_initialized) {
595
			throw new TInvalidOperationException('cron_property_unchangeable', 'UserManager');
596
		}
597
		if (!is_string($provider) && !($provider instanceof IUserManager) && $provider !== null) {
0 ignored issues
show
introduced by
The condition $provider !== null is always false.
Loading history...
598
			throw new TConfigurationException('cron_usermanager_invalid', is_object($provider) ? get_class($provider) : $provider);
599
		}
600
		$this->_userManager = $provider;
0 ignored issues
show
Documentation Bug introduced by
It seems like $provider can also be of type string. However, the property $_userManager is declared as type Prado\Security\IUserManager. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
601
	}
602
603
	/**
604
	 * @return bool allow request cron task processing, default false
605
	 */
606
	public function getEnableRequestCron()
607
	{
608
		return $this->_enableRequestCron;
609
	}
610
611
	/**
612
	 * @param mixed $allow request cron task processing
613
	 * @throws TInvalidOperationException if the module has been initialized
614
	 */
615
	public function setEnableRequestCron($allow)
616
	{
617
		if ($this->_initialized) {
618
			throw new TInvalidOperationException('cron_property_unchangeable', 'EnableRequestCron');
619
		}
620
		$this->_enableRequestCron = TPropertyValue::ensureBoolean($allow);
621
	}
622
623
	/**
624
	 * @return numeric the probability 0.0 .. 100.0 of a request triggering a cron, default 1.0 (out of 100.0).
625
	 */
626
	public function getRequestCronProbability()
627
	{
628
		return $this->_requestCronProbability;
629
	}
630
631
	/**
632
	 * @param numeric $probability the probability 0.0..100.0 of a request triggering a cron
633
	 * @throws TInvalidOperationException if the module has been initialized
634
	 */
635
	public function setRequestCronProbability($probability)
636
	{
637
		if ($this->_initialized) {
638
			throw new TInvalidOperationException('cron_property_unchangeable', 'RequestCronProbability');
639
		}
640
		$this->_requestCronProbability = TPropertyValue::ensureFloat($probability);
0 ignored issues
show
Documentation Bug introduced by
It seems like Prado\TPropertyValue::ensureFloat($probability) of type double is incompatible with the declared type Prado\Util\Cron\numeric of property $_requestCronProbability.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
641
	}
642
643
	/**
644
	 * @return null|bool is cli running from cron.
645
	 * @since 4.2.2
646
	 */
647
	public function getInCronShell()
648
	{
649
		return $this->_inCronShell;
650
	}
651
652
	/**
653
	 * @param null|bool $inCronShell is cli running from cron.  This limits
654
	 * pending tasks to only the current minute.
655
	 * @since 4.2.2
656
	 */
657
	public function setInCronShell($inCronShell)
658
	{
659
		$this->_inCronShell = $inCronShell === null ? null : TPropertyValue::ensureBoolean($inCronShell === '' ? true : $inCronShell);
0 ignored issues
show
introduced by
The condition $inCronShell === '' is always false.
Loading history...
660
	}
661
662
	/**
663
	 * @return array additional tasks in a list.
664
	 */
665
	public function getAdditionalCronTasks()
666
	{
667
		return $this->_additionalCronTasks ?? [];
668
	}
669
670
	/**
671
	 * This will take a string that is an array of tasks that has been
672
	 * through serialize(), or json_encode, or is an xml file of additional tasks.
673
	 * If one task is set, then it is automatically placed into an array.
674
	 * @param null|array|string $tasks additional tasks in an array [0..n],  or
675
	 * a single task.
676
	 * @throws TInvalidOperationException if the module has been initialized
677
	 */
678
	public function setAdditionalCronTasks($tasks)
679
	{
680
		if ($this->_initialized) {
681
			throw new TInvalidOperationException('cron_property_unchangeable', 'AdditionalCronTasks');
682
		}
683
		if (is_string($tasks)) {
684
			if (($t = @unserialize($tasks)) !== false) {
685
				$tasks = $t;
686
			} elseif (($t = json_decode($tasks, true)) !== null) {
687
				$tasks = $t;
688
			} else {
689
				$xmldoc = new TXmlDocument('1.0', 'utf-8');
690
				$xmldoc->loadFromString($tasks);
691
				$tasks = $xmldoc;
692
			}
693
		}
694
		if (is_array($tasks) && isset($tasks[self::TASK_KEY])) {
695
			$tasks = [$tasks];
696
		}
697
		if (!is_array($tasks) && !($tasks instanceof TXmlDocument) && $tasks !== null) {
698
			throw new TInvalidDataTypeException('cron_additional_tasks_invalid', $tasks);
699
		}
700
		$this->_additionalCronTasks = $tasks;
701
	}
702
}
703