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