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