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