TCronModule   F
last analyzed

Complexity

Total Complexity 102

Size/Duplication

Total Lines 632
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 230
c 4
b 1
f 0
dl 0
loc 632
rs 2
wmc 102

35 Methods

Rating   Name   Duplication   Size   Complexity  
A setUserManager() 0 9 6
A getEnableRequestCron() 0 3 1
A logCronTask() 0 4 1
A processPendingTasks() 0 19 6
A getTask() 0 4 1
A setDefaultUserName() 0 6 2
A getUserManager() 0 3 1
B init() 0 44 10
A setRequestCronProbability() 0 6 2
A registerShellAction() 0 4 3
A filterStaleTasks() 0 9 2
A setLastCronTime() 0 3 1
A getTaskInfos() 0 6 3
B readConfiguration() 0 32 10
A logCron() 0 5 1
A updateTaskInfo() 0 10 1
A instanceTask() 0 13 3
A getInCronShell() 0 3 1
A getTasksByType() 0 9 3
A getDefaultUserName() 0 3 1
A getLastCronTime() 0 3 1
A getPendingTasks() 0 9 3
A getRawTasks() 0 3 1
A ensureTasks() 0 21 4
A validateTask() 0 9 3
A setInCronShell() 0 3 3
A getAdditionalCronTasks() 0 3 1
A getPermissions() 0 4 1
B runTask() 0 36 9
A logCronTaskEnd() 0 4 1
B setAdditionalCronTasks() 0 23 10
A getTasks() 0 4 1
A setEnableRequestCron() 0 6 2
A setPersistentData() 0 15 2
A getRequestCronProbability() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TCronModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TCronModule, and based on these observations, apply Extract Interface, too.

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

176
				$app->attachEventHandler('OnEndRequest', [$this, 'processPendingTasks'], /** @scrutinizer ignore-type */ 20);
Loading history...
177
			}
178
		}
179
		$this->_initialized = true;
180
		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

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

256
	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

256
	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...
257
	{
258
		if ($this->dyRegisterShellAction(false) !== true && ($app = $this->getApplication()) instanceof \Prado\Shell\TShellApplication) {
259
			$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

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

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

516
		$task->setLastExecTime(/** @scrutinizer ignore-type */ $time);
Loading history...
517
518
		$this->getApplication()->setGlobalState(self::TASKS_INFO, $tasksInfo, [], true);
519
	}
520
521
	/**
522
	 * Logs the end of the task.
523
	 * @param TCronTask $task
524
	 */
525
	protected function logCronTaskEnd($task)
526
	{
527
		Prado::log('Ending cron task (' . $task->getName() . ', ' . $task->getTask() . ')', TLogger::INFO, TCronModule::class);
528
		$this->dyLogCronTaskEnd($task);
529
	}
530
531
	/**
532
	 * @return float time that cron last was last run
533
	 */
534
	public function getLastCronTime()
535
	{
536
		return $this->getApplication()->getGlobalState(self::LAST_CRON_TIME, 0);
537
	}
538
539
	/**
540
	 * @param float $time time that cron was last run
541
	 */
542
	public function setLastCronTime($time)
543
	{
544
		$this->getApplication()->setGlobalState(self::LAST_CRON_TIME, TPropertyValue::ensureFloat($time), 0, true);
545
	}
546
547
	/**
548
	 * Objects should handle fxGetCronTasks($sender, $param)
549
	 * @param bool $forceupdate if true, ignores the caching
550
	 * @return \Prado\Util\Cron\TCronTaskInfo[]
551
	 */
552
	public function getTaskInfos($forceupdate = false)
553
	{
554
		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...
555
			$this->_tasksInfo = $this->raiseEvent('fxGetCronTaskInfos', $this, null);
556
		}
557
		return $this->_tasksInfo;
558
	}
559
560
	/**
561
	 * @return string the default user id of Tasks without users ids
562
	 */
563
	public function getDefaultUserName()
564
	{
565
		return $this->_defaultUserName;
566
	}
567
568
	/**
569
	 * @param string $id the default user id of Tasks without users ids
570
	 * @throws TInvalidOperationException if the module has been initialized
571
	 */
572
	public function setDefaultUserName($id)
573
	{
574
		if ($this->_initialized) {
575
			throw new TInvalidOperationException('cron_property_unchangeable', 'DefaultUserName');
576
		}
577
		$this->_defaultUserName = TPropertyValue::ensureString($id);
578
	}
579
580
	/**
581
	 * @return null|IUserManager user manager instance
582
	 */
583
	public function getUserManager()
584
	{
585
		return $this->_userManager;
586
	}
587
588
	/**
589
	 * @param null|IUserManager|string $provider the user manager module ID or the user manager object
590
	 * @throws TInvalidOperationException if the module has been initialized
591
	 * @throws TConfigurationException if the user manager is not a string, not an instance of IUserManager, and not null
592
	 */
593
	public function setUserManager($provider)
594
	{
595
		if ($this->_initialized) {
596
			throw new TInvalidOperationException('cron_property_unchangeable', 'UserManager');
597
		}
598
		if (!is_string($provider) && !($provider instanceof IUserManager) && $provider !== null) {
0 ignored issues
show
introduced by
The condition $provider !== null is always false.
Loading history...
599
			throw new TConfigurationException('cron_usermanager_invalid', is_object($provider) ? $provider::class : $provider);
600
		}
601
		$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...
602
	}
603
604
	/**
605
	 * @return bool allow request cron task processing, default false
606
	 */
607
	public function getEnableRequestCron()
608
	{
609
		return $this->_enableRequestCron;
610
	}
611
612
	/**
613
	 * @param mixed $allow request cron task processing
614
	 * @throws TInvalidOperationException if the module has been initialized
615
	 */
616
	public function setEnableRequestCron($allow)
617
	{
618
		if ($this->_initialized) {
619
			throw new TInvalidOperationException('cron_property_unchangeable', 'EnableRequestCron');
620
		}
621
		$this->_enableRequestCron = TPropertyValue::ensureBoolean($allow);
622
	}
623
624
	/**
625
	 * @return numeric the probability 0.0 .. 100.0 of a request triggering a cron, default 1.0 (out of 100.0).
626
	 */
627
	public function getRequestCronProbability()
628
	{
629
		return $this->_requestCronProbability;
630
	}
631
632
	/**
633
	 * @param numeric $probability the probability 0.0..100.0 of a request triggering a cron
634
	 * @throws TInvalidOperationException if the module has been initialized
635
	 */
636
	public function setRequestCronProbability($probability)
637
	{
638
		if ($this->_initialized) {
639
			throw new TInvalidOperationException('cron_property_unchangeable', 'RequestCronProbability');
640
		}
641
		$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...
642
	}
643
644
	/**
645
	 * @return null|bool is cli running from cron.
646
	 * @since 4.2.2
647
	 */
648
	public function getInCronShell()
649
	{
650
		return $this->_inCronShell;
651
	}
652
653
	/**
654
	 * @param null|bool $inCronShell is cli running from cron.  This limits
655
	 * pending tasks to only the current minute.
656
	 * @since 4.2.2
657
	 */
658
	public function setInCronShell($inCronShell)
659
	{
660
		$this->_inCronShell = $inCronShell === null ? null : TPropertyValue::ensureBoolean($inCronShell === '' ? true : $inCronShell);
0 ignored issues
show
introduced by
The condition $inCronShell === '' is always false.
Loading history...
661
	}
662
663
	/**
664
	 * @return array additional tasks in a list.
665
	 */
666
	public function getAdditionalCronTasks()
667
	{
668
		return $this->_additionalCronTasks ?? [];
669
	}
670
671
	/**
672
	 * This will take a string that is an array of tasks that has been
673
	 * through serialize(), or json_encode, or is an xml file of additional tasks.
674
	 * If one task is set, then it is automatically placed into an array.
675
	 * @param null|array|string $tasks additional tasks in an array [0..n],  or
676
	 * a single task.
677
	 * @throws TInvalidOperationException if the module has been initialized
678
	 */
679
	public function setAdditionalCronTasks($tasks)
680
	{
681
		if ($this->_initialized) {
682
			throw new TInvalidOperationException('cron_property_unchangeable', 'AdditionalCronTasks');
683
		}
684
		if (is_string($tasks)) {
685
			if (($t = @unserialize($tasks)) !== false) {
686
				$tasks = $t;
687
			} elseif (($t = json_decode($tasks, true)) !== null) {
688
				$tasks = $t;
689
			} else {
690
				$xmldoc = new TXmlDocument('1.0', 'utf-8');
691
				$xmldoc->loadFromString($tasks);
692
				$tasks = $xmldoc;
693
			}
694
		}
695
		if (is_array($tasks) && isset($tasks[self::TASK_KEY])) {
696
			$tasks = [$tasks];
697
		}
698
		if (!is_array($tasks) && !($tasks instanceof TXmlDocument) && $tasks !== null) {
699
			throw new TInvalidDataTypeException('cron_additional_tasks_invalid', $tasks);
700
		}
701
		$this->_additionalCronTasks = $tasks;
702
	}
703
}
704