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