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