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