‪TYPO3CMS  10.4
SchedulerModuleController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
37 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
38 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
50 
56 {
57 
63  protected ‪$submittedData = [];
64 
71  protected ‪$messages = [];
72 
76  protected ‪$cshKey = '_MOD_system_txschedulerM1';
77 
81  protected ‪$scheduler;
82 
86  protected ‪$backendTemplatePath = '';
87 
91  protected ‪$view;
92 
96  protected ‪$moduleUri;
97 
104 
108  protected ‪$iconFactory;
109 
113  protected ‪$action;
114 
120  protected ‪$MOD_MENU = [
121  'function' => []
122  ];
123 
129  protected ‪$MOD_SETTINGS = [];
130 
134  public function ‪__construct()
135  {
136  $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
137  $this->‪getLanguageService()->‪includeLLFile('EXT:scheduler/Resources/Private/Language/locallang.xlf');
138  $this->backendTemplatePath = ‪ExtensionManagementUtility::extPath('scheduler') . 'Resources/Private/Templates/Backend/SchedulerModule/';
139  $this->view = GeneralUtility::makeInstance(StandaloneView::class);
140  $this->view->getRequest()->setControllerExtensionName('scheduler');
141  $this->view->setPartialRootPaths([‪ExtensionManagementUtility::extPath('scheduler') . 'Resources/Private/Partials/Backend/SchedulerModule/']);
142  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
143  $this->moduleUri = (string)$uriBuilder->buildUriFromRoute('system_txschedulerM1');
144  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
145  $this->scheduler = GeneralUtility::makeInstance(Scheduler::class);
146 
147  $this->‪getPageRenderer()->‪loadRequireJsModule('TYPO3/CMS/Backend/Modal');
148  }
149 
156  public function ‪mainAction(ServerRequestInterface $request): ResponseInterface
157  {
158  $parsedBody = $request->getParsedBody();
159  $queryParams = $request->getQueryParams();
160 
161  $this->‪setCurrentAction(‪Action::cast($parsedBody['CMD'] ?? $queryParams['CMD'] ?? null));
162  $this->MOD_MENU = [
163  'function' => [
164  'scheduler' => $this->‪getLanguageService()->‪getLL('function.scheduler'),
165  'check' => $this->‪getLanguageService()->‪getLL('function.check'),
166  'info' => $this->‪getLanguageService()->‪getLL('function.info')
167  ]
168  ];
169  $settings = $parsedBody['SET'] ?? $queryParams['SET'] ?? null;
170  $this->MOD_SETTINGS = ‪BackendUtility::getModuleData($this->MOD_MENU, $settings, 'system_txschedulerM1', '', '', '');
171 
172  // Set the form
173  $content = '<form name="tx_scheduler_form" id="tx_scheduler_form" method="post" action="">';
174 
175  // Prepare main content
176  $content .= '<h1>' . $this->‪getLanguageService()->‪getLL('function.' . $this->MOD_SETTINGS['function']) . '</h1>';
177  $previousCMD = ‪Action::cast($parsedBody['previousCMD'] ?? $queryParams['previousCMD'] ?? null);
178  $content .= $this->‪getModuleContent($previousCMD);
179  $content .= '<div id="extraFieldsSection"></div></form><div id="extraFieldsHidden"></div>';
180 
181  $this->‪getButtons($request);
182  $this->‪getModuleMenu();
183 
184  $this->moduleTemplate->setContent($content);
185  return new ‪HtmlResponse($this->moduleTemplate->renderContent());
186  }
187 
193  public function ‪getCurrentAction(): ‪Action
194  {
195  return ‪$this->action;
196  }
197 
201  protected function ‪getModuleMenu(): void
202  {
203  $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
204  $menu->setIdentifier('SchedulerJumpMenu');
206  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
207  foreach ($this->MOD_MENU['function'] as $controller => $title) {
208  $item = $menu
209  ->makeMenuItem()
210  ->setHref(
211  (string)$uriBuilder->buildUriFromRoute(
212  'system_txschedulerM1',
213  [
214  'id' => 0,
215  'SET' => [
216  'function' => $controller
217  ]
218  ]
219  )
220  )
221  ->setTitle($title);
222  if ($controller === $this->MOD_SETTINGS['function']) {
223  $item->setActive(true);
224  }
225  $menu->addMenuItem($item);
226  }
227  $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
228  }
229 
236  protected function ‪getModuleContent(Action $previousAction): string
237  {
238  $content = '';
239  $sectionTitle = '';
240  // Get submitted data
241  $this->submittedData = GeneralUtility::_GPmerged('tx_scheduler');
242  $this->submittedData['uid'] = (int)$this->submittedData['uid'];
243  // If a save command was submitted, handle saving now
244  if (in_array((string)$this->‪getCurrentAction(), [‪Action::SAVE, ‪Action::SAVE_CLOSE, ‪Action::SAVE_NEW], true)) {
245  // First check the submitted data
246  $result = $this->‪preprocessData();
247 
248  // If result is ok, proceed with saving
249  if ($result) {
250  $this->‪saveTask();
251 
252  if ($this->action->equals(‪Action::SAVE_CLOSE)) {
253  // Display default screen
255  } elseif ($this->action->equals(‪Action::SAVE)) {
256  // After saving a "add form", return to edit
258  } elseif ($this->action->equals(‪Action::SAVE_NEW)) {
259  // Unset submitted data, so that empty form gets displayed
260  unset($this->submittedData);
261  // After saving a "add/edit form", return to add
263  } else {
264  // Return to edit form
265  $this->‪setCurrentAction($previousAction);
266  }
267  } else {
268  $this->‪setCurrentAction($previousAction);
269  }
270  }
271 
272  // Handle chosen action
273  switch ((string)$this->MOD_SETTINGS['function']) {
274  case 'scheduler':
275  $this->‪executeTasks();
276 
277  switch ((string)$this->‪getCurrentAction()) {
278  case ‪Action::ADD:
279  case ‪Action::EDIT:
280  try {
281  // Try adding or editing
282  $content .= $this->‪editTaskAction();
283  $sectionTitle = $this->‪getLanguageService()->‪getLL('action.' . $this->‪getCurrentAction());
284  } catch (\LogicException|\UnexpectedValueException|\OutOfBoundsException $e) {
285  // Catching all types of exceptions that were previously handled and
286  // converted to messages
287  $content .= $this->‪listTasksAction();
288  } catch (\Exception $e) {
289  // Catching all "unexpected" exceptions not previously handled
290  $this->‪addMessage($e->getMessage(), ‪FlashMessage::ERROR);
291  $content .= $this->‪listTasksAction();
292  }
293  break;
294  case ‪Action::DELETE:
295  $this->‪deleteTask();
296  $content .= $this->‪listTasksAction();
297  break;
298  case ‪Action::STOP:
299  $this->‪stopTask();
300  $content .= $this->‪listTasksAction();
301  break;
303  $this->‪toggleDisableAction();
304  $content .= $this->‪listTasksAction();
305  break;
308  $content .= $this->‪listTasksAction();
309  break;
310  case ‪Action::LIST:
311  $content .= $this->‪listTasksAction();
312  }
313  break;
314 
315  // Setup check screen
316  case 'check':
317  // @todo move check to the report module
318  $content .= $this->‪checkScreenAction();
319  break;
320 
321  // Information screen
322  case 'info':
323  $content .= $this->‪infoScreenAction();
324  break;
325  }
326  // Wrap the content
327  return '<h2>' . $sectionTitle . '</h2><div class="tx_scheduler_mod1">' . $content . '</div>';
328  }
329 
336  protected function ‪checkScreenAction(): string
337  {
338  $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'CheckScreen.html');
339 
340  // Display information about last automated run, as stored in the system registry
341  $registry = GeneralUtility::makeInstance(Registry::class);
342  $lastRun = $registry->get('tx_scheduler', 'lastRun');
343  if (!is_array($lastRun)) {
344  $message = $this->‪getLanguageService()->‪getLL('msg.noLastRun');
346  } else {
347  if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) {
348  $message = $this->‪getLanguageService()->‪getLL('msg.incompleteLastRun');
350  } else {
351  $startDate = date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']);
352  $startTime = date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']);
353  $endDate = date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']);
354  $endTime = date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']);
355  $label = 'automatically';
356  if ($lastRun['type'] === 'manual') {
357  $label = 'manually';
358  }
359  $type = $this->‪getLanguageService()->‪getLL('label.' . $label);
360  $message = sprintf($this->‪getLanguageService()->getLL('msg.lastRun'), $type, $startDate, $startTime, $endDate, $endTime);
362  }
363  }
364  $this->view->assign('lastRunMessage', $message);
365  $this->view->assign('lastRunSeverity', $severity);
366 
368  $this->view->assign('composerMode', true);
369  } else {
370  // Check if CLI script is executable or not
371  $script = GeneralUtility::getFileAbsFileName('EXT:core/bin/typo3');
372  $this->view->assign('script', $script);
373  // Skip this check if running Windows, as rights do not work the same way on this platform
374  // (i.e. the script will always appear as *not* executable)
376  $isExecutable = true;
377  } else {
378  $isExecutable = is_executable($script);
379  }
380  if ($isExecutable) {
381  $message = $this->‪getLanguageService()->‪getLL('msg.cliScriptExecutable');
382  $severity = ‪InfoboxViewHelper::STATE_OK;
383  } else {
384  $message = $this->‪getLanguageService()->‪getLL('msg.cliScriptNotExecutable');
386  }
387  $this->view->assign('isExecutableMessage', $message);
388  $this->view->assign('isExecutableSeverity', $severity);
389  }
390 
391  $this->view->assign('now', $this->‪getServerTime());
392 
393  return $this->view->render();
394  }
395 
401  protected function ‪infoScreenAction(): string
402  {
403  $registeredClasses = $this->‪getRegisteredClasses();
404  // No classes available, display information message
405  if (empty($registeredClasses)) {
406  $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'InfoScreenNoClasses.html');
407  return $this->view->render();
408  }
409 
410  $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'InfoScreen.html');
411  $this->view->assign('registeredClasses', $registeredClasses);
412 
413  return $this->view->render();
414  }
415 
419  protected function ‪deleteTask(): void
420  {
421  try {
422  // Try to fetch the task and delete it
423  $task = $this->scheduler->fetchTask($this->submittedData['uid']);
424  // If the task is currently running, it may not be deleted
425  if ($task->isExecutionRunning()) {
426  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.maynotDeleteRunningTask'), ‪FlashMessage::ERROR);
427  } else {
428  if ($this->scheduler->removeTask($task)) {
429  $this->‪getBackendUser()->‪writelog(SystemLogType::EXTENSION, SystemLogGenericAction::UNDEFINED, SystemLogErrorClassification::MESSAGE, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was deleted', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
430  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.deleteSuccess'));
431  } else {
432  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.deleteError'), ‪FlashMessage::ERROR);
433  }
434  }
435  } catch (\UnexpectedValueException $e) {
436  // The task could not be unserialized properly, simply update the database record
437  $taskUid = (int)$this->submittedData['uid'];
438  $result = GeneralUtility::makeInstance(ConnectionPool::class)
439  ->getConnectionForTable('tx_scheduler_task')
440  ->update('tx_scheduler_task', ['deleted' => 1], ['uid' => $taskUid]);
441  if ($result) {
442  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.deleteSuccess'));
443  } else {
444  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.deleteError'), ‪FlashMessage::ERROR);
445  }
446  } catch (\OutOfBoundsException $e) {
447  // The task was not found, for some reason
448  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), ‪FlashMessage::ERROR);
449  }
450  }
451 
458  protected function ‪stopTask(): void
459  {
460  try {
461  // Try to fetch the task and stop it
462  $task = $this->scheduler->fetchTask($this->submittedData['uid']);
463  if ($task->isExecutionRunning()) {
464  // If the task is indeed currently running, clear marked executions
465  $result = $task->unmarkAllExecutions();
466  if ($result) {
467  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.stopSuccess'));
468  } else {
469  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.stopError'), ‪FlashMessage::ERROR);
470  }
471  } else {
472  // The task is not running, nothing to unmark
473  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.maynotStopNonRunningTask'), ‪FlashMessage::WARNING);
474  }
475  } catch (\Exception $e) {
476  // The task was not found, for some reason
477  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), ‪FlashMessage::ERROR);
478  }
479  }
480 
484  protected function ‪toggleDisableAction(): void
485  {
486  $task = $this->scheduler->fetchTask($this->submittedData['uid']);
487  $task->setDisabled(!$task->isDisabled());
488  // If a disabled single task is enabled again, we register it for a
489  // single execution at next scheduler run.
490  if ($task->getType() === ‪AbstractTask::TYPE_SINGLE) {
491  $task->registerSingleExecution(time());
492  }
493  $task->save();
494  }
495 
499  protected function ‪setNextExecutionTimeAction(): void
500  {
501  $task = $this->scheduler->fetchTask($this->submittedData['uid']);
502  $task->setRunOnNextCronJob(true);
503  $task->save();
504  }
505 
511  protected function ‪editTaskAction(): string
512  {
513  $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'EditTask.html');
514 
515  $registeredClasses = $this->‪getRegisteredClasses();
516  $registeredTaskGroups = $this->‪getRegisteredTaskGroups();
517 
518  $taskInfo = [];
519  $task = null;
520  $process = 'edit';
521 
522  if ($this->submittedData['uid'] > 0) {
523  // If editing, retrieve data for existing task
524  try {
525  $taskRecord = $this->scheduler->fetchTaskRecord($this->submittedData['uid']);
526  // If there's a registered execution, the task should not be edited
527  if (!empty($taskRecord['serialized_executions'])) {
528  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.maynotEditRunningTask'), ‪FlashMessage::ERROR);
529  throw new \LogicException('Running tasks cannot not be edited', 1251232849);
530  }
531 
532  // Get the task object
534  $task = unserialize($taskRecord['serialized_task_object']);
535 
536  // Set some task information
537  $taskInfo['disable'] = $taskRecord['disable'];
538  $taskInfo['description'] = $taskRecord['description'];
539  $taskInfo['task_group'] = $taskRecord['task_group'];
540 
541  // Check that the task object is valid
542  if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
543  // The task object is valid, process with fetching current data
544  $taskInfo['class'] = get_class($task);
545  // Get execution information
546  $taskInfo['start'] = (int)$task->getExecution()->getStart();
547  $taskInfo['end'] = (int)$task->getExecution()->getEnd();
548  $taskInfo['interval'] = $task->getExecution()->getInterval();
549  $taskInfo['croncmd'] = $task->getExecution()->getCronCmd();
550  $taskInfo['multiple'] = $task->getExecution()->getMultiple();
551  if (!empty($taskInfo['interval']) || !empty($taskInfo['croncmd'])) {
552  // Guess task type from the existing information
553  // If an interval or a cron command is defined, it's a recurring task
554  $taskInfo['type'] = ‪AbstractTask::TYPE_RECURRING;
555  $taskInfo['frequency'] = $taskInfo['interval'] ?: $taskInfo['croncmd'];
556  } else {
557  // It's not a recurring task
558  // Make sure interval and cron command are both empty
559  $taskInfo['type'] = ‪AbstractTask::TYPE_SINGLE;
560  $taskInfo['frequency'] = '';
561  $taskInfo['end'] = 0;
562  }
563  } else {
564  // The task object is not valid
565  // Issue error message
566  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.invalidTaskClassEdit'), get_class($task)), ‪FlashMessage::ERROR);
567  // Initialize empty values
568  $taskInfo['start'] = 0;
569  $taskInfo['end'] = 0;
570  $taskInfo['frequency'] = '';
571  $taskInfo['multiple'] = false;
572  $taskInfo['type'] = ‪AbstractTask::TYPE_SINGLE;
573  }
574  } catch (\OutOfBoundsException $e) {
575  // Add a message and continue throwing the exception
576  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), ‪FlashMessage::ERROR);
577  throw $e;
578  }
579  } else {
580  // If adding a new object, set some default values
581  $taskInfo['class'] = key($registeredClasses);
582  $taskInfo['type'] = ‪AbstractTask::TYPE_RECURRING;
583  $taskInfo['start'] = ‪$GLOBALS['EXEC_TIME'];
584  $taskInfo['end'] = '';
585  $taskInfo['frequency'] = '';
586  $taskInfo['multiple'] = 0;
587  $process = 'add';
588  }
589 
590  // If some data was already submitted, use it to override
591  // existing data
592  if (!empty($this->submittedData)) {
593  ‪ArrayUtility::mergeRecursiveWithOverrule($taskInfo, $this->submittedData);
594  }
595 
596  // Get the extra fields to display for each task that needs some
597  $allAdditionalFields = [];
598  if ($process === 'add') {
599  foreach ($registeredClasses as $class => $registrationInfo) {
600  if (!empty($registrationInfo['provider'])) {
602  $providerObject = GeneralUtility::makeInstance($registrationInfo['provider']);
603  if ($providerObject instanceof AdditionalFieldProviderInterface) {
604  $additionalFields = $providerObject->getAdditionalFields($taskInfo, null, $this);
605  $allAdditionalFields = array_merge($allAdditionalFields, [$class => $additionalFields]);
606  }
607  }
608  }
609  } elseif ($task !== null && !empty($registeredClasses[$taskInfo['class']]['provider'])) {
610  // only try to fetch additionalFields if the task is valid
611  $providerObject = GeneralUtility::makeInstance($registeredClasses[$taskInfo['class']]['provider']);
612  if ($providerObject instanceof AdditionalFieldProviderInterface) {
613  $allAdditionalFields[$taskInfo['class']] = $providerObject->getAdditionalFields($taskInfo, $task, $this);
614  }
615  }
616 
617  // Load necessary JavaScript
618  $this->‪getPageRenderer()->‪loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
619  $this->‪getPageRenderer()->‪loadRequireJsModule('TYPO3/CMS/Backend/DateTimePicker');
620 
621  // Start rendering the add/edit form
622  $this->view->assign('uid', htmlspecialchars((string)$this->submittedData['uid']));
623  $this->view->assign('cmd', htmlspecialchars((string)$this->‪getCurrentAction()));
624  $this->view->assign('csh', $this->cshKey);
625  $this->view->assign('lang', 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:');
626 
627  $table = [];
628 
629  // Disable checkbox
630  $this->view->assign('task_disable', ($taskInfo['disable'] ? ' checked="checked"' : ''));
631  $this->view->assign('task_disable_label', 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:disable');
632 
633  // Task class selector
634  // On editing, don't allow changing of the task class, unless it was not valid
635  if ($this->submittedData['uid'] > 0 && !empty($taskInfo['class'])) {
636  $this->view->assign('task_class', $taskInfo['class']);
637  $this->view->assign('task_class_title', $registeredClasses[$taskInfo['class']]['title']);
638  $this->view->assign('task_class_extension', $registeredClasses[$taskInfo['class']]['extension']);
639  } else {
640  // Group registered classes by classname
641  $groupedClasses = [];
642  foreach ($registeredClasses as $class => $classInfo) {
643  $groupedClasses[$classInfo['extension']][$class] = $classInfo;
644  }
645  ksort($groupedClasses);
646  foreach ($groupedClasses as $extension => $class) {
647  foreach ($groupedClasses[$extension] as $class => $classInfo) {
648  $selected = $class == $taskInfo['class'] ? ' selected="selected"' : '';
649  $groupedClasses[$extension][$class]['selected'] = $selected;
650  }
651  }
652  $this->view->assign('groupedClasses', $groupedClasses);
653  }
654 
655  // Task type selector
656  $this->view->assign('task_type_selected_1', ((int)$taskInfo['type'] === ‪AbstractTask::TYPE_SINGLE ? ' selected="selected"' : ''));
657  $this->view->assign('task_type_selected_2', ((int)$taskInfo['type'] === ‪AbstractTask::TYPE_RECURRING ? ' selected="selected"' : ''));
658 
659  // Task group selector
660  foreach ($registeredTaskGroups as $key => $taskGroup) {
661  $selected = $taskGroup['uid'] == $taskInfo['task_group'] ? ' selected="selected"' : '';
662  $registeredTaskGroups[$key]['selected'] = $selected;
663  }
664  $this->view->assign('registeredTaskGroups', $registeredTaskGroups);
665 
666  // Start date/time field
667  $dateFormat = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '%H:%M %m-%d-%Y' : '%H:%M %d-%m-%Y';
668  $this->view->assign('start_value_hr', ($taskInfo['start'] > 0 ? strftime($dateFormat, $taskInfo['start']) : ''));
669  $this->view->assign('start_value', $taskInfo['start']);
670 
671  // End date/time field
672  // NOTE: datetime fields need a special id naming scheme
673  $this->view->assign('end_value_hr', ($taskInfo['end'] > 0 ? strftime($dateFormat, $taskInfo['end']) : ''));
674  $this->view->assign('end_value', $taskInfo['end']);
675 
676  // Frequency input field
677  $this->view->assign('frequency', $taskInfo['frequency']);
678 
679  // Multiple execution selector
680  $this->view->assign('multiple', ($taskInfo['multiple'] ? 'checked="checked"' : ''));
681 
682  // Description
683  $this->view->assign('description', $taskInfo['description']);
684 
685  // Display additional fields
686  $additionalFieldList = [];
687  foreach ($allAdditionalFields as $class => ‪$fields) {
688  if ($class == $taskInfo['class']) {
689  $additionalFieldsStyle = '';
690  } else {
691  $additionalFieldsStyle = ' style="display: none"';
692  }
693  // Add each field to the display, if there are indeed any
694  if (is_array(‪$fields)) {
695  foreach (‪$fields as $fieldID => $fieldInfo) {
696  $htmlClassName = strtolower(str_replace('\\', '-', (string)$class));
697  $field = [];
698  $field['htmlClassName'] = $htmlClassName;
699  $field['code'] = $fieldInfo['code'];
700  $field['cshKey'] = $fieldInfo['cshKey'];
701  $field['cshLabel'] = $fieldInfo['cshLabel'];
702  $field['langLabel'] = $fieldInfo['label'];
703  $field['fieldID'] = $fieldID;
704  $field['additionalFieldsStyle'] = $additionalFieldsStyle;
705  $field['browseButton'] = $this->‪getBrowseButton($fieldID, $fieldInfo);
706  $additionalFieldList[] = $field;
707  }
708  }
709  }
710  $this->view->assign('additionalFields', $additionalFieldList);
711 
712  $this->view->assign('returnUrl', (string)GeneralUtility::getIndpEnv('REQUEST_URI'));
713  $this->view->assign('table', implode(LF, $table));
714  $this->view->assign('now', $this->‪getServerTime());
715  $this->view->assign('frequencyOptions', (array)‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['frequencyOptions']);
716 
717  return $this->view->render();
718  }
719 
725  protected function ‪getBrowseButton($fieldID, array $fieldInfo): string
726  {
727  if (isset($fieldInfo['browser']) && ($fieldInfo['browser'] === 'page')) {
728  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
729  $url = (string)$uriBuilder->buildUriFromRoute('wizard_element_browser');
730 
731  $title = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.browse_db'));
732  return '
733  <div><a href="' . htmlspecialchars($url) . '" data-trigger-for="' . htmlspecialchars($fieldID) . '" data-mode="db" data-params="" class="btn btn-default t3js-element-browser" title="' . $title . '">
734  <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-insert-record" data-identifier="actions-insert-record">
735  <span class="icon-markup">' . $this->iconFactory->getIcon(
736  'actions-insert-record',
738  )->render() . '</span>
739  </span>
740  </a><span id="page_' . $fieldID . '">&nbsp;' . htmlspecialchars($fieldInfo['pageTitle']) . '</span></div>';
741  }
742  return '';
743  }
744 
748  protected function ‪executeTasks(): void
749  {
750  // Continue if some elements have been chosen for execution
751  if (isset($this->submittedData['execute']) && !empty($this->submittedData['execute'])) {
752  // Get list of registered classes
753  $registeredClasses = $this->‪getRegisteredClasses();
754  // Loop on all selected tasks
755  foreach ($this->submittedData['execute'] as $uid) {
756  try {
757  // Try fetching the task
758  $task = $this->scheduler->fetchTask($uid);
759  $class = get_class($task);
760  $name = $registeredClasses[$class]['title'] . ' (' . $registeredClasses[$class]['extension'] . ')';
761  if (GeneralUtility::_POST('go_cron') !== null) {
762  $task->setRunOnNextCronJob(true);
763  $task->save();
764  } else {
765  // Now try to execute it and report on outcome
766  try {
767  $result = $this->scheduler->executeTask($task);
768  if ($result) {
769  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.executed'), $name));
770  } else {
771  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.notExecuted'), $name), ‪FlashMessage::ERROR);
772  }
773  } catch (\Exception $e) {
774  // An exception was thrown, display its message as an error
775  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.executionFailed'), $name, $e->getMessage()), ‪FlashMessage::ERROR);
776  }
777  }
778  } catch (\OutOfBoundsException $e) {
779  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.taskNotFound'), $uid), ‪FlashMessage::ERROR);
780  } catch (\UnexpectedValueException $e) {
781  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.executionFailed'), $uid, $e->getMessage()), ‪FlashMessage::ERROR);
782  }
783  }
784  // Record the run in the system registry
785  $this->scheduler->recordLastRun('manual');
786  // Make sure to switch to list view after execution
788  }
789  }
790 
796  protected function ‪listTasksAction(): string
797  {
798  $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'ListTasks.html');
799 
800  // Define display format for dates
801  $dateFormat = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
802 
803  // Get list of registered task groups
804  $registeredTaskGroups = $this->‪getRegisteredTaskGroups();
805 
806  // add an empty entry for non-grouped tasks
807  // add in front of list
808  array_unshift($registeredTaskGroups, ['uid' => 0, 'groupName' => '']);
809 
810  // Get all registered tasks
811  // Just to get the number of entries
812  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
813  ->getQueryBuilderForTable('tx_scheduler_task');
814  $queryBuilder->getRestrictions()->removeAll();
815 
816  $result = $queryBuilder->select('t.*')
817  ->addSelect(
818  'g.groupName AS taskGroupName',
819  'g.description AS taskGroupDescription',
820  'g.deleted AS isTaskGroupDeleted'
821  )
822  ->from('tx_scheduler_task', 't')
823  ->leftJoin(
824  't',
825  'tx_scheduler_task_group',
826  'g',
827  $queryBuilder->expr()->eq('t.task_group', $queryBuilder->quoteIdentifier('g.uid'))
828  )
829  ->where(
830  $queryBuilder->expr()->eq('t.deleted', 0)
831  )
832  ->orderBy('g.sorting')
833  ->execute();
834 
835  // Loop on all tasks
836  $temporaryResult = [];
837  while ($row = $result->fetch()) {
838  if ($row['taskGroupName'] === null || $row['isTaskGroupDeleted'] === '1') {
839  $row['taskGroupName'] = '';
840  $row['taskGroupDescription'] = '';
841  $row['task_group'] = 0;
842  }
843  $temporaryResult[$row['task_group']]['groupName'] = $row['taskGroupName'];
844  $temporaryResult[$row['task_group']]['groupDescription'] = $row['taskGroupDescription'];
845  $temporaryResult[$row['task_group']]['tasks'][] = $row;
846  }
847 
848  // No tasks defined, display information message
849  if (empty($temporaryResult)) {
850  $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'ListTasksNoTasks.html');
851  return $this->view->render();
852  }
853 
854  $this->‪getPageRenderer()->‪loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
855  $this->‪getPageRenderer()->‪loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
856 
857  $tasks = $temporaryResult;
858 
859  $registeredClasses = $this->‪getRegisteredClasses();
860  $missingClasses = [];
861  foreach ($temporaryResult as $taskIndex => $taskGroup) {
862  foreach ($taskGroup['tasks'] as $recordIndex => $schedulerRecord) {
863  if ((int)$schedulerRecord['disable'] === 1) {
864  $translationKey = 'enable';
865  } else {
866  $translationKey = 'disable';
867  }
868  $tasks[$taskIndex]['tasks'][$recordIndex]['translationKey'] = $translationKey;
869 
870  // Define some default values
871  $lastExecution = '-';
872  $isRunning = false;
873  $showAsDisabled = false;
874  // Restore the serialized task and pass it a reference to the scheduler object
876  $task = unserialize($schedulerRecord['serialized_task_object']);
877  $class = get_class($task);
878  if ($class === \__PHP_Incomplete_Class::class && preg_match('/^O:[0-9]+:"(?P<classname>.+?)"/', $schedulerRecord['serialized_task_object'], $matches) === 1) {
879  $class = $matches['classname'];
880  }
881  $tasks[$taskIndex]['tasks'][$recordIndex]['class'] = $class;
882  // Assemble information about last execution
883  if (!empty($schedulerRecord['lastexecution_time'])) {
884  $lastExecution = date($dateFormat, (int)$schedulerRecord['lastexecution_time']);
885  if ($schedulerRecord['lastexecution_context'] === 'CLI') {
886  $context = $this->‪getLanguageService()->‪getLL('label.cron');
887  } else {
888  $context = $this->‪getLanguageService()->‪getLL('label.manual');
889  }
890  $lastExecution .= ' (' . $context . ')';
891  }
892  $tasks[$taskIndex]['tasks'][$recordIndex]['lastExecution'] = $lastExecution;
893 
894  if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
895  $tasks[$taskIndex]['tasks'][$recordIndex]['validClass'] = true;
896  // The task object is valid
897  $labels = [];
898  $additionalInformation = $task->getAdditionalInformation();
899  if ($task instanceof ProgressProviderInterface) {
900  $progress = round((float)$task->getProgress(), 2);
901  $tasks[$taskIndex]['tasks'][$recordIndex]['progress'] = $progress;
902  }
903  $tasks[$taskIndex]['tasks'][$recordIndex]['classTitle'] = $registeredClasses[$class]['title'];
904  $tasks[$taskIndex]['tasks'][$recordIndex]['classExtension'] = $registeredClasses[$class]['extension'];
905  $tasks[$taskIndex]['tasks'][$recordIndex]['additionalInformation'] = $additionalInformation;
906  // Check if task currently has a running execution
907  if (!empty($schedulerRecord['serialized_executions'])) {
908  $labels[] = [
909  'class' => 'success',
910  'text' => $this->‪getLanguageService()->‪getLL('status.running')
911  ];
912  $isRunning = true;
913  }
914  $tasks[$taskIndex]['tasks'][$recordIndex]['isRunning'] = $isRunning;
915 
916  // Prepare display of next execution date
917  // If task is currently running, date is not displayed (as next hasn't been calculated yet)
918  // Also hide the date if task is disabled (the information doesn't make sense, as it will not run anyway)
919  if ($isRunning || $schedulerRecord['disable']) {
920  $nextDate = '-';
921  } else {
922  $nextDate = date($dateFormat, (int)$schedulerRecord['nextexecution']);
923  if (empty($schedulerRecord['nextexecution'])) {
924  $nextDate = $this->‪getLanguageService()->‪getLL('none');
925  } elseif ($schedulerRecord['nextexecution'] < ‪$GLOBALS['EXEC_TIME']) {
926  $labels[] = [
927  'class' => 'warning',
928  'text' => $this->‪getLanguageService()->‪getLL('status.late'),
929  'description' => $this->‪getLanguageService()->‪getLL('status.legend.scheduled')
930  ];
931  }
932  }
933  $tasks[$taskIndex]['tasks'][$recordIndex]['nextDate'] = $nextDate;
934  // Get execution type
935  if ($task->getType() === ‪AbstractTask::TYPE_SINGLE) {
936  $execType = $this->‪getLanguageService()->‪getLL('label.type.single');
937  $frequency = '-';
938  } else {
939  $execType = $this->‪getLanguageService()->‪getLL('label.type.recurring');
940  if ($task->getExecution()->getCronCmd() == '') {
941  $frequency = $task->getExecution()->getInterval();
942  } else {
943  $frequency = $task->getExecution()->getCronCmd();
944  }
945  }
946  // Check the disable status
947  // Row is shown dimmed if task is disabled, unless it is still running
948  if ($schedulerRecord['disable'] && !$isRunning) {
949  $labels[] = [
950  'class' => 'default',
951  'text' => $this->‪getLanguageService()->‪getLL('status.disabled')
952  ];
953  $showAsDisabled = true;
954  }
955  $tasks[$taskIndex]['tasks'][$recordIndex]['execType'] = $execType;
956  $tasks[$taskIndex]['tasks'][$recordIndex]['frequency'] = $frequency;
957  // Get multiple executions setting
958  if ($task->getExecution()->getMultiple()) {
959  $multiple = $this->‪getLanguageService()->‪sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:yes');
960  } else {
961  $multiple = $this->‪getLanguageService()->‪sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:no');
962  }
963  $tasks[$taskIndex]['tasks'][$recordIndex]['multiple'] = $multiple;
964 
965  // Check if the last run failed
966  if (!empty($schedulerRecord['lastexecution_failure'])) {
967  // Try to get the stored exception array
969  $exceptionArray = @unserialize($schedulerRecord['lastexecution_failure']);
970  // If the exception could not be unserialized, issue a default error message
971  if (!is_array($exceptionArray) || empty($exceptionArray)) {
972  $labelDescription = $this->‪getLanguageService()->‪getLL('msg.executionFailureDefault');
973  } else {
974  $labelDescription = sprintf($this->‪getLanguageService()->getLL('msg.executionFailureReport'), $exceptionArray['code'], $exceptionArray['message']);
975  }
976  $labels[] = [
977  'class' => 'danger',
978  'text' => $this->‪getLanguageService()->‪getLL('status.failure'),
979  'description' => $labelDescription
980  ];
981  }
982  $tasks[$taskIndex]['tasks'][$recordIndex]['labels'] = $labels;
983  if ($showAsDisabled) {
984  $tasks[$taskIndex]['tasks'][$recordIndex]['showAsDisabled'] = 'disabled';
985  }
986  } else {
987  $missingClasses[] = $tasks[$taskIndex]['tasks'][$recordIndex];
988  unset($tasks[$taskIndex]['tasks'][$recordIndex]);
989  }
990  }
991  }
992 
993  $this->view->assign('tasks', $tasks);
994  $this->view->assign('missingClasses', $missingClasses);
995  $this->view->assign('moduleUri', $this->moduleUri);
996  $this->view->assign('now', $this->‪getServerTime());
997 
998  return $this->view->render();
999  }
1000 
1007  protected function ‪makeStatusLabel(array $labels): string
1008  {
1009  $htmlLabels = [];
1010  foreach ($labels as $label) {
1011  if (empty($label['text'])) {
1012  continue;
1013  }
1014  $htmlLabels[] = '<span class="label label-' . htmlspecialchars($label['class']) . ' pull-right" title="' . htmlspecialchars($label['description']) . '">' . htmlspecialchars($label['text']) . '</span>';
1015  }
1016 
1017  return implode('&nbsp;', $htmlLabels);
1018  }
1019 
1023  protected function ‪saveTask(): void
1024  {
1025  // If a task is being edited fetch old task data
1026  if (!empty($this->submittedData['uid'])) {
1027  try {
1028  $taskRecord = $this->scheduler->fetchTaskRecord($this->submittedData['uid']);
1030  $task = unserialize($taskRecord['serialized_task_object']);
1031  } catch (\OutOfBoundsException $e) {
1032  // If the task could not be fetched, issue an error message
1033  // and exit early
1034  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), ‪FlashMessage::ERROR);
1035  return;
1036  }
1037  // Register single execution
1038  if ((int)$this->submittedData['type'] === ‪AbstractTask::TYPE_SINGLE) {
1039  $task->registerSingleExecution($this->submittedData['start']);
1040  } else {
1041  if (!empty($this->submittedData['croncmd'])) {
1042  // Definition by cron-like syntax
1043  $interval = 0;
1044  $cronCmd = $this->submittedData['croncmd'];
1045  } else {
1046  // Definition by interval
1047  $interval = $this->submittedData['interval'];
1048  $cronCmd = '';
1049  }
1050  // Register recurring execution
1051  $task->registerRecurringExecution($this->submittedData['start'], $interval, $this->submittedData['end'], $this->submittedData['multiple'], $cronCmd);
1052  }
1053  // Set disable flag
1054  $task->setDisabled($this->submittedData['disable']);
1055  // Set description
1056  $task->setDescription($this->submittedData['description']);
1057  // Set task group
1058  $task->setTaskGroup($this->submittedData['task_group']);
1059  // Save additional input values
1060  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1062  $providerObject = GeneralUtility::makeInstance(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1063  if ($providerObject instanceof AdditionalFieldProviderInterface) {
1064  $providerObject->saveAdditionalFields($this->submittedData, $task);
1065  }
1066  }
1067  // Save to database
1068  $result = $this->scheduler->saveTask($task);
1069  if ($result) {
1070  $this->‪getBackendUser()->‪writelog(SystemLogType::EXTENSION, SystemLogGenericAction::UNDEFINED, SystemLogErrorClassification::MESSAGE, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was updated', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
1071  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.updateSuccess'));
1072  } else {
1073  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.updateError'), ‪FlashMessage::ERROR);
1074  }
1075  } else {
1076  // A new task is being created
1077  // Create an instance of chosen class
1079  $task = GeneralUtility::makeInstance($this->submittedData['class']);
1080  if ((int)$this->submittedData['type'] === ‪AbstractTask::TYPE_SINGLE) {
1081  // Set up single execution
1082  $task->registerSingleExecution($this->submittedData['start']);
1083  } else {
1084  // Set up recurring execution
1085  $task->registerRecurringExecution($this->submittedData['start'], $this->submittedData['interval'], $this->submittedData['end'], $this->submittedData['multiple'], $this->submittedData['croncmd']);
1086  }
1087  // Save additional input values
1088  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1090  $providerObject = GeneralUtility::makeInstance(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1091  if ($providerObject instanceof AdditionalFieldProviderInterface) {
1092  $providerObject->saveAdditionalFields($this->submittedData, $task);
1093  }
1094  }
1095  // Set disable flag
1096  $task->setDisabled($this->submittedData['disable']);
1097  // Set description
1098  $task->setDescription($this->submittedData['description']);
1099  // Set description
1100  $task->setTaskGroup($this->submittedData['task_group']);
1101  // Add to database
1102  $result = $this->scheduler->addTask($task);
1103  if ($result) {
1104  $this->‪getBackendUser()->‪writelog(SystemLogType::EXTENSION, SystemLogGenericAction::UNDEFINED, SystemLogErrorClassification::MESSAGE, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was added', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
1105  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.addSuccess'));
1106 
1107  // set the uid of the just created task so that we
1108  // can continue editing after initial saving
1109  $this->submittedData['uid'] = $task->getTaskUid();
1110  } else {
1111  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.addError'), ‪FlashMessage::ERROR);
1112  }
1113  }
1114  }
1115 
1116  /*************************
1117  *
1118  * INPUT PROCESSING UTILITIES
1119  *
1120  *************************/
1126  protected function ‪preprocessData()
1127  {
1128  $cronErrorCode = 0;
1129  $result = true;
1130  // Validate id
1131  $this->submittedData['uid'] = empty($this->submittedData['uid']) ? 0 : (int)$this->submittedData['uid'];
1132  // Validate selected task class
1133  if (!class_exists($this->submittedData['class'])) {
1134  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.noTaskClassFound'), ‪FlashMessage::ERROR);
1135  }
1136  // Check start date
1137  if (empty($this->submittedData['start'])) {
1138  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.noStartDate'), ‪FlashMessage::ERROR);
1139  $result = false;
1140  } elseif (is_string($this->submittedData['start']) && (!is_numeric($this->submittedData['start']))) {
1141  try {
1142  $this->submittedData['start'] = $this->‪convertToTimestamp($this->submittedData['start']);
1143  } catch (\Exception $e) {
1144  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.invalidStartDate'), ‪FlashMessage::ERROR);
1145  $result = false;
1146  }
1147  } else {
1148  $this->submittedData['start'] = (int)$this->submittedData['start'];
1149  }
1150  // Check end date, if recurring task
1151  if ((int)$this->submittedData['type'] === ‪AbstractTask::TYPE_RECURRING && !empty($this->submittedData['end'])) {
1152  if (is_string($this->submittedData['end']) && (!is_numeric($this->submittedData['end']))) {
1153  try {
1154  $this->submittedData['end'] = $this->‪convertToTimestamp($this->submittedData['end']);
1155  } catch (\Exception $e) {
1156  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.invalidStartDate'), ‪FlashMessage::ERROR);
1157  $result = false;
1158  }
1159  } else {
1160  $this->submittedData['end'] = (int)$this->submittedData['end'];
1161  }
1162  if ($this->submittedData['end'] < $this->submittedData['start']) {
1163  $this->‪addMessage(
1164  $this->‪getLanguageService()->getLL('msg.endDateSmallerThanStartDate'),
1166  );
1167  $result = false;
1168  }
1169  }
1170  // Set default values for interval and cron command
1171  $this->submittedData['interval'] = 0;
1172  $this->submittedData['croncmd'] = '';
1173  // Check type and validity of frequency, if recurring
1174  if ((int)$this->submittedData['type'] === ‪AbstractTask::TYPE_RECURRING) {
1175  $frequency = trim($this->submittedData['frequency']);
1176  if (empty($frequency)) {
1177  // Empty frequency, not valid
1178  $this->‪addMessage($this->‪getLanguageService()->getLL('msg.noFrequency'), ‪FlashMessage::ERROR);
1179  $result = false;
1180  } else {
1181  $cronErrorMessage = '';
1182  // Try interpreting the cron command
1183  try {
1184  ‪NormalizeCommand::normalize($frequency);
1185  $this->submittedData['croncmd'] = $frequency;
1186  } catch (\Exception $e) {
1187  // Store the exception's result
1188  $cronErrorMessage = $e->getMessage();
1189  $cronErrorCode = $e->getCode();
1190  // Check if the frequency is a valid number
1191  // If yes, assume it is a frequency in seconds, and unset cron error code
1192  if (is_numeric($frequency)) {
1193  $this->submittedData['interval'] = (int)$frequency;
1194  $cronErrorCode = 0;
1195  }
1196  }
1197  // If there's a cron error code, issue validation error message
1198  if ($cronErrorCode > 0) {
1199  $this->‪addMessage(sprintf($this->‪getLanguageService()->getLL('msg.frequencyError'), $cronErrorMessage, $cronErrorCode), ‪FlashMessage::ERROR);
1200  $result = false;
1201  }
1202  }
1203  }
1204  // Validate additional input fields
1205  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1207  $providerObject = GeneralUtility::makeInstance(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1208  if ($providerObject instanceof AdditionalFieldProviderInterface) {
1209  // The validate method will return true if all went well, but that must not
1210  // override previous false values => AND the returned value with the existing one
1211  $result &= $providerObject->validateAdditionalFields($this->submittedData, $this);
1212  }
1213  }
1214  return $result;
1215  }
1216 
1223  protected function ‪convertToTimestamp(string $input): int
1224  {
1225  // Convert to ISO 8601 dates
1226  $dateTime = new \DateTime($input);
1227  $value = $dateTime->getTimestamp();
1228  if ($value !== 0) {
1229  $value -= date('Z', $value);
1230  }
1231  return $value;
1232  }
1233 
1240  protected function ‪addMessage($message, $severity = ‪FlashMessage::OK)
1241  {
1242  $this->moduleTemplate->addFlashMessage($message, '', $severity);
1243  }
1244 
1258  protected function ‪getRegisteredClasses(): array
1259  {
1260  $list = [];
1261  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] ?? [] as $class => $registrationInformation) {
1262  $title = isset($registrationInformation['title']) ? $this->‪getLanguageService()->‪sL($registrationInformation['title']) : '';
1263  $description = isset($registrationInformation['description']) ? $this->‪getLanguageService()->‪sL($registrationInformation['description']) : '';
1264  $list[$class] = [
1265  'extension' => $registrationInformation['extension'],
1266  'title' => $title,
1267  'description' => $description,
1268  'provider' => $registrationInformation['additionalFields'] ?? ''
1269  ];
1270  }
1271  return $list;
1272  }
1273 
1279  protected function ‪getRegisteredTaskGroups(): array
1280  {
1281  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1282  ->getQueryBuilderForTable('tx_scheduler_task_group');
1283 
1284  return $queryBuilder
1285  ->select('*')
1286  ->from('tx_scheduler_task_group')
1287  ->orderBy('sorting')
1288  ->execute()
1289  ->fetchAll();
1290  }
1291 
1297  protected function ‪getButtons(ServerRequestInterface $request): void
1298  {
1299  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1300  // CSH
1301  $helpButton = $buttonBar->makeHelpButton()
1302  ->setModuleName('_MOD_system_txschedulerM1')
1303  ->setFieldName('');
1304  $buttonBar->addButton($helpButton);
1305 
1306  // Add and Reload
1308  $reloadButton = $buttonBar->makeLinkButton()
1309  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
1310  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', ‪Icon::SIZE_SMALL))
1311  ->setHref($this->moduleUri);
1312  $buttonBar->addButton($reloadButton, ‪ButtonBar::BUTTON_POSITION_RIGHT, 1);
1313  if ($this->MOD_SETTINGS['function'] === 'scheduler' && !empty($this->‪getRegisteredClasses())) {
1314  $addButton = $buttonBar->makeLinkButton()
1315  ->setTitle($this->‪getLanguageService()->getLL('action.add'))
1316  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-add', ‪Icon::SIZE_SMALL))
1317  ->setHref($this->moduleUri . '&CMD=' . ‪Action::ADD);
1318  $buttonBar->addButton($addButton, ‪ButtonBar::BUTTON_POSITION_LEFT, 2);
1319  }
1320  }
1321 
1322  // Close and Save
1323  if (in_array((string)$this->‪getCurrentAction(), [‪Action::ADD, ‪Action::EDIT], true)) {
1324  // Close
1325  $closeButton = $buttonBar->makeLinkButton()
1326  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel'))
1327  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', ‪Icon::SIZE_SMALL))
1328  ->setHref($this->moduleUri);
1329  $buttonBar->addButton($closeButton, ‪ButtonBar::BUTTON_POSITION_LEFT, 2);
1330  // Save, SaveAndClose, SaveAndNew
1331  $saveButtonDropdown = $buttonBar->makeSplitButton();
1332  $saveButton = $buttonBar->makeInputButton()
1333  ->setName('CMD')
1334  ->setValue(‪Action::SAVE)
1335  ->setForm('tx_scheduler_form')
1336  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', ‪Icon::SIZE_SMALL))
1337  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:save'));
1338  $saveButtonDropdown->addItem($saveButton);
1339  $saveAndNewButton = $buttonBar->makeInputButton()
1340  ->setName('CMD')
1341  ->setValue(‪Action::SAVE_NEW)
1342  ->setForm('tx_scheduler_form')
1343  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-new', ‪Icon::SIZE_SMALL))
1344  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.saveAndCreateNewTask'));
1345  $saveButtonDropdown->addItem($saveAndNewButton);
1346  $saveAndCloseButton = $buttonBar->makeInputButton()
1347  ->setName('CMD')
1348  ->setValue(‪Action::SAVE_CLOSE)
1349  ->setForm('tx_scheduler_form')
1350  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-close', ‪Icon::SIZE_SMALL))
1351  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:saveAndClose'));
1352  $saveButtonDropdown->addItem($saveAndCloseButton);
1353  $buttonBar->addButton($saveButtonDropdown, ‪ButtonBar::BUTTON_POSITION_LEFT, 3);
1354  }
1355 
1356  // Delete
1357  if ($this->‪getCurrentAction()->equals(‪Action::EDIT)) {
1358  $deleteButton = $buttonBar->makeLinkButton()
1359  ->setHref($this->moduleUri . '&CMD=' . ‪Action::DELETE . '&tx_scheduler[uid]=' . $request->getQueryParams()['tx_scheduler']['uid'])
1360  ->setClasses('t3js-modal-trigger')
1361  ->setDataAttributes([
1362  'severity' => 'warning',
1363  'title' => $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete'),
1364  'button-close-text' => $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel'),
1365  'content' => $this->‪getLanguageService()->getLL('msg.delete'),
1366  ])
1367  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-edit-delete', ‪Icon::SIZE_SMALL))
1368  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete'));
1369  $buttonBar->addButton($deleteButton, ‪ButtonBar::BUTTON_POSITION_LEFT, 4);
1370  }
1371 
1372  // Shortcut
1373  $shortcutButton = $buttonBar->makeShortcutButton()
1374  ->setModuleName('system_txschedulerM1')
1375  ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
1376  ->setSetVariables(['function']);
1377  $buttonBar->addButton($shortcutButton);
1378  }
1379 
1385  protected function ‪setCurrentAction(‪Action ‪$action): void
1386  {
1387  $this->action = ‪$action;
1388  }
1389 
1393  protected function ‪getServerTime(): string
1394  {
1395  $dateFormat = ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' T (e';
1396  return date($dateFormat) . ', GMT ' . date('P') . ')';
1397  }
1398 
1403  protected function ‪getLanguageService(): LanguageService
1404  {
1405  return ‪$GLOBALS['LANG'];
1406  }
1407 
1413  protected function ‪getBackendUser(): BackendUserAuthentication
1414  {
1415  return ‪$GLOBALS['BE_USER'];
1416  }
1417 
1421  protected function ‪getPageRenderer(): PageRenderer
1422  {
1423  return GeneralUtility::makeInstance(PageRenderer::class);
1424  }
1425 }
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$MOD_SETTINGS
‪array $MOD_SETTINGS
Definition: SchedulerModuleController.php:117
‪TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand\normalize
‪static string normalize($cronCommand)
Definition: NormalizeCommand.php:40
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\SAVE
‪const SAVE
Definition: Action.php:32
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:30
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$scheduler
‪Scheduler $scheduler
Definition: SchedulerModuleController.php:77
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$moduleUri
‪string $moduleUri
Definition: SchedulerModuleController.php:89
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\ADD
‪const ADD
Definition: Action.php:28
‪TYPO3\CMS\Core\Localization\LanguageService\includeLLFile
‪array includeLLFile($fileRef, $setGlobal=null, $mergeLocalOntoDefault=null)
Definition: LanguageService.php:297
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\BUTTON_POSITION_LEFT
‪const BUTTON_POSITION_LEFT
Definition: ButtonBar.php:36
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getBrowseButton
‪string getBrowseButton($fieldID, array $fieldInfo)
Definition: SchedulerModuleController.php:713
‪TYPO3\CMS\Backend\Template\Components\ButtonBar
Definition: ButtonBar.php:32
‪TYPO3\CMS\Core\SysLog\Action
Definition: Cache.php:18
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand
Definition: NormalizeCommand.php:28
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getModuleContent
‪string getModuleContent(Action $previousAction)
Definition: SchedulerModuleController.php:224
‪TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper\STATE_INFO
‪const STATE_INFO
Definition: InfoboxViewHelper.php:62
‪TYPO3\CMS\Scheduler\Controller
Definition: SchedulerModuleController.php:18
‪TYPO3\CMS\Core\Registry
Definition: Registry.php:33
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\LIST
‪const LIST
Definition: Action.php:31
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\SET_NEXT_EXECUTION_TIME
‪const SET_NEXT_EXECUTION_TIME
Definition: Action.php:35
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getModuleMenu
‪getModuleMenu()
Definition: SchedulerModuleController.php:189
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\executeTasks
‪executeTasks()
Definition: SchedulerModuleController.php:736
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\deleteTask
‪deleteTask()
Definition: SchedulerModuleController.php:407
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$moduleTemplate
‪ModuleTemplate $moduleTemplate
Definition: SchedulerModuleController.php:95
‪TYPO3\CMS\Core\Core\Environment\isWindows
‪static bool isWindows()
Definition: Environment.php:292
‪TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper\STATE_ERROR
‪const STATE_ERROR
Definition: InfoboxViewHelper.php:65
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:33
‪TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper\STATE_WARNING
‪const STATE_WARNING
Definition: InfoboxViewHelper.php:64
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:654
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getRegisteredTaskGroups
‪array getRegisteredTaskGroups()
Definition: SchedulerModuleController.php:1267
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\preprocessData
‪bool preprocessData()
Definition: SchedulerModuleController.php:1114
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:194
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\__construct
‪__construct()
Definition: SchedulerModuleController.php:122
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$cshKey
‪string $cshKey
Definition: SchedulerModuleController.php:73
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:43
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$backendTemplatePath
‪string $backendTemplatePath
Definition: SchedulerModuleController.php:81
‪TYPO3\CMS\Core\Page\PageRenderer\loadRequireJsModule
‪loadRequireJsModule($mainModuleName, $callBackFunction=null)
Definition: PageRenderer.php:1493
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\convertToTimestamp
‪int convertToTimestamp(string $input)
Definition: SchedulerModuleController.php:1211
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:43
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Core\Page\PageRenderer
Definition: PageRenderer.php:42
‪TYPO3\CMS\Core\Messaging\AbstractMessage\WARNING
‪const WARNING
Definition: AbstractMessage.php:30
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\TOGGLE_HIDDEN
‪const TOGGLE_HIDDEN
Definition: Action.php:37
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\infoScreenAction
‪string infoScreenAction()
Definition: SchedulerModuleController.php:389
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$action
‪Action $action
Definition: SchedulerModuleController.php:103
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$iconFactory
‪IconFactory $iconFactory
Definition: SchedulerModuleController.php:99
‪TYPO3\CMS\Scheduler\Task\AbstractTask
Definition: AbstractTask.php:35
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\STOP
‪const STOP
Definition: Action.php:36
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\toggleDisableAction
‪toggleDisableAction()
Definition: SchedulerModuleController.php:472
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getLanguageService
‪LanguageService getLanguageService()
Definition: SchedulerModuleController.php:1391
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController
Definition: SchedulerModuleController.php:56
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:38
‪TYPO3\CMS\Backend\Utility\BackendUtility\getModuleData
‪static array getModuleData( $MOD_MENU, $CHANGED_SETTINGS, $modName, $type='', $dontValidateList='', $setDefaultList='')
Definition: BackendUtility.php:2893
‪TYPO3\CMS\Scheduler\ProgressProviderInterface
Definition: ProgressProviderInterface.php:22
‪TYPO3\CMS\Scheduler\Scheduler
Definition: Scheduler.php:36
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\EDIT
‪const EDIT
Definition: Action.php:30
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\stopTask
‪stopTask()
Definition: SchedulerModuleController.php:446
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\addMessage
‪addMessage($message, $severity=FlashMessage::OK)
Definition: SchedulerModuleController.php:1228
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getCurrentAction
‪Action getCurrentAction()
Definition: SchedulerModuleController.php:181
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\setCurrentAction
‪setCurrentAction(Action $action)
Definition: SchedulerModuleController.php:1373
‪TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper\STATE_OK
‪const STATE_OK
Definition: InfoboxViewHelper.php:63
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\DELETE
‪const DELETE
Definition: Action.php:29
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\setNextExecutionTimeAction
‪setNextExecutionTimeAction()
Definition: SchedulerModuleController.php:487
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$MOD_MENU
‪array $MOD_MENU
Definition: SchedulerModuleController.php:109
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\checkScreenAction
‪string checkScreenAction()
Definition: SchedulerModuleController.php:324
‪TYPO3\CMS\Core\Messaging\AbstractMessage\OK
‪const OK
Definition: AbstractMessage.php:29
‪TYPO3\CMS\Scheduler\Task\AbstractTask\TYPE_RECURRING
‪const TYPE_RECURRING
Definition: AbstractTask.php:39
‪TYPO3\CMS\Core\Core\Environment\isComposerMode
‪static bool isComposerMode()
Definition: Environment.php:144
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$messages
‪array $messages
Definition: SchedulerModuleController.php:69
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:24
‪TYPO3\CMS\Fluid\View\StandaloneView
Definition: StandaloneView.php:34
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication\writelog
‪int writelog($type, $action, $error, $details_nr, $details, $data, $tablename='', $recuid='', $recpid='', $event_pid=-1, $NEWid='', $userId=0)
Definition: BackendUserAuthentication.php:2290
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action
Definition: Action.php:26
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getServerTime
‪string getServerTime()
Definition: SchedulerModuleController.php:1381
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\SAVE_CLOSE
‪const SAVE_CLOSE
Definition: Action.php:33
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Scheduler\Task\AbstractTask\TYPE_SINGLE
‪const TYPE_SINGLE
Definition: AbstractTask.php:38
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:40
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static string extPath($key, $script='')
Definition: ExtensionManagementUtility.php:127
‪TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper
Definition: InfoboxViewHelper.php:59
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$view
‪StandaloneView $view
Definition: SchedulerModuleController.php:85
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getButtons
‪getButtons(ServerRequestInterface $request)
Definition: SchedulerModuleController.php:1285
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\saveTask
‪saveTask()
Definition: SchedulerModuleController.php:1011
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getRegisteredClasses
‪array getRegisteredClasses()
Definition: SchedulerModuleController.php:1246
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Localization\LanguageService\getLL
‪string getLL($index)
Definition: LanguageService.php:154
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\mainAction
‪ResponseInterface mainAction(ServerRequestInterface $request)
Definition: SchedulerModuleController.php:144
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\BUTTON_POSITION_RIGHT
‪const BUTTON_POSITION_RIGHT
Definition: ButtonBar.php:41
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\editTaskAction
‪string editTaskAction()
Definition: SchedulerModuleController.php:499
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\makeStatusLabel
‪string makeStatusLabel(array $labels)
Definition: SchedulerModuleController.php:995
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getPageRenderer
‪PageRenderer getPageRenderer()
Definition: SchedulerModuleController.php:1409
‪TYPO3\CMS\Core\Messaging\AbstractMessage\ERROR
‪const ERROR
Definition: AbstractMessage.php:31
‪TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface
Definition: AdditionalFieldProviderInterface.php:25
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\getBackendUser
‪TYPO3 CMS Core Authentication BackendUserAuthentication getBackendUser()
Definition: SchedulerModuleController.php:1401
‪TYPO3\CMS\Core\Http\HtmlResponse
Definition: HtmlResponse.php:26
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:24
‪TYPO3\CMS\Scheduler\Task\Enumeration\Action\SAVE_NEW
‪const SAVE_NEW
Definition: Action.php:34
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\$submittedData
‪array $submittedData
Definition: SchedulerModuleController.php:62
‪TYPO3\CMS\Scheduler\Controller\SchedulerModuleController\listTasksAction
‪string listTasksAction()
Definition: SchedulerModuleController.php:784