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