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