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