Test Failed
Branch master (7b1793)
by Tymoteusz
15:35
created

SchedulerModuleController::deleteTask()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 30
Code Lines 21

Duplication

Lines 8
Ratio 26.67 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
nc 24
nop 0
dl 8
loc 30
rs 8.439
c 0
b 0
f 0
1
<?php
2
namespace TYPO3\CMS\Scheduler\Controller;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
20
use TYPO3\CMS\Backend\Template\ModuleTemplate;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Core\Database\ConnectionPool;
23
use TYPO3\CMS\Core\Imaging\Icon;
24
use TYPO3\CMS\Core\Imaging\IconFactory;
25
use TYPO3\CMS\Core\Localization\LanguageService;
26
use TYPO3\CMS\Core\Messaging\FlashMessage;
27
use TYPO3\CMS\Core\Page\PageRenderer;
28
use TYPO3\CMS\Core\Registry;
29
use TYPO3\CMS\Core\Utility\ArrayUtility;
30
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Fluid\View\StandaloneView;
33
use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
34
use TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface;
35
use TYPO3\CMS\Scheduler\CronCommand\NormalizeCommand;
36
use TYPO3\CMS\Scheduler\ProgressProviderInterface;
37
use TYPO3\CMS\Scheduler\Scheduler;
38
use TYPO3\CMS\Scheduler\Task\AbstractTask;
39
40
/**
41
 * Module 'TYPO3 Scheduler administration module' for the 'scheduler' extension.
42
 */
43
class SchedulerModuleController
44
{
45
    /**
46
     * Array containing submitted data when editing or adding a task
47
     *
48
     * @var array
49
     */
50
    protected $submittedData = [];
51
52
    /**
53
     * Array containing all messages issued by the application logic
54
     * Contains the error's severity and the message itself
55
     *
56
     * @var array
57
     */
58
    protected $messages = [];
59
60
    /**
61
     * @var string Key of the CSH file
62
     */
63
    protected $cshKey = '_MOD_system_txschedulerM1';
64
65
    /**
66
     * @var Scheduler Local scheduler instance
67
     */
68
    protected $scheduler;
69
70
    /**
71
     * @var string
72
     */
73
    protected $backendTemplatePath = '';
74
75
    /**
76
     * @var StandaloneView
77
     */
78
    protected $view;
79
80
    /**
81
     * @var string Base URI of scheduler module
82
     */
83
    protected $moduleUri;
84
85
    /**
86
     * ModuleTemplate Container
87
     *
88
     * @var ModuleTemplate
89
     */
90
    protected $moduleTemplate;
91
92
    /**
93
     * @var IconFactory
94
     */
95
    protected $iconFactory;
96
97
    /**
98
     * The value of GET/POST var, 'CMD'
99
     *
100
     * @see init()
101
     * @var mixed
102
     */
103
    public $CMD;
104
105
    /**
106
     * The module menu items array. Each key represents a key for which values can range between the items in the array of that key.
107
     *
108
     * @see init()
109
     * @var array
110
     */
111
    protected $MOD_MENU = [
112
        'function' => []
113
    ];
114
115
    /**
116
     * Current settings for the keys of the MOD_MENU array
117
     *
118
     * @see $MOD_MENU
119
     * @var array
120
     */
121
    protected $MOD_SETTINGS = [];
122
123
    /**
124
     * Default constructor
125
     */
126
    public function __construct()
127
    {
128
        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
129
        $this->getLanguageService()->includeLLFile('EXT:scheduler/Resources/Private/Language/locallang.xlf');
130
        $this->backendTemplatePath = ExtensionManagementUtility::extPath('scheduler') . 'Resources/Private/Templates/Backend/SchedulerModule/';
131
        $this->view = GeneralUtility::makeInstance(StandaloneView::class);
132
        $this->view->getRequest()->setControllerExtensionName('scheduler');
133
        $this->view->setPartialRootPaths([ExtensionManagementUtility::extPath('scheduler') . 'Resources/Private/Partials/Backend/SchedulerModule/']);
134
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
135
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
136
        $this->moduleUri = (string)$uriBuilder->buildUriFromRoute('system_txschedulerM1');
137
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
138
        $this->scheduler = GeneralUtility::makeInstance(Scheduler::class);
139
140
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
141
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/SplitButtons');
142
    }
143
144
    /**
145
     * Injects the request object for the current request or subrequest
146
     * Simply calls main() and init() and outputs the content
147
     *
148
     * @param ServerRequestInterface $request the current request
149
     * @param ResponseInterface $response
150
     * @return ResponseInterface the response with the content
151
     */
152
    public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

152
    public function mainAction(/** @scrutinizer ignore-unused */ ServerRequestInterface $request, ResponseInterface $response)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
153
    {
154
        $this->CMD = GeneralUtility::_GP('CMD');
155
        $this->MOD_MENU = [
156
            'function' => [
157
                'scheduler' => $this->getLanguageService()->getLL('function.scheduler'),
158
                'check' => $this->getLanguageService()->getLL('function.check'),
159
                'info' => $this->getLanguageService()->getLL('function.info')
160
            ]
161
        ];
162
        $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), 'system_txschedulerM1', '', '', '');
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Core\Utility\GeneralUtility::_GP('SET') can also be of type string; however, parameter $CHANGED_SETTINGS of TYPO3\CMS\Backend\Utilit...tility::getModuleData() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
        $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, /** @scrutinizer ignore-type */ GeneralUtility::_GP('SET'), 'system_txschedulerM1', '', '', '');
Loading history...
163
        // Access check!
164
        // The page will show only if user has admin rights
165
        if ($this->getBackendUser()->isAdmin()) {
166
            // Set the form
167
            $content = '<form name="tx_scheduler_form" id="tx_scheduler_form" method="post" action="">';
168
169
            // Prepare main content
170
            $content .= '<h1>' . $this->getLanguageService()->getLL('function.' . $this->MOD_SETTINGS['function']) . '</h1>';
171
            $content .= $this->getModuleContent();
172
            $content .= '<div id="extraFieldsSection"></div></form><div id="extraFieldsHidden"></div>';
173
        } else {
174
            // If no access, only display the module's title
175
            $content = '<h1>' . $this->getLanguageService()->getLL('title.') . '</h1>';
176
            $content .='<div style="padding-top: 5px;"></div>';
177
        }
178
        $this->getButtons();
179
        $this->getModuleMenu();
180
181
        $this->moduleTemplate->setContent($content);
182
        $response->getBody()->write($this->moduleTemplate->renderContent());
183
        return $response;
184
    }
185
186
    /**
187
     * Generates the action menu
188
     */
189 View Code Duplication
    protected function getModuleMenu()
190
    {
191
        $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
192
        $menu->setIdentifier('SchedulerJumpMenu');
193
        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
194
        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
195
        foreach ($this->MOD_MENU['function'] as $controller => $title) {
196
            $item = $menu
197
                ->makeMenuItem()
198
                ->setHref(
199
                    (string)$uriBuilder->buildUriFromRoute(
200
                        'system_txschedulerM1',
201
                        [
202
                            'id' => 0,
203
                            'SET' => [
204
                                'function' => $controller
205
                            ]
206
                        ]
207
                    )
208
                )
209
                ->setTitle($title);
210
            if ($controller === $this->MOD_SETTINGS['function']) {
211
                $item->setActive(true);
212
            }
213
            $menu->addMenuItem($item);
214
        }
215
        $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
216
    }
217
218
    /**
219
     * Generate the module's content
220
     *
221
     * @return string HTML of the module's main content
222
     */
223
    protected function getModuleContent()
224
    {
225
        $content = '';
226
        $sectionTitle = '';
227
        // Get submitted data
228
        $this->submittedData = GeneralUtility::_GPmerged('tx_scheduler');
229
        $this->submittedData['uid'] = (int)$this->submittedData['uid'];
230
        // If a save command was submitted, handle saving now
231
        if ($this->CMD === 'save' || $this->CMD === 'saveclose' || $this->CMD === 'savenew') {
232
            $previousCMD = GeneralUtility::_GP('previousCMD');
233
            // First check the submitted data
234
            $result = $this->preprocessData();
235
            // If result is ok, proceed with saving
236
            if ($result) {
237
                $this->saveTask();
238
                if ($this->CMD === 'saveclose') {
239
                    // Unset command, so that default screen gets displayed
240
                    unset($this->CMD);
241
                } elseif ($this->CMD === 'save') {
242
                    // After saving a "add form", return to edit
243
                    $this->CMD = 'edit';
244
                } elseif ($this->CMD === 'savenew') {
245
                    // Unset submitted data, so that empty form gets displayed
246
                    unset($this->submittedData);
247
                    // After saving a "add/edit form", return to add
248
                    $this->CMD = 'add';
249
                } else {
250
                    // Return to edit form
251
                    $this->CMD = $previousCMD;
252
                }
253
            } else {
254
                $this->CMD = $previousCMD;
255
            }
256
        }
257
258
        // Handle chosen action
259
        switch ((string)$this->MOD_SETTINGS['function']) {
260
            case 'scheduler':
261
                $this->executeTasks();
262
263
                switch ($this->CMD) {
264
                    case 'add':
265
                    case 'edit':
266
                        try {
267
                            // Try adding or editing
268
                            $content .= $this->editTaskAction();
269
                            $sectionTitle = $this->getLanguageService()->getLL('action.' . $this->CMD);
270
                        } catch (\Exception $e) {
271
                            if ($e->getCode() === 1305100019) {
272
                                // Invalid controller class name exception
273
                                $this->addMessage($e->getMessage(), FlashMessage::ERROR);
274
                            }
275
                            // An exception may also happen when the task to
276
                            // edit could not be found. In this case revert
277
                            // to displaying the list of tasks
278
                            // It can also happen when attempting to edit a running task
279
                            $content .= $this->listTasksAction();
280
                        }
281
                        break;
282
                    case 'delete':
283
                        $this->deleteTask();
284
                        $content .= $this->listTasksAction();
285
                        break;
286
                    case 'stop':
287
                        $this->stopTask();
288
                        $content .= $this->listTasksAction();
289
                        break;
290
                    case 'toggleHidden':
291
                        $this->toggleDisableAction();
292
                        $content .= $this->listTasksAction();
293
                        break;
294
                    case 'setNextExecutionTime':
295
                        $this->setNextExecutionTimeAction();
296
                        $content .= $this->listTasksAction();
297
                        break;
298
                    case 'list':
299
300
                    default:
301
                        $content .= $this->listTasksAction();
302
                }
303
                break;
304
305
            // Setup check screen
306
            case 'check':
307
                // @todo move check to the report module
308
                $content .= $this->checkScreenAction();
309
                break;
310
311
            // Information screen
312
            case 'info':
313
                $content .= $this->infoScreenAction();
314
                break;
315
        }
316
        // Wrap the content
317
        return '<h2>' . $sectionTitle . '</h2><div class="tx_scheduler_mod1">' . $content . '</div>';
318
    }
319
320
    /**
321
     * This method displays the result of a number of checks
322
     * on whether the Scheduler is ready to run or running properly
323
     *
324
     * @return string Further information
325
     */
326
    protected function checkScreenAction()
327
    {
328
        $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'CheckScreen.html');
329
330
        // Display information about last automated run, as stored in the system registry
331
        $registry = GeneralUtility::makeInstance(Registry::class);
332
        $lastRun = $registry->get('tx_scheduler', 'lastRun');
333
        if (!is_array($lastRun)) {
334
            $message = $this->getLanguageService()->getLL('msg.noLastRun');
335
            $severity = InfoboxViewHelper::STATE_WARNING;
336
        } else {
337
            if (empty($lastRun['end']) || empty($lastRun['start']) || empty($lastRun['type'])) {
338
                $message = $this->getLanguageService()->getLL('msg.incompleteLastRun');
339
                $severity = InfoboxViewHelper::STATE_WARNING;
340
            } else {
341
                $startDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']);
342
                $startTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']);
343
                $endDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']);
344
                $endTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']);
345
                $label = 'automatically';
346
                if ($lastRun['type'] === 'manual') {
347
                    $label = 'manually';
348
                }
349
                $type = $this->getLanguageService()->getLL('label.' . $label);
350
                $message = sprintf($this->getLanguageService()->getLL('msg.lastRun'), $type, $startDate, $startTime, $endDate, $endTime);
0 ignored issues
show
Bug introduced by
It seems like $startDate can also be of type false; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
                $message = sprintf($this->getLanguageService()->getLL('msg.lastRun'), $type, /** @scrutinizer ignore-type */ $startDate, $startTime, $endDate, $endTime);
Loading history...
351
                $severity = InfoboxViewHelper::STATE_INFO;
352
            }
353
        }
354
        $this->view->assign('lastRunMessage', $message);
355
        $this->view->assign('lastRunSeverity', $severity);
356
357
        // Check if CLI script is executable or not
358
        $script = PATH_site . 'typo3/sysext/core/bin/typo3';
359
        $this->view->assign('script', $script);
360
361
        // Skip this check if running Windows, as rights do not work the same way on this platform
362
        // (i.e. the script will always appear as *not* executable)
363
        if (TYPO3_OS === 'WIN') {
364
            $isExecutable = true;
365
        } else {
366
            $isExecutable = is_executable($script);
367
        }
368
        if ($isExecutable) {
369
            $message = $this->getLanguageService()->getLL('msg.cliScriptExecutable');
370
            $severity = InfoboxViewHelper::STATE_OK;
371
        } else {
372
            $message = $this->getLanguageService()->getLL('msg.cliScriptNotExecutable');
373
            $severity = InfoboxViewHelper::STATE_ERROR;
374
        }
375
        $this->view->assign('isExecutableMessage', $message);
376
        $this->view->assign('isExecutableSeverity', $severity);
377
        $this->view->assign('now', $this->getServerTime());
378
379
        return $this->view->render();
380
    }
381
382
    /**
383
     * This method gathers information about all available task classes and displays it
384
     *
385
     * @return string html
386
     */
387
    protected function infoScreenAction()
388
    {
389
        $registeredClasses = $this->getRegisteredClasses();
390
        // No classes available, display information message
391
        if (empty($registeredClasses)) {
392
            $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'InfoScreenNoClasses.html');
393
            return $this->view->render();
394
        }
395
396
        $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'InfoScreen.html');
397
        $this->view->assign('registeredClasses', $registeredClasses);
398
399
        return $this->view->render();
400
    }
401
402
    /**
403
     * Renders the task progress bar.
404
     *
405
     * @param float $progress Task progress
406
     * @return string Progress bar markup
407
     */
408
    protected function renderTaskProgressBar($progress)
409
    {
410
        $progressText = $this->getLanguageService()->getLL('status.progress') . ':&nbsp;' . $progress . '%';
411
        return '<div class="progress">'
412
        . '<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="' . $progress . '" aria-valuemin="0" aria-valuemax="100" style="width: ' . $progress . '%;">' . $progressText . '</div>'
413
        . '</div>';
414
    }
415
416
    /**
417
     * Delete a task from the execution queue
418
     */
419
    protected function deleteTask()
420
    {
421
        try {
422
            // Try to fetch the task and delete it
423
            $task = $this->scheduler->fetchTask($this->submittedData['uid']);
424
            // If the task is currently running, it may not be deleted
425
            if ($task->isExecutionRunning()) {
426
                $this->addMessage($this->getLanguageService()->getLL('msg.maynotDeleteRunningTask'), FlashMessage::ERROR);
427 View Code Duplication
            } else {
428
                if ($this->scheduler->removeTask($task)) {
429
                    $this->getBackendUser()->writelog(4, 0, 0, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was deleted', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
430
                    $this->addMessage($this->getLanguageService()->getLL('msg.deleteSuccess'));
431
                } else {
432
                    $this->addMessage($this->getLanguageService()->getLL('msg.deleteError'), FlashMessage::ERROR);
433
                }
434
            }
435
        } catch (\UnexpectedValueException $e) {
436
            // The task could not be unserialized properly, simply update the database record
437
            $taskUid = (int)$this->submittedData['uid'];
438
            $result = GeneralUtility::makeInstance(ConnectionPool::class)
439
                ->getConnectionForTable('tx_scheduler_task')
440
                ->update('tx_scheduler_task', ['deleted' => 1], ['uid' => $taskUid]);
441
            if ($result) {
442
                $this->addMessage($this->getLanguageService()->getLL('msg.deleteSuccess'));
443
            } else {
444
                $this->addMessage($this->getLanguageService()->getLL('msg.deleteError'), FlashMessage::ERROR);
445
            }
446
        } catch (\OutOfBoundsException $e) {
447
            // The task was not found, for some reason
448
            $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
449
        }
450
    }
451
452
    /**
453
     * Clears the registered running executions from the task
454
     * Note that this doesn't actually stop the running script. It just unmarks
455
     * all executions.
456
     * @todo find a way to really kill the running task
457
     */
458
    protected function stopTask()
459
    {
460
        try {
461
            // Try to fetch the task and stop it
462
            $task = $this->scheduler->fetchTask($this->submittedData['uid']);
463
            if ($task->isExecutionRunning()) {
464
                // If the task is indeed currently running, clear marked executions
465
                $result = $task->unmarkAllExecutions();
466
                if ($result) {
467
                    $this->addMessage($this->getLanguageService()->getLL('msg.stopSuccess'));
468
                } else {
469
                    $this->addMessage($this->getLanguageService()->getLL('msg.stopError'), FlashMessage::ERROR);
470
                }
471
            } else {
472
                // The task is not running, nothing to unmark
473
                $this->addMessage($this->getLanguageService()->getLL('msg.maynotStopNonRunningTask'), FlashMessage::WARNING);
474
            }
475
        } catch (\Exception $e) {
476
            // The task was not found, for some reason
477
            $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
478
        }
479
    }
480
481
    /**
482
     * Toggles the disabled state of the submitted task
483
     */
484
    protected function toggleDisableAction()
485
    {
486
        $task = $this->scheduler->fetchTask($this->submittedData['uid']);
487
        $task->setDisabled(!$task->isDisabled());
488
        // If a disabled single task is enabled again, we register it for a
489
        // single execution at next scheduler run.
490
        if ($task->getType() === AbstractTask::TYPE_SINGLE) {
491
            $task->registerSingleExecution(time());
492
        }
493
        $task->save();
494
    }
495
496
    /**
497
     * Sets the next execution time of the submitted task to now
498
     */
499
    protected function setNextExecutionTimeAction()
500
    {
501
        $task = $this->scheduler->fetchTask($this->submittedData['uid']);
502
        $task->setRunOnNextCronJob(true);
503
        $task->save();
504
    }
505
506
    /**
507
     * Return a form to add a new task or edit an existing one
508
     *
509
     * @return string HTML form to add or edit a task
510
     */
511
    protected function editTaskAction()
512
    {
513
        $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'EditTask.html');
514
515
        $registeredClasses = $this->getRegisteredClasses();
516
        $registeredTaskGroups = $this->getRegisteredTaskGroups();
517
518
        $taskInfo = [];
519
        $task = null;
520
        $process = 'edit';
521
522
        if ($this->submittedData['uid'] > 0) {
523
            // If editing, retrieve data for existing task
524
            try {
525
                $taskRecord = $this->scheduler->fetchTaskRecord($this->submittedData['uid']);
526
                // If there's a registered execution, the task should not be edited
527
                if (!empty($taskRecord['serialized_executions'])) {
528
                    $this->addMessage($this->getLanguageService()->getLL('msg.maynotEditRunningTask'), FlashMessage::ERROR);
529
                    throw new \LogicException('Runnings tasks cannot not be edited', 1251232849);
530
                }
531
532
                // Get the task object
533
                /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask */
534
                $task = unserialize($taskRecord['serialized_task_object']);
535
536
                // Set some task information
537
                $taskInfo['disable'] = $taskRecord['disable'];
538
                $taskInfo['description'] = $taskRecord['description'];
539
                $taskInfo['task_group'] = $taskRecord['task_group'];
540
541
                // Check that the task object is valid
542
                if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
543
                    // The task object is valid, process with fetching current data
544
                    $taskInfo['class'] = get_class($task);
545
                    // Get execution information
546
                    $taskInfo['start'] = (int)$task->getExecution()->getStart();
547
                    $taskInfo['end'] = (int)$task->getExecution()->getEnd();
548
                    $taskInfo['interval'] = $task->getExecution()->getInterval();
549
                    $taskInfo['croncmd'] = $task->getExecution()->getCronCmd();
550
                    $taskInfo['multiple'] = $task->getExecution()->getMultiple();
551
                    if (!empty($taskInfo['interval']) || !empty($taskInfo['croncmd'])) {
552
                        // Guess task type from the existing information
553
                        // If an interval or a cron command is defined, it's a recurring task
554
                        $taskInfo['type'] = AbstractTask::TYPE_RECURRING;
555
                        $taskInfo['frequency'] = $taskInfo['interval'] ?: $taskInfo['croncmd'];
556
                    } else {
557
                        // It's not a recurring task
558
                        // Make sure interval and cron command are both empty
559
                        $taskInfo['type'] = AbstractTask::TYPE_SINGLE;
560
                        $taskInfo['frequency'] = '';
561
                        $taskInfo['end'] = 0;
562
                    }
563
                } else {
564
                    // The task object is not valid
565
                    // Issue error message
566
                    $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.invalidTaskClassEdit'), get_class($task)), FlashMessage::ERROR);
567
                    // Initialize empty values
568
                    $taskInfo['start'] = 0;
569
                    $taskInfo['end'] = 0;
570
                    $taskInfo['frequency'] = '';
571
                    $taskInfo['multiple'] = false;
572
                    $taskInfo['type'] = AbstractTask::TYPE_SINGLE;
573
                }
574
            } catch (\OutOfBoundsException $e) {
575
                // Add a message and continue throwing the exception
576
                $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
577
                throw $e;
578
            }
579
        } else {
580
            // If adding a new object, set some default values
581
            $taskInfo['class'] = key($registeredClasses);
582
            $taskInfo['type'] = AbstractTask::TYPE_RECURRING;
583
            $taskInfo['start'] = $GLOBALS['EXEC_TIME'];
584
            $taskInfo['end'] = '';
585
            $taskInfo['frequency'] = '';
586
            $taskInfo['multiple'] = 0;
587
            $process = 'add';
588
        }
589
590
        // If some data was already submitted, use it to override
591
        // existing data
592
        if (!empty($this->submittedData)) {
593
            ArrayUtility::mergeRecursiveWithOverrule($taskInfo, $this->submittedData);
594
        }
595
596
        // Get the extra fields to display for each task that needs some
597
        $allAdditionalFields = [];
598
        if ($process === 'add') {
599
            foreach ($registeredClasses as $class => $registrationInfo) {
600
                if (!empty($registrationInfo['provider'])) {
601
                    /** @var $providerObject AdditionalFieldProviderInterface */
602
                    $providerObject = GeneralUtility::makeInstance($registrationInfo['provider']);
603
                    if ($providerObject instanceof AdditionalFieldProviderInterface) {
604
                        $additionalFields = $providerObject->getAdditionalFields($taskInfo, null, $this);
605
                        $allAdditionalFields = array_merge($allAdditionalFields, [$class => $additionalFields]);
606
                    }
607
                }
608
            }
609
        } else {
610
            if (!empty($registeredClasses[$taskInfo['class']]['provider'])) {
611
                $providerObject = GeneralUtility::makeInstance($registeredClasses[$taskInfo['class']]['provider']);
612
                if ($providerObject instanceof AdditionalFieldProviderInterface) {
613
                    $allAdditionalFields[$taskInfo['class']] = $providerObject->getAdditionalFields($taskInfo, $task, $this);
614
                }
615
            }
616
        }
617
618
        // Load necessary JavaScript
619
        $this->getPageRenderer()->loadJquery();
620
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
621
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/DateTimePicker');
622
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Scheduler/PageBrowser');
623
        $this->getPageRenderer()->addJsInlineCode('browse-button', '
624
            function setFormValueFromBrowseWin(fieldReference, elValue, elName) {
625
                var res = elValue.split("_");
626
                var element = document.getElementById(fieldReference);
627
                element.value = res[1];
628
            }
629
        ');
630
631
        // Start rendering the add/edit form
632
        $this->view->assign('uid', htmlspecialchars($this->submittedData['uid']));
633
        $this->view->assign('cmd', htmlspecialchars($this->CMD));
634
        $this->view->assign('csh', $this->cshKey);
635
        $this->view->assign('lang', 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:');
636
637
        $table = [];
638
639
        // Disable checkbox
640
        $this->view->assign('task_disable', ($taskInfo['disable'] ? ' checked="checked"' : ''));
641
        $this->view->assign('task_disable_label', 'LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disable');
642
643
        // Task class selector
644
        // On editing, don't allow changing of the task class, unless it was not valid
645
        if ($this->submittedData['uid'] > 0 && !empty($taskInfo['class'])) {
646
            $this->view->assign('task_class', $taskInfo['class']);
647
            $this->view->assign('task_class_title', $registeredClasses[$taskInfo['class']]['title']);
648
            $this->view->assign('task_class_extension', $registeredClasses[$taskInfo['class']]['extension']);
649
        } else {
650
            // Group registered classes by classname
651
            $groupedClasses = [];
652
            foreach ($registeredClasses as $class => $classInfo) {
653
                $groupedClasses[$classInfo['extension']][$class] = $classInfo;
654
            }
655
            ksort($groupedClasses);
656
            foreach ($groupedClasses as $extension => $class) {
657
                foreach ($groupedClasses[$extension] as $class => $classInfo) {
658
                    $selected = $class == $taskInfo['class'] ? ' selected="selected"' : '';
659
                    $groupedClasses[$extension][$class]['selected'] = $selected;
660
                }
661
            }
662
            $this->view->assign('groupedClasses', $groupedClasses);
663
        }
664
665
        // Task type selector
666
        $this->view->assign('task_type_selected_1', ((int)$taskInfo['type'] === AbstractTask::TYPE_SINGLE ? ' selected="selected"' : ''));
667
        $this->view->assign('task_type_selected_2', ((int)$taskInfo['type'] === AbstractTask::TYPE_RECURRING ? ' selected="selected"' : ''));
668
669
        // Task group selector
670
        foreach ($registeredTaskGroups as $key => $taskGroup) {
671
            $selected = $taskGroup['uid'] == $taskInfo['task_group'] ? ' selected="selected"' : '';
672
            $registeredTaskGroups[$key]['selected'] = $selected;
673
        }
674
        $this->view->assign('registeredTaskGroups', $registeredTaskGroups);
675
676
        // Start date/time field
677
        $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '%H:%M %m-%d-%Y' : '%H:%M %d-%m-%Y';
678
        $this->view->assign('start_value_hr', ($taskInfo['start'] > 0 ? strftime($dateFormat, $taskInfo['start']) : ''));
679
        $this->view->assign('start_value', $taskInfo['start']);
680
681
        // End date/time field
682
        // NOTE: datetime fields need a special id naming scheme
683
        $this->view->assign('end_value_hr', ($taskInfo['end'] > 0 ? strftime($dateFormat, $taskInfo['end']) : ''));
684
        $this->view->assign('end_value', $taskInfo['end']);
685
686
        // Frequency input field
687
        $this->view->assign('frequency', $taskInfo['frequency']);
688
689
        // Multiple execution selector
690
        $this->view->assign('multiple', ($taskInfo['multiple'] ? 'checked="checked"' : ''));
691
692
        // Description
693
        $this->view->assign('description', $taskInfo['description']);
694
695
        // Display additional fields
696
        $additionalFieldList = [];
697
        foreach ($allAdditionalFields as $class => $fields) {
698
            if ($class == $taskInfo['class']) {
699
                $additionalFieldsStyle = '';
700
            } else {
701
                $additionalFieldsStyle = ' style="display: none"';
702
            }
703
            // Add each field to the display, if there are indeed any
704
            if (isset($fields) && is_array($fields)) {
705
                foreach ($fields as $fieldID => $fieldInfo) {
706
                    $htmlClassName = strtolower(str_replace('\\', '-', $class));
707
                    $field = [];
708
                    $field['htmlClassName'] = $htmlClassName;
709
                    $field['code'] = $fieldInfo['code'];
710
                    $field['cshKey'] = $fieldInfo['cshKey'];
711
                    $field['cshLabel'] = $fieldInfo['cshLabel'];
712
                    $field['langLabel'] = $fieldInfo['label'];
713
                    $field['fieldID'] = $fieldID;
714
                    $field['additionalFieldsStyle'] = $additionalFieldsStyle;
715
                    $field['browseButton'] = $this->getBrowseButton($fieldID, $fieldInfo);
716
                    $additionalFieldList[] = $field;
717
                }
718
            }
719
        }
720
        $this->view->assign('additionalFields', $additionalFieldList);
721
722
        $this->view->assign('table', implode(LF, $table));
723
        $this->view->assign('now', $this->getServerTime());
724
725
        return $this->view->render();
726
    }
727
728
    /**
729
     * @param string $fieldID The id of the field witch contains the page id
730
     * @param array $fieldInfo The array with the field info, contains the page title shown beside the button
731
     * @return string HTML code for the browse button
732
     */
733
    protected function getBrowseButton($fieldID, array $fieldInfo)
734
    {
735
        if (isset($fieldInfo['browser']) && ($fieldInfo['browser'] === 'page')) {
736
            /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
737
            $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
738
            $url = (string)$uriBuilder->buildUriFromRoute(
739
                'wizard_element_browser',
740
                ['mode' => 'db', 'bparams' => $fieldID . '|||pages|']
741
            );
742
            $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.browse_db'));
743
            return '
744
                <div><a href="#" data-url=' . htmlspecialchars($url) . ' class="btn btn-default t3js-pageBrowser" title="' . $title . '">
745
                    <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-insert-record" data-identifier="actions-insert-record">
746
                        <span class="icon-markup">' . $this->iconFactory->getIcon(
747
                'actions-insert-record',
748
                    Icon::SIZE_SMALL
749
            )->render() . '</span>
750
                    </span>
751
                </a><span id="page_' . $fieldID . '">&nbsp;' . htmlspecialchars($fieldInfo['pageTitle']) . '</span></div>';
752
        }
753
        return '';
754
    }
755
756
    /**
757
     * Execute all selected tasks
758
     */
759
    protected function executeTasks()
760
    {
761
        // Continue if some elements have been chosen for execution
762
        if (isset($this->submittedData['execute']) && !empty($this->submittedData['execute'])) {
763
            // Get list of registered classes
764
            $registeredClasses = $this->getRegisteredClasses();
765
            // Loop on all selected tasks
766
            foreach ($this->submittedData['execute'] as $uid) {
767
                try {
768
                    // Try fetching the task
769
                    $task = $this->scheduler->fetchTask($uid);
770
                    $class = get_class($task);
771
                    $name = $registeredClasses[$class]['title'] . ' (' . $registeredClasses[$class]['extension'] . ')';
772
                    if (GeneralUtility::_POST('go_cron') !== null) {
773
                        $task->setRunOnNextCronJob(true);
774
                        $task->save();
775
                    } else {
776
                        // Now try to execute it and report on outcome
777
                        try {
778
                            $result = $this->scheduler->executeTask($task);
779
                            if ($result) {
780
                                $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.executed'), $name));
781
                            } else {
782
                                $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.notExecuted'), $name), FlashMessage::ERROR);
783
                            }
784
                        } catch (\Exception $e) {
785
                            // An exception was thrown, display its message as an error
786
                            $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.executionFailed'), $name, $e->getMessage()), FlashMessage::ERROR);
787
                        }
788
                    }
789
                } catch (\OutOfBoundsException $e) {
790
                    $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $uid), FlashMessage::ERROR);
791
                } catch (\UnexpectedValueException $e) {
792
                    $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.executionFailed'), $uid, $e->getMessage()), FlashMessage::ERROR);
793
                }
794
            }
795
            // Record the run in the system registry
796
            $this->scheduler->recordLastRun('manual');
797
            // Make sure to switch to list view after execution
798
            $this->CMD = 'list';
799
        }
800
    }
801
802
    /**
803
     * Assemble display of list of scheduled tasks
804
     *
805
     * @return string Table of pending tasks
806
     */
807
    protected function listTasksAction()
808
    {
809
        $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'ListTasks.html');
810
811
        // Define display format for dates
812
        $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
813
814
        // Get list of registered task groups
815
        $registeredTaskGroups = $this->getRegisteredTaskGroups();
816
817
        // add an empty entry for non-grouped tasks
818
        // add in front of list
819
        array_unshift($registeredTaskGroups, ['uid' => 0, 'groupName' => '']);
820
821
        // Get all registered tasks
822
        // Just to get the number of entries
823
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
824
            ->getQueryBuilderForTable('tx_scheduler_task');
825
        $queryBuilder->getRestrictions()->removeAll();
826
827
        $result = $queryBuilder->select('t.*')
828
            ->addSelect(
829
                'g.groupName AS taskGroupName',
830
                'g.description AS taskGroupDescription',
831
                'g.deleted AS isTaskGroupDeleted'
832
            )
833
            ->from('tx_scheduler_task', 't')
834
            ->leftJoin(
835
                't',
836
                'tx_scheduler_task_group',
837
                'g',
838
                $queryBuilder->expr()->eq('t.task_group', $queryBuilder->quoteIdentifier('g.uid'))
839
            )
840
            ->where(
841
                $queryBuilder->expr()->eq('t.deleted', 0)
842
            )
843
            ->orderBy('g.sorting')
844
            ->execute();
845
846
        // Loop on all tasks
847
        $temporaryResult = [];
848
        while ($row = $result->fetch()) {
849
            if ($row['taskGroupName'] === null || $row['isTaskGroupDeleted'] === '1') {
850
                $row['taskGroupName'] = '';
851
                $row['taskGroupDescription'] = '';
852
                $row['task_group'] = 0;
853
            }
854
            $temporaryResult[$row['task_group']]['groupName'] = $row['taskGroupName'];
855
            $temporaryResult[$row['task_group']]['groupDescription'] = $row['taskGroupDescription'];
856
            $temporaryResult[$row['task_group']]['tasks'][] = $row;
857
        }
858
859
        // No tasks defined, display information message
860
        if (empty($temporaryResult)) {
861
            $this->view->setTemplatePathAndFilename($this->backendTemplatePath . 'ListTasksNoTasks.html');
862
            return $this->view->render();
863
        }
864
865
        $this->getPageRenderer()->loadJquery();
866
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Scheduler/Scheduler');
867
        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
868
        $table = [];
869
        // Header row
870
        $table[] =
871
            '<thead><tr>'
872
                . '<th><a class="btn btn-default" href="#" id="checkall" title="' . htmlspecialchars($this->getLanguageService()->getLL('label.checkAll')) . '">' . $this->moduleTemplate->getIconFactory()->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a></th>'
873
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.id')) . '</th>'
874
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('task')) . '</th>'
875
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.type')) . '</th>'
876
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.frequency')) . '</th>'
877
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.parallel')) . '</th>'
878
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.lastExecution')) . '</th>'
879
                . '<th>' . htmlspecialchars($this->getLanguageService()->getLL('label.nextExecution')) . '</th>'
880
                . '<th></th>'
881
            . '</tr></thead>';
882
883
        $registeredClasses = $this->getRegisteredClasses();
884
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
885
        $collapseIcon = $iconFactory->getIcon('actions-view-list-collapse', Icon::SIZE_SMALL)->render();
886
        $expandIcon = $iconFactory->getIcon('actions-view-list-expand', Icon::SIZE_SMALL)->render();
887
        foreach ($temporaryResult as $taskIndex => $taskGroup) {
888
            $collapseExpandIcons = '<span class="taskGroup_' . $taskIndex . '">' . $collapseIcon . '</span>'
889
                . '<span class="taskGroup_' . $taskIndex . '" style="display: none;">' . $expandIcon . '</span>';
890
            if (!empty($taskGroup['groupName'])) {
891
                $groupText = '<strong>' . htmlspecialchars($taskGroup['groupName']) . '</strong>';
892
                if (!empty($taskGroup['groupDescription'])) {
893
                    $groupText .= '<br>' . nl2br(htmlspecialchars($taskGroup['groupDescription']));
894
                }
895
                $table[] = '<tr class="taskGroup" data-task-group-id="' . $taskIndex . '"><td colspan="8">' . $groupText . '</td><td style="text-align:right;">' . $collapseExpandIcons . '</td></tr>';
896
            } else {
897
                if (count($temporaryResult) > 1) {
898
                    $table[] = '<tr class="taskGroup" data-task-group-id="0"><td colspan="8"><strong>' . htmlspecialchars($this->getLanguageService()->getLL('label.noGroup')) . '</strong></td><td style="text-align:right;">' . $collapseExpandIcons . '</td></tr>';
899
                }
900
            }
901
902
            foreach ($taskGroup['tasks'] as $schedulerRecord) {
903
                // Define action icons
904
                $link = htmlspecialchars($this->moduleUri . '&CMD=edit&tx_scheduler[uid]=' . $schedulerRecord['uid']);
905
                $editAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default" href="' . $link . '" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:edit')) . '">' .
906
                    $this->moduleTemplate->getIconFactory()->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
907
                if ((int)$schedulerRecord['disable'] === 1) {
908
                    $translationKey = 'enable';
909
                    $icon = $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-unhide', Icon::SIZE_SMALL);
910
                } else {
911
                    $translationKey = 'disable';
912
                    $icon = $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-hide', Icon::SIZE_SMALL);
913
                }
914
                $toggleHiddenAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default" href="' . htmlspecialchars($this->moduleUri
915
                    . '&CMD=toggleHidden&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" title="'
916
                    . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:' . $translationKey))
917
                    . '">' . $icon->render() . '</a>';
918
                $deleteAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars($this->moduleUri . '&CMD=delete&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" '
919
                    . ' data-severity="warning"'
920
                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')) . '"'
921
                    . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
922
                    . ' data-content="' . htmlspecialchars($this->getLanguageService()->getLL('msg.delete')) . '"'
923
                    . ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete')) . '">' .
924
                    $this->moduleTemplate->getIconFactory()->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</a>';
925
                $stopAction = '<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="' . htmlspecialchars($this->moduleUri . '&CMD=stop&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" '
926
                    . ' data-severity="warning"'
927
                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')) . '"'
928
                    . ' data-button-close-text="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel')) . '"'
929
                    . ' data-content="' . htmlspecialchars($this->getLanguageService()->getLL('msg.stop')) . '"'
930
                    . ' title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:stop')) . '">' .
931
                    $this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL)->render() . '</a>';
932
                $runAction = '<a class="btn btn-default" data-toggle="tooltip" data-container="body" href="' . htmlspecialchars($this->moduleUri . '&tx_scheduler[execute][]=' . $schedulerRecord['uid']) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('action.run_task')) . '">' .
933
                    $this->moduleTemplate->getIconFactory()->getIcon('extensions-scheduler-run-task', Icon::SIZE_SMALL)->render() . '</a>';
934
                $runCronAction = '<a class="btn btn-default" data-toggle="tooltip" data-container="body" href="' . htmlspecialchars($this->moduleUri . '&CMD=setNextExecutionTime&tx_scheduler[uid]=' . $schedulerRecord['uid']) . '" title="' . htmlspecialchars($this->getLanguageService()->getLL('action.run_task_cron')) . '">' .
935
                    $this->moduleTemplate->getIconFactory()->getIcon('extensions-scheduler-run-task-cron', Icon::SIZE_SMALL)->render() . '</a>';
936
937
                // Define some default values
938
                $lastExecution = '-';
939
                $isRunning = false;
940
                $showAsDisabled = false;
941
                $startExecutionElement = '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
942
                // Restore the serialized task and pass it a reference to the scheduler object
943
                /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask|ProgressProviderInterface */
944
                $task = unserialize($schedulerRecord['serialized_task_object']);
945
                $class = get_class($task);
946
                if ($class === '__PHP_Incomplete_Class' && preg_match('/^O:[0-9]+:"(?P<classname>.+?)"/', $schedulerRecord['serialized_task_object'], $matches) === 1) {
947
                    $class = $matches['classname'];
948
                }
949
                // Assemble information about last execution
950
                if (!empty($schedulerRecord['lastexecution_time'])) {
951
                    $lastExecution = date($dateFormat, $schedulerRecord['lastexecution_time']);
952
                    if ($schedulerRecord['lastexecution_context'] === 'CLI') {
953
                        $context = $this->getLanguageService()->getLL('label.cron');
954
                    } else {
955
                        $context = $this->getLanguageService()->getLL('label.manual');
956
                    }
957
                    $lastExecution .= ' (' . $context . ')';
958
                }
959
960
                if (isset($registeredClasses[get_class($task)]) && $this->scheduler->isValidTaskObject($task)) {
961
                    // The task object is valid
962
                    $labels = [];
963
                    $name = htmlspecialchars($registeredClasses[$class]['title'] . ' (' . $registeredClasses[$class]['extension'] . ')');
964
                    $additionalInformation = $task->getAdditionalInformation();
0 ignored issues
show
Bug introduced by
The method getAdditionalInformation() does not exist on TYPO3\CMS\Scheduler\ProgressProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

964
                    /** @scrutinizer ignore-call */ 
965
                    $additionalInformation = $task->getAdditionalInformation();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
965
                    if ($task instanceof ProgressProviderInterface) {
966
                        $progress = round((float)$task->getProgress(), 2);
967
                        $name .= $this->renderTaskProgressBar($progress);
968
                    }
969
                    if (!empty($additionalInformation)) {
970
                        $name .= '<div class="additional-information">' . nl2br(htmlspecialchars($additionalInformation)) . '</div>';
971
                    }
972
                    // Check if task currently has a running execution
973
                    if (!empty($schedulerRecord['serialized_executions'])) {
974
                        $labels[] = [
975
                            'class' => 'success',
976
                            'text' => $this->getLanguageService()->getLL('status.running')
977
                        ];
978
                        $isRunning = true;
979
                    }
980
981
                    // Prepare display of next execution date
982
                    // If task is currently running, date is not displayed (as next hasn't been calculated yet)
983
                    // Also hide the date if task is disabled (the information doesn't make sense, as it will not run anyway)
984
                    if ($isRunning || $schedulerRecord['disable']) {
985
                        $nextDate = '-';
986
                    } else {
987
                        $nextDate = date($dateFormat, $schedulerRecord['nextexecution']);
988
                        if (empty($schedulerRecord['nextexecution'])) {
989
                            $nextDate = $this->getLanguageService()->getLL('none');
990
                        } elseif ($schedulerRecord['nextexecution'] < $GLOBALS['EXEC_TIME']) {
991
                            $labels[] = [
992
                                'class' => 'warning',
993
                                'text' => $this->getLanguageService()->getLL('status.late'),
994
                                'description' => $this->getLanguageService()->getLL('status.legend.scheduled')
995
                            ];
996
                        }
997
                    }
998
                    // Get execution type
999
                    if ($task->getType() === AbstractTask::TYPE_SINGLE) {
0 ignored issues
show
Bug introduced by
The method getType() does not exist on TYPO3\CMS\Scheduler\ProgressProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

999
                    if ($task->/** @scrutinizer ignore-call */ getType() === AbstractTask::TYPE_SINGLE) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1000
                        $execType = $this->getLanguageService()->getLL('label.type.single');
1001
                        $frequency = '-';
1002
                    } else {
1003
                        $execType = $this->getLanguageService()->getLL('label.type.recurring');
1004
                        if ($task->getExecution()->getCronCmd() == '') {
0 ignored issues
show
Bug introduced by
The method getExecution() does not exist on TYPO3\CMS\Scheduler\ProgressProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1004
                        if ($task->/** @scrutinizer ignore-call */ getExecution()->getCronCmd() == '') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1005
                            $frequency = $task->getExecution()->getInterval();
1006
                        } else {
1007
                            $frequency = $task->getExecution()->getCronCmd();
1008
                        }
1009
                    }
1010
                    // Get multiple executions setting
1011
                    if ($task->getExecution()->getMultiple()) {
1012
                        $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes');
1013
                    } else {
1014
                        $multiple = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:no');
1015
                    }
1016
                    // Define checkbox
1017
                    $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>';
1018
1019
                    $actions = '<div class="btn-group" role="group">' . $editAction . $toggleHiddenAction . $deleteAction . '</div>';
1020
1021
                    // Check the disable status
1022
                    // Row is shown dimmed if task is disabled, unless it is still running
1023
                    if ($schedulerRecord['disable'] && !$isRunning) {
1024
                        $labels[] = [
1025
                            'class' => 'default',
1026
                            'text' => $this->getLanguageService()->getLL('status.disabled')
1027
                        ];
1028
                        $showAsDisabled = true;
1029
                    }
1030
1031
                    // Show no action links (edit, delete) if task is running
1032
                    if ($isRunning) {
1033
                        $actions = '<div class="btn-group" role="group">' . $stopAction . '</div>';
1034
                    } else {
1035
                        $actions .= '&nbsp;<div class="btn-group" role="group">' . $runCronAction . $runAction . '</div>';
1036
                    }
1037
1038
                    // Check if the last run failed
1039
                    if (!empty($schedulerRecord['lastexecution_failure'])) {
1040
                        // Try to get the stored exception array
1041
                        /** @var $exceptionArray array */
1042
                        $exceptionArray = @unserialize($schedulerRecord['lastexecution_failure']);
1043
                        // If the exception could not be unserialized, issue a default error message
1044
                        if (!is_array($exceptionArray) || empty($exceptionArray)) {
1045
                            $labelDescription = $this->getLanguageService()->getLL('msg.executionFailureDefault');
1046
                        } else {
1047
                            $labelDescription = sprintf($this->getLanguageService()->getLL('msg.executionFailureReport'), $exceptionArray['code'], $exceptionArray['message']);
1048
                        }
1049
                        $labels[] = [
1050
                            'class' => 'danger',
1051
                            'text' => $this->getLanguageService()->getLL('status.failure'),
1052
                            'description' => $labelDescription
1053
                        ];
1054
                    }
1055
                    // Format the execution status,
1056
                    // including failure feedback, if any
1057
                    $taskDesc = '';
1058
                    if ($schedulerRecord['description'] !== '') {
1059
                        $taskDesc = '<span class="description">' . nl2br(htmlspecialchars($schedulerRecord['description'])) . '</span>';
1060
                    }
1061
                    $taskName = '<span class="name"><a href="' . $link . '">' . $name . '</a></span>';
1062
1063
                    $table[] =
1064
                        '<tr class="' . ($showAsDisabled ? 'disabled' : '') . ' taskGroup_' . $taskIndex . '">'
1065
                            . '<td><span class="t-span">' . $startExecutionElement . '</span></td>'
1066
                            . '<td class="right"><span class="t-span">' . $schedulerRecord['uid'] . '</span></td>'
1067
                            . '<td><span class="t-span">' . $this->makeStatusLabel($labels) . $taskName . $taskDesc . '</span></td>'
1068
                            . '<td><span class="t-span">' . $execType . '</span></td>'
1069
                            . '<td><span class="t-span">' . $frequency . '</span></td>'
1070
                            . '<td><span class="t-span">' . $multiple . '</span></td>'
1071
                            . '<td><span class="t-span">' . $lastExecution . '</span></td>'
1072
                            . '<td><span class="t-span">' . $nextDate . '</span></td>'
0 ignored issues
show
Bug introduced by
Are you sure $nextDate of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1072
                            . '<td><span class="t-span">' . /** @scrutinizer ignore-type */ $nextDate . '</span></td>'
Loading history...
1073
                            . '<td class="nowrap"><span class="t-span">' . $actions . '</span></td>'
1074
                        . '</tr>';
1075
                } else {
1076
                    // The task object is not valid
1077
                    // Prepare to issue an error
1078
                    $executionStatusOutput = '<span class="label label-danger">'
1079
                        . htmlspecialchars(sprintf(
1080
                            $this->getLanguageService()->getLL('msg.invalidTaskClass'),
1081
                            $class
1082
                        ))
1083
                        . '</span>';
1084
                    $table[] =
1085
                        '<tr>'
1086
                            . '<td>' . $startExecutionElement . '</td>'
1087
                            . '<td class="right">' . $schedulerRecord['uid'] . '</td>'
1088
                            . '<td colspan="6">' . $executionStatusOutput . '</td>'
1089
                            . '<td class="nowrap"><div class="btn-group" role="group">'
1090
                                . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
1091
                                . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
1092
                                . $deleteAction
1093
                                . '<span class="btn btn-default disabled">' . $this->moduleTemplate->getIconFactory()->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>'
1094
                            . '</div></td>'
1095
                        . '</tr>';
1096
                }
1097
            }
1098
        }
1099
1100
        $this->view->assign('table', '<table class="table table-striped table-hover">' . implode(LF, $table) . '</table>');
1101
        $this->view->assign('now', $this->getServerTime());
1102
1103
        return $this->view->render();
1104
    }
1105
1106
    /**
1107
     * Generates bootstrap labels containing the label statuses
1108
     *
1109
     * @param array $labels
1110
     * @return string
1111
     */
1112
    protected function makeStatusLabel(array $labels)
1113
    {
1114
        $htmlLabels = [];
1115
        foreach ($labels as $label) {
1116
            if (empty($label['text'])) {
1117
                continue;
1118
            }
1119
            $htmlLabels[] = '<span class="label label-' . htmlspecialchars($label['class']) . ' pull-right" title="' . htmlspecialchars($label['description']) . '">' . htmlspecialchars($label['text']) . '</span>';
1120
        }
1121
1122
        return implode('&nbsp;', $htmlLabels);
1123
    }
1124
1125
    /**
1126
     * Saves a task specified in the backend form to the database
1127
     */
1128
    protected function saveTask()
1129
    {
1130
        // If a task is being edited fetch old task data
1131
        if (!empty($this->submittedData['uid'])) {
1132
            try {
1133
                $taskRecord = $this->scheduler->fetchTaskRecord($this->submittedData['uid']);
1134
                /** @var $task \TYPO3\CMS\Scheduler\Task\AbstractTask */
1135
                $task = unserialize($taskRecord['serialized_task_object']);
1136
            } catch (\OutOfBoundsException $e) {
1137
                // If the task could not be fetched, issue an error message
1138
                // and exit early
1139
                $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.taskNotFound'), $this->submittedData['uid']), FlashMessage::ERROR);
1140
                return;
1141
            }
1142
            // Register single execution
1143
            if ((int)$this->submittedData['type'] === AbstractTask::TYPE_SINGLE) {
1144
                $task->registerSingleExecution($this->submittedData['start']);
1145
            } else {
1146
                if (!empty($this->submittedData['croncmd'])) {
1147
                    // Definition by cron-like syntax
1148
                    $interval = 0;
1149
                    $cronCmd = $this->submittedData['croncmd'];
1150
                } else {
1151
                    // Definition by interval
1152
                    $interval = $this->submittedData['interval'];
1153
                    $cronCmd = '';
1154
                }
1155
                // Register recurring execution
1156
                $task->registerRecurringExecution($this->submittedData['start'], $interval, $this->submittedData['end'], $this->submittedData['multiple'], $cronCmd);
1157
            }
1158
            // Set disable flag
1159
            $task->setDisabled($this->submittedData['disable']);
1160
            // Set description
1161
            $task->setDescription($this->submittedData['description']);
1162
            // Set task group
1163
            $task->setTaskGroup($this->submittedData['task_group']);
1164
            // Save additional input values
1165 View Code Duplication
            if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1166
                /** @var $providerObject AdditionalFieldProviderInterface */
1167
                $providerObject = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1168
                if ($providerObject instanceof AdditionalFieldProviderInterface) {
1169
                    $providerObject->saveAdditionalFields($this->submittedData, $task);
1170
                }
1171
            }
1172
            // Save to database
1173
            $result = $this->scheduler->saveTask($task);
1174 View Code Duplication
            if ($result) {
1175
                $this->getBackendUser()->writelog(4, 0, 0, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was updated', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
1176
                $this->addMessage($this->getLanguageService()->getLL('msg.updateSuccess'));
1177
            } else {
1178
                $this->addMessage($this->getLanguageService()->getLL('msg.updateError'), FlashMessage::ERROR);
1179
            }
1180
        } else {
1181
            // A new task is being created
1182
            // Create an instance of chosen class
1183
            /** @var $task AbstractTask */
1184
            $task = GeneralUtility::makeInstance($this->submittedData['class']);
1185
            if ((int)$this->submittedData['type'] === AbstractTask::TYPE_SINGLE) {
1186
                // Set up single execution
1187
                $task->registerSingleExecution($this->submittedData['start']);
1188
            } else {
1189
                // Set up recurring execution
1190
                $task->registerRecurringExecution($this->submittedData['start'], $this->submittedData['interval'], $this->submittedData['end'], $this->submittedData['multiple'], $this->submittedData['croncmd']);
1191
            }
1192
            // Save additional input values
1193 View Code Duplication
            if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1194
                /** @var $providerObject AdditionalFieldProviderInterface */
1195
                $providerObject = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1196
                if ($providerObject instanceof AdditionalFieldProviderInterface) {
1197
                    $providerObject->saveAdditionalFields($this->submittedData, $task);
1198
                }
1199
            }
1200
            // Set disable flag
1201
            $task->setDisabled($this->submittedData['disable']);
1202
            // Set description
1203
            $task->setDescription($this->submittedData['description']);
1204
            // Set description
1205
            $task->setTaskGroup($this->submittedData['task_group']);
1206
            // Add to database
1207
            $result = $this->scheduler->addTask($task);
1208 View Code Duplication
            if ($result) {
1209
                $this->getBackendUser()->writelog(4, 0, 0, 0, 'Scheduler task "%s" (UID: %s, Class: "%s") was added', [$task->getTaskTitle(), $task->getTaskUid(), $task->getTaskClassName()]);
1210
                $this->addMessage($this->getLanguageService()->getLL('msg.addSuccess'));
1211
1212
                // set the uid of the just created task so that we
1213
                // can continue editing after initial saving
1214
                $this->submittedData['uid'] = $task->getTaskUid();
1215
            } else {
1216
                $this->addMessage($this->getLanguageService()->getLL('msg.addError'), FlashMessage::ERROR);
1217
            }
1218
        }
1219
    }
1220
1221
    /*************************
1222
     *
1223
     * INPUT PROCESSING UTILITIES
1224
     *
1225
     *************************/
1226
    /**
1227
     * Checks the submitted data and performs some pre-processing on it
1228
     *
1229
     * @return bool true if everything was ok, false otherwise
1230
     */
1231
    protected function preprocessData()
1232
    {
1233
        $result = true;
1234
        // Validate id
1235
        $this->submittedData['uid'] = empty($this->submittedData['uid']) ? 0 : (int)$this->submittedData['uid'];
1236
        // Validate selected task class
1237
        if (!class_exists($this->submittedData['class'])) {
1238
            $this->addMessage($this->getLanguageService()->getLL('msg.noTaskClassFound'), FlashMessage::ERROR);
1239
        }
1240
        // Check start date
1241
        if (empty($this->submittedData['start'])) {
1242
            $this->addMessage($this->getLanguageService()->getLL('msg.noStartDate'), FlashMessage::ERROR);
1243
            $result = false;
1244
        } elseif (is_string($this->submittedData['start']) && (!is_numeric($this->submittedData['start']))) {
1245
            try {
1246
                $this->submittedData['start'] = $this->convertToTimestamp($this->submittedData['start']);
1247
            } catch (\Exception $e) {
1248
                $this->addMessage($this->getLanguageService()->getLL('msg.invalidStartDate'), FlashMessage::ERROR);
1249
                $result = false;
1250
            }
1251
        } else {
1252
            $this->submittedData['start'] = (int)$this->submittedData['start'];
1253
        }
1254
        // Check end date, if recurring task
1255
        if ((int)$this->submittedData['type'] === AbstractTask::TYPE_RECURRING && !empty($this->submittedData['end'])) {
1256
            if (is_string($this->submittedData['end']) && (!is_numeric($this->submittedData['end']))) {
1257
                try {
1258
                    $this->submittedData['end'] = $this->convertToTimestamp($this->submittedData['end']);
1259
                } catch (\Exception $e) {
1260
                    $this->addMessage($this->getLanguageService()->getLL('msg.invalidStartDate'), FlashMessage::ERROR);
1261
                    $result = false;
1262
                }
1263
            } else {
1264
                $this->submittedData['end'] = (int)$this->submittedData['end'];
1265
            }
1266
            if ($this->submittedData['end'] < $this->submittedData['start']) {
1267
                $this->addMessage(
1268
                    $this->getLanguageService()->getLL('msg.endDateSmallerThanStartDate'),
1269
                    FlashMessage::ERROR
1270
                );
1271
                $result = false;
1272
            }
1273
        }
1274
        // Set default values for interval and cron command
1275
        $this->submittedData['interval'] = 0;
1276
        $this->submittedData['croncmd'] = '';
1277
        // Check type and validity of frequency, if recurring
1278
        if ((int)$this->submittedData['type'] === AbstractTask::TYPE_RECURRING) {
1279
            $frequency = trim($this->submittedData['frequency']);
1280
            if (empty($frequency)) {
1281
                // Empty frequency, not valid
1282
                $this->addMessage($this->getLanguageService()->getLL('msg.noFrequency'), FlashMessage::ERROR);
1283
                $result = false;
1284
            } else {
1285
                $cronErrorCode = 0;
1286
                $cronErrorMessage = '';
1287
                // Try interpreting the cron command
1288
                try {
1289
                    NormalizeCommand::normalize($frequency);
1290
                    $this->submittedData['croncmd'] = $frequency;
1291
                } catch (\Exception $e) {
1292
                    // Store the exception's result
1293
                    $cronErrorMessage = $e->getMessage();
1294
                    $cronErrorCode = $e->getCode();
1295
                    // Check if the frequency is a valid number
1296
                    // If yes, assume it is a frequency in seconds, and unset cron error code
1297
                    if (is_numeric($frequency)) {
1298
                        $this->submittedData['interval'] = (int)$frequency;
1299
                        unset($cronErrorCode);
1300
                    }
1301
                }
1302
                // If there's a cron error code, issue validation error message
1303
                if (!empty($cronErrorCode)) {
1304
                    $this->addMessage(sprintf($this->getLanguageService()->getLL('msg.frequencyError'), $cronErrorMessage, $cronErrorCode), FlashMessage::ERROR);
1305
                    $result = false;
1306
                }
1307
            }
1308
        }
1309
        // Validate additional input fields
1310 View Code Duplication
        if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields'])) {
1311
            /** @var $providerObject AdditionalFieldProviderInterface */
1312
            $providerObject = GeneralUtility::makeInstance($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData['class']]['additionalFields']);
1313
            if ($providerObject instanceof AdditionalFieldProviderInterface) {
1314
                // The validate method will return true if all went well, but that must not
1315
                // override previous false values => AND the returned value with the existing one
1316
                $result &= $providerObject->validateAdditionalFields($this->submittedData, $this);
1317
            }
1318
        }
1319
        return $result;
1320
    }
1321
1322
    /**
1323
     * Convert input to DateTime and retrieve timestamp
1324
     *
1325
     * @param string $input
1326
     * @return int
1327
     */
1328
    protected function convertToTimestamp(string $input): int
1329
    {
1330
        // Convert to ISO 8601 dates
1331
        $dateTime = new \DateTime($input);
1332
        $value = $dateTime->getTimestamp();
1333
        if ($value !== 0) {
1334
            $value -= date('Z', $value);
1335
        }
1336
        return $value;
1337
    }
1338
1339
    /**
1340
     * This method is used to add a message to the internal queue
1341
     *
1342
     * @param string $message The message itself
1343
     * @param int $severity Message level (according to FlashMessage class constants)
1344
     */
1345
    public function addMessage($message, $severity = FlashMessage::OK)
1346
    {
1347
        $this->moduleTemplate->addFlashMessage($message, '', $severity);
1348
    }
1349
1350
    /**
1351
     * This method fetches a list of all classes that have been registered with the Scheduler
1352
     * For each item the following information is provided, as an associative array:
1353
     *
1354
     * ['extension']	=>	Key of the extension which provides the class
1355
     * ['filename']		=>	Path to the file containing the class
1356
     * ['title']		=>	String (possibly localized) containing a human-readable name for the class
1357
     * ['provider']		=>	Name of class that implements the interface for additional fields, if necessary
1358
     *
1359
     * The name of the class itself is used as the key of the list array
1360
     *
1361
     * @return array List of registered classes
1362
     */
1363
    protected function getRegisteredClasses()
1364
    {
1365
        $list = [];
1366
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] ?? [] as $class => $registrationInformation) {
1367
            $title = isset($registrationInformation['title']) ? $this->getLanguageService()->sL($registrationInformation['title']) : '';
1368
            $description = isset($registrationInformation['description']) ? $this->getLanguageService()->sL($registrationInformation['description']) : '';
1369
            $list[$class] = [
1370
                'extension' => $registrationInformation['extension'],
1371
                'title' => $title,
1372
                'description' => $description,
1373
                'provider' => isset($registrationInformation['additionalFields']) ? $registrationInformation['additionalFields'] : ''
1374
            ];
1375
        }
1376
        return $list;
1377
    }
1378
1379
    /**
1380
     * This method fetches list of all group that have been registered with the Scheduler
1381
     *
1382
     * @return array List of registered groups
1383
     */
1384
    protected function getRegisteredTaskGroups()
1385
    {
1386
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1387
            ->getQueryBuilderForTable('tx_scheduler_task_group');
1388
1389
        return $queryBuilder
1390
            ->select('*')
1391
            ->from('tx_scheduler_task_group')
1392
            ->orderBy('sorting')
1393
            ->execute()
1394
            ->fetchAll();
1395
    }
1396
1397
    /**
1398
     * Create the panel of buttons for submitting the form or otherwise perform operations.
1399
     */
1400
    protected function getButtons()
1401
    {
1402
        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1403
        // CSH
1404
        $helpButton = $buttonBar->makeHelpButton()
1405
            ->setModuleName('_MOD_system_txschedulerM1')
1406
            ->setFieldName('');
1407
        $buttonBar->addButton($helpButton);
1408
        // Add and Reload
1409
        if (empty($this->CMD) || $this->CMD === 'list' || $this->CMD === 'delete' || $this->CMD === 'stop' || $this->CMD === 'toggleHidden' || $this->CMD === 'setNextExecutionTime') {
1410
            $reloadButton = $buttonBar->makeLinkButton()
1411
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.reload'))
1412
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL))
1413
                ->setHref($this->moduleUri);
1414
            $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
1415
            if ($this->MOD_SETTINGS['function'] === 'scheduler' && !empty($this->getRegisteredClasses())) {
1416
                $addButton = $buttonBar->makeLinkButton()
1417
                    ->setTitle($this->getLanguageService()->getLL('action.add'))
1418
                    ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-add', Icon::SIZE_SMALL))
1419
                    ->setHref($this->moduleUri . '&CMD=add');
1420
                $buttonBar->addButton($addButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1421
            }
1422
        }
1423
        // Close and Save
1424
        if ($this->CMD === 'add' || $this->CMD === 'edit') {
1425
            // Close
1426
            $closeButton = $buttonBar->makeLinkButton()
1427
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:cancel'))
1428
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL))
1429
                ->setOnClick('document.location=' . GeneralUtility::quoteJSvalue($this->moduleUri))
1430
                ->setHref('#');
1431
            $buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1432
            // Save, SaveAndClose, SaveAndNew
1433
            $saveButtonDropdown = $buttonBar->makeSplitButton();
1434
            $saveButton = $buttonBar->makeInputButton()
1435
                ->setName('CMD')
1436
                ->setValue('save')
1437
                ->setForm('tx_scheduler_form')
1438
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
1439
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:save'));
1440
            $saveButtonDropdown->addItem($saveButton);
1441
            $saveAndNewButton = $buttonBar->makeInputButton()
1442
                ->setName('CMD')
1443
                ->setValue('savenew')
1444
                ->setForm('tx_scheduler_form')
1445
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-new', Icon::SIZE_SMALL))
1446
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:saveAndCreateNewDoc'));
1447
            $saveButtonDropdown->addItem($saveAndNewButton);
1448
            $saveAndCloseButton = $buttonBar->makeInputButton()
1449
                ->setName('CMD')
1450
                ->setValue('saveclose')
1451
                ->setForm('tx_scheduler_form')
1452
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save-close', Icon::SIZE_SMALL))
1453
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:saveAndClose'));
1454
            $saveButtonDropdown->addItem($saveAndCloseButton);
1455
            $buttonBar->addButton($saveButtonDropdown, ButtonBar::BUTTON_POSITION_LEFT, 3);
1456
        }
1457
        // Edit
1458 View Code Duplication
        if ($this->CMD === 'edit') {
1459
            $deleteButton = $buttonBar->makeInputButton()
1460
                ->setName('CMD')
1461
                ->setValue('delete')
1462
                ->setForm('tx_scheduler_form')
1463
                ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-edit-delete', Icon::SIZE_SMALL))
1464
                ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:delete'));
1465
            $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
1466
        }
1467
        // Shortcut
1468
        $shortcutButton = $buttonBar->makeShortcutButton()
1469
            ->setModuleName('system_txschedulerM1')
1470
            ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
1471
            ->setSetVariables(['function']);
1472
        $buttonBar->addButton($shortcutButton);
1473
    }
1474
1475
    /**
1476
     * @return string
1477
     */
1478
    protected function getServerTime()
1479
    {
1480
        $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' T (e';
1481
        return date($dateFormat) . ', GMT ' . date('P') . ')';
0 ignored issues
show
Bug introduced by
Are you sure date('P') of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1481
        return date($dateFormat) . ', GMT ' . /** @scrutinizer ignore-type */ date('P') . ')';
Loading history...
Bug introduced by
Are you sure date($dateFormat) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1481
        return /** @scrutinizer ignore-type */ date($dateFormat) . ', GMT ' . date('P') . ')';
Loading history...
1482
    }
1483
1484
    /**
1485
     * Returns the Language Service
1486
     * @return LanguageService
1487
     */
1488
    protected function getLanguageService()
1489
    {
1490
        return $GLOBALS['LANG'];
1491
    }
1492
1493
    /**
1494
     * Returns the global BackendUserAuthentication object.
1495
     *
1496
     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1497
     */
1498
    protected function getBackendUser()
1499
    {
1500
        return $GLOBALS['BE_USER'];
1501
    }
1502
1503
    /**
1504
     * @return PageRenderer
1505
     */
1506
    protected function getPageRenderer()
1507
    {
1508
        return GeneralUtility::makeInstance(PageRenderer::class);
1509
    }
1510
}
1511