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