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