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