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