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