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