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