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