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