Passed
Pull Request — master (#817)
by
unknown
05:07
created

TCronModule::registerShellAction()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 2
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 4
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 string the cli class to instance for CLI command line actions; this changes for TDbCronModule */
122
	protected $_shellClass = 'Prado\\Util\\Cron\\TShellCronAction';
123
124
	/** @var array[] any additional tasks to install from properties */
125
	private $_additionalCronTasks;
126
127
	/**
128
	 * Initializes the module.  Read the configuration, installs Shell Actions,
129
	 * should Request cron be activated.
130
	 * @param array|\Prado\Xml\TXmlElement $config
131
	 * @throws TConfigurationException when the user manage doesn't exist or is invalid
132
	 */
133
	public function init($config)
134
	{
135
		$app = $this->getApplication();
136
137
		//Get the IUserManager module from the string
138
		if (is_string($this->_userManager)) {
0 ignored issues
show
introduced by
The condition is_string($this->_userManager) is always false.
Loading history...
139
			if (($users = $app->getModule($this->_userManager)) === null) {
140
				throw new TConfigurationException('cron_usermanager_nonexistent', $this->_userManager);
141
			}
142
			if (!($users instanceof IUserManager)) {
143
				throw new TConfigurationException('cron_usermanager_invalid', $this->_userManager);
144
			}
145
			$this->_userManager = $users;
146
		}
147
148
		// Otherwise manually look for the IUserManager
149
		if ($this->_userManager === null) {
150
			$users = $app->getModulesByType('Prado\\Security\\IUserManager');
151
			foreach ($users as $id => $module) {
152
				$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...
153
				break;
154
			}
155
		}
156
		$this->_tasksInstanced = false;
157
158
		//read config tasks and schedule
159
		$this->readConfiguration($config);
160
161
		//Read additional Config from Property
162
		$this->readConfiguration($this->_additionalCronTasks);
163
		$app->attachEventHandler('onAuthenticationComplete', [$this, 'registerShellAction']);
164
165
		if (php_sapi_name() !== 'cli' && $this->getEnableRequestCron()) {
166
			if (100.0 * ((float) (mt_rand()) / (float) (mt_getrandmax())) <= $this->getRequestCronProbability()) {
167
				$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

167
				$app->attachEventHandler('OnEndRequest', [$this, 'processPendingTasks'], /** @scrutinizer ignore-type */ 20);
Loading history...
168
			}
169
		}
170
		$this->_initialized = true;
171
		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

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

247
	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

247
	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...
248
	{
249
		if ($this->dyRegisterShellAction(false) !== true && ($app = $this->getApplication()) instanceof \Prado\Shell\TShellApplication) {
250
			$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

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

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

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