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