[TASK] Use strict comparison for strings
[Packages/TYPO3.CMS.git] / typo3 / sysext / scheduler / Classes / Controller / SchedulerModuleController.php
1 <?php
2 namespace TYPO3\CMS\Scheduler\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
20 use TYPO3\CMS\Backend\Template\ModuleTemplate;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Database\ConnectionPool;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Messaging\FlashMessage;
25 use TYPO3\CMS\Core\Page\PageRenderer;
26 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
29 use TYPO3\CMS\Scheduler\Task\AbstractTask;
30
31 /**
32 * Module 'TYPO3 Scheduler administration module' for the 'scheduler' extension.
33 */
34 class SchedulerModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass
35 {
36 /**
37 * Array containing submitted data when editing or adding a task
38 *
39 * @var array
40 */
41 protected $submittedData = [];
42
43 /**
44 * Array containing all messages issued by the application logic
45 * Contains the error's severity and the message itself
46 *
47 * @var array
48 */
49 protected $messages = [];
50
51 /**
52 * @var string Key of the CSH file
53 */
54 protected $cshKey;
55
56 /**
57 * @var \TYPO3\CMS\Scheduler\Scheduler Local scheduler instance
58 */
59 protected $scheduler;
60
61 /**
62 * @var string
63 */
64 protected $backendTemplatePath = '';
65
66 /**
67 * @var \TYPO3\CMS\Fluid\View\StandaloneView
68 */
69 protected $view;
70
71 /**
72 * The name of the module
73 *
74 * @var string
75 */
76 protected $moduleName = 'system_txschedulerM1';
77
78 /**
79 * @var string Base URI of scheduler module
80 */
81 protected $moduleUri;
82
83 /**
84 * ModuleTemplate Container
85 *
86 * @var ModuleTemplate
87 */
88 protected $moduleTemplate;
89
90 /**
91 * @return \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController
92 */
93 public function __construct()
94 {
95 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
96 $this->getLanguageService()->includeLLFile('EXT:scheduler/Resources/Private/Language/locallang.xlf');
97 $this->MCONF = [
98 'name' => $this->moduleName,
99 ];
100 $this->cshKey = '_MOD_' . $this->moduleName;
101 $this->backendTemplatePath = ExtensionManagementUtility::extPath('scheduler') . 'Resources/Private/Templates/Backend/SchedulerModule/';
102 $this->view = GeneralUtility::makeInstance(\TYPO3\CMS\Fluid\View\StandaloneView::class);
103 $this->view->getRequest()->setControllerExtensionName('scheduler');
104 $this->view->setPartialRootPaths([ExtensionManagementUtility::extPath('scheduler') . 'Resources/Private/Partials/Backend/SchedulerModule/']);
105 $this->moduleUri = BackendUtility::getModuleUrl($this->moduleName);
106
107 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
108 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
109 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/SplitButtons');
110 }
111
112 /**
113 * Initializes the backend module
114 *
115 * @return void
116 */
117 public function init()
118 {
119 parent::init();
120
121 // Create scheduler instance
122 $this->scheduler = GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Scheduler::class);
123 }
124
125 /**
126 * Adds items to the ->MOD_MENU array. Used for the function menu selector.
127 *
128 * @return void
129 */
130 public function menuConfig()
131 {
132 $this->MOD_MENU = [
133 'function' => [
134 'scheduler' => $this->getLanguageService()->getLL('function.scheduler'),
135 'check' => $this->getLanguageService()->getLL('function.check'),
136 'info' => $this->getLanguageService()->getLL('function.info')
137 ]
138 ];
139 parent::menuConfig();
140 }
141
142 /**
143 * Main function of the module. Write the content to $this->content
144 *
145 * @return void
146 */
147 public function main()
148 {
149 // Access check!
150 // The page will show only if user has admin rights
151 if ($this->getBackendUser()->isAdmin()) {
152 // Set the form
153 $this->content = '<form name="tx_scheduler_form" id="tx_scheduler_form" method="post" action="">';
154
155 // Prepare main content
156 $this->content .= '<h1>' . $this->getLanguageService()->getLL('function.' . $this->MOD_SETTINGS['function']) . '</h1>';
157 $this->content .= $this->getModuleContent();
158 $this->content .= '</form>';
159 } else {
160 // If no access, only display the module's title
161 $this->content = '<h1>' . $this->getLanguageService()->getLL('title.') . '</h1>';
162 $this->content .='<div style="padding-top: 5px;"></div>';
163 }
164 $this->getButtons();
165 $this->getModuleMenu();
166 }
167
168 /**
169 * Generates the action menu
170 */
171 protected function getModuleMenu()
172 {
173 $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
174 $menu->setIdentifier('SchedulerJumpMenu');
175
176 foreach ($this->MOD_MENU['function'] as $controller => $title) {
177 $item = $menu
178 ->makeMenuItem()
179 ->setHref(
180 BackendUtility::getModuleUrl(
181 $this->moduleName,
182 [
183 'id' => $this->id,
184 'SET' => [
185 'function' => $controller
186 ]
187 ]
188 )
189 )
190 ->setTitle($title);
191 if ($controller === $this->MOD_SETTINGS['function']) {
192 $item->setActive(true);
193 }
194 $menu->addMenuItem($item);
195 }
196 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
197 }
198
199 /**
200 * Generate the module's content
201 *
202 * @return string HTML of the module's main content
203 */
204 protected function getModuleContent()
205 {
206 $content = '';
207 $sectionTitle = '';
208 // Get submitted data
209 $this->submittedData = GeneralUtility::_GPmerged('tx_scheduler');
210 $this->submittedData['uid'] = (int)$this->submittedData['uid'];
211 // If a save command was submitted, handle saving now
212 if ($this->CMD === 'save' || $this->CMD === 'saveclose' || $this->CMD === 'savenew') {
213 $previousCMD = GeneralUtility::_GP('previousCMD');
214 // First check the submitted data
215 $result = $this->preprocessData();
216 // If result is ok, proceed with saving
217 if ($result) {
218 $this->saveTask();
219 if ($this->CMD === 'saveclose') {
220 // Unset command, so that default screen gets displayed
221 unset($this->CMD);
222 } elseif ($this->CMD === 'save') {
223 // After saving a "add form", return to edit
224 $this->CMD = 'edit';
225 } elseif ($this->CMD === 'savenew') {
226 // Unset submitted data, so that empty form gets displayed
227 unset($this->submittedData);
228 // After saving a "add/edit form", return to add
229 $this->CMD = 'add';
230 } else {
231 // Return to edit form
232 $this->CMD = $previousCMD;
233 }
234 } else {
235 $this->CMD = $previousCMD;
236 }
237 }
238
239 // Handle chosen action
240 switch ((string)$this->MOD_SETTINGS['function']) {
241 case 'scheduler':
242 $this->executeTasks();
243
244 switch ($this->CMD) {
245 case 'add':
246 case 'edit':
247 try {
248 // Try adding or editing
249 $content .= $this->editTaskAction();
250 $sectionTitle = $this->getLanguageService()->getLL('action.' . $this->CMD);
251 } catch (\Exception $e) {
252 if ($e->getCode() === 1305100019) {
253 // Invalid controller class name exception
254 $this->addMessage($e->getMessage(), FlashMessage::ERROR);
255 }
256 // An exception may also happen when the task to
257 // edit could not be found. In this case revert
258 // to displaying the list of tasks
259 // It can also happen when attempting to edit a running task
260 $content .= $this->listTasksAction();
261 }
262 break;
263 case 'delete':
264 $this->deleteTask();
265 $content .= $this->listTasksAction();
266 break;
267 case 'stop':
268 $this->stopTask();
269 $content .= $this->listTasksAction();
270 break;
271 case 'toggleHidden':
272 $this->toggleDisableAction();
273 $content .= $this->listTasksAction();
274 break;
275 case 'setNextExecutionTime':
276 $this->setNextExecutionTimeAction();
277 $content .= $this->listTasksAction();
278 break;
279 case 'list':
280
281 default:
282 $content .= $this->listTasksAction();
283 }
284 break;
285
286 // Setup check screen
287 case 'check':
288 // @todo move check to the report module
289 $content .= $this->checkScreenAction();
290 break;
291
292 // Information screen
293 case 'info':
294 $content .= $this->infoScreenAction();
295 break;
296 }
297 // Wrap the content
298 return '<h2>' . $sectionTitle . '</h2><div class="tx_scheduler_mod1">' . $content . '</div>';
299 }
300
301 /**
302 * Injects the request object for the current request or subrequest
303 * Simply calls main() and init() and outputs the content
304 *
305 * @param ServerRequestInterface $request the current request
306 * @param ResponseInterface $response
307 * @return ResponseInterface the response with the content
308 */
309 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
310 {
311 $GLOBALS['SOBE'] = $this;
312 $this->init();
313 $this->main();
314
315 $this->moduleTemplate->setContent($this->content);
316 $response->getBody()->write($this->moduleTemplate->renderContent());
317 return $response;
318 }
319
320 /**
321 * This method displays the result of a number of checks
322 * on whether the Scheduler is ready to run or running properly
323 *
324 * @return string Further information
325 */
326 protected function checkScreenAction()
327 {
328 $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'CheckScreen.html');
329
330 // Display information about last automated run, as stored in the system registry
331 /** @var $registry \TYPO3\CMS\Core\Registry */
332 $registry = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Registry::class);
333 $lastRun = $registry->get('tx_scheduler', 'lastRun');
334 if (!is_array($lastRun)) {
335 $message = $this->getLanguageService()->getLL('msg.noLastRun');
336 $severity = InfoboxViewHelper::STATE_WARNING;
337 } else {
338 if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) {
339 $message = $this->getLanguageService()->getLL('msg.incompleteLastRun');
340 $severity = InfoboxViewHelper::STATE_WARNING;
341 } else {
342 $startDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']);
343 $startTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']);
344 $endDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']);
345 $endTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']);
346 $label = 'automatically';
347 if ($lastRun['type'] === 'manual') {
348 $label = 'manually';
349 }
350 $type = $this->getLanguageService()->getLL('label.' . $label);
351 $message = sprintf($this->getLanguageService()->getLL('msg.lastRun'), $type, $startDate, $startTime, $endDate, $endTime);
352 $severity = InfoboxViewHelper::STATE_INFO;
353 }
354 }
355 $this->view->assign('lastRunMessage', $message);
356 $this->view->assign('lastRunSeverity', $severity);
357
358 // Check if CLI script is executable or not
359 $script = PATH_typo3 . 'cli_dispatch.phpsh';
360 $this->view->assign('script', $script);
361
362 // Skip this check if running Windows, as rights do not work the same way on this platform
363 // (i.e. the script will always appear as *not* executable)
364 if (TYPO3_OS === 'WIN') {
365 $isExecutable = true;
366 } else {
367 $isExecutable = is_executable($script);
368 }
369 if ($isExecutable) {
370 $message = $this->getLanguageService()->getLL('msg.cliScriptExecutable');
371 $severity = InfoboxViewHelper::STATE_OK;
372 } else {
373 $message = $this->getLanguageService()->getLL('msg.cliScriptNotExecutable');
374 $severity = InfoboxViewHelper::STATE_ERROR;
375 }
376 $this->view->assign('isExecutableMessage', $message);
377 $this->view->assign('isExecutableSeverity', $severity);
378 $this->view->assign('now', $this->getServerTime());
379
380 return $this->view->render();
381 }
382
383 /**
384 * This method gathers information about all available task classes and displays it
385 *
386 * @return string html
387 */
388 protected function infoScreenAction()
389 {
390 $registeredClasses = $this->getRegisteredClasses();
391 // No classes available, display information message
392 if (empty($registeredClasses)) {
393 $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'InfoScreenNoClasses.html');
394 return $this->view->render();
395 }
396
397 $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'InfoScreen.html');
398 $this->view->assign('registeredClasses', $registeredClasses);
399
400 return $this->view->render();
401 }
402
403 /**
404 * Renders the task progress bar.
405 *
406 * @param float $progress Task progress
407 * @return string Progress bar markup
408 */
409 protected function renderTaskProgressBar($progress)
410 {
411 $progressText = $this->getLanguageService()->getLL('status.progress') . ':&nbsp;' . $progress . '%';
412 return '<div class="progress">'
413 . '<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="' . $progress . '" aria-valuemin="0" aria-valuemax="100" style="width: ' . $progress . '%;">' . $progressText . '</div>'
414 . '</div>';
415 }
416
417 /**
418 * Delete a task from the execution queue
419 *
420 * @return void
421 */
422 protected function deleteTask()
423 {
424 try {
425 // Try to fetch the task and delete it
426 $task = $this->scheduler->fetchTask($this->submittedData['uid']);
427 // If the task is currently running, it may not be deleted
428 if ($task->isExecutionRunning()) {
429 $this->addMessage($this->getLanguageService()->getLL('msg.maynotDeleteRunningTask'), FlashMessage::ERROR);
430 } else {
431 if ($this->scheduler->removeTask($task)) {
432 $this->getBackendUser()->writelog(4, 0, 0, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was deleted', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
433 $this->addMessage($this->getLanguageService()->getLL('msg.deleteSuccess'));
434 } else {
435 $this->addMessage($this->getLanguageService()->getLL('msg.deleteError'), FlashMessage::ERROR);
436 }
437 }
438 } catch (\UnexpectedValueException $e) {
439 // The task could not be unserialized properly, simply delete the database record
440 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_scheduler_task');
441 $result = $queryBuilder->delete('tx_scheduler_task')
442 ->where(
443 $queryBuilder->expr()->eq(
444 'uid',
445 $queryBuilder->createNamedParameter($this->submittedData['uid'], \PDO::PARAM_INT)
446 )
447 )
448 ->execute();
449
450 if ($result) {
451 $this->addMessage($this->getLanguageService()->getLL('msg.deleteSuccess'));
452 } else {
453 $this->addMessage($this->getLanguageService()->getLL('msg.deleteError'), FlashMessage::ERROR);
454 }
455 } catch (\OutOfBoundsException $e) {
456 // The task was not found, for some reason
457 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
458 }
459 }
460
461 /**
462 * Clears the registered running executions from the task
463 * Note that this doesn't actually stop the running script. It just unmarks
464 * all executions.
465 * @todo find a way to really kill the running task
466 *
467 * @return void
468 */
469 protected function stopTask()
470 {
471 try {
472 // Try to fetch the task and stop it
473 $task = $this->scheduler->fetchTask($this->submittedData['uid']);
474 if ($task->isExecutionRunning()) {
475 // If the task is indeed currently running, clear marked executions
476 $result = $task->unmarkAllExecutions();
477 if ($result) {
478 $this->addMessage($this->getLanguageService()->getLL('msg.stopSuccess'));
479 } else {
480 $this->addMessage($this->getLanguageService()->getLL('msg.stopError'), FlashMessage::ERROR);
481 }
482 } else {
483 // The task is not running, nothing to unmark
484 $this->addMessage($this->getLanguageService()->getLL('msg.maynotStopNonRunningTask'), FlashMessage::WARNING);
485 }
486 } catch (\Exception $e) {
487 // The task was not found, for some reason
488 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
489 }
490 }
491
492 /**
493 * Toggles the disabled state of the submitted task
494 *
495 * @return void
496 */
497 protected function toggleDisableAction()
498 {
499 $task = $this->scheduler->fetchTask($this->submittedData['uid']);
500 $task->setDisabled(!$task->isDisabled());
501 // If a disabled single task is enabled again, we register it for a
502 // single execution at next scheduler run.
503 if ($task->getType() === AbstractTask::TYPE_SINGLE) {
504 $task->registerSingleExecution(time());
505 }
506 $task->save();
507 }
508
509 /**
510 * Sets the next execution time of the submitted task to now
511 *
512 * @return void
513 */
514 protected function setNextExecutionTimeAction()
515 {
516 $task = $this->scheduler->fetchTask($this->submittedData['uid']);
517 $task->setRunOnNextCronJob(true);
518 $task->save();
519 }
520
521 /**
522 * Return a form to add a new task or edit an existing one
523 *
524 * @return string HTML form to add or edit a task
525 */
526 protected function editTaskAction()
527 {
528 $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'EditTask.html');
529
530 $registeredClasses = $this->getRegisteredClasses();
531 $registeredTaskGroups = $this->getRegisteredTaskGroups();
532
533 $taskInfo = [];
534 $task = null;
535 $process = 'edit';
536
537 if ($this->submittedData['uid'] > 0) {
538 // If editing, retrieve data for existing task
539 try {
540 $taskRecord = $this->scheduler->fetchTaskRecord($this->submittedData['uid']);
541 // If there's a registered execution, the task should not be edited
542 if (!empty($taskRecord['serialized_executions'])) {
543 $this->addMessage($this->getLanguageService()->getLL('msg.maynotEditRunningTask'), FlashMessage::ERROR);
544 throw new \LogicException('Runnings tasks cannot not be edited', 1251232849);
545 }
546
547 // Get the task object
548 /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask */
549 $task = unserialize($taskRecord['serialized_task_object']);
550
551 // Set some task information
552 $taskInfo['disable'] = $taskRecord['disable'];
553 $taskInfo['description'] = $taskRecord['description'];
554 $taskInfo['task_group'] = $taskRecord['task_group'];
555
556 // Check that the task object is valid
557 if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
558 // The task object is valid, process with fetching current data
559 $taskInfo['class'] = get_class($task);
560 // Get execution information
561 $taskInfo['start'] = (int)$task->getExecution()->getStart();
562 $taskInfo['end'] = (int)$task->getExecution()->getEnd();
563 $taskInfo['interval'] = $task->getExecution()->getInterval();
564 $taskInfo['croncmd'] = $task->getExecution()->getCronCmd();
565 $taskInfo['multiple'] = $task->getExecution()->getMultiple();
566 if (!empty($taskInfo['interval']) || !empty($taskInfo['croncmd'])) {
567 // Guess task type from the existing information
568 // If an interval or a cron command is defined, it's a recurring task
569 $taskInfo['type'] = AbstractTask::TYPE_RECURRING;
570 $taskInfo['frequency'] = $taskInfo['interval'] ?: $taskInfo['croncmd'];
571 } else {
572 // It's not a recurring task
573 // Make sure interval and cron command are both empty
574 $taskInfo['type'] = AbstractTask::TYPE_SINGLE;
575 $taskInfo['frequency'] = '';
576 $taskInfo['end'] = 0;
577 }
578 } else {
579 // The task object is not valid
580 // Issue error message
581 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.invalidTaskClassEdit'), get_class($task)), FlashMessage::ERROR);
582 // Initialize empty values
583 $taskInfo['start'] = 0;
584 $taskInfo['end'] = 0;
585 $taskInfo['frequency'] = '';
586 $taskInfo['multiple'] = false;
587 $taskInfo['type'] = AbstractTask::TYPE_SINGLE;
588 }
589 } catch (\OutOfBoundsException $e) {
590 // Add a message and continue throwing the exception
591 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
592 throw $e;
593 }
594 } else {
595 // If adding a new object, set some default values
596 $taskInfo['class'] = key($registeredClasses);
597 $taskInfo['type'] = AbstractTask::TYPE_RECURRING;
598 $taskInfo['start'] = $GLOBALS['EXEC_TIME'];
599 $taskInfo['end'] = '';
600 $taskInfo['frequency'] = '';
601 $taskInfo['multiple'] = 0;
602 $process = 'add';
603 }
604
605 // If some data was already submitted, use it to override
606 // existing data
607 if (!empty($this->submittedData)) {
608 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($taskInfo, $this->submittedData);
609 }
610
611 // Get the extra fields to display for each task that needs some
612 $allAdditionalFields = [];
613 if ($process === 'add') {
614 foreach ($registeredClasses as $class => $registrationInfo) {
615 if (!empty($registrationInfo['provider'])) {
616 /** @var $providerObject \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface */
617 $providerObject = GeneralUtility::getUserObj($registrationInfo['provider']);
618 if ($providerObject instanceof \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface) {
619 $additionalFields = $providerObject->getAdditionalFields($taskInfo, null, $this);
620 $allAdditionalFields = array_merge($allAdditionalFields, [$class => $additionalFields]);
621 }
622 }
623 }
624 } else {
625 if (!empty($registeredClasses[$taskInfo['class']]['provider'])) {
626 $providerObject = GeneralUtility::getUserObj($registeredClasses[$taskInfo['class']]['provider']);
627 if ($providerObject instanceof \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface) {
628 $allAdditionalFields[$taskInfo['class']] = $providerObject->getAdditionalFields($taskInfo, $task, $this);
629 }
630 }
631 }
632
633 // Load necessary JavaScript
634 $this->getPageRenderer()->loadJquery();
635 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
636 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/DateTimePicker');
637
638 // Start rendering the add/edit form
639 $this->view->assign('uid', htmlspecialchars($this->submittedData['uid']));
640 $this->view->assign('cmd', htmlspecialchars($this->CMD));
641
642 $table = [];
643
644 // Disable checkbox
645 $label = '<label>' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable') . '</label>';
646 $table[] =
647 '<div class="form-section" id="task_disable_row"><div class="form-group">'
648 . BackendUtility::wrapInHelp($this->cshKey, 'task_disable', $label)
649 . '<div class="form-control-wrap">'
650 . '<input type="hidden" name="tx_scheduler[disable]" value="0">'
651 . '<input class="checkbox" type="checkbox" name="tx_scheduler[disable]" value="1" id="task_disable" ' . ($taskInfo['disable'] ? ' checked="checked"' : '') . '>'
652 . '</div>'
653 . '</div></div>';
654
655 // Task class selector
656 $label = '<label>' . $this->getLanguageService()->getLL('label.class') . '</label>';
657
658 // On editing, don't allow changing of the task class, unless it was not valid
659 if ($this->submittedData['uid'] > 0 && !empty($taskInfo['class'])) {
660 $cell = '<div>' . $registeredClasses[$taskInfo['class']]['title'] . ' (' . $registeredClasses[$taskInfo['class']]['extension'] . ')</div>';
661 $cell .= '<input type="hidden" name="tx_scheduler[class]" id="task_class" value="' . htmlspecialchars($taskInfo['class']) . '">';
662 } else {
663 $cell = '<select name="tx_scheduler[class]" id="task_class" class="form-control">';
664 // Group registered classes by classname
665 $groupedClasses = [];
666 foreach ($registeredClasses as $class => $classInfo) {
667 $groupedClasses[$classInfo['extension']][$class] = $classInfo;
668 }
669 ksort($groupedClasses);
670 // Loop on all grouped classes to display a selector
671 foreach ($groupedClasses as $extension => $class) {
672 $cell .= '<optgroup label="' . htmlspecialchars($extension) . '">';
673 foreach ($groupedClasses[$extension] as $class => $classInfo) {
674 $selected = $class == $taskInfo['class'] ? ' selected="selected"' : '';
675 $cell .= '<option value="' . htmlspecialchars($class) . '"' . 'title="' . htmlspecialchars($classInfo['description']) . '" ' . $selected . '>' . htmlspecialchars($classInfo['title']) . '</option>';
676 }
677 $cell .= '</optgroup>';
678 }
679 $cell .= '</select>';
680 }
681 $table[] =
682 '<div class="form-section" id="task_class_row"><div class="form-group">'
683 . BackendUtility::wrapInHelp($this->cshKey, 'task_class', $label)
684 . '<div class="form-control-wrap">'
685 . $cell
686 . '</div>'
687 . '</div></div>';
688
689 // Task type selector
690 $label = '<label>' . $this->getLanguageService()->getLL('label.type') . '</label>';
691 $table[] =
692 '<div class="form-section" id="task_type_row"><div class="form-group">'
693 . BackendUtility::wrapInHelp($this->cshKey, 'task_type', $label)
694 . '<div class="form-control-wrap">'
695 . '<select name="tx_scheduler[type]" id="task_type" class="form-control">'
696 . '<option value="1" ' . ((int)$taskInfo['type'] === AbstractTask::TYPE_SINGLE ? ' selected="selected"' : '') . '>' . $this->getLanguageService()->getLL('label.type.single') . '</option>'
697 . '<option value="2" ' . ((int)$taskInfo['type'] === AbstractTask::TYPE_RECURRING ? ' selected="selected"' : '') . '>' . $this->getLanguageService()->getLL('label.type.recurring') . '</option>'
698 . '</select>'
699 . '</div>'
700 . '</div></div>';
701
702 // Task group selector
703 $label = '<label>' . $this->getLanguageService()->getLL('label.group') . '</label>';
704 $cell = '<select name="tx_scheduler[task_group]" id="task_class" class="form-control">';
705
706 // Loop on all groups to display a selector
707 $cell .= '<option value="0" title=""></option>';
708 foreach ($registeredTaskGroups as $taskGroup) {
709 $selected = $taskGroup['uid'] == $taskInfo['task_group'] ? ' selected="selected"' : '';
710 $cell .= '<option value="' . $taskGroup['uid'] . '"' . 'title="';
711 $cell .= htmlspecialchars($taskGroup['groupName']) . '"' . $selected . '>';
712 $cell .= htmlspecialchars($taskGroup['groupName']) . '</option>';
713 }
714 $cell .= '</select>';
715
716 $table[] =
717 '<div class="form-section" id="task_group_row"><div class="form-group">'
718 . BackendUtility::wrapInHelp($this->cshKey, 'task_group', $label)
719 . '<div class="form-control-wrap">'
720 . $cell
721 . '</div>'
722 . '</div></div>';
723
724 $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '%H:%M %m-%d-%Y' : '%H:%M %d-%m-%Y';
725
726 $label = '<label>' . BackendUtility::wrapInHelp($this->cshKey, 'task_start', $this->getLanguageService()->getLL('label.start')) . '</label>';
727 $value = ($taskInfo['start'] > 0 ? strftime($dateFormat, $taskInfo['start']) : '');
728 $table[] =
729 '<div class="form-section"><div class="row"><div class="form-group col-sm-6" id="task_start_col">'
730 . $label
731 . '<div class="form-control-wrap">'
732 . '<div class="input-group" id="tceforms-datetimefield-task_start_row-wrapper">'
733 . '<input name="tx_scheduler[start]_hr" value="' . $value . '" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="datetime" data-date-offset="0" type="text" id="tceforms-datetimefield-task_start_row">'
734 . '<input name="tx_scheduler[start]" value="' . $taskInfo['start'] . '" type="hidden">'
735 . '<span class="input-group-btn"><label class="btn btn-default" for="tceforms-datetimefield-task_start_row"><span class="fa fa-calendar"></span></label></span>'
736 . '</div>'
737 . '</div>'
738 . '</div>';
739
740 // End date/time field
741 // NOTE: datetime fields need a special id naming scheme
742 $value = ($taskInfo['end'] > 0 ? strftime($dateFormat, $taskInfo['end']) : '');
743 $label = '<label>' . $this->getLanguageService()->getLL('label.end') . '</label>';
744 $table[] =
745 '<div class="form-group col-sm-6" id="task_end_col">'
746 . BackendUtility::wrapInHelp($this->cshKey, 'task_end', $label)
747 . '<div class="form-control-wrap">'
748 . '<div class="input-group" id="tceforms-datetimefield-task_end_row-wrapper">'
749 . '<input name="tx_scheduler[end]_hr" value="' . $value . '" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="datetime" data-date-offset="0" type="text" id="tceforms-datetimefield-task_end_row">'
750 . '<input name="tx_scheduler[end]" value="' . $taskInfo['end'] . '" type="hidden">'
751 . '<span class="input-group-btn"><label class="btn btn-default" for="tceforms-datetimefield-task_end_row"><span class="fa fa-calendar"></span></label></span>'
752 . '</div>'
753 . '</div>'
754 . '</div></div></div>';
755
756 // Frequency input field
757 $label = '<label>' . $this->getLanguageService()->getLL('label.frequency.long') . '</label>';
758 $table[] =
759 '<div class="form-section" id="task_frequency_row"><div class="form-group">'
760 . BackendUtility::wrapInHelp($this->cshKey, 'task_frequency', $label)
761 . '<div class="form-control-wrap">'
762 . '<input type="text" name="tx_scheduler[frequency]" class="form-control" id="task_frequency" value="' . htmlspecialchars($taskInfo['frequency']) . '">'
763 . '</div>'
764 . '</div></div>';
765
766 // Multiple execution selector
767 $label = '<label>' . $this->getLanguageService()->getLL('label.parallel.long') . '</label>';
768 $table[] =
769 '<div class="form-section" id="task_multiple_row"><div class="form-group">'
770 . BackendUtility::wrapInHelp($this->cshKey, 'task_multiple', $label)
771 . '<div class="form-control-wrap">'
772 . '<input type="hidden" name="tx_scheduler[multiple]" value="0">'
773 . '<input class="checkbox" type="checkbox" name="tx_scheduler[multiple]" value="1" id="task_multiple" ' . ($taskInfo['multiple'] ? 'checked="checked"' : '') . '>'
774 . '</div>'
775 . '</div></div>';
776
777 // Description
778 $label = '<label>' . $this->getLanguageService()->getLL('label.description') . '</label>';
779 $table[] =
780 '<div class="form-section" id="task_description_row"><div class="form-group">'
781 . BackendUtility::wrapInHelp($this->cshKey, 'task_description', $label)
782 . '<div class="form-control-wrap">'
783 . '<textarea class="form-control" name="tx_scheduler[description]">' . htmlspecialchars($taskInfo['description']) . '</textarea>'
784 . '</div>'
785 . '</div></div>';
786
787 // Display additional fields
788 foreach ($allAdditionalFields as $class => $fields) {
789 if ($class == $taskInfo['class']) {
790 $additionalFieldsStyle = '';
791 } else {
792 $additionalFieldsStyle = ' style="display: none"';
793 }
794 // Add each field to the display, if there are indeed any
795 if (isset($fields) && is_array($fields)) {
796 foreach ($fields as $fieldID => $fieldInfo) {
797 $label = '<label>' . $this->getLanguageService()->sL($fieldInfo['label']) . '</label>';
798 $htmlClassName = strtolower(str_replace('\\', '-', $class));
799
800 $table[] =
801 '<div class="form-section extraFields extra_fields_' . $htmlClassName . '" ' . $additionalFieldsStyle . ' id="' . $fieldID . '_row"><div class="form-group">'
802 . BackendUtility::wrapInHelp($fieldInfo['cshKey'], $fieldInfo['cshLabel'], $label)
803 . '<div class="form-control-wrap">' . $fieldInfo['code'] . '</div>'
804 . '</div></div>';
805 }
806 }
807 }
808
809 $this->view->assign('table', implode(LF, $table));
810 $this->view->assign('now', $this->getServerTime());
811
812 return $this->view->render();
813 }
814
815 /**
816 * Execute all selected tasks
817 *
818 * @return void
819 */
820 protected function executeTasks()
821 {
822 // Make sure next automatic scheduler-run is scheduled
823 if (GeneralUtility::_POST('go') !== null) {
824 $this->scheduler->scheduleNextSchedulerRunUsingAtDaemon();
825 }
826 // Continue if some elements have been chosen for execution
827 if (isset($this->submittedData['execute']) && !empty($this->submittedData['execute'])) {
828 // Get list of registered classes
829 $registeredClasses = $this->getRegisteredClasses();
830 // Loop on all selected tasks
831 foreach ($this->submittedData['execute'] as $uid) {
832 try {
833 // Try fetching the task
834 $task = $this->scheduler->fetchTask($uid);
835 $class = get_class($task);
836 $name = $registeredClasses[$class]['title'] . ' (' . $registeredClasses[$class]['extension'] . ')';
837 if (GeneralUtility::_POST('go_cron') !== null) {
838 $task->setRunOnNextCronJob(true);
839 $task->save();
840 } else {
841 // Now try to execute it and report on outcome
842 try {
843 $result = $this->scheduler->executeTask($task);
844 if ($result) {
845 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.executed'), $name));
846 } else {
847 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.notExecuted'), $name), FlashMessage::ERROR);
848 }
849 } catch (\Exception $e) {
850 // An exception was thrown, display its message as an error
851 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.executionFailed'), $name, $e->getMessage()), FlashMessage::ERROR);
852 }
853 }
854 } catch (\OutOfBoundsException $e) {
855 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $uid), FlashMessage::ERROR);
856 } catch (\UnexpectedValueException $e) {
857 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.executionFailed'), $uid, $e->getMessage()), FlashMessage::ERROR);
858 }
859 }
860 // Record the run in the system registry
861 $this->scheduler->recordLastRun('manual');
862 // Make sure to switch to list view after execution
863 $this->CMD = 'list';
864 }
865 }
866
867 /**
868 * Assemble display of list of scheduled tasks
869 *
870 * @return string Table of pending tasks
871 */
872 protected function listTasksAction()
873 {
874 $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'ListTasks.html');
875
876 // Define display format for dates
877 $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
878
879 // Get list of registered task groups
880 $registeredTaskGroups = $this->getRegisteredTaskGroups();
881
882 // add an empty entry for non-grouped tasks
883 // add in front of list
884 array_unshift($registeredTaskGroups, ['uid' => 0, 'groupName' => '']);
885
886 // Get all registered tasks
887 // Just to get the number of entries
888 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
889 ->getQueryBuilderForTable('tx_scheduler_task');
890 $queryBuilder->getRestrictions()->removeAll();
891
892 $result = $queryBuilder->select('t.*')
893 ->addSelect(
894 'g.groupName AS taskGroupName',
895 'g.description AS taskGroupDescription',
896 'g.deleted AS isTaskGroupDeleted'
897 )
898 ->from('tx_scheduler_task', 't')
899 ->leftJoin(
900 't',
901 'tx_scheduler_task_group',
902 'g',
903 $queryBuilder->expr()->eq('t.task_group', $queryBuilder->quoteIdentifier('g.uid'))
904 )
905 ->orderBy('g.sorting')
906 ->execute();
907
908 // Loop on all tasks
909 $temporaryResult = [];
910 while ($row = $result->fetch()) {
911 if ($row['taskGroupName'] === null || $row['isTaskGroupDeleted'] === '1') {
912 $row['taskGroupName'] = '';
913 $row['taskGroupDescription'] = '';
914 $row['task_group'] = 0;
915 }
916 $temporaryResult[$row['task_group']]['groupName'] = $row['taskGroupName'];
917 $temporaryResult[$row['task_group']]['groupDescription'] = $row['taskGroupDescription'];
918 $temporaryResult[$row['task_group']]['tasks'][] = $row;
919 }
920
921 // No tasks defined, display information message
922 if (empty($temporaryResult)) {
923 $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'ListTasksNoTasks.html');
924 return $this->view->render();
925 }
926
927 $this->getPageRenderer()->loadJquery();
928 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
929 $table = [];
930 // Header row
931 $table[] =
932 '<thead><tr>'
933 . '<th><a class="btn btn-default" href="#" id="checkall" title="' . htmlspecialchars($this->getLanguageService()->getLL('label.checkAll')) . '" class="icon">' . $this->moduleTemplate->getIconFactory()->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a></th>'
934 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.id')) . '</th>'
935 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('task')) . '</th>'
936 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.type')) . '</th>'
937 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.frequency')) . '</th>'
938 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.parallel')) . '</th>'
939 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.lastExecution')) . '</th>'
940 . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.nextExecution')) . '</th>'
941 . '<th></th>'
942 . '</tr></thead>';
943
944 $registeredClasses = $this->getRegisteredClasses();
945 foreach ($temporaryResult as $taskGroup) {
946 if (!empty($taskGroup['groupName'])) {
947 $groupText = '<strong>' . htmlspecialchars($taskGroup['groupName']) . '</strong>';
948 if (!empty($taskGroup['groupDescription'])) {
949 $groupText .= '<br>' . nl2br(htmlspecialchars($taskGroup['groupDescription']));
950 }
951 $table[] = '<tr><td colspan="9">' . $groupText . '</td></tr>';
952 }
953
954 foreach ($taskGroup['tasks'] as $schedulerRecord) {
955 // Define action icons
956 $link = htmlspecialchars($this->moduleUri . '&CMD=edit&tx_scheduler[uid]=' . $schedulerRecord['uid']);
957 $editAction = '<a class="btn btn-default" href="' . $link . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:edit')) . '" class="icon">' .
958 $this->moduleTemplate->getIconFactory()->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
959 if ((int)$schedulerRecord['disable'] === 1) {
960 $translationKey = 'enable';
961 $icon = $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-unhide', Icon::SIZE_SMALL);
962 } else {
963 $translationKey = 'disable';
964 $icon = $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-hide', Icon::SIZE_SMALL);
965 }
966 $toggleHiddenAction = '<a class="btn btn-default" href="' . htmlspecialchars($this->moduleUri
967 . '&CMD=toggleHidden&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" title="'
968 . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:' . $translationKey))
969 . '" class="icon">' . $icon->render() . '</a>';
970 $deleteAction = '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars($this->moduleUri . '&CMD=delete&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" '
971 . ' data-severity="warning"'
972 . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')) . '"'
973 . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
974 . ' data-content="' . htmlspecialchars($this->getLanguageService()->getLL('msg.delete')) . '"'
975 . ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')) . '" class="icon">' .
976 $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
977 $stopAction = '<a class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars($this->moduleUri . '&CMD=stop&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" '
978 . ' data-severity="warning"'
979 . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')) . '"'
980 . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
981 . ' data-content="' . htmlspecialchars($this->getLanguageService()->getLL('msg.stop')) . '"'
982 . ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')) . '" class="icon">' .
983 $this->moduleTemplate->getIconFactory()->getIcon('actions-document-close', Icon::SIZE_SMALL)->render() . '</a>';
984 $runAction = '<a class="btn btn-default" href="' . htmlspecialchars($this->moduleUri . '&tx_scheduler[execute][]=' . $schedulerRecord['uid']) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('action.run_task')) . '" class="icon">' .
985 $this->moduleTemplate->getIconFactory()->getIcon('extensions-scheduler-run-task', Icon::SIZE_SMALL)->render() . '</a>';
986 $runCronAction = '<a class="btn btn-default" href="' . htmlspecialchars($this->moduleUri . '&CMD=setNextExecutionTime&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" title="' . $this->getLanguageService()->getLL('action.run_task_cron', true) . '" class="icon">' .
987 $this->moduleTemplate->getIconFactory()->getIcon('extensions-scheduler-run-task-cron', Icon::SIZE_SMALL)->render() . '</a>';
988
989 // Define some default values
990 $lastExecution = '-';
991 $isRunning = false;
992 $showAsDisabled = false;
993 $startExecutionElement = '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
994 // Restore the serialized task and pass it a reference to the scheduler object
995 /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask|\TYPO3\CMS\Scheduler\ProgressProviderInterface */
996 $task = unserialize($schedulerRecord['serialized_task_object']);
997 $class = get_class($task);
998 if ($class === '__PHP_Incomplete_Class' && preg_match('/^O:[0-9]+:"(?P<classname>.+?)"/', $schedulerRecord['serialized_task_object'], $matches) === 1) {
999 $class = $matches['classname'];
1000 }
1001 // Assemble information about last execution
1002 if (!empty($schedulerRecord['lastexecution_time'])) {
1003 $lastExecution = date($dateFormat, $schedulerRecord['lastexecution_time']);
1004 if ($schedulerRecord['lastexecution_context'] === 'CLI') {
1005 $context = $this->getLanguageService()->getLL('label.cron');
1006 } else {
1007 $context = $this->getLanguageService()->getLL('label.manual');
1008 }
1009 $lastExecution .= ' (' . $context . ')';
1010 }
1011
1012 if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
1013 // The task object is valid
1014 $labels = [];
1015 $name = htmlspecialchars($registeredClasses[$class]['title'] . ' (' . $registeredClasses[$class]['extension'] . ')');
1016 $additionalInformation = $task->getAdditionalInformation();
1017 if ($task instanceof \TYPO3\CMS\Scheduler\ProgressProviderInterface) {
1018 $progress = round((float)$task->getProgress(), 2);
1019 $name .= $this->renderTaskProgressBar($progress);
1020 }
1021 if (!empty($additionalInformation)) {
1022 $name .= '<div class="additional-information">' . nl2br(htmlspecialchars($additionalInformation)) . '</div>';
1023 }
1024 // Check if task currently has a running execution
1025 if (!empty($schedulerRecord['serialized_executions'])) {
1026 $labels[] = [
1027 'class' => 'success',
1028 'text' => $this->getLanguageService()->getLL('status.running')
1029 ];
1030 $isRunning = true;
1031 }
1032
1033 // Prepare display of next execution date
1034 // If task is currently running, date is not displayed (as next hasn't been calculated yet)
1035 // Also hide the date if task is disabled (the information doesn't make sense, as it will not run anyway)
1036 if ($isRunning || $schedulerRecord['disable']) {
1037 $nextDate = '-';
1038 } else {
1039 $nextDate = date($dateFormat, $schedulerRecord['nextexecution']);
1040 if (empty($schedulerRecord['nextexecution'])) {
1041 $nextDate = $this->getLanguageService()->getLL('none');
1042 } elseif ($schedulerRecord['nextexecution'] < $GLOBALS['EXEC_TIME']) {
1043 $labels[] = [
1044 'class' => 'warning',
1045 'text' => $this->getLanguageService()->getLL('status.late'),
1046 'description' => $this->getLanguageService()->getLL('status.legend.scheduled')
1047 ];
1048 }
1049 }
1050 // Get execution type
1051 if ($task->getType() === AbstractTask::TYPE_SINGLE) {
1052 $execType = $this->getLanguageService()->getLL('label.type.single');
1053 $frequency = '-';
1054 } else {
1055 $execType = $this->getLanguageService()->getLL('label.type.recurring');
1056 if ($task->getExecution()->getCronCmd() == '') {
1057 $frequency = $task->getExecution()->getInterval();
1058 } else {
1059 $frequency = $task->getExecution()->getCronCmd();
1060 }
1061 }
1062 // Get multiple executions setting
1063 if ($task->getExecution()->getMultiple()) {
1064 $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes');
1065 } else {
1066 $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:no');
1067 }
1068 // Define checkbox
1069 $startExecutionElement = '<label class="btn btn-default btn-checkbox"><input type="checkbox" name="tx_scheduler[execute][]" value="' . $schedulerRecord['uid'] . '" id="task_' . $schedulerRecord['uid'] . '"><span class="t3-icon fa"></span></label>';
1070
1071 $actions = '<div class="btn-group" role="group">' . $editAction . $toggleHiddenAction . $deleteAction . '</div>';
1072
1073 // Check the disable status
1074 // Row is shown dimmed if task is disabled, unless it is still running
1075 if ($schedulerRecord['disable'] && !$isRunning) {
1076 $labels[] = [
1077 'class' => 'default',
1078 'text' => $this->getLanguageService()->getLL('status.disabled')
1079 ];
1080 $showAsDisabled = true;
1081 }
1082
1083 // Show no action links (edit, delete) if task is running
1084 if ($isRunning) {
1085 $actions = '<div class="btn-group" role="group">' . $stopAction . '</div>';
1086 } else {
1087 $actions .= '&nbsp;<div class="btn-group" role="group">' . $runAction . $runCronAction . '</div>';
1088 }
1089
1090 // Check if the last run failed
1091 if (!empty($schedulerRecord['lastexecution_failure'])) {
1092 // Try to get the stored exception array
1093 /** @var $exceptionArray array */
1094 $exceptionArray = @unserialize($schedulerRecord['lastexecution_failure']);
1095 // If the exception could not be unserialized, issue a default error message
1096 if (!is_array($exceptionArray) || empty($exceptionArray)) {
1097 $labelDescription = $this->getLanguageService()->getLL('msg.executionFailureDefault');
1098 } else {
1099 $labelDescription = sprintf($this->getLanguageService()->getLL('msg.executionFailureReport'), $exceptionArray['code'], $exceptionArray['message']);
1100 }
1101 $labels[] = [
1102 'class' => 'danger',
1103 'text' => $this->getLanguageService()->getLL('status.failure'),
1104 'description' => $labelDescription
1105 ];
1106 }
1107 // Format the execution status,
1108 // including failure feedback, if any
1109 $taskDesc = '';
1110 if ($schedulerRecord['description'] !== '') {
1111 $taskDesc = '<span class="description">' . nl2br(htmlspecialchars($schedulerRecord['description'])) . '</span>';
1112 }
1113 $taskName = '<span class="name"><a href="' . $link . '">' . $name . '</a></span>';
1114
1115 $table[] =
1116 '<tr class="' . ($showAsDisabled ? 'disabled' : '') . '">'
1117 . '<td>' . $startExecutionElement . '</td>'
1118 . '<td class="right">' . $schedulerRecord['uid'] . '</td>'
1119 . '<td>' . $this->makeStatusLabel($labels) . $taskName . $taskDesc . '</td>'
1120 . '<td>' . $execType . '</td>'
1121 . '<td>' . $frequency . '</td>'
1122 . '<td>' . $multiple . '</td>'
1123 . '<td>' . $lastExecution . '</td>'
1124 . '<td>' . $nextDate . '</td>'
1125 . '<td nowrap="nowrap">' . $actions . '</td>'
1126 . '</tr>';
1127 } else {
1128 // The task object is not valid
1129 // Prepare to issue an error
1130 $executionStatusOutput = '<span class="label label-danger">'
1131 . htmlspecialchars(sprintf(
1132 $this->getLanguageService()->getLL('msg.invalidTaskClass'),
1133 $class
1134 ))
1135 . '</span>';
1136 $table[] =
1137 '<tr>'
1138 . '<td>' . $startExecutionElement . '</td>'
1139 . '<td class="right">' . $schedulerRecord['uid'] . '</td>'
1140 . '<td colspan="6">' . $executionStatusOutput . '</td>'
1141 . '<td nowrap="nowrap"><div class="btn-group" role="group">'
1142 . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
1143 . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
1144 . $deleteAction
1145 . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
1146 . '</div></td>'
1147 . '</tr>';
1148 }
1149 }
1150 }
1151
1152 $this->view->assign('table', '<table class="table table-striped table-hover">' . implode(LF, $table) . '</table>');
1153 $this->view->assign('now', $this->getServerTime());
1154
1155 return $this->view->render();
1156 }
1157
1158 /**
1159 * Generates bootstrap labels containing the label statuses
1160 *
1161 * @param array $labels
1162 * @return string
1163 */
1164 protected function makeStatusLabel(array $labels)
1165 {
1166 $htmlLabels = [];
1167 foreach ($labels as $label) {
1168 if (empty($label['text'])) {
1169 continue;
1170 }
1171 $htmlLabels[] = '<span class="label label-' . htmlspecialchars($label['class']) . ' pull-right" title="' . htmlspecialchars($label['description']) . '">' . htmlspecialchars($label['text']) . '</span>';
1172 }
1173
1174 return implode('&nbsp;', $htmlLabels);
1175 }
1176
1177 /**
1178 * Saves a task specified in the backend form to the database
1179 *
1180 * @return void
1181 */
1182 protected function saveTask()
1183 {
1184 // If a task is being edited fetch old task data
1185 if (!empty($this->submittedData['uid'])) {
1186 try {
1187 $taskRecord = $this->scheduler->fetchTaskRecord($this->submittedData['uid']);
1188 /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask */
1189 $task = unserialize($taskRecord['serialized_task_object']);
1190 } catch (\OutOfBoundsException $e) {
1191 // If the task could not be fetched, issue an error message
1192 // and exit early
1193 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
1194 return;
1195 }
1196 // Register single execution
1197 if ((int)$this->submittedData['type'] === AbstractTask::TYPE_SINGLE) {
1198 $task->registerSingleExecution($this->submittedData['start']);
1199 } else {
1200 if (!empty($this->submittedData['croncmd'])) {
1201 // Definition by cron-like syntax
1202 $interval = 0;
1203 $cronCmd = $this->submittedData['croncmd'];
1204 } else {
1205 // Definition by interval
1206 $interval = $this->submittedData['interval'];
1207 $cronCmd = '';
1208 }
1209 // Register recurring execution
1210 $task->registerRecurringExecution($this->submittedData['start'], $interval, $this->submittedData['end'], $this->submittedData['multiple'], $cronCmd);
1211 }
1212 // Set disable flag
1213 $task->setDisabled($this->submittedData['disable']);
1214 // Set description
1215 $task->setDescription($this->submittedData['description']);
1216 // Set task group
1217 $task->setTaskGroup($this->submittedData['task_group']);
1218 // Save additional input values
1219 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1220 /** @var $providerObject \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface */
1221 $providerObject = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1222 if ($providerObject instanceof \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface) {
1223 $providerObject->saveAdditionalFields($this->submittedData, $task);
1224 }
1225 }
1226 // Save to database
1227 $result = $this->scheduler->saveTask($task);
1228 if ($result) {
1229 $this->getBackendUser()->writelog(4, 0, 0, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was updated', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
1230 $this->addMessage($this->getLanguageService()->getLL('msg.updateSuccess'));
1231 } else {
1232 $this->addMessage($this->getLanguageService()->getLL('msg.updateError'), FlashMessage::ERROR);
1233 }
1234 } else {
1235 // A new task is being created
1236 // Create an instance of chosen class
1237 /** @var $task AbstractTask */
1238 $task = GeneralUtility::makeInstance($this->submittedData['class']);
1239 if ((int)$this->submittedData['type'] === AbstractTask::TYPE_SINGLE) {
1240 // Set up single execution
1241 $task->registerSingleExecution($this->submittedData['start']);
1242 } else {
1243 // Set up recurring execution
1244 $task->registerRecurringExecution($this->submittedData['start'], $this->submittedData['interval'], $this->submittedData['end'], $this->submittedData['multiple'], $this->submittedData['croncmd']);
1245 }
1246 // Save additional input values
1247 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1248 /** @var $providerObject \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface */
1249 $providerObject = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1250 if ($providerObject instanceof \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface) {
1251 $providerObject->saveAdditionalFields($this->submittedData, $task);
1252 }
1253 }
1254 // Set disable flag
1255 $task->setDisabled($this->submittedData['disable']);
1256 // Set description
1257 $task->setDescription($this->submittedData['description']);
1258 // Set description
1259 $task->setTaskGroup($this->submittedData['task_group']);
1260 // Add to database
1261 $result = $this->scheduler->addTask($task);
1262 if ($result) {
1263 $this->getBackendUser()->writelog(4, 0, 0, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was added', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
1264 $this->addMessage($this->getLanguageService()->getLL('msg.addSuccess'));
1265
1266 // set the uid of the just created task so that we
1267 // can continue editing after initial saving
1268 $this->submittedData['uid'] = $task->getTaskUid();
1269 } else {
1270 $this->addMessage($this->getLanguageService()->getLL('msg.addError'), FlashMessage::ERROR);
1271 }
1272 }
1273 }
1274
1275 /*************************
1276 *
1277 * INPUT PROCESSING UTILITIES
1278 *
1279 *************************/
1280 /**
1281 * Checks the submitted data and performs some pre-processing on it
1282 *
1283 * @return bool true if everything was ok, false otherwise
1284 */
1285 protected function preprocessData()
1286 {
1287 $result = true;
1288 // Validate id
1289 $this->submittedData['uid'] = empty($this->submittedData['uid']) ? 0 : (int)$this->submittedData['uid'];
1290 // Validate selected task class
1291 if (!class_exists($this->submittedData['class'])) {
1292 $this->addMessage($this->getLanguageService()->getLL('msg.noTaskClassFound'), FlashMessage::ERROR);
1293 }
1294 // Check start date
1295 if (empty($this->submittedData['start'])) {
1296 $this->addMessage($this->getLanguageService()->getLL('msg.noStartDate'), FlashMessage::ERROR);
1297 $result = false;
1298 } else {
1299 try {
1300 $this->submittedData['start'] = (int)$this->submittedData['start'];
1301 } catch (\Exception $e) {
1302 $this->addMessage($this->getLanguageService()->getLL('msg.invalidStartDate'), FlashMessage::ERROR);
1303 $result = false;
1304 }
1305 }
1306 // Check end date, if recurring task
1307 if ((int)$this->submittedData['type'] === AbstractTask::TYPE_RECURRING && !empty($this->submittedData['end'])) {
1308 try {
1309 $this->submittedData['end'] = (int)$this->submittedData['end'];
1310 if ($this->submittedData['end'] < $this->submittedData['start']) {
1311 $this->addMessage($this->getLanguageService()->getLL('msg.endDateSmallerThanStartDate'), FlashMessage::ERROR);
1312 $result = false;
1313 }
1314 } catch (\Exception $e) {
1315 $this->addMessage($this->getLanguageService()->getLL('msg.invalidEndDate'), FlashMessage::ERROR);
1316 $result = false;
1317 }
1318 }
1319 // Set default values for interval and cron command
1320 $this->submittedData['interval'] = 0;
1321 $this->submittedData['croncmd'] = '';
1322 // Check type and validity of frequency, if recurring
1323 if ((int)$this->submittedData['type'] === AbstractTask::TYPE_RECURRING) {
1324 $frequency = trim($this->submittedData['frequency']);
1325 if (empty($frequency)) {
1326 // Empty frequency, not valid
1327 $this->addMessage($this->getLanguageService()->getLL('msg.noFrequency'), FlashMessage::ERROR);
1328 $result = false;
1329 } else {
1330 $cronErrorCode = 0;
1331 $cronErrorMessage = '';
1332 // Try interpreting the cron command
1333 try {
1334 \TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand::normalize($frequency);
1335 $this->submittedData['croncmd'] = $frequency;
1336 } catch (\Exception $e) {
1337 // Store the exception's result
1338 $cronErrorMessage = $e->getMessage();
1339 $cronErrorCode = $e->getCode();
1340 // Check if the frequency is a valid number
1341 // If yes, assume it is a frequency in seconds, and unset cron error code
1342 if (is_numeric($frequency)) {
1343 $this->submittedData['interval'] = (int)$frequency;
1344 unset($cronErrorCode);
1345 }
1346 }
1347 // If there's a cron error code, issue validation error message
1348 if (!empty($cronErrorCode)) {
1349 $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.frequencyError'), $cronErrorMessage, $cronErrorCode), FlashMessage::ERROR);
1350 $result = false;
1351 }
1352 }
1353 }
1354 // Validate additional input fields
1355 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1356 /** @var $providerObject \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface */
1357 $providerObject = GeneralUtility::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1358 if ($providerObject instanceof \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface) {
1359 // The validate method will return true if all went well, but that must not
1360 // override previous false values => AND the returned value with the existing one
1361 $result &= $providerObject->validateAdditionalFields($this->submittedData, $this);
1362 }
1363 }
1364 return $result;
1365 }
1366
1367 /*************************
1368 *
1369 * APPLICATION LOGIC UTILITIES
1370 *
1371 *************************/
1372 /**
1373 * This method is used to add a message to the internal queue
1374 *
1375 * @param string $message The message itself
1376 * @param int $severity Message level (according to FlashMessage class constants)
1377 * @return void
1378 */
1379 public function addMessage($message, $severity = FlashMessage::OK)
1380 {
1381 $this->moduleTemplate->addFlashMessage($message, '', $severity);
1382 }
1383
1384 /**
1385 * This method fetches a list of all classes that have been registered with the Scheduler
1386 * For each item the following information is provided, as an associative array:
1387 *
1388 * ['extension'] => Key of the extension which provides the class
1389 * ['filename'] => Path to the file containing the class
1390 * ['title'] => String (possibly localized) containing a human-readable name for the class
1391 * ['provider'] => Name of class that implements the interface for additional fields, if necessary
1392 *
1393 * The name of the class itself is used as the key of the list array
1394 *
1395 * @return array List of registered classes
1396 */
1397 protected function getRegisteredClasses()
1398 {
1399 $list = [];
1400 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'])) {
1401 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] as $class => $registrationInformation) {
1402 $title = isset($registrationInformation['title']) ? $this->getLanguageService()->sL($registrationInformation['title']) : '';
1403 $description = isset($registrationInformation['description']) ? $this->getLanguageService()->sL($registrationInformation['description']) : '';
1404 $list[$class] = [
1405 'extension' => $registrationInformation['extension'],
1406 'title' => $title,
1407 'description' => $description,
1408 'provider' => isset($registrationInformation['additionalFields']) ? $registrationInformation['additionalFields'] : ''
1409 ];
1410 }
1411 }
1412 return $list;
1413 }
1414
1415 /**
1416 * This method fetches list of all group that have been registered with the Scheduler
1417 *
1418 * @return array List of registered groups
1419 */
1420 protected function getRegisteredTaskGroups()
1421 {
1422 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1423 ->getQueryBuilderForTable('tx_scheduler_task_group');
1424
1425 return $queryBuilder
1426 ->select('*')
1427 ->from('tx_scheduler_task_group')
1428 ->orderBy('sorting')
1429 ->execute()
1430 ->fetchAll();
1431 }
1432
1433 /*************************
1434 *
1435 * RENDERING UTILITIES
1436 *
1437 *************************/
1438 /**
1439 * Gets the filled markers that are used in the HTML template.
1440 *
1441 * @return array The filled marker array
1442 */
1443 protected function getTemplateMarkers()
1444 {
1445 return [
1446 'CONTENT' => $this->content,
1447 'TITLE' => $this->getLanguageService()->getLL('title')
1448 ];
1449 }
1450
1451 /**
1452 * Create the panel of buttons for submitting the form or otherwise perform operations.
1453 */
1454 protected function getButtons()
1455 {
1456 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1457 // CSH
1458 $helpButton = $buttonBar->makeHelpButton()
1459 ->setModuleName('_MOD_' . $this->moduleName)
1460 ->setFieldName('');
1461 $buttonBar->addButton($helpButton);
1462 // Add and Reload
1463 if (empty($this->CMD) || $this->CMD === 'list' || $this->CMD === 'delete' || $this->CMD === 'stop' || $this->CMD === 'toggleHidden' || $this->CMD === 'setNextExecutionTime') {
1464 $reloadButton = $buttonBar->makeLinkButton()
1465 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.reload'))
1466 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL))
1467 ->setHref($this->moduleUri);
1468 $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
1469 if ($this->MOD_SETTINGS['function'] === 'scheduler' && !empty($this->getRegisteredClasses())) {
1470 $addButton = $buttonBar->makeLinkButton()
1471 ->setTitle($this->getLanguageService()->getLL('action.add'))
1472 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-new', Icon::SIZE_SMALL))
1473 ->setHref($this->moduleUri . '&CMD=add');
1474 $buttonBar->addButton($addButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1475 }
1476 }
1477 // Close and Save
1478 if ($this->CMD === 'add' || $this->CMD === 'edit') {
1479 // Close
1480 $closeButton = $buttonBar->makeLinkButton()
1481 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel'))
1482 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-close', Icon::SIZE_SMALL))
1483 ->setOnClick('document.location=' . GeneralUtility::quoteJSvalue($this->moduleUri))
1484 ->setHref('#');
1485 $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1486 // Save, SaveAndClose, SaveAndNew
1487 $saveButtonDropdown = $buttonBar->makeSplitButton();
1488 $saveButton = $buttonBar->makeInputButton()
1489 ->setName('CMD')
1490 ->setValue('save')
1491 ->setForm('tx_scheduler_form')
1492 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
1493 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:save'));
1494 $saveButtonDropdown->addItem($saveButton);
1495 $saveAndNewButton = $buttonBar->makeInputButton()
1496 ->setName('CMD')
1497 ->setValue('savenew')
1498 ->setForm('tx_scheduler_form')
1499 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-new', Icon::SIZE_SMALL))
1500 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:saveAndCreateNewDoc'));
1501 $saveButtonDropdown->addItem($saveAndNewButton);
1502 $saveAndCloseButton = $buttonBar->makeInputButton()
1503 ->setName('CMD')
1504 ->setValue('saveclose')
1505 ->setForm('tx_scheduler_form')
1506 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-close', Icon::SIZE_SMALL))
1507 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:saveAndClose'));
1508 $saveButtonDropdown->addItem($saveAndCloseButton);
1509 $buttonBar->addButton($saveButtonDropdown, ButtonBar::BUTTON_POSITION_LEFT, 3);
1510 }
1511 // Edit
1512 if ($this->CMD === 'edit') {
1513 $deleteButton = $buttonBar->makeInputButton()
1514 ->setName('CMD')
1515 ->setValue('delete')
1516 ->setForm('tx_scheduler_form')
1517 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-edit-delete', Icon::SIZE_SMALL))
1518 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete'));
1519 $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
1520 }
1521 // Shortcut
1522 $shortcutButton = $buttonBar->makeShortcutButton()
1523 ->setModuleName($this->moduleName)
1524 ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
1525 ->setSetVariables(['function']);
1526 $buttonBar->addButton($shortcutButton);
1527 }
1528
1529 /**
1530 * @return string
1531 */
1532 protected function getServerTime()
1533 {
1534 $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' T (e';
1535 return date($dateFormat) . ', GMT ' . date('P') . ')';
1536 }
1537
1538 /**
1539 * Returns the global BackendUserAuthentication object.
1540 *
1541 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1542 */
1543 protected function getBackendUser()
1544 {
1545 return $GLOBALS['BE_USER'];
1546 }
1547 }