2 /***************************************************************
5 * (c) 2009-2011 François Suter <francois@typo3.org>
6 * (c) 2005 Christian Jul Jensen <julle@typo3.org>
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.
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
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.
23 * This copyright notice MUST APPEAR in all copies of the script!
24 ***************************************************************/
27 * Module 'TYPO3 Scheduler administration module' for the 'scheduler' extension.
29 * @author François Suter <francois@typo3.org>
30 * @author Christian Jul Jensen <julle@typo3.org>
31 * @author Ingo Renner <ingo@typo3.org>
33 * @subpackage tx_scheduler
35 class tx_scheduler_Module
extends t3lib_SCbase
{
38 * Back path to typo3 main dir
40 * @var string $backPath
45 * Array containing submitted data when editing or adding a task
47 * @var array $submittedData
49 protected $submittedData = array();
52 * Array containing all messages issued by the application logic
53 * Contains the error's severity and the message itself
55 * @var array $messages
57 protected $messages = array();
60 * @var string Key of the CSH file
66 * @var tx_scheduler Local scheduler instance
73 * @return tx_scheduler_Module
75 public function __construct() {
76 $this->backPath
= $GLOBALS['BACK_PATH'];
78 $this->cshKey
= '_MOD_' . $GLOBALS['MCONF']['name'];
82 * Initializes the backend module
86 public function init() {
89 // Initialize document
90 $this->doc
= t3lib_div
::makeInstance('template');
91 $this->doc
->setModuleTemplate(t3lib_extMgm
::extPath('scheduler') . 'mod1/mod_template.html');
92 $this->doc
->getPageRenderer()->addCssFile(t3lib_extMgm
::extRelPath('scheduler') . 'res/tx_scheduler_be.css');
93 $this->doc
->backPath
= $this->backPath
;
94 $this->doc
->bodyTagId
= 'typo3-mod-php';
95 $this->doc
->bodyTagAdditions
= 'class="tx_scheduler_mod1"';
97 // Create scheduler instance
98 $this->scheduler
= t3lib_div
::makeInstance('tx_scheduler');
102 * Adds items to the ->MOD_MENU array. Used for the function menu selector.
106 public function menuConfig() {
107 $this->MOD_MENU
= array(
109 'scheduler' => $GLOBALS['LANG']->getLL('function.scheduler'),
110 'check' => $GLOBALS['LANG']->getLL('function.check'),
111 'info' => $GLOBALS['LANG']->getLL('function.info'),
115 parent
::menuConfig();
119 * Main function of the module. Write the content to $this->content
123 public function main() {
125 // The page will show only if user has admin rights
126 if ($GLOBALS['BE_USER']->user
['admin']) {
129 $this->doc
->form
= '<form name="tx_scheduler_form" id="tx_scheduler_form" method="post" action="">';
131 // JavaScript for main function menu
132 $this->doc
->JScode
= '
133 <script language="javascript" type="text/javascript">
135 function jumpToUrl(URL) {
136 document.location = URL;
140 $this->doc
->getPageRenderer()->addInlineSetting('scheduler', 'runningIcon', t3lib_extMgm
::extRelPath('scheduler') . 'res/gfx/status_running.png');
142 // Prepare main content
143 $this->content
= $this->doc
->header(
144 $GLOBALS['LANG']->getLL('function.' . $this->MOD_SETTINGS
['function'])
146 $this->content
.= $this->doc
->spacer(5);
147 $this->content
.= $this->getModuleContent();
149 // If no access, only display the module's title
150 $this->content
= $this->doc
->header($GLOBALS['LANG']->getLL('title'));
151 $this->content
.= $this->doc
->spacer(5);
154 // Place content inside template
155 $content = $this->doc
->moduleBody(
157 $this->getDocHeaderButtons(),
158 $this->getTemplateMarkers()
161 // Renders the module page
162 $this->content
= $this->doc
->render(
163 $GLOBALS['LANG']->getLL('title'),
169 * Generate the module's content
171 * @return string HTML of the module's main content
173 protected function getModuleContent() {
177 // Get submitted data
178 $this->submittedData
= t3lib_div
::_GPmerged('tx_scheduler');
180 // If a save command was submitted, handle saving now
181 if ($this->CMD
== 'save') {
182 $previousCMD = t3lib_div
::_GP('previousCMD');
183 // First check the submitted data
184 $result = $this->preprocessData();
186 // If result is ok, proceed with saving
189 // Unset command, so that default screen gets displayed
193 // Go back to previous step
195 $this->CMD
= $previousCMD;
199 // Handle chosen action
200 switch((string)$this->MOD_SETTINGS
['function']) {
202 // Scheduler's main screen
203 $content .= $this->executeTasks();
205 // Execute chosen action
206 switch ($this->CMD
) {
210 // Try adding or editing
211 $content .= $this->editTask();
212 $sectionTitle = $GLOBALS['LANG']->getLL('action.' . $this->CMD
);
213 } catch (Exception
$e) {
214 // An exception may happen when the task to
215 // edit could not be found. In this case revert
216 // to displaying the list of tasks
217 // It can also happen when attempting to edit a running task
218 $content .= $this->listTasks();
223 $content .= $this->listTasks();
227 $content .= $this->listTasks();
231 $content .= $this->listTasks();
236 // Setup check screen
237 // TODO: move check to the report module
238 $content .= $this->displayCheckScreen();
241 // Information screen
242 $content .= $this->displayInfoScreen();
246 // Wrap the content in a section
247 return $this->doc
->section($sectionTitle, '<div class="tx_scheduler_mod1">' . $content . '</div>', 0, 1);
251 * This method actually prints out the module's HTML content
255 public function render() {
260 * This method checks the status of the '_cli_scheduler' user
261 * It will differentiate between a non-existing user and an existing,
262 * but disabled user (as per enable fields)
264 * @return integer -1 if user doesn't exist
265 * 0 if user exists, but is disabled
266 * 1 if user exists and is not disabled
268 protected function checkSchedulerUser() {
269 $schedulerUserStatus = -1;
270 // Assemble base WHERE clause
271 $where = 'username = \'_cli_scheduler\' AND admin = 0' . t3lib_BEfunc
::deleteClause('be_users');
272 // Check if user exists at all
273 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
278 if ($GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
279 $schedulerUserStatus = 0;
280 // Check if user exists and is enabled
281 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
284 $where . t3lib_BEfunc
::BEenableFields('be_users')
286 if ($GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
287 $schedulerUserStatus = 1;
290 $GLOBALS['TYPO3_DB']->sql_free_result($res);
292 return $schedulerUserStatus;
296 * This method creates the "cli_scheduler" BE user if it doesn't exist
300 protected function createSchedulerUser() {
301 // Check _cli_scheduler user status
302 $checkUser = $this->checkSchedulerUser();
303 // Prepare default message
304 $message = $GLOBALS['LANG']->getLL('msg.userExists');
305 $severity = t3lib_FlashMessage
::WARNING
;
306 // If the user does not exist, try creating it
307 if ($checkUser == -1) {
308 // Prepare necessary data for _cli_scheduler user creation
309 $password = md5(uniqid('scheduler', TRUE));
310 $data = array('be_users' => array('NEW' => array('username' => '_cli_scheduler', 'password' => $password, 'pid' => 0)));
311 /** @var t3lib_TCEmain $tcemain */
312 $tcemain = t3lib_div
::makeInstance('t3lib_TCEmain');
313 $tcemain->stripslashes_values
= 0;
314 $tcemain->start($data, array());
315 $tcemain->process_datamap();
316 // Check if a new uid was indeed generated (i.e. a new record was created)
317 // (counting TCEmain errors doesn't work as some failures don't report errors)
318 $numberOfNewIDs = count($tcemain->substNEWwithIDs
);
319 if ($numberOfNewIDs == 1) {
320 $message = $GLOBALS['LANG']->getLL('msg.userCreated');
321 $severity = t3lib_FlashMessage
::OK
;
323 $message = $GLOBALS['LANG']->getLL('msg.userNotCreated');
324 $severity = t3lib_FlashMessage
::ERROR
;
327 $this->addMessage($message, $severity);
331 * This method displays the result of a number of checks
332 * on whether the Scheduler is ready to run or running properly
334 * @return string Further information
336 protected function displayCheckScreen() {
338 $severity = t3lib_FlashMessage
::OK
;
340 // First, check if _cli_scheduler user creation was requested
341 if ($this->CMD
== 'user') {
342 $this->createSchedulerUser();
345 // Start generating the content
346 $content = $GLOBALS['LANG']->getLL('msg.schedulerSetupCheck');
348 // Display information about last automated run, as stored in the system registry
349 /** @var t3lib_Registry $registry */
350 $registry = t3lib_div
::makeInstance('t3lib_Registry');
351 $lastRun = $registry->get('tx_scheduler', 'lastRun');
352 if (!is_array($lastRun)) {
353 $message = $GLOBALS['LANG']->getLL('msg.noLastRun');
354 $severity = t3lib_FlashMessage
::WARNING
;
356 if (empty($lastRun['end']) ||
empty($lastRun['start']) ||
empty($lastRun['type'])) {
357 $message = $GLOBALS['LANG']->getLL('msg.incompleteLastRun');
358 $severity = t3lib_FlashMessage
::WARNING
;
360 $startDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['start']);
361 $startTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['start']);
362 $endDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $lastRun['end']);
363 $endTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $lastRun['end']);
364 $label = 'automatically';
365 if ($lastRun['type'] == 'manual') {
368 $type = $GLOBALS['LANG']->getLL('label.' . $label);
369 $message = sprintf($GLOBALS['LANG']->getLL('msg.lastRun'), $type, $startDate, $startTime, $endDate, $endTime);
370 $severity = t3lib_FlashMessage
::INFO
;
373 /** @var $flashMessage t3lib_FlashMessage */
374 $flashMessage = t3lib_div
::makeInstance(
375 't3lib_FlashMessage',
380 $content .= '<div class="info-block">';
381 $content .= '<h3>' . $GLOBALS['LANG']->getLL('hdg.lastRun') . '</h3>';
382 $content .= $flashMessage->render();
383 $content .= '</div>';
386 $content .= '<div class="info-block">';
387 $content .= '<h3>' . $GLOBALS['LANG']->getLL('hdg.schedulerUser') . '</h3>';
388 $content .= '<p>' . $GLOBALS['LANG']->getLL('msg.schedulerUser') . '</p>';
390 $checkUser = $this->checkSchedulerUser();
391 if ($checkUser == -1) {
392 $link = $GLOBALS['MCONF']['_'] . '&SET[function]=check&CMD=user';
393 $message = sprintf($GLOBALS['LANG']->getLL('msg.schedulerUserMissing'), htmlspecialchars($link));
394 $severity = t3lib_FlashMessage
::ERROR
;
395 } elseif ($checkUser == 0) {
396 $message = $GLOBALS['LANG']->getLL('msg.schedulerUserFoundButDisabled');
397 $severity = t3lib_FlashMessage
::WARNING
;
399 $message = $GLOBALS['LANG']->getLL('msg.schedulerUserFound');
400 $severity = t3lib_FlashMessage
::OK
;
402 $flashMessage = t3lib_div
::makeInstance(
403 't3lib_FlashMessage',
408 $content .= $flashMessage->render() . '</div>';
410 // Check if CLI script is executable or not
411 $script = PATH_typo3
. 'cli_dispatch.phpsh';
412 $isExecutable = FALSE;
413 // Skip this check if running Windows, as rights do not work the same way on this platform
414 // (i.e. the script will always appear as *not* executable)
415 if (TYPO3_OS
=== 'WIN') {
416 $isExecutable = TRUE;
418 $isExecutable = is_executable($script);
420 $content .= '<div class="info-block">';
421 $content .= '<h3>' . $GLOBALS['LANG']->getLL('hdg.cliScript') . '</h3>';
422 $content .= '<p>' . sprintf($GLOBALS['LANG']->getLL('msg.cliScript'), $script) . '</p>';
425 $message = $GLOBALS['LANG']->getLL('msg.cliScriptExecutable');
426 $severity = t3lib_FlashMessage
::OK
;
428 $message = $GLOBALS['LANG']->getLL('msg.cliScriptNotExecutable');
429 $severity = t3lib_FlashMessage
::ERROR
;
431 $flashMessage = t3lib_div
::makeInstance(
432 't3lib_FlashMessage',
437 $content .= $flashMessage->render() . '</div>';
443 * This method gathers information about all available task classes and displays it
445 * @return string HTML content to display
447 protected function displayInfoScreen() {
449 $registeredClasses = self
::getRegisteredClasses();
451 // No classes available, display information message
452 if (count($registeredClasses) == 0) {
453 /** @var t3lib_FlashMessage $flashMessage */
454 $flashMessage = t3lib_div
::makeInstance('t3lib_FlashMessage',
455 $GLOBALS['LANG']->getLL('msg.noTasksDefined'),
457 t3lib_FlashMessage
::INFO
459 $content .= $flashMessage->render();
461 // Display the list of all available classes
463 // Initialise table layout
464 $tableLayout = array (
465 'table' => array ('<table border="0" cellspacing="1" cellpadding="2" class="typo3-dblist">', '</table>'),
467 'tr' => array('<tr class="t3-row-header" valign="top">', '</tr>'),
468 'defCol' => array('<td>', '</td>')
471 'tr' => array('<tr class="db_list_normal">', '</tr>'),
472 'defCol' => array('<td>', '</td>')
479 $table[$tr][] = $GLOBALS['LANG']->getLL('label.name');
480 $table[$tr][] = $GLOBALS['LANG']->getLL('label.extension');
481 $table[$tr][] = $GLOBALS['LANG']->getLL('label.description');
485 // Display information about each service
486 foreach ($registeredClasses as $class => $classInfo) {
487 $table[$tr][] = $classInfo['title'];
488 $table[$tr][] = $classInfo['extension'];
489 $table[$tr][] = $classInfo['description'];
490 $link = $GLOBALS['MCONF']['_'] . '&SET[function]=list&CMD=add&tx_scheduler[class]=' . $class;
491 $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>';
495 // Render the table and return it
496 $content = '<div>' . $GLOBALS['LANG']->getLL('msg.infoScreenIntro') . '</div>';
497 $content .= $this->doc
->spacer(5);
498 $content .= $this->doc
->table($table, $tableLayout);
505 * Display the current server's time along with a help text about server time
506 * usage in the Scheduler
508 * @return string HTML to display
510 protected function displayServerTime() {
511 // Get the current time, formatted
512 $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' T (e';
513 $now = date($dateFormat) . ', GMT ' . date('P') . ')';
514 // Display the help text
515 $serverTime = '<h4>' . $GLOBALS['LANG']->getLL('label.serverTime') . '</h4>';
516 $serverTime .= '<p>' . $GLOBALS['LANG']->getLL('msg.serverTimeHelp') . '</p>';
517 $serverTime .= '<p>' . sprintf($GLOBALS['LANG']->getLL('msg.serverTime'), $now) . '</p>';
522 * Delete a task from the execution queue
526 protected function deleteTask() {
528 // Try to fetch the task and delete it
529 $task = $this->scheduler
->fetchTask($this->submittedData
['uid']);
530 // If the task is currently running, it may not be deleted
531 if ($task->isExecutionRunning()) {
532 $this->addMessage($GLOBALS['LANG']->getLL('msg.maynotDeleteRunningTask'), t3lib_FlashMessage
::ERROR
);
534 if ($this->scheduler
->removeTask($task)) {
535 $this->addMessage($GLOBALS['LANG']->getLL('msg.deleteSuccess'));
537 $this->addMessage($GLOBALS['LANG']->getLL('msg.deleteError'), t3lib_FlashMessage
::ERROR
);
540 } catch (UnexpectedValueException
$e) {
541 // The task could not be unserialized properly, simply delete the database record
542 $result = $GLOBALS['TYPO3_DB']->exec_DELETEquery('tx_scheduler_task', 'uid = ' . intval($this->submittedData
['uid']));
544 $this->addMessage($GLOBALS['LANG']->getLL('msg.deleteSuccess'));
546 $this->addMessage($GLOBALS['LANG']->getLL('msg.deleteError'), t3lib_FlashMessage
::ERROR
);
548 } catch (OutOfBoundsException
$e) {
549 // The task was not found, for some reason
550 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.taskNotFound'), $this->submittedData
['uid']), t3lib_FlashMessage
::ERROR
);
555 * Clears the registered running executions from the task
556 * Note that this doesn't actually stop the running script. It just unmarks
558 * TODO: find a way to really kill the running task
562 protected function stopTask() {
564 // Try to fetch the task and stop it
565 $task = $this->scheduler
->fetchTask($this->submittedData
['uid']);
566 if ($task->isExecutionRunning()) {
567 // If the task is indeed currently running, clear marked executions
569 $result = $task->unmarkAllExecutions();
571 $this->addMessage($GLOBALS['LANG']->getLL('msg.stopSuccess'));
573 $this->addMessage($GLOBALS['LANG']->getLL('msg.stopError'), t3lib_FlashMessage
::ERROR
);
576 // The task is not running, nothing to unmark
578 $this->addMessage($GLOBALS['LANG']->getLL('msg.maynotStopNonRunningTask'), t3lib_FlashMessage
::WARNING
);
580 } catch (Exception
$e) {
581 // The task was not found, for some reason
582 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.taskNotFound'), $this->submittedData
['uid']), t3lib_FlashMessage
::ERROR
);
587 * Return a form to add a new task or edit an existing one
589 * @return string HTML form to add or edit a task
591 protected function editTask() {
592 $registeredClasses = self
::getRegisteredClasses();
598 if ($this->submittedData
['uid'] > 0) {
599 // If editing, retrieve data for existing task
601 $taskRecord = $this->scheduler
->fetchTaskRecord($this->submittedData
['uid']);
602 // If there's a registered execution, the task should not be edited
603 if (!empty($taskRecord['serialized_executions'])) {
604 $this->addMessage($GLOBALS['LANG']->getLL('msg.maynotEditRunningTask'), t3lib_FlashMessage
::ERROR
);
605 throw new LogicException('Runnings tasks cannot not be edited', 1251232849);
607 // Get the task object
608 /** @var $task tx_scheduler_Task */
609 $task = unserialize($taskRecord['serialized_task_object']);
611 // Set some task information
612 $class = $taskRecord['classname'];
613 $taskInfo['disable'] = $taskRecord['disable'];
615 // Check that the task object is valid
616 if ($this->scheduler
->isValidTaskObject($task)) {
617 // The task object is valid, process with fetching current data
619 $taskInfo['class'] = $class;
621 // Get execution information
622 $taskInfo['start'] = $task->getExecution()->getStart();
623 $taskInfo['end'] = $task->getExecution()->getEnd();
624 $taskInfo['interval'] = $task->getExecution()->getInterval();
625 $taskInfo['croncmd'] = $task->getExecution()->getCronCmd();
626 $taskInfo['multiple'] = $task->getExecution()->getMultiple();
628 if (!empty($taskInfo['interval']) ||
!empty($taskInfo['croncmd'])) {
629 // Guess task type from the existing information
630 // If an interval or a cron command is defined, it's a recurring task
632 // FIXME: remove magic numbers for the type, use class constants instead
633 $taskInfo['type'] = 2;
634 $taskInfo['frequency'] = (empty($taskInfo['interval'])) ?
$taskInfo['croncmd'] : $taskInfo['interval'];
636 // It's not a recurring task
637 // Make sure interval and cron command are both empty
638 $taskInfo['type'] = 1;
639 $taskInfo['frequency'] = '';
640 $taskInfo['end'] = 0;
643 // The task object is not valid
645 // Issue error message
646 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.invalidTaskClassEdit'), $class), t3lib_FlashMessage
::ERROR
);
647 // Initialize empty values
648 $taskInfo['start'] = 0;
649 $taskInfo['end'] = 0;
650 $taskInfo['frequency'] = '';
651 $taskInfo['multiple'] = FALSE;
652 $taskInfo['type'] = 1;
654 } catch (OutOfBoundsException
$e) {
655 // Add a message and continue throwing the exception
656 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.taskNotFound'), $this->submittedData
['uid']), t3lib_FlashMessage
::ERROR
);
660 // If adding a new object, set some default values
661 $taskInfo['class'] = key($registeredClasses);
662 $taskInfo['type'] = 2;
663 $taskInfo['start'] = $GLOBALS['EXEC_TIME'];
664 $taskInfo['end'] = '';
665 $taskInfo['frequency'] = '';
666 $taskInfo['multiple'] = 0;
670 if (count($this->submittedData
) > 0) {
671 // If some data was already submitted, use it to override
673 $taskInfo = t3lib_div
::array_merge_recursive_overrule($taskInfo, $this->submittedData
);
676 // Get the extra fields to display for each task that needs some
677 $allAdditionalFields = array();
678 if ($process == 'add') {
679 foreach ($registeredClasses as $class => $registrationInfo) {
680 if (!empty($registrationInfo['provider'])) {
681 /** @var $providerObject tx_scheduler_AdditionalFieldProvider */
682 $providerObject = t3lib_div
::getUserObj($registrationInfo['provider']);
683 if ($providerObject instanceof tx_scheduler_AdditionalFieldProvider
) {
684 $additionalFields = $providerObject->getAdditionalFields($taskInfo, NULL, $this);
685 $allAdditionalFields = array_merge($allAdditionalFields, array($class => $additionalFields));
690 // In case of edit, get only the extra fields for the current task class
692 if (!empty($registeredClasses[$taskInfo['class']]['provider'])) {
693 $providerObject = t3lib_div
::getUserObj($registeredClasses[$taskInfo['class']]['provider']);
694 if ($providerObject instanceof tx_scheduler_AdditionalFieldProvider
) {
695 $allAdditionalFields[$taskInfo['class']] = $providerObject->getAdditionalFields($taskInfo, $task, $this);
700 // Load necessary JavaScript
701 /** @var $pageRenderer t3lib_PageRenderer */
702 $pageRenderer = $this->doc
->getPageRenderer();
703 $pageRenderer->loadExtJS();
704 $pageRenderer->addJsFile(t3lib_extMgm
::extRelPath('scheduler') . 'res/tx_scheduler_be.js');
705 $pageRenderer->addJsFile($this->backPath
. '../t3lib/js/extjs/tceforms.js');
707 // Define settings for Date Picker
708 $typo3Settings = array(
709 'datePickerUSmode' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ?
1 : 0,
710 'dateFormat' => array('j-n-Y', 'G:i j-n-Y'),
711 'dateFormatUS' => array('n-j-Y', 'G:i n-j-Y'),
713 $pageRenderer->addInlineSettingArray('', $typo3Settings);
715 // Define table layout for add/edit form
716 $tableLayout = array (
717 'table' => array ('<table border="0" cellspacing="0" cellpadding="0" id="edit_form" class="typo3-usersettings">', '</table>'),
720 // Define a style for hiding
721 // Some fields will be hidden when the task is not recurring
723 if ($taskInfo['type'] == 1) {
724 $style = ' style="display: none"';
727 // Start rendering the add/edit form
728 $content .= '<input type="hidden" name="tx_scheduler[uid]" value="' . $this->submittedData
['uid'] . '" />';
729 $content .= '<input type="hidden" name="previousCMD" value="' . $this->CMD
. '" />';
730 $content .= '<input type="hidden" name="CMD" value="save" />';
734 $defaultCell = array('<td class="td-input">', '</td>');
737 $label = '<label for="task_disable">' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:disable') . '</label>';
738 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_disable', $label);
740 '<input type="hidden" name="tx_scheduler[disable]" value="0" />
741 <input type="checkbox" name="tx_scheduler[disable]" value="1" id="task_disable"' . ($taskInfo['disable'] == 1 ?
' checked="checked"' : '') . ' />';
742 $tableLayout[$tr] = array (
743 'tr' => array('<tr id="task_disable_row">', '</tr>'),
744 'defCol' => $defaultCell,
745 '0' => array('<td class="td-label">', '</td>')
749 // Task class selector
750 $label = '<label for="task_class">' . $GLOBALS['LANG']->getLL('label.class') . '</label>';
751 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_class', $label);
752 // On editing, don't allow changing of the task class, unless it was not valid
753 if ($this->submittedData
['uid'] > 0 && !empty($taskInfo['class'])) {
754 $cell = $registeredClasses[$taskInfo['class']]['title'] . ' (' . $registeredClasses[$taskInfo['class']]['extension'] . ')';
755 $cell .= '<input type="hidden" name="tx_scheduler[class]" id="task_class" value="' . $taskInfo['class'] . '" />';
757 $cell = '<select name="tx_scheduler[class]" id="task_class" class="wide" onchange="actOnChangedTaskClass(this)">';
758 // Loop on all registered classes to display a selector
759 foreach ($registeredClasses as $class => $classInfo) {
760 $selected = ($class == $taskInfo['class']) ?
' selected="selected"' : '';
761 $cell .= '<option value="' . $class . '"' . $selected . '>' . $classInfo['title'] . ' (' . $classInfo['extension'] . ')' . '</option>';
763 $cell .= '</select>';
765 $table[$tr][] = $cell;
766 // Make sure each row has a unique id, for manipulation with JS
767 $tableLayout[$tr] = array (
768 'tr' => array('<tr id="task_class_row">', '</tr>'),
769 'defCol' => $defaultCell,
770 '0' => array('<td class="td-label">', '</td>')
774 // Task type selector
775 $label = '<label for="task_type">' . $GLOBALS['LANG']->getLL('label.type') . '</label>';
776 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_type', $label);
778 '<select name="tx_scheduler[type]" id="task_type" onchange="actOnChangedTaskType(this)">' .
779 '<option value="1"' . ($taskInfo['type'] == 1 ?
' selected="selected"' : '') . '>' . $GLOBALS['LANG']->getLL('label.type.single') . '</option>' .
780 '<option value="2"' . ($taskInfo['type'] == 2 ?
' selected="selected"' : '') . '>' . $GLOBALS['LANG']->getLL('label.type.recurring') . '</option>' .
782 $tableLayout[$tr] = array (
783 'tr' => array('<tr id="task_type_row">', '</tr>'),
784 'defCol' => $defaultCell,
785 '0' => array('<td class="td-label">', '</td>'),
789 // Start date/time field
790 // NOTE: datetime fields need a special id naming scheme
791 $label = '<label for="tceforms-datetimefield-task_start">' . $GLOBALS['LANG']->getLL('label.start') . '</label>';
792 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_start', $label);
793 $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'])) . '" />' .
794 t3lib_iconWorks
::getSpriteIcon(
795 'actions-edit-pick-date',
797 'style' => 'cursor:pointer;',
798 'id' => 'picker-tceforms-datetimefield-task_start'
801 $tableLayout[$tr] = array (
802 'tr' => array('<tr id="task_start_row">', '</tr>'),
803 'defCol' => $defaultCell,
804 '0' => array('<td class="td-label">', '</td>')
808 // End date/time field
809 // NOTE: datetime fields need a special id naming scheme
810 $label = '<label for="tceforms-datetimefield-task_end">' . $GLOBALS['LANG']->getLL('label.end') . '</label>';
811 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_end', $label);
812 $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'])) . '" />' .
813 t3lib_iconWorks
::getSpriteIcon(
814 'actions-edit-pick-date',
816 'style' => 'cursor:pointer;',
817 'id' => 'picker-tceforms-datetimefield-task_end'
820 $tableLayout[$tr] = array (
821 'tr' => array('<tr id="task_end_row"' . $style . '>', '</tr>'),
822 'defCol' => $defaultCell,
823 '0' => array('<td class="td-label">', '</td>'),
827 // Frequency input field
828 $label = '<label for="task_frequency">' . $GLOBALS['LANG']->getLL('label.frequency.long') . '</label>';
829 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_frequency', $label);
830 $cell = '<input type="text" name="tx_scheduler[frequency]" id="task_frequency" value="' . $taskInfo['frequency'] . '" />';
831 $table[$tr][] = $cell;
832 $tableLayout[$tr] = array (
833 'tr' => array('<tr id="task_frequency_row"' . $style . '>', '</tr>'),
834 'defCol' => $defaultCell,
835 '0' => array('<td class="td-label">', '</td>'),
839 // Multiple execution selector
840 $label = '<label for="task_multiple">' . $GLOBALS['LANG']->getLL('label.parallel.long') . '</label>';
841 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($this->cshKey
, 'task_multiple', $label);
843 '<input type="hidden" name="tx_scheduler[multiple]" value="0" />
844 <input type="checkbox" name="tx_scheduler[multiple]" value="1" id="task_multiple"' . ($taskInfo['multiple'] == 1 ?
' checked="checked"' : '') . ' />';
845 $tableLayout[$tr] = array (
846 'tr' => array('<tr id="task_multiple_row"' . $style . '>', '</tr>'),
847 'defCol' => $defaultCell,
848 '0' => array('<td class="td-label">', '</td>')
852 // Display additional fields
853 foreach ($allAdditionalFields as $class => $fields) {
854 if ($class == $taskInfo['class']) {
855 $additionalFieldsStyle = '';
857 $additionalFieldsStyle = ' style="display: none"';
860 // Add each field to the display, if there are indeed any
861 if (isset($fields) && is_array($fields)) {
862 foreach ($fields as $fieldID => $fieldInfo) {
863 $label = '<label for="' . $fieldID . '">' . $GLOBALS['LANG']->sL($fieldInfo['label']) . '</label>';
864 $table[$tr][] = t3lib_BEfunc
::wrapInHelp($fieldInfo['cshKey'], $fieldInfo['cshLabel'], $label);
865 $table[$tr][] = $fieldInfo['code'];
866 $tableLayout[$tr] = array (
867 'tr' => array('<tr id="' . $fieldID . '_row"' . $additionalFieldsStyle .' class="extraFields extra_fields_' . $class . '">', '</tr>'),
868 'defCol' => $defaultCell,
869 '0' => array('<td class="td-label">', '</td>')
876 // Render the add/edit task form
877 $content .= '<div style="float: left;"><div class="typo3-dyntabmenu-divs">';
878 $content .= $this->doc
->table($table, $tableLayout);
879 $content .= '</div></div>';
881 $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) . '" /> '
882 . '<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>';
884 // Display information about server time usage
885 $content .= $this->displayServerTime();
891 * Execute all selected tasks
895 protected function executeTasks() {
896 // Continue if some elements have been chosen for execution
897 if (isset($this->submittedData
['execute']) && count($this->submittedData
['execute']) > 0) {
899 // Get list of registered classes
900 $registeredClasses = self
::getRegisteredClasses();
902 // Loop on all selected tasks
903 foreach ($this->submittedData
['execute'] as $uid) {
906 // Try fetching the task
907 $task = $this->scheduler
->fetchTask($uid);
908 $class = get_class($task);
909 $name = $registeredClasses[$class]['title']. ' (' . $registeredClasses[$class]['extension'] . ')';
910 // Now try to execute it and report on outcome
912 $result = $this->scheduler
->executeTask($task);
914 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.executed'), $name));
916 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.notExecuted'), $name), t3lib_FlashMessage
::ERROR
);
919 catch (Exception
$e) {
920 // An exception was thrown, display its message as an error
921 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.executionFailed'), $name, $e->getMessage()), t3lib_FlashMessage
::ERROR
);
924 // The task was not found, for some reason
925 catch (OutOfBoundsException
$e) {
926 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.taskNotFound'), $uid), t3lib_FlashMessage
::ERROR
);
928 // The task object was not valid
929 catch (UnexpectedValueException
$e) {
930 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.executionFailed'), $name, $e->getMessage()), t3lib_FlashMessage
::ERROR
);
933 // Record the run in the system registry
934 $this->scheduler
->recordLastRun('manual');
935 // Make sure to switch to list view after execution
941 * Assemble display of list of scheduled tasks
943 * @return string Table of pending tasks
945 protected function listTasks() {
946 // Define display format for dates
947 $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
950 // Get list of registered classes
951 $registeredClasses = self
::getRegisteredClasses();
953 // Get all registered tasks
956 'FROM' => 'tx_scheduler_task',
958 'ORDERBY' => 'nextexecution'
961 $res = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($query);
962 $numRows = $GLOBALS['TYPO3_DB']->sql_num_rows($res);
963 // No tasks defined, display information message
965 /** @var t3lib_FlashMessage $flashMessage */
966 $flashMessage = t3lib_div
::makeInstance('t3lib_FlashMessage',
967 $GLOBALS['LANG']->getLL('msg.noTasks'),
969 t3lib_FlashMessage
::INFO
971 $content .= $flashMessage->render();
973 // Load ExtJS framework and specific JS library
974 /** @var $pageRenderer t3lib_PageRenderer */
975 $pageRenderer = $this->doc
->getPageRenderer();
976 $pageRenderer->loadExtJS();
977 $pageRenderer->addJsFile(t3lib_extMgm
::extRelPath('scheduler') . 'res/tx_scheduler_be.js');
979 // Initialise table layout
980 $tableLayout = array(
982 '<table border="0" cellspacing="1" cellpadding="2" class="typo3-dblist">', '</table>'
985 'tr' => array('<tr class="t3-row-header">', '</tr>'),
986 'defCol' => array('<td>', '</td>'),
987 '1' => array('<td style="width: 36px;">', '</td>'),
988 '3' => array('<td colspan="2">', '</td>'),
991 'tr' => array('<tr class="db_list_normal">', '</tr>'),
992 'defCol' => array('<td>', '</td>'),
993 '1' => array('<td class="right">', '</td>'),
994 '2' => array('<td class="right">', '</td>'),
997 $disabledTaskRow = array (
998 'tr' => array('<tr class="db_list_normal disabled">', '</tr>'),
999 'defCol' => array('<td>', '</td>'),
1000 '1' => array('<td class="right">', '</td>'),
1001 '2' => array('<td class="right">', '</td>'),
1003 $rowWithSpan = array (
1004 'tr' => array('<tr class="db_list_normal">', '</tr>'),
1005 'defCol' => array('<td>', '</td>'),
1006 '1' => array('<td class="right">', '</td>'),
1007 '2' => array('<td class="right">', '</td>'),
1008 '3' => array('<td colspan="6">', '</td>'),
1014 $table[$tr][] = '<a href="#" onclick="toggleCheckboxes();" title="' . $GLOBALS['LANG']->getLL('label.checkAll', TRUE) . '" class="icon">' .
1015 t3lib_iconWorks
::getSpriteIcon('actions-document-select') .
1017 $table[$tr][] = ' ';
1018 $table[$tr][] = $GLOBALS['LANG']->getLL('label.id');
1019 $table[$tr][] = $GLOBALS['LANG']->getLL('task');
1020 $table[$tr][] = $GLOBALS['LANG']->getLL('label.type');
1021 $table[$tr][] = $GLOBALS['LANG']->getLL('label.frequency');
1022 $table[$tr][] = $GLOBALS['LANG']->getLL('label.parallel');
1023 $table[$tr][] = $GLOBALS['LANG']->getLL('label.lastExecution');
1024 $table[$tr][] = $GLOBALS['LANG']->getLL('label.nextExecution');
1027 // Loop on all tasks
1028 while (($schedulerRecord = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
1029 // Define action icons
1030 $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>';
1031 $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>';
1032 $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>';
1033 // Define some default values
1034 $lastExecution = '-';
1036 $executionStatus = 'scheduled';
1037 $executionStatusOutput = '';
1043 $startExecutionElement = ' ';
1045 // Restore the serialized task and pass it a reference to the scheduler object
1046 /** @var $task tx_scheduler_Task */
1047 $task = unserialize($schedulerRecord['serialized_task_object']);
1049 // Assemble information about last execution
1051 if (!empty($schedulerRecord['lastexecution_time'])) {
1052 $lastExecution = date($dateFormat, $schedulerRecord['lastexecution_time']);
1053 if ($schedulerRecord['lastexecution_context'] == 'CLI') {
1054 $context = $GLOBALS['LANG']->getLL('label.cron');
1056 $context = $GLOBALS['LANG']->getLL('label.manual');
1058 $lastExecution .= ' (' . $context . ')';
1061 if ($this->scheduler
->isValidTaskObject($task)) {
1062 // The task object is valid
1064 $name = htmlspecialchars($registeredClasses[$schedulerRecord['classname']]['title']. ' (' . $registeredClasses[$schedulerRecord['classname']]['extension'] . ')');
1065 $additionalInformation = $task->getAdditionalInformation();
1066 if (!empty($additionalInformation)) {
1067 $name .= '<br />[' . htmlspecialchars($additionalInformation) . ']';
1070 // Check if task currently has a running execution
1071 if (!empty($schedulerRecord['serialized_executions'])) {
1073 $executionStatus = 'running';
1076 // Prepare display of next execution date
1077 // If task is currently running, date is not displayed (as next hasn't been calculated yet)
1078 // Also hide the date if task is disabled (the information doesn't make sense, as it will not run anyway)
1079 if ($isRunning ||
$schedulerRecord['disable'] == 1) {
1083 $nextDate = date($dateFormat, $schedulerRecord['nextexecution']);
1084 if (empty($schedulerRecord['nextexecution'])) {
1085 $nextDate = $GLOBALS['LANG']->getLL('none');
1086 } elseif ($schedulerRecord['nextexecution'] < $GLOBALS['EXEC_TIME']) {
1087 // Next execution is overdue, highlight date
1088 $nextDate = '<span class="late" title="' . $GLOBALS['LANG']->getLL('status.legend.scheduled') . '">' . $nextDate . '</span>';
1089 $executionStatus = 'late';
1093 // Get execution type
1094 if ($task->getExecution()->getInterval() == 0 && $task->getExecution()->getCronCmd() == '') {
1095 $execType = $GLOBALS['LANG']->getLL('label.type.single');
1098 $execType = $GLOBALS['LANG']->getLL('label.type.recurring');
1099 if ($task->getExecution()->getCronCmd() == '') {
1100 $frequency = $task->getExecution()->getInterval();
1102 $frequency = $task->getExecution()->getCronCmd();
1106 // Get multiple executions setting
1107 if ($task->getExecution()->getMultiple()) {
1108 $multiple = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:yes');
1110 $multiple = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:no');
1114 $startExecutionElement = '<input type="checkbox" name="tx_scheduler[execute][]" value="' . $schedulerRecord['uid'] . '" id="task_' . $schedulerRecord['uid'] . '" class="checkboxes" />';
1116 // Show no action links (edit, delete) if task is running
1117 $actions = $editAction . $deleteAction;
1119 $actions = $stopAction;
1122 // Check the disable status
1123 // Row is shown dimmed if task is disabled, unless it is still running
1124 if ($schedulerRecord['disable'] == 1 && !$isRunning) {
1125 $tableLayout[$tr] = $disabledTaskRow;
1126 $executionStatus = 'disabled';
1129 // Check if the last run failed
1130 $failureOutput = '';
1131 if (!empty($schedulerRecord['lastexecution_failure'])) {
1132 // Try to get the stored exception object
1133 /** @var $exception Exception */
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');
1139 $failureDetail = sprintf($GLOBALS['LANG']->getLL('msg.executionFailureReport'), $exception->getCode(), $exception->getMessage());
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) . '" />';
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') . ' id="executionstatus_' . $schedulerRecord['uid'] . '" alt="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.' . $executionStatus)) . '" title="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.legend.' . $executionStatus)) . '" />' . $failureOutput;
1148 $table[$tr][] = $startExecutionElement;
1149 $table[$tr][] = $actions;
1150 $table[$tr][] = $schedulerRecord['uid'];
1151 $table[$tr][] = $executionStatusOutput;
1152 $table[$tr][] = $name;
1153 $table[$tr][] = $execType;
1154 $table[$tr][] = $frequency;
1155 $table[$tr][] = $multiple;
1156 $table[$tr][] = $lastExecution;
1157 $table[$tr][] = $nextDate;
1160 // The task object is not valid
1161 // Prepare to issue an error
1163 /** @var t3lib_FlashMessage $flashMessage */
1164 $flashMessage = t3lib_div
::makeInstance('t3lib_FlashMessage',
1165 sprintf($GLOBALS['LANG']->getLL('msg.invalidTaskClass'), $schedulerRecord['classname']),
1167 t3lib_FlashMessage
::ERROR
1169 $executionStatusOutput = $flashMessage->render();
1171 $tableLayout[$tr] = $rowWithSpan;
1172 $table[$tr][] = $startExecutionElement;
1173 $table[$tr][] = $deleteAction;
1174 $table[$tr][] = $schedulerRecord['uid'];
1175 $table[$tr][] = $executionStatusOutput;
1181 $content .= $this->doc
->table($table, $tableLayout);
1183 $content .= '<input type="submit" class="button" name="go" id="scheduler_executeselected" value="' . $GLOBALS['LANG']->getLL('label.executeSelected') . '" />';
1186 if (count($registeredClasses) > 0) {
1187 // Display add new task link
1188 $link = $GLOBALS['MCONF']['_'] . '&CMD=add';
1189 $content .= '<p><a href="' . htmlspecialchars($link) .'"><img '
1190 . t3lib_iconWorks
::skinImg($this->backPath
, 'gfx/new_el.gif')
1191 . ' alt="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xml:new', TRUE)
1192 . '" /> ' . $GLOBALS['LANG']->getLL('action.add') . '</a></p>';
1194 /** @var t3lib_FlashMessage $flashMessage */
1195 $flashMessage = t3lib_div
::makeInstance('t3lib_FlashMessage',
1196 $GLOBALS['LANG']->getLL('msg.noTasksDefined'),
1198 t3lib_FlashMessage
::INFO
1200 $content .= $flashMessage->render();
1203 // Display legend, if there's at least one registered task
1204 // Also display information about the usage of server time
1206 $content .= $this->doc
->spacer(20);
1207 $content .= '<h4>' . $GLOBALS['LANG']->getLL('status.legend') . '</h4>
1209 <li><img ' . t3lib_iconWorks
::skinImg(t3lib_extMgm
::extRelPath('scheduler'), 'res/gfx/status_failure.png') . ' alt="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.failure')) . '" title="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.failure')) . '" /> ' . $GLOBALS['LANG']->getLL('status.legend.failure') . '</li>
1210 <li><img ' . t3lib_iconWorks
::skinImg(t3lib_extMgm
::extRelPath('scheduler'), 'res/gfx/status_late.png') . ' alt="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.late')) . '" title="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.late')) . '" /> ' . $GLOBALS['LANG']->getLL('status.legend.late') . '</li>
1211 <li><img ' . t3lib_iconWorks
::skinImg(t3lib_extMgm
::extRelPath('scheduler'), 'res/gfx/status_running.png') . ' alt="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.running')) . '" title="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.running')) . '" /> ' . $GLOBALS['LANG']->getLL('status.legend.running') . '</li>
1212 <li><img ' . t3lib_iconWorks
::skinImg(t3lib_extMgm
::extRelPath('scheduler'), 'res/gfx/status_scheduled.png') . ' alt="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.scheduled')) . '" title="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.scheduled')) . '" /> ' . $GLOBALS['LANG']->getLL('status.legend.scheduled') . '</li>
1213 <li><img ' . t3lib_iconWorks
::skinImg(t3lib_extMgm
::extRelPath('scheduler'), 'res/gfx/status_disabled.png') . ' alt="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.disabled')) . '" title="' . htmlspecialchars($GLOBALS['LANG']->getLL('status.disabled')) . '" /> ' . $GLOBALS['LANG']->getLL('status.legend.disabled') . '</li>
1215 $content .= $this->doc
->spacer(10);
1216 $content .= $this->displayServerTime();
1220 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1226 * Saves a task specified in the backend form to the database
1230 protected function saveTask() {
1232 // If a task is being edited fetch old task data
1233 if (!empty($this->submittedData
['uid'])) {
1235 $taskRecord = $this->scheduler
->fetchTaskRecord($this->submittedData
['uid']);
1236 /** @var tx_scheduler_Task $task */
1237 $task = unserialize($taskRecord['serialized_task_object']);
1238 } catch (OutOfBoundsException
$e) {
1239 // If the task could not be fetched, issue an error message
1241 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.taskNotFound'), $this->submittedData
['uid']), t3lib_FlashMessage
::ERROR
);
1245 // Register single execution
1246 if ($this->submittedData
['type'] == 1) {
1247 $task->registerSingleExecution($this->submittedData
['start']);
1249 // Else, it's a recurring task
1251 if (!empty($this->submittedData
['croncmd'])) {
1252 // Definition by cron-like syntax
1255 $cronCmd = $this->submittedData
['croncmd'];
1257 // Definition by interval
1259 $interval = $this->submittedData
['interval'];
1263 // Register recurring execution
1264 $task->registerRecurringExecution($this->submittedData
['start'], $interval, $this->submittedData
['end'], $this->submittedData
['multiple'], $cronCmd);
1268 $task->setDisabled($this->submittedData
['disable']);
1270 // Save additional input values
1271 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData
['class']]['additionalFields'])) {
1272 /** @var $providerObject tx_scheduler_AdditionalFieldProvider */
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);
1280 $result = $this->scheduler
->saveTask($task);
1282 $this->addMessage($GLOBALS['LANG']->getLL('msg.updateSuccess'));
1284 $this->addMessage($GLOBALS['LANG']->getLL('msg.updateError'), t3lib_FlashMessage
::ERROR
);
1287 // A new task is being created
1289 // Create an instance of chosen class
1290 $task = t3lib_div
::makeInstance($this->submittedData
['class']);
1292 if ($this->submittedData
['type'] == 1) {
1293 // Set up single execution
1294 $task->registerSingleExecution($this->submittedData
['start']);
1296 // Set up recurring execution
1297 $task->registerRecurringExecution($this->submittedData
['start'], $this->submittedData
['interval'], $this->submittedData
['end'], $this->submittedData
['multiple'], $this->submittedData
['croncmd']);
1300 // Save additional input values
1301 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData
['class']]['additionalFields'])) {
1302 /** @var $providerObject tx_scheduler_AdditionalFieldProvider */
1303 $providerObject = t3lib_div
::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData
['class']]['additionalFields']);
1304 if ($providerObject instanceof tx_scheduler_AdditionalFieldProvider
) {
1305 $providerObject->saveAdditionalFields($this->submittedData
, $task);
1310 $task->setDisabled($this->submittedData
['disable']);
1313 $result = $this->scheduler
->addTask($task);
1315 $this->addMessage($GLOBALS['LANG']->getLL('msg.addSuccess'));
1317 $this->addMessage($GLOBALS['LANG']->getLL('msg.addError'), t3lib_FlashMessage
::ERROR
);
1323 /*************************
1325 * INPUT PROCESSING UTILITIES
1327 *************************/
1330 * Checks the submitted data and performs some pre-processing on it
1332 * @return boolean TRUE if everything was ok, FALSE otherwise
1334 protected function preprocessData() {
1338 $this->submittedData
['uid'] = (empty($this->submittedData
['uid'])) ?
0 : intval($this->submittedData
['uid']);
1340 // Validate selected task class
1341 if (!class_exists($this->submittedData
['class'])) {
1342 $this->addMessage($GLOBALS['LANG']->getLL('msg.noTaskClassFound'), t3lib_FlashMessage
::ERROR
);
1346 if (empty($this->submittedData
['start'])) {
1347 $this->addMessage($GLOBALS['LANG']->getLL('msg.noStartDate'), t3lib_FlashMessage
::ERROR
);
1351 $timestamp = $this->checkDate($this->submittedData
['start']);
1352 $this->submittedData
['start'] = $timestamp;
1353 } catch (Exception
$e) {
1354 $this->addMessage($GLOBALS['LANG']->getLL('msg.invalidStartDate'), t3lib_FlashMessage
::ERROR
);
1359 // Check end date, if recurring task
1360 if ($this->submittedData
['type'] == 2 && !empty($this->submittedData
['end'])) {
1362 $timestamp = $this->checkDate($this->submittedData
['end']);
1363 $this->submittedData
['end'] = $timestamp;
1365 if ($this->submittedData
['end'] < $this->submittedData
['start']) {
1366 $this->addMessage($GLOBALS['LANG']->getLL('msg.endDateSmallerThanStartDate'), t3lib_FlashMessage
::ERROR
);
1369 } catch (Exception
$e) {
1370 $this->addMessage($GLOBALS['LANG']->getLL('msg.invalidEndDate'), t3lib_FlashMessage
::ERROR
);
1375 // Set default values for interval and cron command
1376 $this->submittedData
['interval'] = 0;
1377 $this->submittedData
['croncmd'] = '';
1379 // Check type and validity of frequency, if recurring
1380 if ($this->submittedData
['type'] == 2) {
1381 $frequency = trim($this->submittedData
['frequency']);
1383 if (empty($frequency)) {
1384 // Empty frequency, not valid
1386 $this->addMessage($GLOBALS['LANG']->getLL('msg.noFrequency'), t3lib_FlashMessage
::ERROR
);
1390 $cronErrorMessage = '';
1392 // Try interpreting the cron command
1394 tx_scheduler_CronCmd_Normalize
::normalize($frequency);
1395 $this->submittedData
['croncmd'] = $frequency;
1397 // If the cron command was invalid, we may still have a valid frequency in seconds
1398 catch (Exception
$e) {
1399 // Store the exception's result
1400 $cronErrorMessage = $e->getMessage();
1401 $cronErrorCode = $e->getCode();
1402 // Check if the frequency is a valid number
1403 // If yes, assume it is a frequency in seconds, and unset cron error code
1404 if (is_numeric($frequency)) {
1405 $this->submittedData
['interval'] = intval($frequency);
1406 unset($cronErrorCode);
1409 // If there's a cron error code, issue validation error message
1410 if (!empty($cronErrorCode)) {
1411 $this->addMessage(sprintf($GLOBALS['LANG']->getLL('msg.frequencyError'), $cronErrorMessage, $cronErrorCode), t3lib_FlashMessage
::ERROR
);
1417 // Validate additional input fields
1418 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData
['class']]['additionalFields'])) {
1419 $providerObject = t3lib_div
::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][$this->submittedData
['class']]['additionalFields']);
1420 if ($providerObject instanceof tx_scheduler_AdditionalFieldProvider
) {
1421 // The validate method will return TRUE if all went well, but that must not
1422 // override previous FALSE values => AND the returned value with the existing one
1423 $result &= $providerObject->validateAdditionalFields($this->submittedData
, $this);
1431 * This method checks whether the given string can be considered a valid date or not
1432 * Allowed values are anything that matches natural language (see PHP function strtotime())
1433 * or TYPO3's date syntax: HH:ii yyyy-mm-dd
1434 * If the string is a valid date, the corresponding timestamp is returned.
1435 * Otherwise an exception is thrown
1437 * @param string $string String to check
1438 * @return integer Unix timestamp
1440 public function checkDate($string) {
1441 // Try with strtotime
1442 $timestamp = strtotime($string);
1444 // That failed. Try TYPO3's standard date/time input format
1445 if ($timestamp === FALSE) {
1446 // Split time and date
1447 $dateParts = t3lib_div
::trimExplode(' ', $string, TRUE);
1448 // Proceed if there are indeed two parts
1449 // Extract each component of date and time
1450 if (count($dateParts) == 2) {
1451 list($time, $date) = $dateParts;
1452 list($hour, $minutes) = t3lib_div
::trimExplode(':', $time, TRUE);
1453 list($day, $month, $year) = t3lib_div
::trimExplode('-', $date, TRUE);
1454 // Get a timestamp from all these parts
1455 $timestamp = @mktime
($hour, $minutes, 0, $month, $day, $year);
1457 // If the timestamp is still FALSE, throw an exception
1458 if ($timestamp === FALSE) {
1459 throw new InvalidArgumentException('"' . $string . '" seems not to be a correct date.', 1294587694);
1465 /*************************
1467 * APPLICATION LOGIC UTILITIES
1469 *************************/
1472 * This method is used to add a message to the internal queue
1474 * @param string $message The message itself
1475 * @param integer $severity Message level (according to t3lib_FlashMessage class constants)
1478 public function addMessage($message, $severity = t3lib_FlashMessage
::OK
) {
1479 $message = t3lib_div
::makeInstance(
1480 't3lib_FlashMessage',
1486 t3lib_FlashMessageQueue
::addMessage($message);
1490 * This method a list of all classes that have been registered with the Scheduler
1491 * For each item the following information is provided, as an associative array:
1493 * ['extension'] => Key of the extension which provides the class
1494 * ['filename'] => Path to the file containing the class
1495 * ['title'] => String (possibly localized) containing a human-readable name for the class
1496 * ['provider'] => Name of class that implements the interface for additional fields, if necessary
1498 * The name of the class itself is used as the key of the list array
1500 * @return array List of registered classes
1502 protected static function getRegisteredClasses() {
1504 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'])) {
1505 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] as $class => $registrationInformation) {
1507 $title = isset($registrationInformation['title']) ?
$GLOBALS['LANG']->sL($registrationInformation['title']) : '';
1508 $description = isset($registrationInformation['description']) ?
$GLOBALS['LANG']->sL($registrationInformation['description']) : '';
1510 $list[$class] = array(
1511 'extension' => $registrationInformation['extension'],
1513 'description' => $description,
1514 'provider' => isset($registrationInformation['additionalFields']) ?
$registrationInformation['additionalFields'] : ''
1523 /*************************
1525 * RENDERING UTILITIES
1527 *************************/
1530 * Gets the filled markers that are used in the HTML template.
1532 * @return array The filled marker array
1534 protected function getTemplateMarkers() {
1536 'CSH' => t3lib_BEfunc
::wrapInHelp('_MOD_tools_txschedulerM1', ''),
1537 'FUNC_MENU' => $this->getFunctionMenu(),
1538 'CONTENT' => $this->content
,
1539 'TITLE' => $GLOBALS['LANG']->getLL('title'),
1546 * Gets the function menu selector for this backend module.
1548 * @return string The HTML representation of the function menu selector
1550 protected function getFunctionMenu() {
1551 $functionMenu = t3lib_BEfunc
::getFuncMenu(
1554 $this->MOD_SETTINGS
['function'],
1555 $this->MOD_MENU
['function']
1558 return $functionMenu;
1562 * Gets the buttons that shall be rendered in the docHeader.
1564 * @return array Available buttons for the docHeader
1566 protected function getDocHeaderButtons() {
1569 'shortcut' => $this->getShortcutButton(),
1572 if (empty($this->CMD
) ||
$this->CMD
== 'list') {
1573 $buttons['reload'] = '<a href="' . $GLOBALS['MCONF']['_'] . '" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.reload', TRUE) . '">' .
1574 t3lib_iconWorks
::getSpriteIcon('actions-system-refresh') .
1582 * Gets the button to set a new shortcut in the backend (if current user is allowed to).
1584 * @return string HTML representation of the shortcut button
1586 protected function getShortcutButton() {
1588 if ($GLOBALS['BE_USER']->mayMakeShortcut()) {
1589 $result = $this->doc
->makeShortcutIcon('', 'function', $this->MCONF
['name']);
1596 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['ext/scheduler/class.tx_scheduler_module.php'])) {
1597 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['ext/scheduler/class.tx_scheduler_module.php']);