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