first commit
No files matched your search
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Scheduler;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Base class for providers of additional fields
|
||||
*/
|
||||
abstract class AbstractAdditionalFieldProvider implements AdditionalFieldProviderInterface
|
||||
{
|
||||
/**
|
||||
* Add a flash message
|
||||
*
|
||||
* @param string $message the flash message content
|
||||
* @param int $severity the flash message severity
|
||||
*/
|
||||
protected function addMessage(string $message, int $severity = FlashMessage::OK): void
|
||||
{
|
||||
$flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, '', $severity);
|
||||
$service = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$queue = $service->getMessageQueueByIdentifier();
|
||||
$queue->enqueue($flashMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes who want to provide additional fields when adding a task
|
||||
*/
|
||||
interface AdditionalFieldProviderInterface
|
||||
{
|
||||
/**
|
||||
* Gets additional fields to render in the form to add/edit a task
|
||||
*
|
||||
* @param array $taskInfo Values of the fields from the add/edit task form
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task The task object being edited. Null when adding a task!
|
||||
* @param \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $schedulerModule Reference to the scheduler backend module
|
||||
* @return array A two dimensional array, array('Identifier' => array('fieldId' => array('code' => '', 'label' => '', 'cshKey' => '', 'cshLabel' => ''))
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $schedulerModule);
|
||||
|
||||
/**
|
||||
* Validates the additional fields' values
|
||||
*
|
||||
* @param array $submittedData An array containing the data submitted by the add/edit task form
|
||||
* @param \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $schedulerModule Reference to the scheduler backend module
|
||||
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $schedulerModule);
|
||||
|
||||
/**
|
||||
* Takes care of saving the additional fields' values in the task's object
|
||||
*
|
||||
* @param array $submittedData An array containing the data submitted by the add/edit task form
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the scheduler backend module
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, \TYPO3\CMS\Scheduler\Task\AbstractTask $task);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Command;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Core\Bootstrap;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Scheduler\Scheduler;
|
||||
use TYPO3\CMS\Scheduler\Task\AbstractTask;
|
||||
|
||||
/**
|
||||
* CLI command for the 'scheduler' extension which executes
|
||||
*/
|
||||
class SchedulerCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTask = true;
|
||||
|
||||
/**
|
||||
* @var Scheduler
|
||||
*/
|
||||
protected $scheduler;
|
||||
|
||||
/**
|
||||
* Configure the command by defining the name, options and arguments
|
||||
*/
|
||||
public function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription('Start the TYPO3 Scheduler from the command line.')
|
||||
->setHelp('If no parameter is given, the scheduler executes any tasks that are overdue to run.
|
||||
Call it like this: typo3/sysext/core/bin/typo3 scheduler:run --task=13 -f')
|
||||
->addOption(
|
||||
'task',
|
||||
'i',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'UID of a specific task'
|
||||
)
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force execution of the task which is passed with -i option'
|
||||
)
|
||||
->addOption(
|
||||
'stop',
|
||||
's',
|
||||
InputOption::VALUE_NONE,
|
||||
'Stop the task which is passed with -i option'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute scheduler tasks
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// Make sure the _cli_ user is loaded
|
||||
Bootstrap::initializeBackendAuthentication();
|
||||
|
||||
$this->scheduler = GeneralUtility::makeInstance(Scheduler::class);
|
||||
|
||||
if ((int)$input->getOption('task') > 0) {
|
||||
$taskUid = (int)$input->getOption('task');
|
||||
$stopTask = (bool)($input->hasOption('stop') && $input->getOption('stop'));
|
||||
$force = (bool)($input->hasOption('force') && $input->getOption('force'));
|
||||
$task = $this->getTask($taskUid, $stopTask || $force);
|
||||
|
||||
if ($this->scheduler->isValidTaskObject($task)) {
|
||||
if ($stopTask) {
|
||||
$this->stopTask($task);
|
||||
} else {
|
||||
$this->scheduler->executeTask($task);
|
||||
}
|
||||
}
|
||||
// Record the run in the system registry
|
||||
$this->scheduler->recordLastRun('cli-by-id');
|
||||
} else {
|
||||
$this->loopTasks();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop task
|
||||
*
|
||||
* @param AbstractTask $task
|
||||
*/
|
||||
protected function stopTask($task)
|
||||
{
|
||||
if ($this->scheduler->isValidTaskObject($task)) {
|
||||
$task->unmarkAllExecutions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return task a task for a given UID
|
||||
*
|
||||
* @param int $taskUid
|
||||
* @param bool $force fetch the task regardless if it is queued for execution
|
||||
* @return AbstractTask
|
||||
*/
|
||||
protected function getTask(int $taskUid, bool $force)
|
||||
{
|
||||
if ($force) {
|
||||
$task = $this->scheduler->fetchTask($taskUid);
|
||||
} else {
|
||||
$whereClause = 'uid = ' . (int)$taskUid . ' AND nextexecution != 0 AND nextexecution <= ' . $GLOBALS['EXEC_TIME'];
|
||||
list($task) = $this->scheduler->fetchTasksWithCondition($whereClause);
|
||||
}
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute tasks in loop that are ready to execute
|
||||
*/
|
||||
protected function loopTasks()
|
||||
{
|
||||
do {
|
||||
// Try getting the next task and execute it
|
||||
// If there are no more tasks to execute, an exception is thrown by \TYPO3\CMS\Scheduler\Scheduler::fetchTask()
|
||||
try {
|
||||
$task = $this->scheduler->fetchTask();
|
||||
try {
|
||||
$this->scheduler->executeTask($task);
|
||||
} catch (\Exception $e) {
|
||||
// We ignore any exception that may have been thrown during execution,
|
||||
// as this is a background process.
|
||||
// The exception message has been recorded to the database anyway
|
||||
continue;
|
||||
}
|
||||
} catch (\OutOfBoundsException $e) {
|
||||
$this->hasTask = false;
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
continue;
|
||||
}
|
||||
} while ($this->hasTask);
|
||||
// Record the run in the system registry
|
||||
$this->scheduler->recordLastRun();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\CronCommand;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides calculations for the cron command format.
|
||||
*/
|
||||
class CronCommand
|
||||
{
|
||||
/**
|
||||
* Normalized sections of the cron command.
|
||||
* Allowed are comma separated lists of integers and the character '*'
|
||||
*
|
||||
* field lower and upper bound
|
||||
* ----- --------------
|
||||
* minute 0-59
|
||||
* hour 0-23
|
||||
* day of month 1-31
|
||||
* month 1-12
|
||||
* day of week 1-7
|
||||
*
|
||||
* @var array $cronCommandSections
|
||||
*/
|
||||
protected $cronCommandSections;
|
||||
|
||||
/**
|
||||
* Timestamp of next execution date.
|
||||
* This value starts with 'now + 1 minute' if not set externally
|
||||
* by unit tests. After a call to calculateNextValue() it holds the timestamp of
|
||||
* the next execution date which matches the cron command restrictions.
|
||||
*/
|
||||
protected $timestamp;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $cronCommand The cron command can hold any combination documented as valid
|
||||
* @param bool|int $timestamp Optional start time, used in unit tests
|
||||
*/
|
||||
public function __construct($cronCommand, $timestamp = false)
|
||||
{
|
||||
$cronCommand = NormalizeCommand::normalize($cronCommand);
|
||||
// Explode cron command to sections
|
||||
$this->cronCommandSections = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(' ', $cronCommand);
|
||||
// Initialize the values with the starting time
|
||||
// This takes care that the calculated time is always in the future
|
||||
if ($timestamp === false) {
|
||||
$timestamp = strtotime('+1 minute');
|
||||
} else {
|
||||
$timestamp += 60;
|
||||
}
|
||||
$this->timestamp = $this->roundTimestamp($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the date of the next execution.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function calculateNextValue()
|
||||
{
|
||||
$newTimestamp = $this->getTimestamp();
|
||||
// Calculate next minute and hour field
|
||||
$loopCount = 0;
|
||||
while (true) {
|
||||
$loopCount++;
|
||||
// If there was no match within two days, cron command is invalid.
|
||||
// The second day is needed to catch the summertime leap in some countries.
|
||||
if ($loopCount > 2880) {
|
||||
throw new \RuntimeException('Unable to determine next execution timestamp: Hour and minute combination is invalid.', 1291494126);
|
||||
}
|
||||
if ($this->minuteAndHourMatchesCronCommand($newTimestamp)) {
|
||||
break;
|
||||
}
|
||||
$newTimestamp += 60;
|
||||
}
|
||||
$loopCount = 0;
|
||||
while (true) {
|
||||
$loopCount++;
|
||||
// A date must match within the next 4 years, this high number makes
|
||||
// sure leap year cron command configuration are caught.
|
||||
// If the loop runs longer than that, the cron command is invalid.
|
||||
if ($loopCount > 1464) {
|
||||
throw new \RuntimeException('Unable to determine next execution timestamp: Day of month, month and day of week combination is invalid.', 1291501280);
|
||||
}
|
||||
if ($this->dayMatchesCronCommand($newTimestamp)) {
|
||||
break;
|
||||
}
|
||||
$newTimestamp += $this->numberOfSecondsInDay($newTimestamp);
|
||||
}
|
||||
$this->timestamp = $newTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next timestamp
|
||||
*
|
||||
* @return int Unix timestamp
|
||||
*/
|
||||
public function getTimestamp()
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cron command sections. Array of strings, each containing either
|
||||
* a list of comma separated integers or *
|
||||
*
|
||||
* @return array command sections:
|
||||
* @internal
|
||||
*/
|
||||
public function getCronCommandSections()
|
||||
{
|
||||
return $this->cronCommandSections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if current timestamp matches minute and hour cron command restriction.
|
||||
*
|
||||
* @param int $timestamp to test
|
||||
* @return bool TRUE if cron command conditions are met
|
||||
*/
|
||||
protected function minuteAndHourMatchesCronCommand($timestamp)
|
||||
{
|
||||
$minute = (int)date('i', $timestamp);
|
||||
$hour = (int)date('G', $timestamp);
|
||||
$commandMatch = false;
|
||||
if ($this->isInCommandList($this->cronCommandSections[0], $minute) && $this->isInCommandList($this->cronCommandSections[1], $hour)) {
|
||||
$commandMatch = true;
|
||||
}
|
||||
return $commandMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if current timestamp matches day of month, month and day of week
|
||||
* cron command restriction
|
||||
*
|
||||
* @param int $timestamp to test
|
||||
* @return bool TRUE if cron command conditions are met
|
||||
*/
|
||||
protected function dayMatchesCronCommand($timestamp)
|
||||
{
|
||||
$dayOfMonth = date('j', $timestamp);
|
||||
$month = date('n', $timestamp);
|
||||
$dayOfWeek = date('N', $timestamp);
|
||||
$isInDayOfMonth = $this->isInCommandList($this->cronCommandSections[2], $dayOfMonth);
|
||||
$isInMonth = $this->isInCommandList($this->cronCommandSections[3], $month);
|
||||
$isInDayOfWeek = $this->isInCommandList($this->cronCommandSections[4], $dayOfWeek);
|
||||
// Quote from vixiecron:
|
||||
// Note: The day of a command's execution can be specified by two fields — day of month, and day of week.
|
||||
// If both fields are restricted (i.e., aren't *), the command will be run when either field
|
||||
// matches the current time. For example, `30 4 1,15 * 5' would cause
|
||||
// a command to be run at 4:30 am on the 1st and 15th of each month, plus every Friday.
|
||||
$isDayOfMonthRestricted = (string)$this->cronCommandSections[2] !== '*';
|
||||
$isDayOfWeekRestricted = (string)$this->cronCommandSections[4] !== '*';
|
||||
$commandMatch = false;
|
||||
if ($isInMonth) {
|
||||
if ($isInDayOfMonth && $isDayOfMonthRestricted || $isInDayOfWeek && $isDayOfWeekRestricted || $isInDayOfMonth && !$isDayOfMonthRestricted && $isInDayOfWeek && !$isDayOfWeekRestricted) {
|
||||
$commandMatch = true;
|
||||
}
|
||||
}
|
||||
return $commandMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given number validates a cron command section. The given cron
|
||||
* command must be a 'normalized' list with only comma separated integers or '*'
|
||||
*
|
||||
* @param string $commandExpression: cron command
|
||||
* @param int $numberToMatch: number to look up
|
||||
* @return bool TRUE if number is in list
|
||||
*/
|
||||
protected function isInCommandList($commandExpression, $numberToMatch)
|
||||
{
|
||||
if ((string)$commandExpression === '*') {
|
||||
$inList = true;
|
||||
} else {
|
||||
$inList = \TYPO3\CMS\Core\Utility\GeneralUtility::inList($commandExpression, $numberToMatch);
|
||||
}
|
||||
return $inList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to calculate number of seconds in a day.
|
||||
*
|
||||
* This is not always 86400 (60*60*24) and depends on the timezone:
|
||||
* Some countries like Germany have a summertime / wintertime switch,
|
||||
* on every last sunday in march clocks are forwarded by one hour (set from 2:00 to 3:00),
|
||||
* and on last sunday of october they are set back one hour (from 3:00 to 2:00).
|
||||
* This shortens and lengthens the length of a day by one hour.
|
||||
*
|
||||
* @param int $timestamp Unix timestamp
|
||||
* @return int Number of seconds of day
|
||||
*/
|
||||
protected function numberOfSecondsInDay($timestamp)
|
||||
{
|
||||
$now = mktime(0, 0, 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
|
||||
// Make sure to be in next day, even if day has 25 hours
|
||||
$nextDay = $now + 60 * 60 * 25;
|
||||
$nextDay = mktime(0, 0, 0, date('n', $nextDay), date('j', $nextDay), date('Y', $nextDay));
|
||||
return $nextDay - $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a timestamp down to full minute.
|
||||
*
|
||||
* @param int $timestamp Unix timestamp
|
||||
* @return int Rounded timestamp
|
||||
*/
|
||||
protected function roundTimestamp($timestamp)
|
||||
{
|
||||
return mktime(date('H', $timestamp), date('i', $timestamp), 0, date('n', $timestamp), date('j', $timestamp), date('Y', $timestamp));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\CronCommand;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* Validate and normalize a cron command.
|
||||
*
|
||||
* Special fields like three letter weekdays, ranges and steps are substituted
|
||||
* to a comma separated list of integers. Example:
|
||||
* '2-4 10-40/10 * mar * fri' will be normalized to '2,4 10,20,30,40 * * 3 1,2'
|
||||
*/
|
||||
class NormalizeCommand
|
||||
{
|
||||
/**
|
||||
* Main API method: Get the cron command and normalize it.
|
||||
*
|
||||
* If no exception is thrown, the resulting cron command is validated
|
||||
* and consists of five whitespace separated fields, which are either
|
||||
* the letter '*' or a sorted, unique comma separated list of integers.
|
||||
*
|
||||
* @throws \InvalidArgumentException cron command is invalid or out of bounds
|
||||
* @param string $cronCommand The cron command to normalize
|
||||
* @return string Normalized cron command
|
||||
*/
|
||||
public static function normalize($cronCommand)
|
||||
{
|
||||
$cronCommand = trim($cronCommand);
|
||||
$cronCommand = self::convertKeywordsToCronCommand($cronCommand);
|
||||
$cronCommand = self::normalizeFields($cronCommand);
|
||||
return $cronCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept special cron command keywords and convert to standard cron syntax.
|
||||
* Allowed keywords: @yearly, @annually, @monthly, @weekly, @daily, @midnight, @hourly
|
||||
*
|
||||
* @param string $cronCommand Cron command
|
||||
* @return string Normalized cron command if keyword was found, else unchanged cron command
|
||||
*/
|
||||
protected static function convertKeywordsToCronCommand($cronCommand)
|
||||
{
|
||||
switch ($cronCommand) {
|
||||
case '@yearly':
|
||||
case '@annually':
|
||||
$cronCommand = '0 0 1 1 *';
|
||||
break;
|
||||
case '@monthly':
|
||||
$cronCommand = '0 0 1 * *';
|
||||
break;
|
||||
case '@weekly':
|
||||
$cronCommand = '0 0 * * 0';
|
||||
break;
|
||||
case '@daily':
|
||||
|
||||
case '@midnight':
|
||||
$cronCommand = '0 0 * * *';
|
||||
break;
|
||||
case '@hourly':
|
||||
$cronCommand = '0 * * * *';
|
||||
break;
|
||||
}
|
||||
return $cronCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize cron command field to list of integers or *
|
||||
*
|
||||
* @param string $cronCommand cron command
|
||||
* @return string Normalized cron command
|
||||
*/
|
||||
protected static function normalizeFields($cronCommand)
|
||||
{
|
||||
$fieldArray = self::splitFields($cronCommand);
|
||||
$fieldArray[0] = self::normalizeIntegerField($fieldArray[0], 0, 59);
|
||||
$fieldArray[1] = self::normalizeIntegerField($fieldArray[1], 0, 23);
|
||||
$fieldArray[2] = self::normalizeIntegerField($fieldArray[2], 1, 31);
|
||||
$fieldArray[3] = self::normalizeMonthAndWeekdayField($fieldArray[3], true);
|
||||
$fieldArray[4] = self::normalizeMonthAndWeekdayField($fieldArray[4], false);
|
||||
$normalizedCronCommand = implode(' ', $fieldArray);
|
||||
return $normalizedCronCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a given cron command like '23 * * * *' to an array with five fields.
|
||||
*
|
||||
* @throws \InvalidArgumentException If splitted array does not contain five entries
|
||||
* @param string $cronCommand cron command
|
||||
* @return array
|
||||
*/
|
||||
protected static function splitFields($cronCommand)
|
||||
{
|
||||
$fields = explode(' ', $cronCommand);
|
||||
if (count($fields) !== 5) {
|
||||
throw new \InvalidArgumentException('Unable to split given cron command to five fields.', 1291227373);
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize month field.
|
||||
*
|
||||
* @param string $expression Month field expression
|
||||
* @param bool $isMonthField TRUE if month field is handled, FALSE for weekday field
|
||||
* @return string Normalized expression
|
||||
*/
|
||||
protected static function normalizeMonthAndWeekdayField($expression, $isMonthField = true)
|
||||
{
|
||||
if ((string)$expression === '*') {
|
||||
$fieldValues = '*';
|
||||
} else {
|
||||
// Fragment expression by , / and - and substitute three letter code of month and weekday to numbers
|
||||
$listOfCommaValues = explode(',', $expression);
|
||||
$fieldArray = [];
|
||||
foreach ($listOfCommaValues as $listElement) {
|
||||
if (strpos($listElement, '/') !== false) {
|
||||
list($left, $right) = explode('/', $listElement);
|
||||
if (strpos($left, '-') !== false) {
|
||||
list($leftBound, $rightBound) = explode('-', $left);
|
||||
$leftBound = self::normalizeMonthAndWeekday($leftBound, $isMonthField);
|
||||
$rightBound = self::normalizeMonthAndWeekday($rightBound, $isMonthField);
|
||||
$left = $leftBound . '-' . $rightBound;
|
||||
} else {
|
||||
if ((string)$left !== '*') {
|
||||
$left = self::normalizeMonthAndWeekday($left, $isMonthField);
|
||||
}
|
||||
}
|
||||
$fieldArray[] = $left . '/' . $right;
|
||||
} elseif (strpos($listElement, '-') !== false) {
|
||||
list($left, $right) = explode('-', $listElement);
|
||||
$left = self::normalizeMonthAndWeekday($left, $isMonthField);
|
||||
$right = self::normalizeMonthAndWeekday($right, $isMonthField);
|
||||
$fieldArray[] = $left . '-' . $right;
|
||||
} else {
|
||||
$fieldArray[] = self::normalizeMonthAndWeekday($listElement, $isMonthField);
|
||||
}
|
||||
}
|
||||
$fieldValues = implode(',', $fieldArray);
|
||||
}
|
||||
return $isMonthField ? self::normalizeIntegerField($fieldValues, 1, 12) : self::normalizeIntegerField($fieldValues, 1, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize integer field.
|
||||
*
|
||||
* @throws \InvalidArgumentException If field is invalid or out of bounds
|
||||
* @param string $expression Expression
|
||||
* @param int $lowerBound Lower limit of result list
|
||||
* @param int $upperBound Upper limit of result list
|
||||
* @return string Normalized expression
|
||||
*/
|
||||
protected static function normalizeIntegerField($expression, $lowerBound = 0, $upperBound = 59)
|
||||
{
|
||||
if ((string)$expression === '*') {
|
||||
$fieldValues = '*';
|
||||
} else {
|
||||
$listOfCommaValues = explode(',', $expression);
|
||||
$fieldArray = [];
|
||||
foreach ($listOfCommaValues as $listElement) {
|
||||
if (strpos($listElement, '/') !== false) {
|
||||
list($left, $right) = explode('/', $listElement);
|
||||
if ((string)$left === '*') {
|
||||
$leftList = self::convertRangeToListOfValues($lowerBound . '-' . $upperBound);
|
||||
} else {
|
||||
$leftList = self::convertRangeToListOfValues($left);
|
||||
}
|
||||
$fieldArray[] = self::reduceListOfValuesByStepValue($leftList . '/' . $right);
|
||||
} elseif (strpos($listElement, '-') !== false) {
|
||||
$fieldArray[] = self::convertRangeToListOfValues($listElement);
|
||||
} elseif (MathUtility::canBeInterpretedAsInteger($listElement)) {
|
||||
$fieldArray[] = $listElement;
|
||||
} elseif (strlen($listElement) === 2 && $listElement[0] === '0') {
|
||||
$fieldArray[] = (int)$listElement;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Unable to normalize integer field.', 1291429389);
|
||||
}
|
||||
}
|
||||
$fieldValues = implode(',', $fieldArray);
|
||||
}
|
||||
if ((string)$fieldValues === '') {
|
||||
throw new \InvalidArgumentException('Unable to convert integer field to list of values: Result list empty.', 1291422012);
|
||||
}
|
||||
if ((string)$fieldValues !== '*') {
|
||||
$fieldList = explode(',', $fieldValues);
|
||||
sort($fieldList);
|
||||
$fieldList = array_unique($fieldList);
|
||||
if (current($fieldList) < $lowerBound) {
|
||||
throw new \InvalidArgumentException('Lowest element in list is smaller than allowed.', 1291470084);
|
||||
}
|
||||
if (end($fieldList) > $upperBound) {
|
||||
throw new \InvalidArgumentException('An element in the list is higher than allowed.', 1291470170);
|
||||
}
|
||||
$fieldValues = implode(',', $fieldList);
|
||||
}
|
||||
return (string)$fieldValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a range of integers to a list: 4-6 results in a string '4,5,6'
|
||||
*
|
||||
* @throws \InvalidArgumentException If range can not be converted to list
|
||||
* @param string $range Integer-integer
|
||||
* @return string
|
||||
*/
|
||||
protected static function convertRangeToListOfValues($range)
|
||||
{
|
||||
if ((string)$range === '') {
|
||||
throw new \InvalidArgumentException('Unable to convert range to list of values with empty string.', 1291234985);
|
||||
}
|
||||
$rangeArray = explode('-', $range);
|
||||
// Sanitize fields and cast to integer
|
||||
foreach ($rangeArray as $fieldNumber => $fieldValue) {
|
||||
if (!MathUtility::canBeInterpretedAsInteger($fieldValue)) {
|
||||
throw new \InvalidArgumentException('Unable to convert value to integer.', 1291237668);
|
||||
}
|
||||
$rangeArray[$fieldNumber] = (int)$fieldValue;
|
||||
}
|
||||
|
||||
$rangeArrayCount = count($rangeArray);
|
||||
if ($rangeArrayCount === 1) {
|
||||
$resultList = $rangeArray[0];
|
||||
} elseif ($rangeArrayCount === 2) {
|
||||
$left = $rangeArray[0];
|
||||
$right = $rangeArray[1];
|
||||
if ($left > $right) {
|
||||
throw new \InvalidArgumentException('Unable to convert range to list: Left integer must not be greater than right integer.', 1291237145);
|
||||
}
|
||||
$resultListArray = [];
|
||||
for ($i = $left; $i <= $right; $i++) {
|
||||
$resultListArray[] = $i;
|
||||
}
|
||||
$resultList = implode(',', $resultListArray);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Unable to convert range to list of values.', 1291234986);
|
||||
}
|
||||
return (string)$resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce a given list of values by step value.
|
||||
* Following a range with ``/<number>'' specifies skips of the number's value through the range.
|
||||
* 1-5/2 -> 1,3,5
|
||||
* 2-10/3 -> 2,5,8
|
||||
*
|
||||
* @throws \InvalidArgumentException if step value is invalid or if resulting list is empty
|
||||
* @param string $stepExpression Step value expression
|
||||
* @return string Comma separated list of valid values
|
||||
*/
|
||||
protected static function reduceListOfValuesByStepValue($stepExpression)
|
||||
{
|
||||
if ($stepExpression === '') {
|
||||
throw new \InvalidArgumentException('Unable to convert step values.', 1291234987);
|
||||
}
|
||||
$stepValuesAndStepArray = explode('/', $stepExpression);
|
||||
$stepValuesAndStepArrayCount = count($stepValuesAndStepArray);
|
||||
if ($stepValuesAndStepArrayCount < 1 || $stepValuesAndStepArrayCount > 2) {
|
||||
throw new \InvalidArgumentException('Unable to convert step values: Multiple slashes found.', 1291242168);
|
||||
}
|
||||
$left = $stepValuesAndStepArray[0] ?? '';
|
||||
$right = $stepValuesAndStepArray[1] ?? '';
|
||||
if ($left === '') {
|
||||
throw new \InvalidArgumentException('Unable to convert step values: Left part of / is empty.', 1291414955);
|
||||
}
|
||||
if ($right === '') {
|
||||
throw new \InvalidArgumentException('Unable to convert step values: Right part of / is empty.', 1291414956);
|
||||
}
|
||||
if (!MathUtility::canBeInterpretedAsInteger($right)) {
|
||||
throw new \InvalidArgumentException('Unable to convert step values: Right part must be a single integer.', 1291414957);
|
||||
}
|
||||
$right = (int)$right;
|
||||
$leftArray = explode(',', $left);
|
||||
$validValues = [];
|
||||
$currentStep = $right;
|
||||
foreach ($leftArray as $leftValue) {
|
||||
if (!MathUtility::canBeInterpretedAsInteger($leftValue)) {
|
||||
throw new \InvalidArgumentException('Unable to convert step values: Left part must be a single integer or comma separated list of integers.', 1291414958);
|
||||
}
|
||||
if ($currentStep === 0) {
|
||||
$currentStep = $right;
|
||||
}
|
||||
if ($currentStep === $right) {
|
||||
$validValues[] = (int)$leftValue;
|
||||
}
|
||||
$currentStep--;
|
||||
}
|
||||
if (empty($validValues)) {
|
||||
throw new \InvalidArgumentException('Unable to convert step values: Result value list is empty.', 1291414959);
|
||||
}
|
||||
return implode(',', $validValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatcher method for normalizeMonth and normalizeWeekday
|
||||
*
|
||||
* @param string $expression Month or weekday to be normalized
|
||||
* @param bool $isMonth TRUE if a month is handled, FALSE for weekday
|
||||
* @return string normalized month or weekday
|
||||
*/
|
||||
protected static function normalizeMonthAndWeekday($expression, $isMonth = true)
|
||||
{
|
||||
$expression = $isMonth ? self::normalizeMonth($expression) : self::normalizeWeekday($expression);
|
||||
return (string)$expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a string representation or integer number of a month like
|
||||
* 'jan', 'February', 01, ... and convert to normalized integer value 1 .. 12
|
||||
*
|
||||
* @throws \InvalidArgumentException If month string can not be converted to integer
|
||||
* @param string $month Month representation
|
||||
* @return int month integer representation between 1 and 12
|
||||
*/
|
||||
protected static function normalizeMonth($month)
|
||||
{
|
||||
$timestamp = strtotime('2010-' . $month . '-01');
|
||||
// timestamp must be >= 2010-01-01 and <= 2010-12-01
|
||||
if (!$timestamp || $timestamp < strtotime('2010-01-01') || $timestamp > strtotime('2010-12-01')) {
|
||||
throw new \InvalidArgumentException('Unable to convert given month name.', 1291083486);
|
||||
}
|
||||
return (int)date('n', $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a string representation or integer number of a weekday like
|
||||
* 'mon', 'Friday', 3, ... and convert to normalized integer value 1 .. 7
|
||||
*
|
||||
* @throws \InvalidArgumentException If weekday string can not be converted
|
||||
* @param string $weekday Weekday representation
|
||||
* @return int weekday integer representation between 1 and 7
|
||||
*/
|
||||
protected static function normalizeWeekday($weekday)
|
||||
{
|
||||
$normalizedWeekday = false;
|
||||
// 0 (sunday) -> 7
|
||||
if ((string)$weekday === '0') {
|
||||
$weekday = 7;
|
||||
}
|
||||
if ($weekday >= 1 && $weekday <= 7) {
|
||||
$normalizedWeekday = (int)$weekday;
|
||||
}
|
||||
if (!$normalizedWeekday) {
|
||||
// Convert string representation like 'sun' to integer
|
||||
$timestamp = strtotime('next ' . $weekday, mktime(0, 0, 0, 1, 1, 2010));
|
||||
if (!$timestamp || $timestamp < strtotime('2010-01-01') || $timestamp > strtotime('2010-01-08')) {
|
||||
throw new \InvalidArgumentException('Unable to convert given weekday name.', 1291163589);
|
||||
}
|
||||
$normalizedWeekday = (int)date('N', $timestamp);
|
||||
}
|
||||
return $normalizedWeekday;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Example;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a task that sleeps for some time
|
||||
* This is useful for testing parallel executions
|
||||
* @internal This class is an example is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SleepTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* Number of seconds the task should be sleeping for
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $sleepTime = 10;
|
||||
|
||||
/**
|
||||
* Function executed from the Scheduler.
|
||||
* Goes to sleep ;-)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$time = 10;
|
||||
if (!empty($this->sleepTime)) {
|
||||
$time = $this->sleepTime;
|
||||
}
|
||||
sleep($time);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the sleep duration as additional information
|
||||
*
|
||||
* @return string Information to display
|
||||
*/
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
return $GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.sleepTime') . ': ' . $this->sleepTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Example;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\AbstractTask;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional fields provider class for usage with the Scheduler's sleep task
|
||||
* @internal This class is an example is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SleepTaskAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* This method is used to define new fields for adding or editing a task
|
||||
* In this case, it adds an sleep time field
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
|
||||
// Initialize extra field value
|
||||
if (empty($taskInfo['sleepTime'])) {
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD)) {
|
||||
// In case of new task and if field is empty, set default sleep time
|
||||
$taskInfo['sleepTime'] = 30;
|
||||
} elseif ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
// In case of edit, set to internal value if no data was submitted already
|
||||
$taskInfo['sleepTime'] = $task->sleepTime;
|
||||
} else {
|
||||
// Otherwise set an empty value, as it will not be used anyway
|
||||
$taskInfo['sleepTime'] = '';
|
||||
}
|
||||
}
|
||||
// Write the code for the field
|
||||
$fieldID = 'task_sleepTime';
|
||||
$fieldCode = '<input type="text" class="form-control" name="tx_scheduler[sleepTime]" id="' . $fieldID . '" value="' . $taskInfo['sleepTime'] . '" size="10">';
|
||||
$additionalFields = [];
|
||||
$additionalFields[$fieldID] = [
|
||||
'code' => $fieldCode,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.sleepTime',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldID
|
||||
];
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks any additional data that is relevant to the specific task
|
||||
* If the task class is not relevant, the method is expected to return TRUE
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$submittedData['sleepTime'] = (int)$submittedData['sleepTime'];
|
||||
if ($submittedData['sleepTime'] < 0) {
|
||||
$this->addMessage($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidSleepTime'), FlashMessage::ERROR);
|
||||
$result = false;
|
||||
} else {
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to save any additional input into the current task object
|
||||
* if the task class matches
|
||||
*
|
||||
* @param array $submittedData Array containing the data submitted by the user
|
||||
* @param AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->sleepTime = $submittedData['sleepTime'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Example;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Provides testing procedures
|
||||
* @internal This class is an example is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class TestTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* An email address to be used during the process
|
||||
*
|
||||
* @var string $email
|
||||
*/
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* Function executed from the Scheduler.
|
||||
* Sends an email
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$success = false;
|
||||
if (!empty($this->email)) {
|
||||
// If an email address is defined, send a message to it
|
||||
$this->logger->info('[TYPO3\\CMS\\Scheduler\\Example\\TestTask]: Test email sent to "' . $this->email . '"');
|
||||
// Get execution information
|
||||
$exec = $this->getExecution();
|
||||
// Get call method
|
||||
if (Environment::isCli()) {
|
||||
$calledBy = 'CLI module dispatcher';
|
||||
$site = '-';
|
||||
} else {
|
||||
$calledBy = 'TYPO3 backend';
|
||||
$site = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
|
||||
}
|
||||
$start = $exec->getStart();
|
||||
$end = $exec->getEnd();
|
||||
$interval = $exec->getInterval();
|
||||
$multiple = $exec->getMultiple();
|
||||
$cronCmd = $exec->getCronCmd();
|
||||
$mailBody = 'SCHEDULER TEST-TASK' . LF . '- - - - - - - - - - - - - - - -' . LF . 'UID: ' . $this->taskUid . LF . 'Sitename: ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . LF . 'Site: ' . $site . LF . 'Called by: ' . $calledBy . LF . 'tstamp: ' . date('Y-m-d H:i:s') . ' [' . time() . ']' . LF . 'maxLifetime: ' . $this->scheduler->extConf['maxLifetime'] . LF . 'start: ' . date('Y-m-d H:i:s', $start) . ' [' . $start . ']' . LF . 'end: ' . (empty($end) ? '-' : date('Y-m-d H:i:s', $end) . ' [' . $end . ']') . LF . 'interval: ' . $interval . LF . 'multiple: ' . ($multiple ? 'yes' : 'no') . LF . 'cronCmd: ' . ($cronCmd ? $cronCmd : 'not used');
|
||||
// Prepare mailer and send the mail
|
||||
try {
|
||||
/** @var \TYPO3\CMS\Core\Mail\MailMessage $mailer */
|
||||
$mailer = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
|
||||
$mailer->setFrom([$this->email => 'SCHEDULER TEST-TASK']);
|
||||
$mailer->setReplyTo([$this->email => 'SCHEDULER TEST-TASK']);
|
||||
$mailer->setSubject('SCHEDULER TEST-TASK');
|
||||
$mailer->setBody($mailBody);
|
||||
$mailer->setTo($this->email);
|
||||
$mailsSend = $mailer->send();
|
||||
$success = $mailsSend > 0;
|
||||
} catch (\Exception $e) {
|
||||
throw new \TYPO3\CMS\Core\Exception($e->getMessage(), 1476048416);
|
||||
}
|
||||
} else {
|
||||
// No email defined, just log the task
|
||||
$this->logger->warning('[TYPO3\\CMS\\Scheduler\\Example\\TestTask]: No email address given');
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the destination mail address as additional information
|
||||
*
|
||||
* @return string Information to display
|
||||
*/
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
return $GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.email') . ': ' . $this->email;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Example;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\AbstractTask;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional fields provider class for usage with the Scheduler's test task
|
||||
* @internal This class is an example is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class TestTaskAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* This method is used to define new fields for adding or editing a task
|
||||
* In this case, it adds an email field
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
|
||||
// Initialize extra field value
|
||||
if (empty($taskInfo['email'])) {
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD)) {
|
||||
// In case of new task and if field is empty, set default email address
|
||||
$taskInfo['email'] = $GLOBALS['BE_USER']->user['email'];
|
||||
} elseif ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
// In case of edit, and editing a test task, set to internal value if not data was submitted already
|
||||
$taskInfo['email'] = $task->email;
|
||||
} else {
|
||||
// Otherwise set an empty value, as it will not be used anyway
|
||||
$taskInfo['email'] = '';
|
||||
}
|
||||
}
|
||||
// Write the code for the field
|
||||
$fieldID = 'task_email';
|
||||
$fieldCode = '<input type="text" class="form-control" name="tx_scheduler[email]" id="' . $fieldID . '" value="' . htmlspecialchars($taskInfo['email']) . '" size="30">';
|
||||
$additionalFields = [];
|
||||
$additionalFields[$fieldID] = [
|
||||
'code' => $fieldCode,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.email',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldID
|
||||
];
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks any additional data that is relevant to the specific task
|
||||
* If the task class is not relevant, the method is expected to return TRUE
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$submittedData['email'] = trim($submittedData['email']);
|
||||
if (empty($submittedData['email'])) {
|
||||
$this->addMessage(
|
||||
$GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.noEmail'),
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
$result = false;
|
||||
} else {
|
||||
$result = true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to save any additional input into the current task object
|
||||
* if the task class matches
|
||||
*
|
||||
* @param array $submittedData Array containing the data submitted by the user
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->email = $submittedData['email'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class manages the logic of a particular execution of a task
|
||||
* @internal
|
||||
*/
|
||||
class Execution
|
||||
{
|
||||
/**
|
||||
* Start date of a task (timestamp)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $start;
|
||||
|
||||
/**
|
||||
* End date of a task (timestamp)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $end;
|
||||
|
||||
/**
|
||||
* Interval between executions (in seconds)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $interval;
|
||||
|
||||
/**
|
||||
* Flag for concurrent executions: TRUE if allowed, FALSE otherwise (default)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $multiple = false;
|
||||
|
||||
/**
|
||||
* The cron command string of this task,
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cronCmd;
|
||||
|
||||
/**
|
||||
* This flag is used to mark a new single execution
|
||||
* See explanations in method setIsNewSingleExecution()
|
||||
*
|
||||
* @var bool
|
||||
* @see \TYPO3\CMS\Scheduler\Execution::setIsNewSingleExecution()
|
||||
*/
|
||||
protected $isNewSingleExecution = false;
|
||||
|
||||
/**********************************
|
||||
* Setters and getters
|
||||
**********************************/
|
||||
/**
|
||||
* This method is used to set the start date
|
||||
*
|
||||
* @param int $start Start date (timestamp)
|
||||
*/
|
||||
public function setStart($start)
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the start date
|
||||
*
|
||||
* @return int Start date (timestamp)
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the end date
|
||||
*
|
||||
* @param int $end End date (timestamp)
|
||||
*/
|
||||
public function setEnd($end)
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the end date
|
||||
*
|
||||
* @return int End date (timestamp)
|
||||
*/
|
||||
public function getEnd()
|
||||
{
|
||||
return $this->end;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the interval
|
||||
*
|
||||
* @param int $interval Interval (in seconds)
|
||||
*/
|
||||
public function setInterval($interval)
|
||||
{
|
||||
$this->interval = $interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the interval
|
||||
*
|
||||
* @return int Interval (in seconds)
|
||||
*/
|
||||
public function getInterval()
|
||||
{
|
||||
return $this->interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the multiple execution flag
|
||||
*
|
||||
* @param bool $multiple TRUE if concurrent executions are allowed, FALSE otherwise
|
||||
*/
|
||||
public function setMultiple($multiple)
|
||||
{
|
||||
$this->multiple = $multiple;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the multiple execution flag
|
||||
*
|
||||
* @return bool TRUE if concurrent executions are allowed, FALSE otherwise
|
||||
*/
|
||||
public function getMultiple()
|
||||
{
|
||||
return $this->multiple;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the cron command
|
||||
*
|
||||
* @param string $cmd Cron command, using cron-like syntax
|
||||
*/
|
||||
public function setCronCmd($cmd)
|
||||
{
|
||||
$this->cronCmd = $cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the cron command
|
||||
*
|
||||
* @return string Cron command, using cron-like syntax
|
||||
*/
|
||||
public function getCronCmd()
|
||||
{
|
||||
return $this->cronCmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this is a newly created single execution.
|
||||
* This is necessary for the following reason: if a new single-running task
|
||||
* is created and its start date is in the past (even for only a few seconds),
|
||||
* the next run time calculation (which happens upon saving) will disable
|
||||
* that task, because it was meant to run only once and is in the past.
|
||||
* Setting this flag to TRUE preserves this task for a single run.
|
||||
* Upon next execution, this flag is set to FALSE.
|
||||
*
|
||||
* @param bool $isNewSingleExecution Is newly created single execution?
|
||||
* @see \TYPO3\CMS\Scheduler\Execution::getNextExecution()
|
||||
*/
|
||||
public function setIsNewSingleExecution($isNewSingleExecution)
|
||||
{
|
||||
$this->isNewSingleExecution = $isNewSingleExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this is a newly created single execution
|
||||
*
|
||||
* @return bool Is newly created single execution?
|
||||
*/
|
||||
public function getIsNewSingleExecution()
|
||||
{
|
||||
return $this->isNewSingleExecution;
|
||||
}
|
||||
|
||||
/**********************************
|
||||
* Execution calculations and logic
|
||||
**********************************/
|
||||
/**
|
||||
* This method gets or calculates the next execution date
|
||||
*
|
||||
* @return int Timestamp of the next execution
|
||||
* @throws \OutOfBoundsException
|
||||
*/
|
||||
public function getNextExecution()
|
||||
{
|
||||
if ($this->getIsNewSingleExecution()) {
|
||||
$this->setIsNewSingleExecution(false);
|
||||
return $this->start;
|
||||
}
|
||||
if (!$this->isEnded()) {
|
||||
// If the schedule has not yet run out, find out the next date
|
||||
if (!$this->isStarted()) {
|
||||
// If the schedule hasn't started yet, next date is start date
|
||||
$date = $this->start;
|
||||
} else {
|
||||
// If the schedule has already started, calculate next date
|
||||
if ($this->cronCmd) {
|
||||
// If it uses cron-like syntax, calculate next date
|
||||
$date = $this->getNextCronExecution();
|
||||
} elseif ($this->interval == 0) {
|
||||
// If not and there's no interval either, it's a singe execution: use start date
|
||||
$date = $this->start;
|
||||
} else {
|
||||
// Otherwise calculate date based on interval
|
||||
$now = time();
|
||||
$date = $now + $this->interval - ($now - $this->start) % $this->interval;
|
||||
}
|
||||
// If date is in the future, throw an exception
|
||||
if (!empty($this->end) && $date > $this->end) {
|
||||
throw new \OutOfBoundsException('Next execution date is past end date.', 1250715528);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The event has ended, throw an exception
|
||||
throw new \OutOfBoundsException('Task is past end date.', 1250715544);
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next execution from a cron command
|
||||
*
|
||||
* @return int Next execution (timestamp)
|
||||
*/
|
||||
public function getNextCronExecution()
|
||||
{
|
||||
/** @var \TYPO3\CMS\Scheduler\CronCommand\CronCommand $cronCmd */
|
||||
$cronCmd = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\CronCommand\CronCommand::class, $this->getCronCmd());
|
||||
$cronCmd->calculateNextValue();
|
||||
return $cronCmd->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the schedule for a task is started or not
|
||||
*
|
||||
* @return bool TRUE if the schedule is already active, FALSE otherwise
|
||||
*/
|
||||
public function isStarted()
|
||||
{
|
||||
return $this->start < time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the schedule for a task is passed or not
|
||||
*
|
||||
* @return bool TRUE if the schedule is not active anymore, FALSE otherwise
|
||||
*/
|
||||
public function isEnded()
|
||||
{
|
||||
if (empty($this->end)) {
|
||||
// If no end is defined, the schedule never ends
|
||||
$result = false;
|
||||
} else {
|
||||
// Otherwise check if end is in the past
|
||||
$result = $this->end < time();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Failed execution exception
|
||||
*/
|
||||
class FailedExecutionException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for tasks who can provide their progress
|
||||
*/
|
||||
interface ProgressProviderInterface
|
||||
{
|
||||
/**
|
||||
* Gets the progress of a task.
|
||||
*
|
||||
* @return float Progress of the task as a two decimal precision float. f.e. 44.87
|
||||
*/
|
||||
public function getProgress();
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryHelper;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* TYPO3 Scheduler. This class handles scheduling and execution of tasks.
|
||||
*/
|
||||
class Scheduler implements SingletonInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var array $extConf Settings from the extension manager
|
||||
*/
|
||||
public $extConf = [];
|
||||
|
||||
/**
|
||||
* Constructor, makes sure all derived client classes are included
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Get configuration from the extension manager
|
||||
$this->extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('scheduler');
|
||||
if (empty($this->extConf['maxLifetime'])) {
|
||||
$this->extConf['maxLifetime'] = 1440;
|
||||
}
|
||||
// Clean up the serialized execution arrays
|
||||
$this->cleanExecutionArrays();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a task to the pool
|
||||
*
|
||||
* @param Task\AbstractTask $task The object representing the task to add
|
||||
* @return bool TRUE if the task was successfully added, FALSE otherwise
|
||||
*/
|
||||
public function addTask(Task\AbstractTask $task)
|
||||
{
|
||||
$taskUid = $task->getTaskUid();
|
||||
if (empty($taskUid)) {
|
||||
$fields = [
|
||||
'crdate' => $GLOBALS['EXEC_TIME'],
|
||||
'disable' => (int)$task->isDisabled(),
|
||||
'description' => $task->getDescription(),
|
||||
'task_group' => $task->getTaskGroup(),
|
||||
'serialized_task_object' => 'RESERVED'
|
||||
];
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_scheduler_task');
|
||||
$result = $connection->insert(
|
||||
'tx_scheduler_task',
|
||||
$fields,
|
||||
['serialized_task_object' => Connection::PARAM_LOB]
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$task->setTaskUid($connection->lastInsertId('tx_scheduler_task'));
|
||||
$task->save();
|
||||
$result = true;
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the execution lists of the scheduled tasks, executions older than 24h are removed
|
||||
* @todo find a way to actually kill the job
|
||||
*/
|
||||
protected function cleanExecutionArrays()
|
||||
{
|
||||
$tstamp = $GLOBALS['EXEC_TIME'];
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('tx_scheduler_task');
|
||||
|
||||
// Select all tasks with executions
|
||||
// NOTE: this cleanup is done for disabled tasks too,
|
||||
// to avoid leaving old executions lying around
|
||||
$result = $queryBuilder->select('uid', 'serialized_executions', 'serialized_task_object')
|
||||
->from('tx_scheduler_task')
|
||||
->where(
|
||||
$queryBuilder->expr()->neq(
|
||||
'serialized_executions',
|
||||
$queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute();
|
||||
$maxDuration = $this->extConf['maxLifetime'] * 60;
|
||||
while ($row = $result->fetch()) {
|
||||
$executions = [];
|
||||
if ($serialized_executions = unserialize($row['serialized_executions'])) {
|
||||
foreach ($serialized_executions as $task) {
|
||||
if ($tstamp - $task < $maxDuration) {
|
||||
$executions[] = $task;
|
||||
} else {
|
||||
$task = unserialize($row['serialized_task_object']);
|
||||
$this->log('Removing logged execution, assuming that the process is dead. Execution of \'' . get_class($task) . '\' (UID: ' . $row['uid'] . ') was started at ' . date('Y-m-d H:i:s', $task->getExecutionTime()));
|
||||
}
|
||||
}
|
||||
}
|
||||
$executionCount = count($executions);
|
||||
if (count($serialized_executions) !== $executionCount) {
|
||||
if ($executionCount === 0) {
|
||||
$value = '';
|
||||
} else {
|
||||
$value = serialize($executions);
|
||||
}
|
||||
$connectionPool->getConnectionForTable('tx_scheduler_task')->update(
|
||||
'tx_scheduler_task',
|
||||
['serialized_executions' => $value],
|
||||
['uid' => (int)$row['uid']],
|
||||
['serialized_executions' => Connection::PARAM_LOB]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method executes the given task and properly marks and records that execution
|
||||
* It is expected to return FALSE if the task was barred from running or if it was not saved properly
|
||||
*
|
||||
* @param Task\AbstractTask $task The task to execute
|
||||
* @return bool Whether the task was saved successfully to the database or not
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function executeTask(Task\AbstractTask $task)
|
||||
{
|
||||
$task->setRunOnNextCronJob(false);
|
||||
// Trigger the saving of the task, as this will calculate its next execution time
|
||||
// This should be calculated all the time, even if the execution is skipped
|
||||
// (in case it is skipped, this pushes back execution to the next possible date)
|
||||
$task->save();
|
||||
// Set a scheduler object for the task again,
|
||||
// as it was removed during the save operation
|
||||
$task->setScheduler();
|
||||
$result = true;
|
||||
// Task is already running and multiple executions are not allowed
|
||||
if (!$task->areMultipleExecutionsAllowed() && $task->isExecutionRunning()) {
|
||||
// Log multiple execution error
|
||||
$logMessage = 'Task is already running and multiple executions are not allowed, skipping! Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid();
|
||||
$this->logger->info($logMessage);
|
||||
$result = false;
|
||||
} else {
|
||||
// Log scheduler invocation
|
||||
$this->logger->info('Start execution. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid());
|
||||
// Register execution
|
||||
$executionID = $task->markExecution();
|
||||
$failure = null;
|
||||
try {
|
||||
// Execute task
|
||||
$successfullyExecuted = $task->execute();
|
||||
if (!$successfullyExecuted) {
|
||||
throw new FailedExecutionException('Task failed to execute successfully. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid(), 1250596541);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Store exception, so that it can be saved to database
|
||||
$failure = $e;
|
||||
}
|
||||
// Un-register execution
|
||||
$task->unmarkExecution($executionID, $failure);
|
||||
// Log completion of execution
|
||||
$this->logger->info('Task executed. Class: ' . get_class($task) . ', UID: ' . $task->getTaskUid());
|
||||
// Now that the result of the task execution has been handled,
|
||||
// throw the exception again, if any
|
||||
if ($failure instanceof \Throwable) {
|
||||
throw $failure;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method stores information about the last run of the Scheduler into the system registry
|
||||
*
|
||||
* @param string $type Type of run (manual or command-line (assumed to be cron))
|
||||
*/
|
||||
public function recordLastRun($type = 'cron')
|
||||
{
|
||||
// Validate input value
|
||||
if ($type !== 'manual' && $type !== 'cli-by-id') {
|
||||
$type = 'cron';
|
||||
}
|
||||
/** @var Registry $registry */
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$runInformation = ['start' => $GLOBALS['EXEC_TIME'], 'end' => time(), 'type' => $type];
|
||||
$registry->set('tx_scheduler', 'lastRun', $runInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a task completely from the system.
|
||||
*
|
||||
* @todo find a way to actually kill the existing jobs
|
||||
*
|
||||
* @param Task\AbstractTask $task The object representing the task to delete
|
||||
* @return bool TRUE if task was successfully deleted, FALSE otherwise
|
||||
*/
|
||||
public function removeTask(Task\AbstractTask $task)
|
||||
{
|
||||
$taskUid = $task->getTaskUid();
|
||||
if (!empty($taskUid)) {
|
||||
$result = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_scheduler_task')
|
||||
->update('tx_scheduler_task', ['deleted' => 1], ['uid' => $taskUid]);
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a task in the pool
|
||||
*
|
||||
* @param Task\AbstractTask $task Scheduler task object
|
||||
* @return bool False if submitted task was not of proper class
|
||||
*/
|
||||
public function saveTask(Task\AbstractTask $task)
|
||||
{
|
||||
$result = true;
|
||||
$taskUid = $task->getTaskUid();
|
||||
if (!empty($taskUid)) {
|
||||
try {
|
||||
if ($task->getRunOnNextCronJob()) {
|
||||
$executionTime = time();
|
||||
} else {
|
||||
$executionTime = $task->getNextDueExecution();
|
||||
}
|
||||
$task->setExecutionTime($executionTime);
|
||||
} catch (\Exception $e) {
|
||||
$task->setDisabled(true);
|
||||
$executionTime = 0;
|
||||
}
|
||||
$task->unsetScheduler();
|
||||
$fields = [
|
||||
'nextexecution' => $executionTime,
|
||||
'disable' => (int)$task->isDisabled(),
|
||||
'description' => $task->getDescription(),
|
||||
'task_group' => $task->getTaskGroup(),
|
||||
'serialized_task_object' => serialize($task)
|
||||
];
|
||||
try {
|
||||
GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_scheduler_task')
|
||||
->update(
|
||||
'tx_scheduler_task',
|
||||
$fields,
|
||||
['uid' => $taskUid],
|
||||
['serialized_task_object' => Connection::PARAM_LOB]
|
||||
);
|
||||
} catch (\Doctrine\DBAL\DBALException $e) {
|
||||
$result = false;
|
||||
}
|
||||
} else {
|
||||
$result = false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and unserializes a task object from the db. If an uid is given the object
|
||||
* with the uid is returned, else the object representing the next due task is returned.
|
||||
* If there are no due tasks the method throws an exception.
|
||||
*
|
||||
* @param int $uid Primary key of a task
|
||||
* @return Task\AbstractTask The fetched task object
|
||||
* @throws \OutOfBoundsException
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function fetchTask($uid = 0)
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$queryBuilder = $connectionPool->getQueryBuilderForTable('tx_scheduler_task');
|
||||
|
||||
$queryBuilder->select('t.uid', 't.serialized_task_object')
|
||||
->from('tx_scheduler_task', 't')
|
||||
->setMaxResults(1);
|
||||
// Define where clause
|
||||
// If no uid is given, take any non-disabled task which has a next execution time in the past
|
||||
if (empty($uid)) {
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->leftJoin(
|
||||
't',
|
||||
'tx_scheduler_task_group',
|
||||
'g',
|
||||
$queryBuilder->expr()->eq('t.task_group', $queryBuilder->quoteIdentifier('g.uid'))
|
||||
);
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->eq('t.disable', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->neq('t.nextexecution', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->lte(
|
||||
't.nextexecution',
|
||||
$queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->orX(
|
||||
$queryBuilder->expr()->eq('g.hidden', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->isNull('g.hidden')
|
||||
),
|
||||
$queryBuilder->expr()->eq('t.deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
);
|
||||
$queryBuilder->orderBy('t.nextexecution', 'ASC');
|
||||
} else {
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->eq('t.uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('t.deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
);
|
||||
}
|
||||
|
||||
$row = $queryBuilder->execute()->fetch();
|
||||
if (empty($row)) {
|
||||
if (empty($uid)) {
|
||||
// No uid was passed and no overdue task was found
|
||||
throw new \OutOfBoundsException('No tasks available for execution', 1247827244);
|
||||
}
|
||||
// Although a uid was passed, no task with given was found
|
||||
throw new \OutOfBoundsException('No task with id ' . $uid . ' found', 1422044826);
|
||||
}
|
||||
/** @var Task\AbstractTask $task */
|
||||
$task = unserialize($row['serialized_task_object']);
|
||||
if ($this->isValidTaskObject($task)) {
|
||||
// The task is valid, return it
|
||||
$task->setScheduler();
|
||||
} else {
|
||||
// Forcibly set the disable flag to 1 in the database,
|
||||
// so that the task does not come up again and again for execution
|
||||
$connectionPool->getConnectionForTable('tx_scheduler_task')->update(
|
||||
'tx_scheduler_task',
|
||||
['disable' => 1],
|
||||
['uid' => (int)$row['uid']]
|
||||
);
|
||||
// Throw an exception to raise the problem
|
||||
throw new \UnexpectedValueException('Could not unserialize task', 1255083671);
|
||||
}
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to get the database record for a given task
|
||||
* It returns the database record and not the task object
|
||||
*
|
||||
* @param int $uid Primary key of the task to get
|
||||
* @return array Database record for the task
|
||||
* @see \TYPO3\CMS\Scheduler\Scheduler::fetchTask()
|
||||
* @throws \OutOfBoundsException
|
||||
*/
|
||||
public function fetchTaskRecord($uid)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_scheduler_task');
|
||||
$row = $queryBuilder->select('*')
|
||||
->from('tx_scheduler_task')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
// If the task is not found, throw an exception
|
||||
if (empty($row)) {
|
||||
throw new \OutOfBoundsException('No task', 1247827245);
|
||||
}
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and unserializes task objects selected with some (SQL) condition
|
||||
* Objects are returned as an array
|
||||
*
|
||||
* @param string $where Part of a SQL where clause (without the "WHERE" keyword)
|
||||
* @param bool $includeDisabledTasks TRUE if disabled tasks should be fetched too, FALSE otherwise
|
||||
* @return array List of task objects
|
||||
*/
|
||||
public function fetchTasksWithCondition($where, $includeDisabledTasks = false)
|
||||
{
|
||||
$tasks = [];
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_scheduler_task');
|
||||
|
||||
$queryBuilder
|
||||
->select('serialized_task_object')
|
||||
->from('tx_scheduler_task')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
);
|
||||
|
||||
if (!$includeDisabledTasks) {
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->eq('disable', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($where)) {
|
||||
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($where));
|
||||
}
|
||||
|
||||
$result = $queryBuilder->execute();
|
||||
while ($row = $result->fetch()) {
|
||||
/** @var Task\AbstractTask $task */
|
||||
$task = unserialize($row['serialized_task_object']);
|
||||
// Add the task to the list only if it is valid
|
||||
if ($this->isValidTaskObject($task)) {
|
||||
$task->setScheduler();
|
||||
$tasks[] = $task;
|
||||
}
|
||||
}
|
||||
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method encapsulates a very simple test for the purpose of clarity.
|
||||
* Registered tasks are stored in the database along with a serialized task object.
|
||||
* When a registered task is fetched, its object is unserialized.
|
||||
* At that point, if the class corresponding to the object is not available anymore
|
||||
* (e.g. because the extension providing it has been uninstalled),
|
||||
* the unserialization will produce an incomplete object.
|
||||
* This test checks whether the unserialized object is of the right (parent) class or not.
|
||||
*
|
||||
* @param object $task The object to test
|
||||
* @return bool TRUE if object is a task, FALSE otherwise
|
||||
*/
|
||||
public function isValidTaskObject($task)
|
||||
{
|
||||
return $task instanceof Task\AbstractTask && get_class($task->getExecution()) !== '__PHP_Incomplete_Class';
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a utility method that writes some message to the BE Log
|
||||
* It could be expanded to write to some other log
|
||||
*
|
||||
* @param string $message The message to write to the log
|
||||
* @param int $status Status (0 = message, 1 = error)
|
||||
* @param mixed $code Key for the message
|
||||
*/
|
||||
public function log($message, $status = 0, $code = '')
|
||||
{
|
||||
// this method could be called from the constructor (via "cleanExecutionArrays") and no logger is instantiated
|
||||
// by then, that's why check if the logger is available
|
||||
if (!($this->logger instanceof LoggerInterface)) {
|
||||
$this->setLogger(GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__));
|
||||
}
|
||||
$message = trim('[scheduler]: ' . $code) . ' - ' . $message;
|
||||
switch ((int)$status) {
|
||||
// error (user problem)
|
||||
case 1:
|
||||
$this->logger->alert($message);
|
||||
break;
|
||||
// System Error (which should not happen)
|
||||
case 2:
|
||||
$this->logger->error($message);
|
||||
break;
|
||||
// security notice (admin)
|
||||
case 3:
|
||||
$this->logger->emergency($message);
|
||||
break;
|
||||
// regular message (= 0)
|
||||
default:
|
||||
$this->logger->info($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Scheduler\Execution;
|
||||
|
||||
/**
|
||||
* This is the base class for all Scheduler tasks
|
||||
* It's an abstract class, not designed to be instantiated directly
|
||||
* All Scheduler tasks should inherit from this class
|
||||
*/
|
||||
abstract class AbstractTask implements LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
const TYPE_SINGLE = 1;
|
||||
const TYPE_RECURRING = 2;
|
||||
|
||||
/**
|
||||
* Reference to a scheduler object
|
||||
*
|
||||
* @var \TYPO3\CMS\Scheduler\Scheduler
|
||||
*/
|
||||
protected $scheduler;
|
||||
|
||||
/**
|
||||
* The unique id of the task used to identify it in the database
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $taskUid;
|
||||
|
||||
/**
|
||||
* Disable flag, TRUE if task is disabled, FALSE otherwise
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $disabled = false;
|
||||
|
||||
/**
|
||||
* Run on next cron job flag, TRUE if task should run on next cronjob, FALSE otherwise
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $runOnNextCronJob = false;
|
||||
|
||||
/**
|
||||
* The execution object related to the task
|
||||
*
|
||||
* @var Execution
|
||||
*/
|
||||
protected $execution;
|
||||
|
||||
/**
|
||||
* This variable contains the time of next execution of the task
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $executionTime = 0;
|
||||
|
||||
/**
|
||||
* Description for the task
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* Task group for this task
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $taskGroup;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Using makeInstance instead of setScheduler() here as the logger is injected due to LoggerAwareTrait
|
||||
$this->scheduler = GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Scheduler::class);
|
||||
$this->execution = GeneralUtility::makeInstance(Execution::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main method that is called when a task is executed
|
||||
* It MUST be implemented by all classes inheriting from this one
|
||||
* Note that there is no error handling, errors and failures are expected
|
||||
* to be handled and logged by the client implementations.
|
||||
* Should return TRUE on successful execution, FALSE on error.
|
||||
*
|
||||
* @return bool Returns TRUE on successful execution, FALSE on error
|
||||
*/
|
||||
abstract public function execute();
|
||||
|
||||
/**
|
||||
* This method is designed to return some additional information about the task,
|
||||
* that may help to set it apart from other tasks from the same class
|
||||
* This additional information is used - for example - in the Scheduler's BE module
|
||||
* This method should be implemented in most task classes
|
||||
*
|
||||
* @return string Information to display
|
||||
*/
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the unique id of the task
|
||||
*
|
||||
* @param int $id Primary key (from the database record) of the scheduled task
|
||||
*/
|
||||
public function setTaskUid($id)
|
||||
{
|
||||
$this->taskUid = (int)$id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the unique id of the task
|
||||
*
|
||||
* @return int The id of the task
|
||||
*/
|
||||
public function getTaskUid()
|
||||
{
|
||||
return $this->taskUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the title of the scheduler task
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTaskTitle()
|
||||
{
|
||||
return $GLOBALS['LANG']->sL($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][static::class]['title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the description of the scheduler task
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTaskDescription()
|
||||
{
|
||||
return $GLOBALS['LANG']->sL($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][static::class]['description']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the class name of the scheduler task
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTaskClassName()
|
||||
{
|
||||
return static::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the disable status of the task
|
||||
*
|
||||
* @return bool TRUE if task is disabled, FALSE otherwise
|
||||
*/
|
||||
public function isDisabled()
|
||||
{
|
||||
return $this->disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the disable status of the task
|
||||
*
|
||||
* @param bool $flag TRUE if task should be disabled, FALSE otherwise
|
||||
*/
|
||||
public function setDisabled($flag)
|
||||
{
|
||||
if ($flag) {
|
||||
$this->disabled = true;
|
||||
} else {
|
||||
$this->disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method set the flag for next cron job execution
|
||||
*
|
||||
* @param bool $flag TRUE if task should run with the next cron job, FALSE otherwise
|
||||
*/
|
||||
public function setRunOnNextCronJob($flag)
|
||||
{
|
||||
$this->runOnNextCronJob = $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the run on next cron job status of the task
|
||||
*
|
||||
* @return bool TRUE if task should run on next cron job, FALSE otherwise
|
||||
*/
|
||||
public function getRunOnNextCronJob()
|
||||
{
|
||||
return $this->runOnNextCronJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the timestamp corresponding to the next execution time of the task
|
||||
*
|
||||
* @param int $timestamp Timestamp of next execution
|
||||
*/
|
||||
public function setExecutionTime($timestamp)
|
||||
{
|
||||
$this->executionTime = (int)$timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the task group (uid) of the task
|
||||
*
|
||||
* @return int Uid of task group
|
||||
*/
|
||||
public function getTaskGroup()
|
||||
{
|
||||
return $this->taskGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the task group (uid) of the task
|
||||
*
|
||||
* @param int $taskGroup Uid of task group
|
||||
*/
|
||||
public function setTaskGroup($taskGroup)
|
||||
{
|
||||
$this->taskGroup = (int)$taskGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the timestamp corresponding to the next execution time of the task
|
||||
*
|
||||
* @return int Timestamp of next execution
|
||||
*/
|
||||
public function getExecutionTime()
|
||||
{
|
||||
return $this->executionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to set the description of the task
|
||||
*
|
||||
* @param string $description Description
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the description of the task
|
||||
*
|
||||
* @return string Description
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the singleton instance of the Scheduler
|
||||
* and the logger instance in case it was unserialized
|
||||
*/
|
||||
public function setScheduler()
|
||||
{
|
||||
$this->scheduler = GeneralUtility::makeInstance(\TYPO3\CMS\Scheduler\Scheduler::class);
|
||||
$this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the internal reference to the singleton instance of the Scheduler
|
||||
* and the logger instance.
|
||||
* This is done before a task is serialized, so that the scheduler instance
|
||||
* and the logger instance are not saved to the database
|
||||
*/
|
||||
public function unsetScheduler()
|
||||
{
|
||||
$this->scheduler = null;
|
||||
$this->logger = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single execution of the task
|
||||
*
|
||||
* @param int $timestamp Timestamp of the next execution
|
||||
*/
|
||||
public function registerSingleExecution($timestamp)
|
||||
{
|
||||
/** @var Execution $execution */
|
||||
$execution = GeneralUtility::makeInstance(Execution::class);
|
||||
$execution->setStart($timestamp);
|
||||
$execution->setInterval(0);
|
||||
$execution->setEnd($timestamp);
|
||||
$execution->setCronCmd('');
|
||||
$execution->setMultiple(0);
|
||||
$execution->setIsNewSingleExecution(true);
|
||||
// Replace existing execution object
|
||||
$this->execution = $execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a recurring execution of the task
|
||||
*
|
||||
* @param int $start The first date/time where this execution should occur (timestamp)
|
||||
* @param string $interval Execution interval in seconds
|
||||
* @param int $end The last date/time where this execution should occur (timestamp)
|
||||
* @param bool $multiple Set to FALSE if multiple executions of this task are not permitted in parallel
|
||||
* @param string $cron_cmd Used like in crontab (minute hour day month weekday)
|
||||
*/
|
||||
public function registerRecurringExecution($start, $interval, $end = 0, $multiple = false, $cron_cmd = '')
|
||||
{
|
||||
/** @var Execution $execution */
|
||||
$execution = GeneralUtility::makeInstance(Execution::class);
|
||||
// Set general values
|
||||
$execution->setStart($start);
|
||||
$execution->setEnd($end);
|
||||
$execution->setMultiple($multiple);
|
||||
if (empty($cron_cmd)) {
|
||||
// Use interval
|
||||
$execution->setInterval($interval);
|
||||
$execution->setCronCmd('');
|
||||
} else {
|
||||
// Use cron syntax
|
||||
$execution->setInterval(0);
|
||||
$execution->setCronCmd($cron_cmd);
|
||||
}
|
||||
// Replace existing execution object
|
||||
$this->execution = $execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal execution object
|
||||
*
|
||||
* @param Execution $execution The execution to add
|
||||
*/
|
||||
public function setExecution(Execution $execution)
|
||||
{
|
||||
$this->execution = $execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the execution object
|
||||
*
|
||||
* @return Execution The internal execution object
|
||||
*/
|
||||
public function getExecution()
|
||||
{
|
||||
return $this->execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp for next due execution of the task
|
||||
*
|
||||
* @return int Date and time of the next execution as a timestamp
|
||||
*/
|
||||
public function getNextDueExecution()
|
||||
{
|
||||
// NOTE: this call may throw an exception, but we let it bubble up
|
||||
return $this->execution->getNextExecution();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if several runs of the task are allowed concurrently
|
||||
*
|
||||
* @return bool TRUE if concurrent executions are allowed, FALSE otherwise
|
||||
*/
|
||||
public function areMultipleExecutionsAllowed()
|
||||
{
|
||||
return $this->execution->getMultiple();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if an instance of the task is already running
|
||||
*
|
||||
* @return bool TRUE if an instance is already running, FALSE otherwise
|
||||
*/
|
||||
public function isExecutionRunning()
|
||||
{
|
||||
$isRunning = false;
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_scheduler_task');
|
||||
$row = $queryBuilder
|
||||
->select('serialized_executions')
|
||||
->from('tx_scheduler_task')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($this->taskUid, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
if ($row && !empty($row['serialized_executions'])) {
|
||||
$isRunning = true;
|
||||
}
|
||||
return $isRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds current execution to the execution list
|
||||
* It also logs the execution time and mode
|
||||
*
|
||||
* @return int Execution id
|
||||
*/
|
||||
public function markExecution()
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_scheduler_task');
|
||||
|
||||
$row = $queryBuilder
|
||||
->select('serialized_executions')
|
||||
->from('tx_scheduler_task')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($this->taskUid, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
$runningExecutions = [];
|
||||
if ($row && !empty($row['serialized_executions'])) {
|
||||
$runningExecutions = unserialize($row['serialized_executions']);
|
||||
}
|
||||
// Count the number of existing executions and use that number as a key
|
||||
// (we need to know that number, because it is returned at the end of the method)
|
||||
$numExecutions = count($runningExecutions);
|
||||
$runningExecutions[$numExecutions] = time();
|
||||
GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_scheduler_task')
|
||||
->update(
|
||||
'tx_scheduler_task',
|
||||
[
|
||||
'serialized_executions' => serialize($runningExecutions),
|
||||
'lastexecution_time' => time(),
|
||||
// Define the context in which the script is running
|
||||
'lastexecution_context' => Environment::isCli() ? 'CLI' : 'BE'
|
||||
],
|
||||
[
|
||||
'uid' => $this->taskUid
|
||||
],
|
||||
[
|
||||
'serialized_executions' => Connection::PARAM_LOB,
|
||||
]
|
||||
);
|
||||
return $numExecutions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given execution from list
|
||||
*
|
||||
* @param int $executionID Id of the execution to remove.
|
||||
* @param \Throwable $failure An exception to signal a failed execution
|
||||
*/
|
||||
public function unmarkExecution($executionID, \Throwable $failure = null)
|
||||
{
|
||||
// Get the executions for the task
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_scheduler_task');
|
||||
|
||||
$row = $queryBuilder
|
||||
->select('serialized_executions')
|
||||
->from('tx_scheduler_task')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($this->taskUid, \PDO::PARAM_INT))
|
||||
)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
if ($row && $row['serialized_executions'] !== '') {
|
||||
$runningExecutions = unserialize($row['serialized_executions']);
|
||||
// Remove the selected execution
|
||||
unset($runningExecutions[$executionID]);
|
||||
if (!empty($runningExecutions)) {
|
||||
// Re-serialize the updated executions list (if necessary)
|
||||
$runningExecutionsSerialized = serialize($runningExecutions);
|
||||
} else {
|
||||
$runningExecutionsSerialized = '';
|
||||
}
|
||||
if ($failure instanceof \Throwable) {
|
||||
// Log failed execution
|
||||
$logMessage = 'Task failed to execute successfully. Class: ' . static::class
|
||||
. ', UID: ' . $this->taskUid . ', Code: ' . $failure->getCode() . ', ' . $failure->getMessage();
|
||||
$this->logger->error($logMessage, ['exception' => $failure]);
|
||||
// Do not serialize the complete exception or the trace, this can lead to huge strings > 50MB
|
||||
$failureString = serialize([
|
||||
'code' => $failure->getCode(),
|
||||
'message' => $failure->getMessage(),
|
||||
'file' => $failure->getFile(),
|
||||
'line' => $failure->getLine(),
|
||||
'traceString' => $failure->getTraceAsString(),
|
||||
]);
|
||||
} else {
|
||||
$failureString = '';
|
||||
}
|
||||
// Save the updated executions list
|
||||
GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_scheduler_task')
|
||||
->update(
|
||||
'tx_scheduler_task',
|
||||
[
|
||||
'serialized_executions' => $runningExecutionsSerialized,
|
||||
'lastexecution_failure' => $failureString
|
||||
],
|
||||
[
|
||||
'uid' => $this->taskUid
|
||||
],
|
||||
[
|
||||
'serialized_executions' => Connection::PARAM_LOB,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all marked executions
|
||||
*
|
||||
* @return bool TRUE if the clearing succeeded, FALSE otherwise
|
||||
*/
|
||||
public function unmarkAllExecutions()
|
||||
{
|
||||
// Set the serialized executions field to empty
|
||||
$result = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('tx_scheduler_task')
|
||||
->update(
|
||||
'tx_scheduler_task',
|
||||
[
|
||||
'serialized_executions' => ''
|
||||
],
|
||||
[
|
||||
'uid' => $this->taskUid
|
||||
],
|
||||
[
|
||||
'serialized_executions' => Connection::PARAM_LOB,
|
||||
]
|
||||
);
|
||||
return (bool)$result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the details of the task to the database.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
return $this->scheduler->saveTask($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the task, by replacing the execution object by an empty one
|
||||
* NOTE: the task still needs to be saved after that
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
$this->execution = GeneralUtility::makeInstance(Execution::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the task totally from the system.
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->scheduler->removeTask($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess task type from the existing information
|
||||
* If an interval or a cron command is defined, it's a recurring task
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
if (!empty($this->getExecution()->getInterval()) || !empty($this->getExecution()->getCronCmd())) {
|
||||
return self::TYPE_RECURRING;
|
||||
}
|
||||
return self::TYPE_SINGLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Exception $e
|
||||
*/
|
||||
protected function logException(\Exception $e)
|
||||
{
|
||||
$this->logger->error('A Task Exception was captured: ' . $e->getMessage() . ' (' . $e->getCode() . ')', ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional BE fields for caching framework garbage collection task.
|
||||
* Creates a multi selectbox with all available cache backends to select from.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class CachingFrameworkGarbageCollectionAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* Add a multi select box with all available cache backends.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
|
||||
// Initialize selected fields
|
||||
if (empty($taskInfo['scheduler_cachingFrameworkGarbageCollection_selectedBackends'])) {
|
||||
$taskInfo['scheduler_cachingFrameworkGarbageCollection_selectedBackends'] = [];
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD)) {
|
||||
// In case of new task, set to dbBackend if it's available
|
||||
if (in_array(Typo3DatabaseBackend::class, $this->getRegisteredBackends())) {
|
||||
$taskInfo['scheduler_cachingFrameworkGarbageCollection_selectedBackends'][] = Typo3DatabaseBackend::class;
|
||||
}
|
||||
} elseif ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
// In case of editing the task, set to currently selected value
|
||||
$taskInfo['scheduler_cachingFrameworkGarbageCollection_selectedBackends'] = $task->selectedBackends;
|
||||
}
|
||||
}
|
||||
$fieldName = 'tx_scheduler[scheduler_cachingFrameworkGarbageCollection_selectedBackends][]';
|
||||
$fieldId = 'task_cachingFrameworkGarbageCollection_selectedBackends';
|
||||
$fieldOptions = $this->getCacheBackendOptions($taskInfo['scheduler_cachingFrameworkGarbageCollection_selectedBackends']);
|
||||
$fieldHtml = '<select class="form-control" name="' . $fieldName . '" id="' . $fieldId . '" class="from-control" size="10" multiple="multiple">' . $fieldOptions . '</select>';
|
||||
$additionalFields[$fieldId] = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.cachingFrameworkGarbageCollection.selectBackends',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that all selected backends exist in available backend list
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$validData = true;
|
||||
$availableBackends = $this->getRegisteredBackends();
|
||||
if (is_array($submittedData['scheduler_cachingFrameworkGarbageCollection_selectedBackends'])) {
|
||||
$invalidBackends = array_diff($submittedData['scheduler_cachingFrameworkGarbageCollection_selectedBackends'], $availableBackends);
|
||||
if (!empty($invalidBackends)) {
|
||||
$this->addMessage($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.selectionOfNonExistingCacheBackends'), FlashMessage::ERROR);
|
||||
$validData = false;
|
||||
}
|
||||
} else {
|
||||
$this->addMessage($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.noCacheBackendSelected'), FlashMessage::ERROR);
|
||||
$validData = false;
|
||||
}
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save selected backends in task object
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->selectedBackends = $submittedData['scheduler_cachingFrameworkGarbageCollection_selectedBackends'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build select options of available backends and set currently selected backends
|
||||
*
|
||||
* @param array $selectedBackends Selected backends
|
||||
* @return string HTML of selectbox options
|
||||
*/
|
||||
protected function getCacheBackendOptions(array $selectedBackends)
|
||||
{
|
||||
$options = [];
|
||||
$availableBackends = $this->getRegisteredBackends();
|
||||
foreach ($availableBackends as $backendName) {
|
||||
if (in_array($backendName, $selectedBackends)) {
|
||||
$selected = ' selected="selected"';
|
||||
} else {
|
||||
$selected = '';
|
||||
}
|
||||
$options[] = '<option value="' . $backendName . '"' . $selected . '>' . $backendName . '</option>';
|
||||
}
|
||||
return implode('', $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered caching framework backends
|
||||
*
|
||||
* @return array Registered backends
|
||||
*/
|
||||
protected function getRegisteredBackends()
|
||||
{
|
||||
$backends = [];
|
||||
$cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'];
|
||||
if (is_array($cacheConfigurations)) {
|
||||
foreach ($cacheConfigurations as $cacheConfiguration) {
|
||||
$backend = $cacheConfiguration['backend'];
|
||||
if (!in_array($backend, $backends)) {
|
||||
$backends[] = $backend;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $backends;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Garbage collection of caching framework cache backends.
|
||||
*
|
||||
* This task finds all configured caching framework caches and
|
||||
* calls the garbage collection of a cache if the cache backend
|
||||
* is configured to be cleaned.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class CachingFrameworkGarbageCollectionTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* Backend types that should be cleaned up,
|
||||
* set by additional field provider.
|
||||
*
|
||||
* @var array Selected backends to do garbage collection for
|
||||
*/
|
||||
public $selectedBackends = [];
|
||||
|
||||
/**
|
||||
* Execute garbage collection, called by scheduler.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Global sub-array with all configured caches
|
||||
$cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'];
|
||||
if (is_array($cacheConfigurations)) {
|
||||
// Iterate through configured caches and call garbage collection if
|
||||
// backend is within selected backends in additional field of task
|
||||
foreach ($cacheConfigurations as $cacheName => $cacheConfiguration) {
|
||||
// The cache backend used for this cache
|
||||
$usedCacheBackend = $cacheConfiguration['backend'];
|
||||
if (in_array($usedCacheBackend, $this->selectedBackends)) {
|
||||
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache($cacheName)->collectGarbage();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Scheduler\Task\Enumeration;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Type\Enumeration;
|
||||
|
||||
/**
|
||||
* List of possible scheduler actions
|
||||
*/
|
||||
final class Action extends Enumeration
|
||||
{
|
||||
const __default = self::LIST;
|
||||
const ADD = 'add';
|
||||
const DELETE = 'delete';
|
||||
const EDIT = 'edit';
|
||||
const LIST = 'list';
|
||||
const SAVE = 'save';
|
||||
const SAVE_CLOSE = 'saveclose';
|
||||
const SAVE_NEW = 'savenew';
|
||||
const SET_NEXT_EXECUTION_TIME = 'setNextExecutionTime';
|
||||
const STOP = 'stop';
|
||||
const TOGGLE_HIDDEN = 'toggleHidden';
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use TYPO3\CMS\Core\Console\CommandRegistry;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder;
|
||||
|
||||
/**
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ExecuteSchedulableCommandAdditionalFieldProvider implements AdditionalFieldProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var Command[]
|
||||
*/
|
||||
protected $schedulableCommands = [];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
|
||||
*/
|
||||
protected $reflectionService;
|
||||
|
||||
/**
|
||||
* @var ExecuteSchedulableCommandTask
|
||||
*/
|
||||
protected $task;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class);
|
||||
foreach ($commandRegistry->getSchedulableCommands() as $commandIdentifier => $command) {
|
||||
$this->schedulableCommands[$commandIdentifier] = $command;
|
||||
}
|
||||
|
||||
ksort($this->schedulableCommands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render additional information fields within the scheduler backend.
|
||||
*
|
||||
* @param array &$taskInfo Array information of task to return
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (BE module of the Scheduler)
|
||||
* @return array Additional fields
|
||||
* @see \TYPO3\CMS\Scheduler\AdditionalFieldProvider#getAdditionalFields($taskInfo, $task, $schedulerModule)
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule): array
|
||||
{
|
||||
$this->task = $task;
|
||||
if ($this->task !== null) {
|
||||
$this->task->setScheduler();
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$fields['action'] = $this->getActionField();
|
||||
|
||||
if ($this->task !== null && isset($this->schedulableCommands[$this->task->getCommandIdentifier()])) {
|
||||
$command = $this->schedulableCommands[$this->task->getCommandIdentifier()];
|
||||
$fields['description'] = $this->getCommandDescriptionField($command->getDescription());
|
||||
$argumentFields = $this->getCommandArgumentFields($command->getDefinition());
|
||||
$fields = array_merge($fields, $argumentFields);
|
||||
$optionFields = $this->getCommandOptionFields($command->getDefinition());
|
||||
$fields = array_merge($fields, $optionFields);
|
||||
$this->task->save(); // todo: this seems to be superfluous
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates additional selected fields
|
||||
*
|
||||
* @param array &$submittedData
|
||||
* @param SchedulerModuleController $schedulerModule
|
||||
* @return bool
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule): bool
|
||||
{
|
||||
if (!isset($this->schedulableCommands[$submittedData['task_executeschedulablecommand']['command']])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$command = $this->schedulableCommands[$submittedData['task_executeschedulablecommand']['command']];
|
||||
|
||||
/** @var FlashMessageService $flashMessageService */
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
|
||||
$hasErrors = false;
|
||||
foreach ($command->getDefinition()->getArguments() as $argument) {
|
||||
foreach ((array)$submittedData['task_executeschedulablecommand']['arguments'] as $argumentName => $argumentValue) {
|
||||
/** @var string $argumentName */
|
||||
/** @var string $argumentValue */
|
||||
if ($argument->getName() !== $argumentName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argument->isRequired() && trim($argumentValue) === '') {
|
||||
// Argument is required and argument value is empty0
|
||||
$flashMessageService->getMessageQueueByIdentifier()->addMessage(
|
||||
new FlashMessage(
|
||||
sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.mandatoryArgumentMissing'),
|
||||
$argumentName
|
||||
),
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.updateError'),
|
||||
FlashMessage::ERROR
|
||||
)
|
||||
);
|
||||
$hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($command->getDefinition()->getOptions() as $optionDefinition) {
|
||||
$optionEnabled = $submittedData['task_executeschedulablecommand']['options'][$optionDefinition->getName()] ?? false;
|
||||
$optionValue = $submittedData['task_executeschedulablecommand']['option_values'][$optionDefinition->getName()] ?? $optionDefinition->getDefault();
|
||||
if ($optionEnabled && $optionDefinition->isValueRequired()) {
|
||||
if ($optionDefinition->isArray()) {
|
||||
$testValues = is_array($optionValue) ? $optionValue : GeneralUtility::trimExplode(',', $optionValue, false);
|
||||
} else {
|
||||
$testValues = [$optionValue];
|
||||
}
|
||||
|
||||
foreach ($testValues as $testValue) {
|
||||
if ($testValue === null || trim($testValue) === '') {
|
||||
// An option that requires a value is used with an empty value
|
||||
$flashMessageService->getMessageQueueByIdentifier()->addMessage(
|
||||
new FlashMessage(
|
||||
sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.mandatoryArgumentMissing'),
|
||||
$optionDefinition->getName()
|
||||
),
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.updateError'),
|
||||
FlashMessage::ERROR
|
||||
)
|
||||
);
|
||||
$hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $hasErrors === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves additional field values
|
||||
*
|
||||
* @param array $submittedData
|
||||
* @param AbstractTask $task
|
||||
* @return bool
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task): bool
|
||||
{
|
||||
$command = $this->schedulableCommands[$submittedData['task_executeschedulablecommand']['command']];
|
||||
|
||||
/** @var ExecuteSchedulableCommandTask $task */
|
||||
$task->setCommandIdentifier($submittedData['task_executeschedulablecommand']['command']);
|
||||
|
||||
$arguments = [];
|
||||
foreach ((array)$submittedData['task_executeschedulablecommand']['arguments'] as $argumentName => $argumentValue) {
|
||||
try {
|
||||
$argumentDefinition = $command->getDefinition()->getArgument($argumentName);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argumentDefinition->isArray()) {
|
||||
$argumentValue = GeneralUtility::trimExplode(',', $argumentValue, true);
|
||||
}
|
||||
|
||||
$arguments[$argumentName] = $argumentValue;
|
||||
}
|
||||
|
||||
$options = [];
|
||||
$optionValues = [];
|
||||
foreach ($command->getDefinition()->getOptions() as $optionDefinition) {
|
||||
$optionEnabled = $submittedData['task_executeschedulablecommand']['options'][$optionDefinition->getName()] ?? false;
|
||||
$options[$optionDefinition->getName()] = (bool)$optionEnabled;
|
||||
|
||||
if ($optionDefinition->isValueRequired() || $optionDefinition->isValueOptional() || $optionDefinition->isArray()) {
|
||||
$optionValue = $submittedData['task_executeschedulablecommand']['option_values'][$optionDefinition->getName()] ?? $optionDefinition->getDefault();
|
||||
if ($optionDefinition->isArray() && !is_array($optionValue)) {
|
||||
// Do not remove empty array values.
|
||||
// One empty array element indicates the existence of one occurence of an array option (InputOption::VALUE_IS_ARRAY) without a value.
|
||||
// Empty array elements are also required for command options like "-vvv" (can be entered as ",,").
|
||||
$optionValue = GeneralUtility::trimExplode(',', $optionValue, false);
|
||||
}
|
||||
} else {
|
||||
// boolean flag: option value must be true if option is added or false otherwise
|
||||
$optionValue = (bool)$optionEnabled;
|
||||
}
|
||||
$optionValues[$optionDefinition->getName()] = $optionValue;
|
||||
}
|
||||
|
||||
$task->setArguments($arguments);
|
||||
$task->setOptions($options);
|
||||
$task->setOptionValues($optionValues);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description of selected command
|
||||
*
|
||||
* @param string $description
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommandDescriptionField(string $description): array
|
||||
{
|
||||
return [
|
||||
'code' => '',
|
||||
'label' => '<strong>' . $description . '</strong>'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a select field containing all possible schedulable commands
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getActionField(): array
|
||||
{
|
||||
$currentlySelectedCommand = $this->task !== null ? $this->task->getCommandIdentifier() : '';
|
||||
$options = [];
|
||||
foreach ($this->schedulableCommands as $commandIdentifier => $command) {
|
||||
$options[$commandIdentifier] = $commandIdentifier . ': ' . $command->getDescription();
|
||||
}
|
||||
return [
|
||||
'code' => $this->renderSelectField($options, $currentlySelectedCommand),
|
||||
'label' => $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.schedulableCommandName')
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a set of fields covering arguments which can or must be used.
|
||||
* Also registers the default values of those fields with the Task, allowing
|
||||
* them to be read upon execution.
|
||||
*
|
||||
* @param InputDefinition $inputDefinition
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommandArgumentFields(InputDefinition $inputDefinition): array
|
||||
{
|
||||
$fields = [];
|
||||
$argumentValues = $this->task->getArguments();
|
||||
foreach ($inputDefinition->getArguments() as $argument) {
|
||||
$name = $argument->getName();
|
||||
$defaultValue = $argument->getDefault();
|
||||
$this->task->addDefaultValue($name, $defaultValue);
|
||||
$value = $argumentValues[$name] ?? $defaultValue;
|
||||
|
||||
if (is_array($value) && $argument->isArray()) {
|
||||
$value = implode(',', $value);
|
||||
}
|
||||
|
||||
$fields[$name] = [
|
||||
'code' => $this->renderArgumentField($argument, (string)$value),
|
||||
'label' => $this->getArgumentLabel($argument)
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a set of fields covering options which can or must be used.
|
||||
* Also registers the default values of those fields with the Task, allowing
|
||||
* them to be read upon execution.
|
||||
*
|
||||
* @param InputDefinition $inputDefinition
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommandOptionFields(InputDefinition $inputDefinition): array
|
||||
{
|
||||
$fields = [];
|
||||
$enabledOptions = $this->task->getOptions();
|
||||
$optionValues = $this->task->getOptionValues();
|
||||
foreach ($inputDefinition->getOptions() as $option) {
|
||||
$name = $option->getName();
|
||||
$defaultValue = $option->getDefault();
|
||||
$this->task->addDefaultValue($name, $defaultValue);
|
||||
$enabled = $enabledOptions[$name] ?? false;
|
||||
$value = $optionValues[$name] ?? $defaultValue;
|
||||
|
||||
if (is_array($value) && $option->isArray()) {
|
||||
$value = implode(',', $value);
|
||||
}
|
||||
|
||||
$fields[$name] = [
|
||||
'code' => $this->renderOptionField($option, (bool)$enabled, (string)$value),
|
||||
'label' => $this->getOptionLabel($option)
|
||||
];
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable label for a command argument
|
||||
*
|
||||
* @param InputArgument $argument
|
||||
* @return string
|
||||
*/
|
||||
protected function getArgumentLabel(InputArgument $argument): string
|
||||
{
|
||||
return 'Argument: ' . $argument->getName() . '. <em>' . htmlspecialchars($argument->getDescription()) . '</em>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable label for a command option
|
||||
*
|
||||
* @param InputOption $option
|
||||
* @return string
|
||||
*/
|
||||
protected function getOptionLabel(InputOption $option): string
|
||||
{
|
||||
return 'Option: ' . htmlspecialchars($option->getName()) . '. <em>' . htmlspecialchars($option->getDescription()) . '</em>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param string $selectedOptionValue
|
||||
* @return string
|
||||
*/
|
||||
protected function renderSelectField(array $options, string $selectedOptionValue): string
|
||||
{
|
||||
$selectTag = new TagBuilder();
|
||||
$selectTag->setTagName('select');
|
||||
$selectTag->forceClosingTag(true);
|
||||
$selectTag->addAttribute('class', 'form-control');
|
||||
$selectTag->addAttribute('name', 'tx_scheduler[task_executeschedulablecommand][command]');
|
||||
|
||||
$optionsHtml = '';
|
||||
foreach ($options as $value => $label) {
|
||||
$optionTag = new TagBuilder();
|
||||
$optionTag->setTagName('option');
|
||||
$optionTag->forceClosingTag(true);
|
||||
$optionTag->addAttribute('title', (string)$label);
|
||||
$optionTag->addAttribute('value', (string)$value);
|
||||
$optionTag->setContent($label);
|
||||
|
||||
if ($value === $selectedOptionValue) {
|
||||
$optionTag->addAttribute('selected', 'selected');
|
||||
}
|
||||
|
||||
$optionsHtml .= $optionTag->render();
|
||||
}
|
||||
|
||||
$selectTag->setContent($optionsHtml);
|
||||
return $selectTag->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a field for defining an argument's value
|
||||
*
|
||||
* @param InputArgument $argument
|
||||
* @param mixed $currentValue
|
||||
* @return string
|
||||
*/
|
||||
protected function renderArgumentField(InputArgument $argument, string $currentValue): string
|
||||
{
|
||||
$name = $argument->getName();
|
||||
$fieldName = 'tx_scheduler[task_executeschedulablecommand][arguments][' . $name . ']';
|
||||
|
||||
$inputTag = new TagBuilder();
|
||||
$inputTag->setTagName('input');
|
||||
$inputTag->addAttribute('type', 'text');
|
||||
$inputTag->addAttribute('name', $fieldName);
|
||||
$inputTag->addAttribute('value', $currentValue);
|
||||
$inputTag->addAttribute('class', 'form-control');
|
||||
|
||||
return $inputTag->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a field for defining an option's value
|
||||
*
|
||||
* @param InputOption $option
|
||||
* @param mixed $currentValue
|
||||
* @return string
|
||||
*/
|
||||
protected function renderOptionField(InputOption $option, bool $enabled, string $currentValue): string
|
||||
{
|
||||
$name = $option->getName();
|
||||
|
||||
$checkboxFieldName = 'tx_scheduler[task_executeschedulablecommand][options][' . $name . ']';
|
||||
$checkboxId = 'tx_scheduler_task_executeschedulablecommand_options_' . $name;
|
||||
$checkboxTag = new TagBuilder();
|
||||
$checkboxTag->setTagName('input');
|
||||
$checkboxTag->addAttribute('id', $checkboxId);
|
||||
$checkboxTag->addAttribute('name', $checkboxFieldName);
|
||||
$checkboxTag->addAttribute('type', 'checkbox');
|
||||
if ($enabled) {
|
||||
$checkboxTag->addAttribute('checked', 'checked');
|
||||
}
|
||||
$html = '<label for="' . $checkboxId . '">'
|
||||
. $checkboxTag->render()
|
||||
. ' ' . $this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.addOptionToCommand')
|
||||
. '</label>';
|
||||
|
||||
if ($option->isValueRequired() || $option->isValueOptional() || $option->isArray()) {
|
||||
$valueFieldName = 'tx_scheduler[task_executeschedulablecommand][option_values][' . $name . ']';
|
||||
$inputTag = new TagBuilder();
|
||||
$inputTag->setTagName('input');
|
||||
$inputTag->addAttribute('name', $valueFieldName);
|
||||
$inputTag->addAttribute('type', 'text');
|
||||
$inputTag->addAttribute('value', $currentValue);
|
||||
$inputTag->addAttribute('class', 'form-control');
|
||||
$html .= $inputTag->render();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
public function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidOptionException;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use TYPO3\CMS\Core\Console\CommandRegistry;
|
||||
use TYPO3\CMS\Core\Console\UnknownCommandException;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class ExecuteSchedulableCommandTask extends AbstractTask
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $commandIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $optionValues = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [];
|
||||
|
||||
/**
|
||||
* @param string $commandIdentifier
|
||||
*/
|
||||
public function setCommandIdentifier(string $commandIdentifier)
|
||||
{
|
||||
$this->commandIdentifier = $commandIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCommandIdentifier(): string
|
||||
{
|
||||
return $this->commandIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main method that is called when a task is executed
|
||||
* It MUST be implemented by all classes inheriting from this one
|
||||
* Note that there is no error handling, errors and failures are expected
|
||||
* to be handled and logged by the client implementations.
|
||||
* Should return TRUE on successful execution, FALSE on error.
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @return bool Returns TRUE on successful execution, FALSE on error
|
||||
*/
|
||||
public function execute(): bool
|
||||
{
|
||||
try {
|
||||
$commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class);
|
||||
$schedulableCommand = $commandRegistry->getCommandByIdentifier($this->commandIdentifier);
|
||||
} catch (UnknownCommandException $e) {
|
||||
throw new \RuntimeException(
|
||||
sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.unregisteredCommand'),
|
||||
$this->commandIdentifier
|
||||
),
|
||||
1505055445,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
$input = new ArrayInput($this->getParameters(false));
|
||||
$output = new NullOutput();
|
||||
|
||||
return $schedulableCommand->run($input, $output) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a text representation of the selected command and arguments
|
||||
*
|
||||
* @return string Information to display
|
||||
*/
|
||||
public function getAdditionalInformation(): string
|
||||
{
|
||||
$label = $this->commandIdentifier;
|
||||
|
||||
try {
|
||||
$commandRegistry = GeneralUtility::makeInstance(CommandRegistry::class);
|
||||
$schedulableCommand = $commandRegistry->getCommandByIdentifier($this->commandIdentifier);
|
||||
} catch (UnknownCommandException $e) {
|
||||
return sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.unregisteredCommand'),
|
||||
$this->commandIdentifier
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$input = new ArrayInput($this->getParameters(true), $schedulableCommand->getDefinition());
|
||||
$arguments = $input->__toString();
|
||||
} catch (\Symfony\Component\Console\Exception\RuntimeException|\Symfony\Component\Console\Exception\InvalidArgumentException $e) {
|
||||
return $label . "\n"
|
||||
. sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.errorParsingArguments'),
|
||||
$e->getMessage()
|
||||
);
|
||||
} catch (InvalidOptionException $e) {
|
||||
return $label . "\n"
|
||||
. sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.errorParsingOptions'),
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
if ($arguments !== '') {
|
||||
$label .= ' ' . $arguments;
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function getOptionValues(): array
|
||||
{
|
||||
return $this->optionValues;
|
||||
}
|
||||
|
||||
public function setOptionValues(array $optionValues)
|
||||
{
|
||||
$this->optionValues = $optionValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $argumentName
|
||||
* @param mixed $argumentValue
|
||||
*/
|
||||
public function addDefaultValue(string $argumentName, $argumentValue)
|
||||
{
|
||||
if (is_bool($argumentValue)) {
|
||||
$argumentValue = (int)$argumentValue;
|
||||
}
|
||||
$this->defaults[$argumentName] = $argumentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LanguageService
|
||||
*/
|
||||
public function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
private function getParameters(bool $forDisplay): array
|
||||
{
|
||||
$options = [];
|
||||
foreach ($this->options as $name => $enabled) {
|
||||
if ($enabled) {
|
||||
$value = $this->optionValues[$name] ?? null;
|
||||
$options['--' . $name] = ($forDisplay && $value === true) ? '' : $value;
|
||||
}
|
||||
}
|
||||
return array_merge($this->arguments, $options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Resource\Index\ExtractorInterface;
|
||||
use TYPO3\CMS\Core\Resource\Index\ExtractorRegistry;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
|
||||
/**
|
||||
* Additional BE fields for task which extracts metadata from storage
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileStorageExtractionAdditionalFieldProvider implements AdditionalFieldProviderInterface
|
||||
{
|
||||
/**
|
||||
* Add additional fields
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $parentObject Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $parentObject)
|
||||
{
|
||||
if ($task !== null && !$task instanceof FileStorageExtractionTask) {
|
||||
throw new \InvalidArgumentException('Task not of type FileStorageExtractionTask', 1384275695);
|
||||
}
|
||||
$additionalFields['scheduler_fileStorageIndexing_storage'] = $this->getAllStoragesField($task);
|
||||
$additionalFields['scheduler_fileStorageIndexing_fileCount'] = $this->getFileCountField($task);
|
||||
$additionalFields['scheduler_fileStorageIndexing_registeredExtractors'] = $this->getRegisteredExtractorsField($task);
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a field configuration including a selectbox for available storages
|
||||
*
|
||||
* @param FileStorageExtractionTask|null $task When editing, reference to the current task object. NULL when adding.
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getAllStoragesField(FileStorageExtractionTask $task = null)
|
||||
{
|
||||
/** @var \TYPO3\CMS\Core\Resource\ResourceStorage[] $storages */
|
||||
$storages = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class)->findAll();
|
||||
$options = [];
|
||||
foreach ($storages as $storage) {
|
||||
if ($task !== null && $task->storageUid === $storage->getUid()) {
|
||||
$options[] = '<option value="' . $storage->getUid() . '" selected="selected">' . $storage->getName() . '</option>';
|
||||
} else {
|
||||
$options[] = '<option value="' . $storage->getUid() . '">' . $storage->getName() . '</option>';
|
||||
}
|
||||
}
|
||||
|
||||
$fieldName = 'tx_scheduler[scheduler_fileStorageIndexing_storage]';
|
||||
$fieldId = 'scheduler_fileStorageIndexing_storage';
|
||||
$fieldHtml = '<select class="form-control" name="' . $fieldName . '" id="' . $fieldId . '">' . implode("\n", $options) . '</select>';
|
||||
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageIndexing.storage',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a field configuration including an input field for the file count
|
||||
*
|
||||
* @param FileStorageExtractionTask|null $task When editing, reference to the current task object. NULL when adding.
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getFileCountField(FileStorageExtractionTask $task = null)
|
||||
{
|
||||
$fieldName = 'tx_scheduler[scheduler_fileStorageIndexing_fileCount]';
|
||||
$fieldId = 'scheduler_fileStorageIndexing_fileCount';
|
||||
$fieldValue = $task !== null ? (int)$task->maxFileCount : 100;
|
||||
$fieldHtml = '<input type="text" class="form-control" name="' . $fieldName . '" id="' . $fieldId . '" value="' . htmlspecialchars($fieldValue) . '">';
|
||||
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.fileCount',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a field configuration telling about the status of registered extractors.
|
||||
*
|
||||
* @param FileStorageExtractionTask|null $task When editing, reference to the current task object. NULL when adding.
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getRegisteredExtractorsField(FileStorageExtractionTask $task = null)
|
||||
{
|
||||
$extractors = ExtractorRegistry::getInstance()->getExtractors();
|
||||
|
||||
if (empty($extractors)) {
|
||||
$labelKey = 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors.without_extractors';
|
||||
$content = '<span class="label label-warning">'
|
||||
. htmlspecialchars($this->getLanguageService()->sL($labelKey))
|
||||
. '</span>';
|
||||
} else {
|
||||
// Assemble the extractor bullet list first.
|
||||
$labelKey = 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors.extractor';
|
||||
$bullets = [];
|
||||
foreach ($extractors as $extractor) {
|
||||
$bullets[] = sprintf(
|
||||
'<li title="%s">%s</li>',
|
||||
get_class($extractor),
|
||||
sprintf($this->getLanguageService()->sL($labelKey), $this->formatExtractorClassName($extractor), $extractor->getPriority())
|
||||
);
|
||||
}
|
||||
|
||||
// Finalize content assembling.
|
||||
$labelKey = 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors.with_extractors';
|
||||
$title = $this->getLanguageService()->sL($labelKey);
|
||||
$content = '<p>' . htmlspecialchars($title) . '</p>';
|
||||
$content .= '<ul>' . implode(LF, $bullets) . '</ul>';
|
||||
}
|
||||
|
||||
$fieldConfiguration = [
|
||||
'code' => $content,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageExtraction.registeredExtractors',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => 'scheduler_fileStorageIndexing_registeredExtractors'
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate additional fields
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $parentObject Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool True if validation was ok (or selected class is not relevant), false otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $parentObject)
|
||||
{
|
||||
if (
|
||||
!MathUtility::canBeInterpretedAsInteger($submittedData['scheduler_fileStorageIndexing_storage'])
|
||||
|| !MathUtility::canBeInterpretedAsInteger($submittedData['scheduler_fileStorageIndexing_fileCount'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (ResourceFactory::getInstance()->getStorageObject($submittedData['scheduler_fileStorageIndexing_storage']) === null) {
|
||||
return false;
|
||||
}
|
||||
if (!MathUtility::isIntegerInRange($submittedData['scheduler_fileStorageIndexing_fileCount'], 1, 9999)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save additional field in task
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param AbstractTask $task Reference to the current task object
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
if (!$task instanceof FileStorageExtractionTask) {
|
||||
throw new \InvalidArgumentException('Task not of type FileStorageExtractionTask', 1384275698);
|
||||
}
|
||||
$task->storageUid = (int)$submittedData['scheduler_fileStorageIndexing_storage'];
|
||||
$task->maxFileCount = (int)$submittedData['scheduler_fileStorageIndexing_fileCount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the class name can be very long considering the namespace, only take the final
|
||||
* part for better readability. The FQN of the class will be displayed as tooltip.
|
||||
*
|
||||
* @param ExtractorInterface $extractor
|
||||
* @return string
|
||||
*/
|
||||
protected function formatExtractorClassName(ExtractorInterface $extractor)
|
||||
{
|
||||
$extractorParts = explode('\\', get_class($extractor));
|
||||
return array_pop($extractorParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \TYPO3\CMS\Core\Localization\LanguageService
|
||||
*/
|
||||
protected function getLanguageService()
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* This task which indexes files in storage
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileStorageExtractionTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* Storage Uid
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $storageUid = -1;
|
||||
|
||||
/**
|
||||
* FileCount
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxFileCount = 100;
|
||||
|
||||
/**
|
||||
* Function execute from the Scheduler
|
||||
*
|
||||
* @return bool TRUE on successful execution, FALSE on error
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$success = false;
|
||||
if ((int)$this->storageUid > 0) {
|
||||
$storage = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getStorageObject($this->storageUid);
|
||||
$currentEvaluatePermissionsValue = $storage->getEvaluatePermissions();
|
||||
$storage->setEvaluatePermissions(false);
|
||||
$indexer = $this->getIndexer($storage);
|
||||
try {
|
||||
$indexer->runMetaDataExtraction((int)$this->maxFileCount);
|
||||
$success = true;
|
||||
} catch (\Exception $e) {
|
||||
$success = false;
|
||||
$this->logException($e);
|
||||
}
|
||||
$storage->setEvaluatePermissions($currentEvaluatePermissionsValue);
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the indexer
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
|
||||
* @return \TYPO3\CMS\Core\Resource\Index\Indexer
|
||||
*/
|
||||
protected function getIndexer(\TYPO3\CMS\Core\Resource\ResourceStorage $storage)
|
||||
{
|
||||
return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\Indexer::class, $storage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Additional BE fields for tasks which indexes files in a storage
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileStorageIndexingAdditionalFieldProvider implements \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface
|
||||
{
|
||||
/**
|
||||
* Add additional fields
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject)
|
||||
{
|
||||
if ($task !== null && !$task instanceof FileStorageIndexingTask) {
|
||||
throw new \InvalidArgumentException('Task not of type FileStorageExtractionTask', 1384275696);
|
||||
}
|
||||
$additionalFields['scheduler_fileStorageIndexing_storage'] = $this->getAllStoragesField($task, $taskInfo);
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a select field of available storages.
|
||||
*
|
||||
* @param FileStorageIndexingTask $task When editing, reference to the current task object. NULL when adding.
|
||||
* @param array $taskInfo
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getAllStoragesField(FileStorageIndexingTask $task = null, $taskInfo)
|
||||
{
|
||||
/** @var \TYPO3\CMS\Core\Resource\ResourceStorage[] $storages */
|
||||
$storages = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\StorageRepository::class)->findAll();
|
||||
$options = [];
|
||||
foreach ($storages as $storage) {
|
||||
$selected = '';
|
||||
if ($task !== null && $task->storageUid === $storage->getUid()) {
|
||||
$selected = ' selected="selected"';
|
||||
} elseif ((int)$taskInfo['scheduler_fileStorageIndexing_storage'] === $storage->getUid()) {
|
||||
$selected = ' selected="selected"';
|
||||
}
|
||||
$options[] = '<option value="' . $storage->getUid() . '" ' . $selected . ' >' . $storage->getName() . '</option>';
|
||||
}
|
||||
|
||||
$fieldName = 'tx_scheduler[scheduler_fileStorageIndexing_storage]';
|
||||
$fieldId = 'scheduler_fileStorageIndexing_storage';
|
||||
$fieldHtml = '<select class="form-control" name="' . $fieldName . '" id="' . $fieldId . '">' . implode("\n", $options) . '</select>';
|
||||
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.fileStorageIndexing.storage',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate additional fields
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool True if validation was ok (or selected class is not relevant), false otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject)
|
||||
{
|
||||
$value = $submittedData['scheduler_fileStorageIndexing_storage'];
|
||||
if (!\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($value)) {
|
||||
return false;
|
||||
}
|
||||
if (\TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getStorageObject($submittedData['scheduler_fileStorageIndexing_storage']) !== null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save additional field in task
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the current task object
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, \TYPO3\CMS\Scheduler\Task\AbstractTask $task)
|
||||
{
|
||||
if (!$task instanceof FileStorageIndexingTask) {
|
||||
throw new \InvalidArgumentException('Task not of type FileStorageExtractionTask', 1384275697);
|
||||
}
|
||||
$task->storageUid = (int)$submittedData['scheduler_fileStorageIndexing_storage'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* This task tries to find changes in storage and writes them back to DB
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class FileStorageIndexingTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* Storage Uid
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $storageUid = -1;
|
||||
|
||||
/**
|
||||
* Function execute from the Scheduler
|
||||
*
|
||||
* @return bool TRUE on successful execution, FALSE on error
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if ((int)$this->storageUid > 0) {
|
||||
$storage = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getStorageObject($this->storageUid);
|
||||
$currentEvaluatePermissionsValue = $storage->getEvaluatePermissions();
|
||||
$storage->setEvaluatePermissions(false);
|
||||
$indexer = $this->getIndexer($storage);
|
||||
$indexer->processChangesInStorages();
|
||||
$storage->setEvaluatePermissions($currentEvaluatePermissionsValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the indexer
|
||||
*
|
||||
* @param \TYPO3\CMS\Core\Resource\ResourceStorage $storage
|
||||
* @return \TYPO3\CMS\Core\Resource\Index\Indexer
|
||||
*/
|
||||
protected function getIndexer(\TYPO3\CMS\Core\Resource\ResourceStorage $storage)
|
||||
{
|
||||
return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\Index\Indexer::class, $storage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional BE fields for ip address anonymization task.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class IpAnonymizationAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* Add additional fields
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$additionalFields = [];
|
||||
$additionalFields['task_ipAnonymization_table'] = $this->getTableAdditionalField($taskInfo, $task, $schedulerModule);
|
||||
$additionalFields['task_ipAnonymization_numberOfDays'] = $this->getNumberOfDaysAdditionalField($taskInfo, $task, $schedulerModule);
|
||||
$additionalFields['task_ipAnonymization_mask'] = $this->getMaskAdditionalField($taskInfo, $task, $schedulerModule);
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a select field of available tables.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getTableAdditionalField(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
$tableConfiguration = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][IpAnonymizationTask::class]['options']['tables'];
|
||||
$options = [];
|
||||
// Add an empty option on top if an existing task is configured
|
||||
// with a table that can not be found in configuration anymore
|
||||
if (!array_key_exists($task->table, $tableConfiguration) && $currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
$options[] = '<option value="" selected="selected"></option>';
|
||||
}
|
||||
foreach ($tableConfiguration as $tableName => $configuration) {
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD) && empty($options)) {
|
||||
// Select first table by default if adding a new task
|
||||
$options[] = '<option value="' . $tableName . '" selected="selected">' . $tableName . '</option>';
|
||||
} elseif ($task->table === $tableName) {
|
||||
// Select currently selected table
|
||||
$options[] = '<option value="' . $tableName . '" selected="selected">' . $tableName . '</option>';
|
||||
} else {
|
||||
$options[] = '<option value="' . $tableName . '">' . $tableName . '</option>';
|
||||
}
|
||||
}
|
||||
$fieldName = 'tx_scheduler[scheduler_ipAnonymization_table]';
|
||||
$fieldId = 'task_ipAnonymization_table';
|
||||
$fieldHtml = [];
|
||||
// Add table drop down html
|
||||
$fieldHtml[] = '<select class="form-control" name="' . $fieldName . '" id="' . $fieldId . '">' . implode(LF, $options) . '</select>';
|
||||
$fieldConfiguration = [
|
||||
'code' => implode(LF, $fieldHtml),
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.table',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input field to get the number of days.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getNumberOfDaysAdditionalField(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$fieldId = 'scheduler_ipAnonymization_numberOfDays';
|
||||
if (empty($taskInfo[$fieldId])) {
|
||||
$taskInfo[$fieldId] = $task->numberOfDays ?? 180;
|
||||
}
|
||||
$fieldName = 'tx_scheduler[' . $fieldId . ']';
|
||||
$fieldHtml = '<input class="form-control" type="text" ' . 'name="' . $fieldName . '" ' . 'id="' . $fieldId . '" ' . 'value="' . (int)$taskInfo[$fieldId] . '" ' . 'size="4">';
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.numberOfDays',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input field to get the mask.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getMaskAdditionalField(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$fieldId = 'scheduler_ipAnonymization_mask';
|
||||
if (empty($taskInfo[$fieldId])) {
|
||||
$taskInfo[$fieldId] = $task->mask ?? 2;
|
||||
}
|
||||
$fieldName = 'tx_scheduler[' . $fieldId . ']';
|
||||
|
||||
$fieldHtml = '';
|
||||
foreach ([1, 2] as $mask) {
|
||||
$selected = (int)$taskInfo[$fieldId] === $mask ? ' selected' : '';
|
||||
$fieldHtml .= '<option value="' . $mask . '"' . $selected . '>'
|
||||
. htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.mask.' . $mask))
|
||||
. '</option>';
|
||||
}
|
||||
|
||||
$fieldHtml = '<select class="form-control" name="' . $fieldName . '" ' . 'id="' . $fieldId . '">' . $fieldHtml . '</select>';
|
||||
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.mask',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate additional fields
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool True if validation was ok (or selected class is not relevant), false otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$validData = $this->validateTableAdditionalField($submittedData, $schedulerModule);
|
||||
$validData &= $this->validateNumberOfDaysAdditionalField($submittedData, $schedulerModule);
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks given table for existence in configuration array
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool True if table exists in configuration, false otherwise
|
||||
*/
|
||||
public function validateTableAdditionalField(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$validData = false;
|
||||
$tableConfiguration = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][IpAnonymizationTask::class]['options']['tables'];
|
||||
if (!isset($submittedData['scheduler_ipAnonymization_table'])) {
|
||||
$validData = true;
|
||||
} elseif (array_key_exists($submittedData['scheduler_ipAnonymization_table'], $tableConfiguration)) {
|
||||
$validData = true;
|
||||
}
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given number of days is a positive integer
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool True if validation was ok (or selected class is not relevant), false otherwise
|
||||
*/
|
||||
public function validateNumberOfDaysAdditionalField(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$validData = false;
|
||||
if (!isset($submittedData['scheduler_ipAnonymization_numberOfDays'])) {
|
||||
$validData = true;
|
||||
} elseif ((int)$submittedData['scheduler_ipAnonymization_numberOfDays'] >= 0) {
|
||||
$validData = true;
|
||||
} else {
|
||||
// Issue error message
|
||||
$this->addMessage($this->getLanguageService()->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidNumberOfDays'), FlashMessage::ERROR);
|
||||
}
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save additional field in task
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->table = $submittedData['scheduler_ipAnonymization_table'];
|
||||
$task->mask = (int)$submittedData['scheduler_ipAnonymization_mask'];
|
||||
$task->numberOfDays = (int)$submittedData['scheduler_ipAnonymization_numberOfDays'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of LanguageService
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\IpAnonymizationUtility;
|
||||
|
||||
/**
|
||||
* Anonymize IP addresses in records
|
||||
*
|
||||
* This task anonymizes IP addresses in tables older than the given number of days.
|
||||
*
|
||||
* Available tables must be registered in
|
||||
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\IpAnonymizationTask::class]['options']['tables']
|
||||
* See ext_localconf.php of scheduler extension for an example
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class IpAnonymizationTask extends AbstractTask
|
||||
{
|
||||
|
||||
/**
|
||||
* @var int Number of days
|
||||
*/
|
||||
public $numberOfDays = 180;
|
||||
|
||||
/**
|
||||
* @var int mask level see \TYPO3\CMS\Core\Utility\IpAnonymizationUtility::anonymizeIp
|
||||
*/
|
||||
public $mask = 2;
|
||||
|
||||
/**
|
||||
* @var string Table to clean up
|
||||
*/
|
||||
public $table = '';
|
||||
|
||||
/**
|
||||
* Execute garbage collection, called by scheduler.
|
||||
*
|
||||
* @throws \RuntimeException If configured table was not cleaned up
|
||||
* @return bool TRUE if task run was successful
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$configuration = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][self::class]['options']['tables'][$this->table] ?? [];
|
||||
if (empty($configuration)) {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: ' . $this->table . ' does not exist in configuration', 1524502548);
|
||||
}
|
||||
$this->handleTable($this->table, $configuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute clean up of a specific table
|
||||
*
|
||||
* @throws \RuntimeException If table configuration is broken
|
||||
* @param string $table The table to handle
|
||||
* @param array $configuration Clean up configuration
|
||||
* @return bool TRUE if cleanup was successful
|
||||
*/
|
||||
protected function handleTable($table, array $configuration)
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
|
||||
if (empty($configuration['dateField'])) {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: "dateField" must be defined for table ' . $table, 1524502549);
|
||||
}
|
||||
if (empty($configuration['ipField'])) {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: "ipField" must be defined for table ' . $table, 1524502666);
|
||||
}
|
||||
$deleteTimestamp = strtotime('-' . $this->numberOfDays . 'days');
|
||||
if ($deleteTimestamp === false) {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: number of days could not be calculated for table ' . $table, 1524526354);
|
||||
}
|
||||
if ($this->mask === 2) {
|
||||
$notLikeMaskPattern = '%.0.0';
|
||||
} else {
|
||||
$notLikeMaskPattern = '%.0';
|
||||
}
|
||||
try {
|
||||
$result = $queryBuilder
|
||||
->select('uid', $configuration['ipField'])
|
||||
->where(
|
||||
$queryBuilder->expr()->lt(
|
||||
$configuration['dateField'],
|
||||
$queryBuilder->createNamedParameter($deleteTimestamp, \PDO::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->neq(
|
||||
$configuration['ipField'],
|
||||
$queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->isNotNull($configuration['ipField']),
|
||||
$queryBuilder->expr()->notLike(
|
||||
$configuration['ipField'],
|
||||
$queryBuilder->createNamedParameter($notLikeMaskPattern, \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->notLike(
|
||||
$configuration['ipField'],
|
||||
$queryBuilder->createNamedParameter('%::', \PDO::PARAM_STR)
|
||||
)
|
||||
)
|
||||
->from($table)
|
||||
->execute();
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$ip = (string)$row[$configuration['ipField']];
|
||||
|
||||
$connection->update(
|
||||
$table,
|
||||
[
|
||||
$configuration['ipField'] => IpAnonymizationUtility::anonymizeIp($ip, (int)$this->mask)
|
||||
],
|
||||
[
|
||||
'uid' => $row['uid']
|
||||
]
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(self::class . ' failed for table ' . $this->table . ' with error: ' . $e->getMessage(), 1524502550);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the selected table as additional information
|
||||
*
|
||||
* @return string Information to display
|
||||
*/
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
return sprintf($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.ipAnonymization.additionalInformationTable'), $this->table, $this->numberOfDays);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional BE fields for optimize database table task.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class OptimizeDatabaseTableAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $languageFile = 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf';
|
||||
|
||||
/**
|
||||
* Add a multi select box with all available database tables.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
|
||||
// Initialize selected fields
|
||||
if (empty($taskInfo['scheduler_optimizeDatabaseTables_selectedTables'])) {
|
||||
$taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = [];
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD)) {
|
||||
// In case of new task, select no tables by default
|
||||
$taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = [];
|
||||
} elseif ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
// In case of editing the task, set to currently selected value
|
||||
$taskInfo['scheduler_optimizeDatabaseTables_selectedTables'] = $task->selectedTables;
|
||||
}
|
||||
}
|
||||
$fieldName = 'tx_scheduler[scheduler_optimizeDatabaseTables_selectedTables][]';
|
||||
$fieldId = 'scheduler_optimizeDatabaseTables_selectedTables';
|
||||
$fieldOptions = $this->getDatabaseTableOptions($taskInfo['scheduler_optimizeDatabaseTables_selectedTables']);
|
||||
$fieldHtml = '<select class="form-control" name="' . $fieldName
|
||||
. '" id="' . $fieldId
|
||||
. '" class="from-control" size="10" multiple="multiple">'
|
||||
. $fieldOptions
|
||||
. '</select>';
|
||||
$additionalFields[$fieldId] = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => $this->languageFile . ':label.optimizeDatabaseTables.selectTables',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId,
|
||||
];
|
||||
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that all selected backends exist in available backend list
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$validData = true;
|
||||
$availableTables = $this->getOptimizableTables();
|
||||
if (is_array($submittedData['scheduler_optimizeDatabaseTables_selectedTables'])) {
|
||||
$invalidTables = array_diff(
|
||||
$submittedData['scheduler_optimizeDatabaseTables_selectedTables'],
|
||||
$availableTables
|
||||
);
|
||||
if (!empty($invalidTables)) {
|
||||
$this->addMessage(
|
||||
$GLOBALS['LANG']->sL($this->languageFile . ':msg.selectionOfNonExistingDatabaseTables'),
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
$validData = false;
|
||||
}
|
||||
} else {
|
||||
$this->addMessage(
|
||||
$GLOBALS['LANG']->sL($this->languageFile . ':msg.noDatabaseTablesSelected'),
|
||||
FlashMessage::ERROR
|
||||
);
|
||||
$validData = false;
|
||||
}
|
||||
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save selected backends in task object
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->selectedTables = $submittedData['scheduler_optimizeDatabaseTables_selectedTables'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build select options of available backends and set currently selected backends
|
||||
*
|
||||
* @param array $selectedTables Selected backends
|
||||
* @return string HTML of selectbox options
|
||||
*/
|
||||
protected function getDatabaseTableOptions(array $selectedTables)
|
||||
{
|
||||
$options = [];
|
||||
$availableTables = $this->getOptimizableTables();
|
||||
foreach ($availableTables as $tableName) {
|
||||
$selected = in_array($tableName, $selectedTables, true) ? ' selected="selected"' : '';
|
||||
$options[] = '<option value="' . $tableName . '"' . $selected . '>' . $tableName . '</option>';
|
||||
}
|
||||
|
||||
return implode('', $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tables that are capable of optimization
|
||||
*
|
||||
* @return array Names of table that can be optimized.
|
||||
*/
|
||||
protected function getOptimizableTables()
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
$defaultConnection = $connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
|
||||
|
||||
// Retrieve all optimizable tables for the default connection
|
||||
$optimizableTables = $this->getOptimizableTablesForConnection($defaultConnection);
|
||||
|
||||
// Retrieve additional optimizable tables that have been remapped to a different connection
|
||||
$tableMap = $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'] ?? [];
|
||||
if ($tableMap) {
|
||||
// Remove all remapped tables from the list of optimizable tables
|
||||
// These tables will be rechecked and possibly re-added to the list
|
||||
// of optimizable tables. This ensures that no orphaned table from
|
||||
// the default connection gets mistakenly labeled as optimizable.
|
||||
$optimizableTables = array_diff($optimizableTables, array_keys($tableMap));
|
||||
|
||||
// Walk each connection and check all tables that have been
|
||||
// remapped to it for optimization support.
|
||||
$connectionNames = array_keys(array_flip($tableMap));
|
||||
foreach ($connectionNames as $connectionName) {
|
||||
$connection = $connectionPool->getConnectionByName($connectionName);
|
||||
$tablesOnConnection = array_keys(array_filter(
|
||||
$tableMap,
|
||||
function ($value) use ($connectionName) {
|
||||
return $value === $connectionName;
|
||||
}
|
||||
));
|
||||
$tables = $this->getOptimizableTablesForConnection($connection, $tablesOnConnection);
|
||||
$optimizableTables = array_merge($optimizableTables, $tables);
|
||||
}
|
||||
}
|
||||
|
||||
sort($optimizableTables);
|
||||
|
||||
return $optimizableTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all optimizable tables for a connection, optionally restricted to the subset
|
||||
* of table names in the $tableNames array.
|
||||
*
|
||||
* @param Connection $connection
|
||||
* @param array $tableNames
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptimizableTablesForConnection(Connection $connection, array $tableNames = []): array
|
||||
{
|
||||
// Return empty list if the database platform is not MySQL
|
||||
if (strpos($connection->getServerVersion(), 'MySQL') !== 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Retrieve all tables from the MySQL informaation schema that have an engine type
|
||||
// that supports the OPTIMIZE TABLE command.
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$queryBuilder->select('TABLE_NAME AS Table', 'ENGINE AS Engine')
|
||||
->from('information_schema.TABLES')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'TABLE_TYPE',
|
||||
$queryBuilder->createNamedParameter('BASE TABLE', \PDO::PARAM_STR)
|
||||
),
|
||||
$queryBuilder->expr()->in(
|
||||
'ENGINE',
|
||||
$queryBuilder->createNamedParameter(['InnoDB', 'MyISAM', 'ARCHIVE'], Connection::PARAM_STR_ARRAY)
|
||||
),
|
||||
$queryBuilder->expr()->eq(
|
||||
'TABLE_SCHEMA',
|
||||
$queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
|
||||
)
|
||||
);
|
||||
|
||||
if (!empty($tableNames)) {
|
||||
$queryBuilder->andWhere(
|
||||
$queryBuilder->expr()->in(
|
||||
'TABLE_NAME',
|
||||
$queryBuilder->createNamedParameter($tableNames, \PDO::PARAM_STR)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$tables = $queryBuilder->execute()->fetchAll();
|
||||
|
||||
return array_column($tables, 'Table');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Perform OPTIMIZE TABLE SQL statements
|
||||
*
|
||||
* This task reorganizes the physical storage of table data and associated index data,
|
||||
* to reduce storage space and improve I/O efficiency when accessing the table. The
|
||||
* exact changes made to each table depend on the storage engine used by that table.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class OptimizeDatabaseTableTask extends AbstractTask
|
||||
{
|
||||
/**
|
||||
* Database tables that should be cleaned up,
|
||||
* set by additional field provider.
|
||||
*
|
||||
* @var array Selected tables to optimize
|
||||
*/
|
||||
public $selectedTables = [];
|
||||
|
||||
/**
|
||||
* Execute table optimization, called by scheduler.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
|
||||
foreach ($this->selectedTables as $tableName) {
|
||||
$connection = $connectionPool->getConnectionForTable($tableName);
|
||||
|
||||
if (strpos($connection->getServerVersion(), 'MySQL') === 0) {
|
||||
try {
|
||||
$connection->query('OPTIMIZE TABLE ' . $connection->quoteIdentifier($tableName));
|
||||
} catch (DBALException $e) {
|
||||
throw new \RuntimeException(
|
||||
TableGarbageCollectionTask::class . ' failed for: ' . $tableName . ': ' .
|
||||
$e->getPrevious()->getMessage(),
|
||||
1441390263
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the selected tables
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
return implode(', ', $this->selectedTables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional BE fields for recycler garbage collection task.
|
||||
*
|
||||
* Creates an integer input field for difference between scheduler run time
|
||||
* and file modification time in days to select from.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class RecyclerGarbageCollectionAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* Default period in days to remove a recycled file
|
||||
*
|
||||
* @var int Default number of days
|
||||
*/
|
||||
protected $defaultNumberOfDays = 30;
|
||||
|
||||
/**
|
||||
* Add an integer input field for difference between scheduler run time
|
||||
* and file modification time in days to select from
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
|
||||
// Initialize selected fields
|
||||
if (!isset($taskInfo['scheduler_recyclerGarbageCollection_numberOfDays'])) {
|
||||
$taskInfo['scheduler_recyclerGarbageCollection_numberOfDays'] = $this->defaultNumberOfDays;
|
||||
if ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
$taskInfo['scheduler_recyclerGarbageCollection_numberOfDays'] = $task->numberOfDays;
|
||||
}
|
||||
}
|
||||
$fieldName = 'tx_scheduler[scheduler_recyclerGarbageCollection_numberOfDays]';
|
||||
$fieldId = 'task_recyclerGarbageCollection_numberOfDays';
|
||||
$fieldValue = (int)$taskInfo['scheduler_recyclerGarbageCollection_numberOfDays'];
|
||||
$fieldHtml = '<input class="form-control" type="text" name="' . $fieldName . '" id="' . $fieldId . '" value="' . htmlspecialchars($fieldValue) . '">';
|
||||
$additionalFields[$fieldId] = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.recyclerGarbageCollection.numberOfDays',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given value is an integer
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool TRUE if validation was ok (or selected class is not relevant), FALSE otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$result = true;
|
||||
// Check if number of days is indeed a number and greater or equals to 0
|
||||
// If not, fail validation and issue error message
|
||||
if (!is_numeric($submittedData['scheduler_recyclerGarbageCollection_numberOfDays']) || (int)$submittedData['scheduler_recyclerGarbageCollection_numberOfDays'] < 0) {
|
||||
$result = false;
|
||||
$this->addMessage($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidNumberOfDays'), FlashMessage::ERROR);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves given integer value in task object
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->numberOfDays = (int)$submittedData['scheduler_recyclerGarbageCollection_numberOfDays'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Recycler folder garbage collection task
|
||||
*
|
||||
* This task finds all "_recycler_" folders below all storages and
|
||||
* deletes all files in them that have not changed for more than
|
||||
* a given number of days.
|
||||
*
|
||||
* Compatible drivers should be implemented correctly for this. The shipped "local driver"
|
||||
* does a "touch()" after the file is moved into the recycler folder.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class RecyclerGarbageCollectionTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask
|
||||
{
|
||||
/**
|
||||
* Elapsed period since last modification before a file will
|
||||
* be deleted in a recycler directory.
|
||||
*
|
||||
* @var int Number of days before cleaning up files
|
||||
*/
|
||||
public $numberOfDays = 0;
|
||||
|
||||
/**
|
||||
* Cleanup recycled files, called by scheduler.
|
||||
*
|
||||
* @return bool TRUE if task run was successful
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$recyclerFolders = [];
|
||||
$storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
|
||||
// takes only _recycler_ folder on the first level into account
|
||||
foreach ($storageRepository->findAll() as $storage) {
|
||||
$rootLevelFolder = $storage->getRootLevelFolder(false);
|
||||
foreach ($rootLevelFolder->getSubfolders() as $subFolder) {
|
||||
if ($subFolder->getRole() === $subFolder::ROLE_RECYCLER) {
|
||||
$recyclerFolders[] = $subFolder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute cleanup
|
||||
$seconds = 60 * 60 * 24 * (int)$this->numberOfDays;
|
||||
$timestamp = $GLOBALS['EXEC_TIME'] - $seconds;
|
||||
foreach ($recyclerFolders as $recyclerFolder) {
|
||||
$this->cleanupRecycledFiles($recyclerFolder, $timestamp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all files in a directory recursively and removes
|
||||
* old ones.
|
||||
*
|
||||
* @param Folder $folder the folder
|
||||
* @param int $timestamp Timestamp of the last file modification
|
||||
*/
|
||||
protected function cleanupRecycledFiles(Folder $folder, $timestamp)
|
||||
{
|
||||
foreach ($folder->getFiles() as $file) {
|
||||
if ($timestamp > $file->getModificationTime()) {
|
||||
$file->delete();
|
||||
}
|
||||
}
|
||||
foreach ($folder->getSubfolders() as $subFolder) {
|
||||
$this->cleanupRecycledFiles($subFolder, $timestamp);
|
||||
// if no more files and subdirectories are in the folder, remove the folder as well
|
||||
if ($subFolder->getFileCount() === 0 && count($subFolder->getSubfolders()) === 0) {
|
||||
$subFolder->delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
|
||||
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
|
||||
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
|
||||
|
||||
/**
|
||||
* Additional BE fields for sys log table garbage collection task.
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class TableGarbageCollectionAdditionalFieldProvider extends AbstractAdditionalFieldProvider
|
||||
{
|
||||
/**
|
||||
* @var array Default number of days by table
|
||||
*/
|
||||
protected $defaultNumberOfDays = [];
|
||||
|
||||
/**
|
||||
* Add additional fields
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$this->initDefaultNumberOfDays();
|
||||
$additionalFields = [];
|
||||
$additionalFields['task_tableGarbageCollection_allTables'] = $this->getAllTablesAdditionalField($taskInfo, $task, $schedulerModule);
|
||||
$additionalFields['task_tableGarbageCollection_table'] = $this->getTableAdditionalField($taskInfo, $task, $schedulerModule);
|
||||
$additionalFields['task_tableGarbageCollection_numberOfDays'] = $this->getNumberOfDaysAdditionalField($taskInfo, $task, $schedulerModule);
|
||||
return $additionalFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the default number of days for all configured tables
|
||||
*/
|
||||
protected function initDefaultNumberOfDays()
|
||||
{
|
||||
$tableConfiguration = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables'];
|
||||
foreach ($tableConfiguration as $tableName => $configuration) {
|
||||
if (isset($configuration['expirePeriod'])) {
|
||||
$this->defaultNumberOfDays[$tableName] = $configuration['expirePeriod'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a select field of available tables.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getAllTablesAdditionalField(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
|
||||
if ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
$checked = $task->allTables === true ? 'checked="checked" ' : '';
|
||||
} else {
|
||||
$checked = '';
|
||||
}
|
||||
|
||||
$fieldName = 'tx_scheduler[scheduler_tableGarbageCollection_allTables]';
|
||||
$fieldId = 'task_tableGarbageCollection_allTables';
|
||||
$fieldHtml = '<div class="checkbox"><label><input type="checkbox" ' . $checked . ' name="' . $fieldName . '" ' . 'id="' . $fieldId . '"></label></div>';
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.allTables',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a select field of available tables.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getTableAdditionalField(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
$tableConfiguration = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables'];
|
||||
$options = [];
|
||||
// Add an empty option on top if an existing task is configured
|
||||
// with a table that can not be found in configuration anymore
|
||||
if (!array_key_exists($task->table, $tableConfiguration) && $currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
$options[] = '<option value="" selected="selected"></option>';
|
||||
}
|
||||
foreach ($tableConfiguration as $tableName => $configuration) {
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD) && empty($options)) {
|
||||
// Select first table by default if adding a new task
|
||||
$options[] = '<option value="' . $tableName . '" selected="selected">' . $tableName . '</option>';
|
||||
} elseif ($task->table === $tableName) {
|
||||
// Select currently selected table
|
||||
$options[] = '<option value="' . $tableName . '" selected="selected">' . $tableName . '</option>';
|
||||
} else {
|
||||
$options[] = '<option value="' . $tableName . '">' . $tableName . '</option>';
|
||||
}
|
||||
}
|
||||
$disabled = $task->allTables === true ? ' disabled="disabled"' : '';
|
||||
$fieldName = 'tx_scheduler[scheduler_tableGarbageCollection_table]';
|
||||
$fieldId = 'task_tableGarbageCollection_table';
|
||||
$fieldHtml = [];
|
||||
// Add table drop down html
|
||||
$fieldHtml[] = '<select class="form-control" name="' . $fieldName . '" ' . $disabled . ' id="' . $fieldId . '">' . implode(LF, $options) . '</select>';
|
||||
// Add js array for default 'number of days' values
|
||||
$fieldHtml[] = '<script type="text/javascript">/*<![CDATA[*/<!--';
|
||||
$fieldHtml[] = 'var defaultNumberOfDays = ' . json_encode($this->defaultNumberOfDays) . ';';
|
||||
$fieldHtml[] = '// -->/*]]>*/</script>';
|
||||
$fieldConfiguration = [
|
||||
'code' => implode(LF, $fieldHtml),
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.table',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an input field to get the number of days.
|
||||
*
|
||||
* @param array $taskInfo Reference to the array containing the info used in the add/edit form
|
||||
* @param AbstractTask|null $task When editing, reference to the current task. NULL when adding.
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return array Array containing all the information pertaining to the additional fields
|
||||
*/
|
||||
protected function getNumberOfDaysAdditionalField(array &$taskInfo, $task, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
|
||||
// Initialize selected fields
|
||||
$disabled = '';
|
||||
if (empty($taskInfo['scheduler_tableGarbageCollection_numberOfDays'])) {
|
||||
if ($currentSchedulerModuleAction->equals(Action::ADD)) {
|
||||
// In case of new task, set to 180 days
|
||||
$taskInfo['scheduler_tableGarbageCollection_numberOfDays'] = 180;
|
||||
} elseif ($currentSchedulerModuleAction->equals(Action::EDIT)) {
|
||||
// In case of editing the task, set to currently selected value
|
||||
$taskInfo['scheduler_tableGarbageCollection_numberOfDays'] = $task->numberOfDays;
|
||||
if ($task->numberOfDays === 0 && !isset($this->defaultNumberOfDays[$task->table])) {
|
||||
$disabled = ' disabled="disabled"';
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($task->allTables === true) {
|
||||
$disabled = ' disabled="disabled"';
|
||||
}
|
||||
$fieldName = 'tx_scheduler[scheduler_tableGarbageCollection_numberOfDays]';
|
||||
$fieldId = 'task_tableGarbageCollection_numberOfDays';
|
||||
$fieldHtml = '<input class="form-control" type="text" ' . 'name="' . $fieldName . '" ' . 'id="' . $fieldId . '" ' . $disabled . 'value="' . (int)$taskInfo['scheduler_tableGarbageCollection_numberOfDays'] . '" ' . 'size="4">';
|
||||
$fieldConfiguration = [
|
||||
'code' => $fieldHtml,
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.numberOfDays',
|
||||
'cshKey' => '_MOD_system_txschedulerM1',
|
||||
'cshLabel' => $fieldId
|
||||
];
|
||||
return $fieldConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate additional fields
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @param SchedulerModuleController $schedulerModule Reference to the calling object (Scheduler's BE module)
|
||||
* @return bool True if validation was ok (or selected class is not relevant), false otherwise
|
||||
*/
|
||||
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule)
|
||||
{
|
||||
$validData = $this->validateAllTablesAdditionalField($submittedData);
|
||||
$validData &= $this->validateTableAdditionalField($submittedData);
|
||||
$validData &= $this->validateNumberOfDaysAdditionalField($submittedData);
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all table field is correct
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @return bool True if data is valid
|
||||
*/
|
||||
public function validateAllTablesAdditionalField(array &$submittedData)
|
||||
{
|
||||
$validData = false;
|
||||
if (!isset($submittedData['scheduler_tableGarbageCollection_allTables'])) {
|
||||
$validData = true;
|
||||
} elseif ($submittedData['scheduler_tableGarbageCollection_allTables'] === 'on') {
|
||||
$validData = true;
|
||||
}
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks given table for existence in configuration array
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @return bool True if table exists in configuration, false otherwise
|
||||
*/
|
||||
public function validateTableAdditionalField(array &$submittedData)
|
||||
{
|
||||
$validData = false;
|
||||
$tableConfiguration = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][TableGarbageCollectionTask::class]['options']['tables'];
|
||||
if (!isset($submittedData['scheduler_tableGarbageCollection_table'])) {
|
||||
$validData = true;
|
||||
} elseif (array_key_exists($submittedData['scheduler_tableGarbageCollection_table'], $tableConfiguration)) {
|
||||
$validData = true;
|
||||
}
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given number of days is a positive integer
|
||||
*
|
||||
* @param array $submittedData Reference to the array containing the data submitted by the user
|
||||
* @return bool True if validation was ok (or selected class is not relevant), false otherwise
|
||||
*/
|
||||
public function validateNumberOfDaysAdditionalField(array &$submittedData)
|
||||
{
|
||||
$validData = false;
|
||||
if (!isset($submittedData['scheduler_tableGarbageCollection_numberOfDays'])) {
|
||||
$validData = true;
|
||||
} elseif ((int)$submittedData['scheduler_tableGarbageCollection_numberOfDays'] >= 0) {
|
||||
$validData = true;
|
||||
} else {
|
||||
// Issue error message
|
||||
$this->addMessage($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidNumberOfDays'), FlashMessage::ERROR);
|
||||
}
|
||||
return $validData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save additional field in task
|
||||
*
|
||||
* @param array $submittedData Contains data submitted by the user
|
||||
* @param \TYPO3\CMS\Scheduler\Task\AbstractTask $task Reference to the current task object
|
||||
*/
|
||||
public function saveAdditionalFields(array $submittedData, AbstractTask $task)
|
||||
{
|
||||
$task->allTables = $submittedData['scheduler_tableGarbageCollection_allTables'] === 'on';
|
||||
$task->table = $submittedData['scheduler_tableGarbageCollection_table'];
|
||||
$task->numberOfDays = (int)$submittedData['scheduler_tableGarbageCollection_numberOfDays'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\Task;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Remove old entries from tables.
|
||||
*
|
||||
* This task deletes rows from tables older than the given number of days.
|
||||
*
|
||||
* Available tables must be registered in
|
||||
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']
|
||||
* See ext_localconf.php of scheduler extension for an example
|
||||
* @internal This class is a specific scheduler task implementation is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class TableGarbageCollectionTask extends AbstractTask
|
||||
{
|
||||
/**
|
||||
* @var bool True if all tables should be cleaned up
|
||||
*/
|
||||
public $allTables = false;
|
||||
|
||||
/**
|
||||
* @var int Number of days
|
||||
*/
|
||||
public $numberOfDays = 180;
|
||||
|
||||
/**
|
||||
* @var string Table to clean up
|
||||
*/
|
||||
public $table = '';
|
||||
|
||||
/**
|
||||
* Execute garbage collection, called by scheduler.
|
||||
*
|
||||
* @throws \RuntimeException If configured table was not cleaned up
|
||||
* @return bool TRUE if task run was successful
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$tableConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][self::class]['options']['tables'];
|
||||
$tableHandled = false;
|
||||
foreach ($tableConfigurations as $tableName => $configuration) {
|
||||
if ($this->allTables || $tableName === $this->table) {
|
||||
$this->handleTable($tableName, $configuration);
|
||||
$tableHandled = true;
|
||||
}
|
||||
}
|
||||
if (!$tableHandled) {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: ' . $this->table . ' does not exist in configuration', 1308354399);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute clean up of a specific table
|
||||
*
|
||||
* @throws \RuntimeException If table configuration is broken
|
||||
* @param string $table The table to handle
|
||||
* @param array $configuration Clean up configuration
|
||||
* @return bool TRUE if cleanup was successful
|
||||
*/
|
||||
protected function handleTable($table, array $configuration)
|
||||
{
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
|
||||
$queryBuilder->delete($table);
|
||||
if (!empty($configuration['expireField'])) {
|
||||
$field = $configuration['expireField'];
|
||||
$dateLimit = $GLOBALS['EXEC_TIME'];
|
||||
// If expire field value is 0, do not delete
|
||||
// Expire field = 0 means no expiration
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->lte($field, $queryBuilder->createNamedParameter($dateLimit, \PDO::PARAM_INT)),
|
||||
$queryBuilder->expr()->gt($field, $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
|
||||
);
|
||||
} elseif (!empty($configuration['dateField'])) {
|
||||
if (!$this->allTables) {
|
||||
$deleteTimestamp = strtotime('-' . $this->numberOfDays . 'days');
|
||||
} else {
|
||||
if (!isset($configuration['expirePeriod'])) {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: No expirePeriod defined for table ' . $table, 1308355095);
|
||||
}
|
||||
$deleteTimestamp = strtotime('-' . $configuration['expirePeriod'] . 'days');
|
||||
}
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->lt(
|
||||
$configuration['dateField'],
|
||||
$queryBuilder->createNamedParameter($deleteTimestamp, \PDO::PARAM_INT)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
throw new \RuntimeException(self::class . ' misconfiguration: Either expireField or dateField must be defined for table ' . $table, 1308355268);
|
||||
}
|
||||
|
||||
try {
|
||||
$queryBuilder->execute();
|
||||
} catch (DBALException $e) {
|
||||
throw new \RuntimeException(self::class . ' failed for table ' . $this->table . ' with error: ' . $e->getMessage(), 1308255491);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the selected table as additional information
|
||||
*
|
||||
* @return string Information to display
|
||||
*/
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
if ($this->allTables) {
|
||||
$message = $GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.additionalInformationAllTables');
|
||||
} else {
|
||||
$message = sprintf($GLOBALS['LANG']->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.additionalInformationTable'), $this->table);
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace TYPO3\CMS\Scheduler\ViewHelpers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
|
||||
|
||||
/**
|
||||
* Create internal link within backend app
|
||||
* @internal
|
||||
*/
|
||||
class ModuleLinkViewHelper extends AbstractViewHelper
|
||||
{
|
||||
use CompileWithRenderStatic;
|
||||
|
||||
/**
|
||||
* Initializes the arguments
|
||||
*/
|
||||
public function initializeArguments()
|
||||
{
|
||||
$this->registerArgument('controller', 'string', 'The "controller" of scheduler. Possible values are "scheduler", "check", "info"', true);
|
||||
$this->registerArgument('action', 'string', 'The action to be called within each controller', true);
|
||||
$this->registerArgument('arguments', 'array', '', false, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render module link with command and arguments
|
||||
*
|
||||
* @param array $arguments
|
||||
* @param \Closure $renderChildrenClosure
|
||||
* @param RenderingContextInterface $renderingContext
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
|
||||
{
|
||||
$moduleArguments = [];
|
||||
$moduleArguments['SET']['function'] = $arguments['controller'];
|
||||
$moduleArguments['CMD'] = $arguments['action'];
|
||||
if (!empty($arguments['arguments'])) {
|
||||
$moduleArguments['tx_scheduler'] = $arguments['arguments'];
|
||||
}
|
||||
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
return (string)$uriBuilder->buildUriFromRoute('system_txschedulerM1', $moduleArguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Commands to be executed by typo3, where the key of the array
|
||||
* is the name of the command (to be called as the first argument after typo3).
|
||||
* Required parameter is the "class" of the command which needs to be a subclass
|
||||
* of Symfony/Console/Command.
|
||||
*/
|
||||
return [
|
||||
'scheduler:run' => [
|
||||
'class' => \TYPO3\CMS\Scheduler\Command\SchedulerCommand::class,
|
||||
// command must not be schedulable, otherwise we'll get an endless recursion
|
||||
'schedulable' => false,
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
return [
|
||||
'ctrl' => [
|
||||
'label' => 'groupName',
|
||||
'tstamp' => 'tstamp',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group',
|
||||
'crdate' => 'crdate',
|
||||
'cruser_id' => 'cruser_id',
|
||||
'delete' => 'deleted',
|
||||
'sortby' => 'sorting',
|
||||
'typeicon_classes' => [
|
||||
'default' => 'mimetypes-x-tx_scheduler_task_group'
|
||||
],
|
||||
'adminOnly' => true, // Only admin users can edit
|
||||
'rootLevel' => 1,
|
||||
'enablecolumns' => [
|
||||
'disabled' => 'hidden'
|
||||
],
|
||||
'searchFields' => 'groupName'
|
||||
],
|
||||
'interface' => [
|
||||
'showRecordFieldList' => 'hidden,groupName'
|
||||
],
|
||||
'columns' => [
|
||||
'groupName' => [
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.groupName',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'size' => 35,
|
||||
'max' => 80,
|
||||
'eval' => 'required,unique,trim',
|
||||
'softref' => 'substitute'
|
||||
]
|
||||
],
|
||||
'description' => [
|
||||
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_tca.xlf:tx_scheduler_task_group.description',
|
||||
'config' => [
|
||||
'type' => 'text'
|
||||
],
|
||||
],
|
||||
'hidden' => [
|
||||
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.enabled',
|
||||
'exclude' => true,
|
||||
'config' => [
|
||||
'type' => 'check',
|
||||
'renderType' => 'checkboxToggle',
|
||||
'default' => 0,
|
||||
'items' => [
|
||||
[
|
||||
0 => '',
|
||||
1 => '',
|
||||
'invertStateDisplay' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'types' => [
|
||||
'1' => [
|
||||
'showitem' => '
|
||||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
|
||||
groupName,
|
||||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
|
||||
hidden,
|
||||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:notes,
|
||||
description,
|
||||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:extended,
|
||||
',
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
# this is file .gitignore
|
||||
|
||||
# ignore everything in this directory
|
||||
_make/*
|
||||
|
||||
# but do not ignore this file
|
||||
!_not_versioned/.gitignore
|
||||
@@ -0,0 +1,147 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _backend-module:
|
||||
|
||||
The backend module
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
.. _setup-check-screen:
|
||||
|
||||
The setup check screen
|
||||
""""""""""""""""""""""
|
||||
|
||||
This screen has already been mentioned in the :ref:`Installation chapter <setup-check>`.
|
||||
It is mostly useful when setting up the Scheduler, as it indicates
|
||||
whether the CLI script is executable or not. When everything is
|
||||
running fine, it contains mostly one useful piece of information: when
|
||||
the last run took place, when it ended and whether it was started
|
||||
manually (i.e. from the BE module) or automatically (i.e. from the
|
||||
command line).
|
||||
|
||||
|
||||
.. _information-screen:
|
||||
|
||||
The information screen
|
||||
""""""""""""""""""""""
|
||||
|
||||
This screen shows a list of all available tasks in the current TYPO3
|
||||
installation. When the Scheduler has just been installed, this will be
|
||||
limited to the two base tasks provided by the extension. The screen
|
||||
shows the name and description of the task. Clicking on the "Add" icon
|
||||
on the right side of each row will open up the new task registration
|
||||
screen, with the task class already predefined.
|
||||
|
||||
.. figure:: ../../Images/InformationScreen.png
|
||||
:alt: Scheduler information screen
|
||||
|
||||
List of available tasks in the Scheduler's information screen
|
||||
|
||||
|
||||
.. _scheduled-tasks-screen:
|
||||
|
||||
The scheduled tasks screen
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
This is the main screen when administering tasks. At first it will be
|
||||
empty and just offer a link to add a new task. When such registered
|
||||
tasks exists, this screen will show a list with various pieces of
|
||||
information.
|
||||
|
||||
|
||||
.. figure:: ../../Images/BackendModuleMainView.png
|
||||
:alt: Scheduler main screen
|
||||
|
||||
Main screen of the Scheduler BE module
|
||||
|
||||
|
||||
Disabled tasks have a gray label sign near the task name. A disabled task is a
|
||||
task that will not be run automatically by the command-line script,
|
||||
but may still be executed from the BE module.
|
||||
|
||||
A late task will appear with an orange label sign near the task name:
|
||||
|
||||
.. figure:: ../../Images/LateTask.png
|
||||
:alt: A late task in the Scheduler main screen
|
||||
|
||||
A late task in the main screen of the Scheduler BE module
|
||||
|
||||
|
||||
The task list can be sorted by clicking the column label. With every click it
|
||||
switches between ascending and descending order of the items of the associated column.
|
||||
|
||||
The table at the center of the above screenshot shows the
|
||||
following:
|
||||
|
||||
- The first column contains checkboxes. Clicking on a checkbox will
|
||||
select that particular scheduled task for immediate execution.
|
||||
Clicking on the icon at the top of the column will toggle all
|
||||
checkboxes. To execute the selected tasks, click on the "Execute
|
||||
selected tasks" button. Read more in "Manually executing a task"
|
||||
below.
|
||||
|
||||
- The second column simply displays the id of the task.
|
||||
|
||||
- The third column contains the name of the task, the extension it is
|
||||
coming from and any additional information specific to the task (for
|
||||
example, the e-mail to which the "test" task will send a message or
|
||||
the sleep duration of the "sleep" task). It also show a summary of the
|
||||
task's status with an icon.
|
||||
|
||||
- The fourth column shows whether the task is recurring or will run only
|
||||
a single time.
|
||||
|
||||
- The fifth column shows the frequency.
|
||||
|
||||
- The sixth columns indicates whether parallel executions are allowed
|
||||
or not.
|
||||
|
||||
- The seventh column shows the last execution time and indicates whether
|
||||
the task was launched manually or was run via the command-line script
|
||||
(cron).
|
||||
|
||||
- The eighth column shows the planned execution time. If the task is
|
||||
overdue, the time will show up in bold, red numbers. A task may have
|
||||
no future execution date if it has reached its end date, if it was
|
||||
meant to run a single time and that execution is done, or if the task
|
||||
is disabled. The next execution time is also hidden for running tasks,
|
||||
as this information makes no sense at that point in time.
|
||||
|
||||
- The last column contains possible actions, mainly editing, disable or
|
||||
deleting a task. There are also buttons for running the task on the
|
||||
next cron job or run it directly.
|
||||
The actions will be unavailable for a task that is currently running,
|
||||
as it is unwise to edit or delete it a task in such a case. Instead a
|
||||
running task will display a "stop" button (see "Stopping a task" below).
|
||||
|
||||
|
||||
|
||||
Note that all dates and times are displayed in the server's time zone.
|
||||
The server time appears at the bottom of the screen.
|
||||
|
||||
At the top of the screen is a link to add a new task.
|
||||
If there are a lot of tasks that appear late,
|
||||
consider changing the frequency at which the cron job is running (see
|
||||
"Choosing a frequency" above).
|
||||
|
||||
Occasionally the following display may appear:
|
||||
|
||||
.. figure:: ../../Images/MissingTaskClass.png
|
||||
:alt: A broken task
|
||||
|
||||
A scheduled task missing its corresponding class
|
||||
|
||||
|
||||
This will typically happen when a task provided by some
|
||||
extension was registered, then the extension was uninstalled, but the
|
||||
task was not deleted beforehand. In such a case, this task stays but
|
||||
the Scheduler doesn't know how to handle it anymore. The solution is
|
||||
either to install the related extension again or delete the registered
|
||||
task.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _deleting-a-task:
|
||||
|
||||
Deleting a task
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
When choosing to delete a task, a pop-up window will appear requesting
|
||||
confirmation. If confirmed, the deletion is final. There is no
|
||||
"deleted" flag in the Scheduler's table, deleted task registrations
|
||||
cannot be recovered.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _adding-editing-task:
|
||||
|
||||
Adding or editing a task
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When adding or editing a task, the following form will show up:
|
||||
|
||||
.. figure:: ../../Images/AddingATask.png
|
||||
:alt: Add task form
|
||||
|
||||
Adding a new scheduled task
|
||||
|
||||
|
||||
Some fields require additional explanations (inline help is
|
||||
available by moving the mouse over the field labels):
|
||||
|
||||
- A disabled task will be skipped by the command-line script. It may
|
||||
still be launched manually, as described above.
|
||||
|
||||
- The class selector is available only when adding a new task. The class
|
||||
cannot be changed when editing a task, since there's no sense in that.
|
||||
|
||||
.. figure:: ../../Images/TaskConfigurationSelectClass.png
|
||||
:alt: Select a class
|
||||
|
||||
Select the class of the scheduled task
|
||||
|
||||
|
||||
- A task must have a start date. The end date is not mandatory, though.
|
||||
A task without end date will run forever. Dates and times must be
|
||||
entered in the server's time zone. The server's time appears at the
|
||||
bottom of the form.
|
||||
|
||||
- The frequency needs be entered only for recurring tasks. It can be
|
||||
either a number of seconds or a cron-like command. Since TYPO3 4.5 the
|
||||
Scheduler supports the full cron syntax, including ranges, steps and
|
||||
keywords like :code:`@weekly` . For more information on cron syntax,
|
||||
see http://en.wikipedia.org/wiki/CRON\_expression.
|
||||
|
||||
- Parallel executions are denied by default (see "Tasks execution"
|
||||
above). They must be allowed explicitly.
|
||||
|
||||
- At the bottom of the form (highlighted area) there may be one or more
|
||||
additional fields. Those fields are specific to each task and will
|
||||
change when a different class is selected.
|
||||
|
||||
If there are some input errors, the form will reload upon submission
|
||||
and display the relevant error messages. When the input is finished
|
||||
and correct, the view switches back to the list view and displays a
|
||||
confirmation message.
|
||||
|
||||
.. figure:: ../../Images/InputValidation.png
|
||||
:alt: Input validation
|
||||
|
||||
Input validation failed when adding a new scheduled task or editing an existing one
|
||||
|
||||
|
||||
.. figure:: ../../Images/InputValidationOk.png
|
||||
:alt: Input validation OK
|
||||
|
||||
Input validation succeeded when adding a new scheduled task or editing an existing one
|
||||
|
||||
|
||||
If an error occurs when validating a cron definition, the
|
||||
Scheduler's built-in cron parser tries to provide an explanation about
|
||||
what's wrong.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _executing-a-task-on-next-cronjob:
|
||||
|
||||
Executing a task on next cron job
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is possible to manually set the next execution time of one or more
|
||||
registered tasks from the BE module to the current time.
|
||||
The selected tasks will then be running on the next cron job.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _grouping-tasks:
|
||||
|
||||
Grouping tasks together
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In case of a high number of different tasks, it may be useful to visually group similar tasks together:
|
||||
|
||||
.. figure:: ../../Images/GroupedTasks.png
|
||||
:alt: Overview of tasks with grouped
|
||||
|
||||
Grouping related tasks together
|
||||
|
||||
|
||||
Scheduler task groups are records stored on the root page (pid=0). They may be created, edited and sorted with Web > List:
|
||||
|
||||
.. figure:: ../../Images/GroupRecords.png
|
||||
:alt: Task group records
|
||||
|
||||
Management of scheduler task groups
|
||||
|
||||
|
||||
It is also possible to create a new task group from within the edit task form whith clicking on the `+` icon next to the
|
||||
task group select box.
|
||||
|
||||
Individual tasks may then be edited as usual and associated to a given scheduler task group.
|
||||
|
||||
The individual groups can be sorted independent of each other by clicking the column label.
|
||||
With every click it switches between ascending and descending order of the items of the associated column
|
||||
in the corresponding group.
|
||||
|
||||
.. note::
|
||||
Tasks inside a hidden group are neither automatically executed nor displayed in the scheduler module.
|
||||
@@ -0,0 +1,33 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _administration:
|
||||
|
||||
======================
|
||||
Backend administration
|
||||
======================
|
||||
|
||||
The Scheduler provides a BE module to manage tasks. It provides three
|
||||
screens: a setup check, an information screen and the main (default)
|
||||
one for actually managing the tasks.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:titlesonly:
|
||||
:glob:
|
||||
|
||||
BackendModule/Index
|
||||
EditTask/Index
|
||||
DeleteTask/Index
|
||||
GroupTask/Index
|
||||
StopTask/Index
|
||||
ManualExecution/Index
|
||||
ExecuteNextCronjob/Index
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _manually-executing-a-task:
|
||||
|
||||
Manually executing a task
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As mentioned above, it is possible to manually execute one or more
|
||||
registered tasks from the BE module. When the selected tasks have
|
||||
finished running, feedback is given about success or failure of each
|
||||
task's run. There's one important thing to note: if the task was
|
||||
overdue, a new execution date will be calculated. If the task was
|
||||
**not** overdue, the previously calculated next execution date stays.
|
||||
|
||||
A single task can be started by pressing the appropriate button in
|
||||
the row of the task. Multiple tasks can be started by selecting the
|
||||
checkboxes on the left side of the task row and then pressing the
|
||||
appropriate button beneath the task list.
|
||||
|
||||
There is one button for directly starting the task and another button
|
||||
for starting the task with the next cron job.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _stopping-a-task:
|
||||
|
||||
Stopping a task
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
As was explained above, a task is marked as being executed while it is
|
||||
running. If the process running the task crashes or is killed for some
|
||||
reason, the task will stay marked as being executed. This will be
|
||||
cleaned up automatically according to the maximum lifetime parameter
|
||||
(also described previously), but this may be inconvenient in some
|
||||
circumstances.
|
||||
|
||||
.. figure:: ../../Images/StoppingATask.png
|
||||
:alt: Stopping a task
|
||||
|
||||
Stopping a running task from the main screen
|
||||
|
||||
|
||||
In such cases it is possible to use the "stop" button to
|
||||
clean up marked executions for the given task. This will make it
|
||||
possible to run this task again. However please note that this will
|
||||
**not** kill a corresponding still running or continuously hanging
|
||||
process.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _technical-background:
|
||||
|
||||
Appendix A – Technical Background
|
||||
---------------------------------
|
||||
|
||||
This section aims to give some more in-depth information about what
|
||||
happens behind the scene in the Scheduler.
|
||||
|
||||
When a task is registered with the Scheduler, an instance of the task
|
||||
class is created and stored in a database record as a serialized
|
||||
object. The database record itself contains additional information
|
||||
about the registration, mostly about past and future executions. The
|
||||
theory is that all the information that is really proper to the task
|
||||
should be defined as member variables of the task class and is thus
|
||||
encapsulated inside the task object. The information which relates to
|
||||
executing a registered task is stored in the Scheduler's database
|
||||
table.
|
||||
|
||||
That being said, a task also contains information about its execution.
|
||||
Indeed each task class has an instance of
|
||||
:code:`TYPO3\CMS\Scheduler\Execution` as a member variable, which contains
|
||||
information such as start and end date and is used to calculate the
|
||||
next execution date.
|
||||
|
||||
When a task is running, its start time is stored into an array, which
|
||||
is serialized and stored in the corresponding database record. If
|
||||
several executions are running at the same time, the array will
|
||||
contain several timestamps. Thus the "serialized\_executions" field
|
||||
actually contains an array of integers and not serialized instances of
|
||||
:code:`TYPO3\CMS\Scheduler\Execution` objects.
|
||||
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _creating-tasks:
|
||||
|
||||
Creating a new task
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
.. _creating-task-class:
|
||||
|
||||
Creating the task class
|
||||
"""""""""""""""""""""""
|
||||
|
||||
This is the heart of a task. It is the code that actually does what
|
||||
the task is supposed to do. A task is represented by a PHP class that
|
||||
extends the base task class:
|
||||
|
||||
::
|
||||
|
||||
namespace Vendor\Extension\Task;
|
||||
class MyTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask {
|
||||
public function execute() {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
The only method that **must** be implemented is the :code:`execute()`
|
||||
method which is expected to perform the task logic. The method must
|
||||
return a boolean value of "true" if the execution was successful, and
|
||||
"false" otherwise. It may also throw exceptions to report more
|
||||
precisely on errors that may happen during the run. The message of the
|
||||
exception is stored in the database so that it can be displayed in the
|
||||
BE module.
|
||||
|
||||
A method called :code:`getAdditionalInformation()` may optionally be
|
||||
implemented too. It is called by the BE module to display additional
|
||||
information about a registered task in the list view. This is quite
|
||||
convenient if a task class may be registered several times with
|
||||
different parameters, in order to tell apart the various
|
||||
registrations.
|
||||
|
||||
If the task class uses a constructor **it is absolutely necessary** to
|
||||
include a call to the parent's constructor, most probably as the first
|
||||
thing inside the constructor, unless there are some very special
|
||||
reasons not to:
|
||||
|
||||
::
|
||||
|
||||
namespace Vendor\Extension\Task;
|
||||
class MyTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
// Your code here...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.. _using-additional-fields:
|
||||
|
||||
Using additional fields
|
||||
"""""""""""""""""""""""
|
||||
|
||||
A task class may require additional parameters to be executed
|
||||
properly. For example, the "test" task is designed to send an e-mail,
|
||||
but the e-mail address must be defined at the time when the task class
|
||||
is actually registered. This appears as an additional field in the
|
||||
task registration form. The Scheduler provides an interface which may
|
||||
be implemented to provide such fields. It is comprised of three
|
||||
methods, which must all be implemented (since it's an interface).
|
||||
|
||||
The implementation may be done in a separate class or in the same
|
||||
class as the task. It may look something like:
|
||||
|
||||
::
|
||||
|
||||
namespace Vendor\Extension\Task;
|
||||
class MyTaskAdditionalFieldProvider implements \TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface {
|
||||
public function getAdditionalFields(array &$taskInfo, $task, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject) {
|
||||
...
|
||||
}
|
||||
public function validateAdditionalFields(array &$submittedData, \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController $parentObject) {
|
||||
...
|
||||
}
|
||||
public function saveAdditionalFields(array $submittedData, \TYPO3\CMS\Scheduler\Task\AbstractTask $task) {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
The three methods of the interface are described below:
|
||||
|
||||
|
||||
.. _getadditionalfields:
|
||||
|
||||
getAdditionalFields
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. container:: table-row
|
||||
|
||||
Method
|
||||
getAdditionalFields
|
||||
|
||||
Purpose
|
||||
This method returns the list of fields to display in the editing form.
|
||||
|
||||
This list of fields is a 2-dimensional array. The first dimension uses
|
||||
the ID of the field (as used in the "id" attribute of the field tag).
|
||||
Then for each field, there must be the HTML code to render the field,
|
||||
the label of the field, the key and the label for the context-
|
||||
sensitive help (CSH).
|
||||
|
||||
All this is documented in the
|
||||
:code:`\TYPO3\CMS\Scheduler\AdditionalFieldProviderInterface` interface and can be
|
||||
seen in action in the existing task classes.
|
||||
|
||||
Parameters
|
||||
:code:`$taskInfo` : an array containing the information about the
|
||||
current task. May be modified inside this method to set default
|
||||
values, for example.
|
||||
|
||||
:code:`$task` : the current task object (when editing; when adding,
|
||||
"null" is passed to the method).
|
||||
|
||||
:code:`$parentObject` : a back-reference to the calling BE module's
|
||||
object.
|
||||
|
||||
|
||||
|
||||
.. _validateadditionalfields:
|
||||
|
||||
validateAdditionalFields
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. container:: table-row
|
||||
|
||||
Method
|
||||
validateAdditionalFields
|
||||
|
||||
Purpose
|
||||
This method is called to validate the values that were input in the
|
||||
additional fields provided by the specific task. It is expected to
|
||||
return false if any of the fields contained errors, true otherwise.
|
||||
|
||||
The method should use the parent object's :code:`addMessage()` method
|
||||
to output messages about validation errors.
|
||||
|
||||
Parameters
|
||||
:code:`$submittedData` : array of values from the submitted form. May
|
||||
be modified inside the method.
|
||||
|
||||
:code:`$parentObject` : a back-reference to the calling BE module's
|
||||
object.
|
||||
|
||||
|
||||
|
||||
.. _saveadditionalfields:
|
||||
|
||||
saveAdditionalFields
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. container:: table-row
|
||||
|
||||
Method
|
||||
saveAdditionalFields
|
||||
|
||||
Purpose
|
||||
This method is used to store the values contained in the additional
|
||||
fields. The simplest method is to simply assign them to member
|
||||
variables, so that they will be stored along the serialized task
|
||||
object in the database (see :ref:`technical-background`).
|
||||
|
||||
Parameters
|
||||
:code:`$submittedData` : array of values from the submitted form,
|
||||
after validation.
|
||||
|
||||
:code:`$task` : the current task object.
|
||||
|
||||
|
||||
|
||||
.. _security-issues:
|
||||
|
||||
Security issues
|
||||
"""""""""""""""
|
||||
|
||||
The handling of additional fields is entirely up to the developer, in
|
||||
particular as far as validation and display is concerned. Thus it is
|
||||
up to you to make sure that the data entered in your additional fields
|
||||
does not contain any harmful input (for example, XSS).
|
||||
|
||||
The safest way to proceed is to very strictly handle input. For
|
||||
example, if all you expect is some integer number, pass the value
|
||||
through a typecast to (int). If it's a string, but you don't
|
||||
expect any markup in it, run it through :code:`strip\_tags()` . Such
|
||||
simple measures can filter most of the harmful code.
|
||||
|
||||
One more thing to mind. In the :code:`getAdditionalFields()` method,
|
||||
the whole form element must be assembled. If the input didn't
|
||||
validate, you may well want to insert it in the field again, so that
|
||||
the user can correct it. Harmful code entered at that point may be
|
||||
executed. It is thus very strongly recommended to use
|
||||
:code:`htmlspecialchars()` in this case. Example:
|
||||
|
||||
::
|
||||
|
||||
$fieldCode = '<input type="text" class="form-control" name="tx_scheduler[email]" id="' . $fieldID . '" value="' . htmlspecialchars($taskInfo['email']) . '" size="30">';
|
||||
|
||||
|
||||
.. _naming-of-additional-fields:
|
||||
|
||||
Naming of additional fields
|
||||
"""""""""""""""""""""""""""
|
||||
|
||||
The Scheduler expects all fields in the add/edit form to be named
|
||||
:code:`tx\_scheduler[...]` . Values from fields that don't follow this
|
||||
pattern will **not** appear in the :code:`$submittedData` array.
|
||||
|
||||
It is also important to think about using field names that will not
|
||||
create conflicts with other existing fields or future fields that may
|
||||
happen. Since there is no name-spacing mechanism available, it is up
|
||||
to each developer to choose proper names. A very good practice is to
|
||||
prepend the extension's key to the additional fields names in order to
|
||||
guarantee the unicity of those names.
|
||||
|
||||
|
||||
.. _bad-examples:
|
||||
|
||||
Bad examples
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Here are some examples of bad additional fields names:
|
||||
|
||||
::
|
||||
|
||||
foo
|
||||
tx_scheduler[name]
|
||||
|
||||
The first name doesn't use the syntax and thus will not be handled by
|
||||
the Scheduler. The second one respects that syntax, but the name is
|
||||
too generic and may cause conflicts with other fields.
|
||||
|
||||
|
||||
.. _good-examples:
|
||||
|
||||
Good examples
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Here are good examples of additional fields names:
|
||||
|
||||
::
|
||||
|
||||
tx_scheduler[myextension_myvalue]
|
||||
tx_scheduler[myextension][myvalue]
|
||||
|
||||
In both cases the proper syntax is used as well as the extension key,
|
||||
making it very unlikely that a naming conflict could happen.
|
||||
|
||||
|
||||
.. _declare-task-class:
|
||||
|
||||
Declaring the task class
|
||||
""""""""""""""""""""""""
|
||||
|
||||
As a last step, the task class must be declared so the Scheduler knows
|
||||
of its existence. The declaration must be placed in the
|
||||
:code:`ext_localconf.php` file of the extension that provides the
|
||||
task. Let's look at one of the base classes declaration as an example:
|
||||
|
||||
::
|
||||
|
||||
// Add caching framework garbage collection task
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\CachingFrameworkGarbageCollectionTask::class] = array(
|
||||
'extension' => $_EXTKEY,
|
||||
'title' => 'LLL:EXT:' . $_EXTKEY . '/locallang.xlf:cachingFrameworkGarbageCollection.name',
|
||||
'description' => 'LLL:EXT:' . $_EXTKEY . '/locallang.xlf:cachingFrameworkGarbageCollection.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\CachingFrameworkGarbageCollectionAdditionalFieldProvider::class
|
||||
);
|
||||
|
||||
The registration is made in the array
|
||||
:code:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']`.
|
||||
The key used corresponds to the task class. Then come 4 parameters:
|
||||
|
||||
- **extension** : the key of the extension that provides the class. This
|
||||
is used for informational purposes.
|
||||
|
||||
- **title** : the name of the task. May use localized labels.
|
||||
|
||||
- **description** : a text describing the task. It is displayed in the
|
||||
information screen of the BE module. May use localized labels.
|
||||
|
||||
- **additionalFields** : the name of the class that provides the
|
||||
additional fields. Leave empty if task class does not require any such
|
||||
fields.
|
||||
|
||||
|
||||
.. _serialized-objects:
|
||||
|
||||
Working with serialized objects
|
||||
"""""""""""""""""""""""""""""""
|
||||
|
||||
When a task is registered with the Scheduler the corresponding object
|
||||
instance is serialized and stored in the database (see Appendix A for
|
||||
more details). This is not a very common practice. There are
|
||||
advantages but also some pitfalls, so please read this section
|
||||
carefully.
|
||||
|
||||
A serialized object may happen to be "out of sync" with its class if
|
||||
the class changes some of its variables or methods. If a variable's
|
||||
name is changed or if variables are added or removed, the serialized
|
||||
object will not reflect these changes. The same goes if a method is
|
||||
renamed, added or deleted. Problems will also arise if the number or
|
||||
order of arguments used to call a method are changed. In such cases
|
||||
weird errors may appear, which can be very difficult to track. The
|
||||
only solution is to delete the registered task and register it anew.
|
||||
|
||||
To minimize such risks it is worth to consider implementing the
|
||||
business logic in a separate class, so that the task class itself
|
||||
changes as little as possible. The :code:`execute()` should be as
|
||||
simple as possible. Consider the following:
|
||||
|
||||
::
|
||||
|
||||
class MyTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask {
|
||||
public function execute() {
|
||||
$businessLogic = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\Vendor\Extension\BusinessLogic::class);
|
||||
$businessLogic->run(arg1, arg2, …);
|
||||
}
|
||||
}
|
||||
|
||||
In such a setup the :code:`execute()` is kept to the strict minimum
|
||||
and the operations themselves are handled by a separate class.
|
||||
|
||||
Also remember that the constructor is **not** called when
|
||||
unserializing an object. If some operations need to be run upon
|
||||
unserialization, implement a :code:`__wakeup()` method instead.
|
||||
|
||||
|
||||
.. _save-task-state:
|
||||
|
||||
Saving a task's state
|
||||
"""""""""""""""""""""
|
||||
|
||||
The task's state is saved automatically at the **start** of its
|
||||
execution. If you need to save a task's state at some point **during**
|
||||
its execution, you can simply call the task's own :code:`save()`
|
||||
method.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _developer-guide:
|
||||
|
||||
Developer's Guide
|
||||
-----------------
|
||||
|
||||
The Scheduler makes it very easy to create a new task class.
|
||||
Furthermore the tasks packaged with this extension provide
|
||||
a good basis to learn by example.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:titlesonly:
|
||||
:glob:
|
||||
|
||||
CreatingTasks/Index
|
||||
SchedulerApi/Index
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _scheduler-api:
|
||||
|
||||
Scheduler API
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
It is possible to refer to the Scheduler from other extensions. Once a
|
||||
:code:`\TYPO3\CMS\Scheduler\Scheduler` object has been instantiated all of its public
|
||||
methods can be used. The PHPdoc of the methods should be enough to
|
||||
understand what each is to be used for. It would be excessive to
|
||||
describe them all here.
|
||||
|
||||
However a few deserve a special mention:
|
||||
|
||||
- :code:`fetchTask()` : this method is used to fetch a registered task
|
||||
from the database given an id. If no id is given, it will return the
|
||||
next due task. The return value is the unserialized task object.
|
||||
|
||||
- :code:`fetchTaskRecord()` : is also used to retrieve a registered task
|
||||
from the database, but it returns the record corresponding to the task
|
||||
registration and not the task object itself. It is not designed fetch
|
||||
the next due task.
|
||||
|
||||
- :code:`fetchTasksWithCondition()` : can be used to retrieve one or
|
||||
more registered tasks, that fit a given SQL condition. It returns an
|
||||
array containing all the records of the matching tasks registrations.
|
||||
|
||||
These are the main methods that will be used from outside the
|
||||
Scheduler as they can retrieve registered tasks from the database.
|
||||
When a task has been fetched, all public methods from the
|
||||
:code:`\TYPO3\CMS\Scheduler\Task\AbstractTask` class can be used.
|
||||
|
||||
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,21 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. This is 'Includes.txt'. It is included at the very top of each and
|
||||
every ReST source file in this documentation project (= manual).
|
||||
|
||||
|
||||
.. ==================================================
|
||||
.. DEFINE SOME TEXTROLES
|
||||
.. --------------------------------------------------
|
||||
|
||||
.. role:: typoscript(code)
|
||||
|
||||
.. role:: ts(typoscript)
|
||||
:class: typoscript
|
||||
|
||||
.. role:: php(code)
|
||||
|
||||
.. highlight:: php
|
||||
@@ -0,0 +1,67 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: Includes.txt
|
||||
|
||||
|
||||
.. _start:
|
||||
|
||||
=========
|
||||
Scheduler
|
||||
=========
|
||||
|
||||
:Extension key:
|
||||
scheduler
|
||||
|
||||
:Version:
|
||||
|release|
|
||||
|
||||
:Language:
|
||||
en
|
||||
|
||||
:Description:
|
||||
Management of scheduled tasks inside the TYPO3 Backend.
|
||||
|
||||
:Keywords:
|
||||
TYPO3 scheduler
|
||||
|
||||
:Copyright:
|
||||
2000-2021
|
||||
|
||||
:Author:
|
||||
TYPO3 CMS Core Development Team
|
||||
|
||||
:License:
|
||||
Open Content License available from `www.opencontent.org/opl.shtml
|
||||
<http://www.opencontent.org/opl.shtml>`_
|
||||
|
||||
:Rendered:
|
||||
|today|
|
||||
|
||||
The content of this document is related to TYPO3,
|
||||
|
||||
a GNU/GPL CMS/Framework available from `www.typo3.org
|
||||
<http://www.typo3.org/>`_
|
||||
|
||||
|
||||
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:titlesonly:
|
||||
:glob:
|
||||
|
||||
Introduction/Index
|
||||
Overview/Index
|
||||
Installation/Index
|
||||
Administration/Index
|
||||
Installation/SchedulerShellScript/Index
|
||||
DevelopersGuide/Index
|
||||
KnownProblems/Index
|
||||
AppendixA/Index
|
||||
Targets
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _base-tasks:
|
||||
|
||||
The base tasks
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The Scheduler comes by default with several tasks:
|
||||
|
||||
- **Caching framework garbage collection** : some cache backends do not
|
||||
have an automatic garbage collection process. For these it is useful
|
||||
to run this Scheduler task to regularly free some space.
|
||||
|
||||
- **Fileadmin garbage collection** : empties :file:`_recycler_` folders in
|
||||
the fileadmin.
|
||||
|
||||
- **Table garbage collection** : cleans up old records from any table in
|
||||
the database. See related section below for more information on
|
||||
configuration.
|
||||
|
||||
|
||||
.. _table-garbage-collection-task:
|
||||
|
||||
Table garbage collection task
|
||||
"""""""""""""""""""""""""""""
|
||||
|
||||
The table garbage collection task can take a more elaborate
|
||||
configuration which is detailed below.
|
||||
|
||||
The task can be registered to clean up a particular table, in which
|
||||
case you simply choose the table and the minimum age of the records to
|
||||
delete from the task configuration screen.
|
||||
|
||||
.. figure:: ../../Images/TableGarbageCollectionTaskConfiguration.png
|
||||
:alt: Table Garbage Collection task configuration
|
||||
|
||||
Configuring the table garbage collection task
|
||||
|
||||
|
||||
It's also possible to clean up all configured table by
|
||||
checking the "Clean all available tables" box. The configuration for
|
||||
the tables to clean up is stored in
|
||||
:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']`.
|
||||
The syntax is the following:
|
||||
|
||||
- option :php:`expireField` can be used to point to a table field
|
||||
containing an expiry timestamp. This timestamp will then be used to
|
||||
decide whether a record has expired or not. If its timestamp is in the
|
||||
past, the record will be deleted.
|
||||
|
||||
- if a table has no expiry field, one can use a combination of a date
|
||||
field and an expiry period to decide which records should be deleted.
|
||||
The corresponding options are :php:`dateField` and :php:`expirePeriod`.
|
||||
The expiry period is expressed in days.
|
||||
|
||||
|
||||
.. _table-garbage-collection-task-example:
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables'] = array(
|
||||
'tx_realurl_errorlog' => array(
|
||||
'dateField' => 'tstamp',
|
||||
'expirePeriod' => '180'
|
||||
),
|
||||
'tx_realurl_uniqalias' => array(
|
||||
'expireField' => 'expire'
|
||||
)
|
||||
);
|
||||
|
||||
The first part of the configuration indicates that records older than
|
||||
180 days should be removed from table :code:`tx_realurl_errorlog` ,
|
||||
based on the timestamp field called "tstamp". The second part
|
||||
indicates that old records should be removed from table
|
||||
:code:`tx_realurl_uniqalias` directly based on the field "expire"
|
||||
which contains expiration dates for each record.
|
||||
|
||||
|
||||
.. _other-tasks:
|
||||
|
||||
Other tasks
|
||||
"""""""""""
|
||||
|
||||
More tasks are provided by system extensions, such as the Extension
|
||||
Manager, which defines one for updating the available extensions list.
|
||||
|
||||
The Scheduler also comes with two sample tasks. As explained at the
|
||||
beginning of this chapter, they can be turned off from the extension's
|
||||
configuration options.
|
||||
|
||||
The "test" task just sends a mail to a given e-mail address. The
|
||||
"sleep" task goes to sleep for a given number of seconds. They are not
|
||||
very useful in the everyday running of a web site, but they are nice
|
||||
to have when testing stuff. If you want to make sure that your
|
||||
Scheduler installation is complete, register an instance of the "test"
|
||||
task and wait for the mail to arrive (if it doesn't arrive, check in
|
||||
the Log if the task has indeed run).
|
||||
|
||||
The "sleep" task is mostly useful for testing parallel executions (by
|
||||
creating an instance of the task that goes to sleep for a longer time
|
||||
than the frequency).
|
||||
|
||||
The base tasks are also there to serve as examples for task developers
|
||||
(see "Developer's Guide" below).
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _cron-job:
|
||||
|
||||
Setting up the cron job
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Tasks registered with the Scheduler can be run manually from the BE
|
||||
module. However this is of limited use. To really benefit from the
|
||||
Scheduler, it must be set up on the server to run regularly. The
|
||||
following chapters describe how to set this up on Unix or Unix-like
|
||||
system (including Mac OS X) and on Windows.
|
||||
|
||||
|
||||
.. _frequency:
|
||||
|
||||
Choosing a frequency
|
||||
""""""""""""""""""""
|
||||
|
||||
Whatever system the Scheduler will run on, the first step is to define
|
||||
the frequency at which it should run. The Scheduler script should set
|
||||
up to run pretty often, but not unnecessarily often either. The
|
||||
frequency should be that of the most often running task or some
|
||||
frequency that fits all tasks.
|
||||
|
||||
For example, if you have some tasks running every quarter of an hour
|
||||
and some others running every hour, it is useless to have the
|
||||
Scheduler run every 5 minutes. On the other hand, if you have tasks
|
||||
scheduled to run every 10 minutes and others every 15 minutes, you
|
||||
will want to run the Scheduler every 5 minutes. Indeed, if you run it
|
||||
only at 10-minute intervals, it will run – assuming it is 8 o'clock –
|
||||
at 08:10, 08:20, 08:30, etc.. So the tasks that should run at 08:15
|
||||
will actually run 5 minutes late.
|
||||
|
||||
|
||||
.. _unix-mac:
|
||||
|
||||
On Unix and Mac OS X
|
||||
""""""""""""""""""""
|
||||
|
||||
On such systems the Scheduler must be set up as a cron job. There are
|
||||
several ways to achieve this, although the simplest is probably to add
|
||||
it to some user's crontab. Edit that user's crontab using:
|
||||
|
||||
::
|
||||
|
||||
crontab -e
|
||||
|
||||
and add a line like
|
||||
|
||||
::
|
||||
|
||||
*/15 * * * * /usr/local/bin/php /home/user/www/typo3/sysext/core/bin/typo3 scheduler:run
|
||||
|
||||
Save the modified crontab. Obviously the paths have to be adapted to
|
||||
your system. The above command will call up the Scheduler every 15
|
||||
minutes.
|
||||
|
||||
.. seealso::
|
||||
|
||||
See :ref:`scheduler-shell-script` for more information about
|
||||
calling the scheduler from the command line.
|
||||
|
||||
If you are editing system crontabs (for example :code:`/etc/crontab`
|
||||
and :code:`/etc/cron.d/\*` ), there will be one additional parameter
|
||||
to enter, i.e. the user with which the job should run. Example
|
||||
(additional user parameter in bold):
|
||||
|
||||
::
|
||||
|
||||
*/15 * * * * www /usr/local/bin/php /home/user/www/typo3/sysext/core/bin/typo3 scheduler:run
|
||||
|
||||
This will run the job as user "www".
|
||||
|
||||
If you are not familiar with cron syntax, refer to some Unix
|
||||
administration book or start with the Wikipedia page about it
|
||||
(http://en.wikipedia.org/wiki/Cron).
|
||||
|
||||
|
||||
.. _windows:
|
||||
|
||||
On Windows
|
||||
""""""""""
|
||||
|
||||
On Windows, cron jobs are called "Scheduled tasks" and run with the
|
||||
:code:`schtasks` utility. :code:`SchTasks.exe` performs operations
|
||||
similar to those provided by Scheduled Tasks in the Control Panel. You
|
||||
can use either tool to create, delete, configure, or simply display
|
||||
scheduled tasks.
|
||||
|
||||
Assuming you want to run the TYPO3 Scheduler every 15 minutes, use the
|
||||
following command line to create a new task:
|
||||
|
||||
::
|
||||
|
||||
schtasks /create /sc minute /mo 15 /tn "T3scheduler" /tr "c:\winstaller\php\php.exe c:\winstaller\htdocs\quickstart\typo3\sysext\core\bin\typo3 scheduler:run"
|
||||
|
||||
At task creation you will be prompted to give a password or you can
|
||||
use the :code:`/u` and :code:`/p` switches to provide user and
|
||||
password information. Note that the user must be a member of the
|
||||
Administrators group on the computer where the command will run.
|
||||
|
||||
The full reference for :code:`schtasks` is available at:
|
||||
http://technet.microsoft.com/en-us/library/bb490996.aspx
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _installation:
|
||||
|
||||
====================
|
||||
Installation & setup
|
||||
====================
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:titlesonly:
|
||||
:glob:
|
||||
|
||||
Installing/Index
|
||||
SetupCheck/Index
|
||||
CronJob/Index
|
||||
BaseTasks/Index
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _installing:
|
||||
|
||||
Installing the extension
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The process is pretty straightforward: just install the extension in
|
||||
the Extension Manager. One database table will be created. Once the
|
||||
extension is installed, the following settings are available:
|
||||
|
||||
- **Enable sample tasks** : the Scheduler provides two sample tasks
|
||||
(called "Scheduler test task" and "Scheduler sleep task") which are
|
||||
useful during development and as code examples. However they have
|
||||
little use in real life, so this option makes it possible to turn them
|
||||
off. If disabled, they won't appear in the list of available tasks
|
||||
anymore.
|
||||
|
||||
- **Maximum lifetime** : it may happen that a task crashes while
|
||||
executing. In this case it will stay in a state marked as "running".
|
||||
That may prevent it from being executed again, if parallel executions
|
||||
are denied (see "Tasks execution" above). The maximum lifetime
|
||||
parameter ensures that old executions are removed after a while. The
|
||||
lifetime is expressed in **minutes** . The default is 15 minutes.
|
||||
|
||||
.. figure:: ../../Images/ExtensionConfiguration.png
|
||||
:alt: Extension configuration
|
||||
|
||||
Configuring the extension settings
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _scheduler-shell-script:
|
||||
|
||||
The Scheduler shell script
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Scheduler provides a PHP shell script designed to be run using
|
||||
TYPO3's command-line dispatcher. To try and run that script a first
|
||||
time, type the following command (adapting the path to TYPO3
|
||||
to match your system and add the full path to the php interpreter
|
||||
in front of the command if php is not on the search path):
|
||||
|
||||
::
|
||||
|
||||
/home/user/www/typo3/sysext/core/bin/typo3 scheduler:run
|
||||
/usr/local/bin/php /home/user/www/typo3/sysext/core/bin/typo3 scheduler:run
|
||||
|
||||
If your installation is a composer based instance you can use this command line
|
||||
in your installation directory:
|
||||
|
||||
::
|
||||
|
||||
bin/typo3 scheduler:run
|
||||
/usr/local/bin/php bin/typo3 scheduler:run
|
||||
|
||||
|
||||
.. _options:
|
||||
|
||||
Providing options to the shell script
|
||||
"""""""""""""""""""""""""""""""""""""
|
||||
|
||||
The shell scripts accepts a number of options which can be provided in any
|
||||
order.
|
||||
|
||||
To run a specific scheduler task you need to provide the uid of the task:
|
||||
|
||||
::
|
||||
|
||||
/home/user/www/typo3/sysext/core/bin/typo3 scheduler:run --task=8
|
||||
|
||||
The example will trigger the task with uid 8.
|
||||
|
||||
To run a task even if it is disabled, you need to provide the force option
|
||||
|
||||
::
|
||||
|
||||
/home/user/www/typo3/sysext/core/bin/typo3 scheduler:run --task=8 -f
|
||||
|
||||
This will also run the task with uid 8 if it is disabled.
|
||||
@@ -0,0 +1,31 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _setup-check:
|
||||
|
||||
Checking the setup
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
After installing the Scheduler, go to its BE module and call up the
|
||||
"Setup check" screen which runs a couple of basic checks on your
|
||||
installation. It will probably look something like this:
|
||||
|
||||
.. figure:: ../../Images/SetupCheck.png
|
||||
:alt: Setup check screen
|
||||
|
||||
Checking the setup of the Scheduler
|
||||
|
||||
The first message shows a warning that the Scheduler has never run
|
||||
yet or an information about the last run.
|
||||
|
||||
The second message should normally be okay. If there's an error
|
||||
instead, it means that permissions to execute TYPO3's command-line
|
||||
dispatcher must be checked (this is not strictly related to the
|
||||
Scheduler).
|
||||
|
||||
The third message shows information about the current server time.
|
||||
@@ -0,0 +1,47 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _introduction:
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
|
||||
.. _what-does-it-do:
|
||||
|
||||
What does it do?
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The Scheduler is designed to be the central place to manage all kind
|
||||
of tasks that need to be executed on a regular basis, without needing
|
||||
someone to actually press a button.
|
||||
|
||||
|
||||
.. _screenshots:
|
||||
|
||||
Screenshots
|
||||
^^^^^^^^^^^
|
||||
|
||||
This is a view of the BE module's main screen, where you can see all
|
||||
tasks that are currently registered with the Scheduler.
|
||||
|
||||
.. figure:: ../Images/BackendModuleMainView.png
|
||||
:alt: Scheduler main screen
|
||||
|
||||
Main screen of the Scheduler BE module
|
||||
|
||||
|
||||
.. _credits:
|
||||
|
||||
Credits
|
||||
^^^^^^^
|
||||
|
||||
The Scheduler derives from extension Gabriel, originally developed by
|
||||
Christian Jul Jensen and further developed by Markus Friedrich.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _known-problems:
|
||||
|
||||
Known problems
|
||||
--------------
|
||||
|
||||
The main problem currently is that a running task cannot be killed,
|
||||
because no relation exists to the (cron) process that is running the
|
||||
Scheduler. The process pid could be retrieved, but that may not work
|
||||
on all platforms. And can the process be killed afterwards? Anyway it
|
||||
may not be safe to do that.
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
|
||||
.. _overview:
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
|
||||
.. _why-a-scheduling-tool:
|
||||
|
||||
Why a scheduling tool?
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When running and maintaining a complex system like TYPO3, there are a
|
||||
number of tasks that need to be automated or executed in the small
|
||||
hours, when no one is around to press the button. Indeed quite a few
|
||||
extensions come with some command-line scripts meant to be run
|
||||
regularly using cron jobs. When each of these scripts need their
|
||||
separate entry in the server's crontab, maintenance complexity
|
||||
increases, as well as the cost of migration. Furthermore there's no
|
||||
simple way to keep an overview of and manage these tasks inside TYPO3.
|
||||
|
||||
The Scheduler aims to address this issue.
|
||||
|
||||
|
||||
.. _tasks-management:
|
||||
|
||||
Tasks management
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Scripts can be developed as Scheduler tasks by extending a base class
|
||||
provided by the Scheduler. They can then be registered with the
|
||||
Scheduler. At that point it becomes possible to set up a schedule for
|
||||
them by using the Scheduler's BE module.
|
||||
|
||||
The BE module provides an overview of all scheduled tasks and some
|
||||
indication of their status, e.g. are they currently running or are
|
||||
they late, did something wrong happen during last execution, etc. It
|
||||
is also possible to manually start the execution of tasks from the BE
|
||||
module.
|
||||
|
||||
|
||||
.. _tasks-execution:
|
||||
|
||||
Tasks execution
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The Scheduler provides a command-line tool to be run by TYPO3's
|
||||
command-line dispatcher. Only this script needs to be registered in
|
||||
the server's cron tab for all other recurring tasks to be executed.
|
||||
Indeed every time the Scheduler is launched by the cron daemon, it
|
||||
will look for all tasks that are due (or overdue) and execute them.
|
||||
|
||||
When a task is executed by the Scheduler it is marked as being
|
||||
executed in the corresponding database record (in the field called
|
||||
"serialized\_executions"). When the task has finished running, the
|
||||
execution is removed from the database record. This mechanism makes it
|
||||
possible to know that a given task is currently running and also helps
|
||||
prevent multiple executions. Indeed it may be that a task requires
|
||||
more time to run than the frequency it is set up for. In such a case a
|
||||
new run may be started which is not always desirable. It is possible
|
||||
to deny such parallel (or multiple) executions.
|
||||
|
||||
|
||||
.. _follow-up:
|
||||
|
||||
Follow-up
|
||||
^^^^^^^^^
|
||||
|
||||
Whenever a task starts or ends a message is written to TYPO'3 system
|
||||
log (viewable in SYSTEM > Log). A message is also
|
||||
written when a parallel execution has been blocked. This makes it
|
||||
possible to follow what happens, since the main purpose of the
|
||||
Scheduler is to run things when nobody is watching.
|
||||
|
||||
A task that fails may report on the reasons for failure using
|
||||
exceptions. Such a message will be logged in the Scheduler's database
|
||||
table and will be displayed in the BE module.
|
||||
|
||||
There's no default output to the command-line as scheduled tasks are
|
||||
designed to run in the background.
|
||||
|
||||
|
||||
.. _glossary:
|
||||
|
||||
Glossary
|
||||
^^^^^^^^
|
||||
|
||||
A few terms need to be defined more precisely:
|
||||
|
||||
- **Task** : this word is used quite generally throughout this document,
|
||||
sometimes to cover different meanings. A task is really a piece of
|
||||
code that does a precise task and can be registered with the Scheduler
|
||||
in order to execute that piece of code at a precise time, recurrently
|
||||
or not.
|
||||
|
||||
- **Task class** : this is the type of task. The "test" task is one
|
||||
particular task class. Its function is to send an email. The "sleep"
|
||||
task is another task class.
|
||||
|
||||
- **Registered task** : an instance of a task class that has been
|
||||
registered with the Scheduler. A given task class may be registered
|
||||
several times, for example if it needs to be executed with different
|
||||
parameters.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[general]
|
||||
|
||||
project = Scheduler
|
||||
version = latest (9-dev)
|
||||
release = latest (9-dev)
|
||||
t3author = François Suter
|
||||
copyright = 1997-2021
|
||||
|
||||
description = This is the documentation of TYPO3's system
|
||||
extension 'scheduler' which lets you register tasks
|
||||
to happen at a specific time.
|
||||
|
||||
|
||||
[html_theme_options]
|
||||
|
||||
# Add 'Edit me on Github' button
|
||||
github_branch = master
|
||||
github_repository = TYPO3/TYPO3.CMS
|
||||
path_to_documentation_dir = typo3/sysext/scheduler/Documentation
|
||||
|
||||
# show as related links
|
||||
project_issues = https://forge.typo3.org/projects/typo3cms-core/issues
|
||||
project_repository = https://github.com/typo3/typo3
|
||||
@@ -0,0 +1,13 @@
|
||||
.. ==================================================
|
||||
.. FOR YOUR INFORMATION
|
||||
.. --------------------------------------------------
|
||||
.. -*- coding: utf-8 -*- with BOM.
|
||||
|
||||
.. include:: Includes.txt
|
||||
|
||||
.. _index-labels-for-crossreferencing:
|
||||
|
||||
Index: Labels for Crossreferencing
|
||||
==================================
|
||||
|
||||
.. ref-targets-list::
|
||||
@@ -0,0 +1,345 @@
|
||||
Some icons used in the TYPO3 project are retrieved from the "Silk" icon set of
|
||||
Mark James, which can be found at http://famfamfam.com/lab/icons/silk/. This
|
||||
set is distributed under a Creative Commons Attribution 2.5 License. The
|
||||
license can be found at http://creativecommons.org/licenses/by/2.5/.
|
||||
---------------------------------
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,494 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
|
||||
<file t3:id="1415814984" source-language="en" datatype="plaintext" original="EXT:scheduler/Resources/Private/Language/locallang.xlf" date="2011-10-17T20:22:37Z" product-name="scheduler">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="action.add" resname="action.add">
|
||||
<source>Add task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.edit" resname="action.edit">
|
||||
<source>Edit task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="button.cancel" resname="button.cancel">
|
||||
<source>Cancel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="function.scheduler" resname="function.scheduler">
|
||||
<source>Scheduled tasks</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="function.check" resname="function.check">
|
||||
<source>Setup check</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="function.info" resname="function.info">
|
||||
<source>Information</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="hdg.cliScript" resname="hdg.cliScript">
|
||||
<source>CLI script</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="hdg.composerMode" resname="hdg.composerMode">
|
||||
<source>System is running in Composer Mode</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.composerMode" resname="msg.composerMode">
|
||||
<source>
|
||||
The system is running in composer mode, the path to the `typo3` executable could not be reliably determined.
|
||||
By default, the command to run in composer mode is `vendor/bin/typo3 scheduler:run`.
|
||||
</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="hdg.lastRun" resname="hdg.lastRun">
|
||||
<source>Last run</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.automatically" resname="label.automatically">
|
||||
<source>automatically</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.cachingFrameworkGarbageCollection.selectBackends" resname="label.cachingFrameworkGarbageCollection.selectBackends">
|
||||
<source>Backend types</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.recyclerGarbageCollection.numberOfDays" resname="label.recyclerGarbageCollection.numberOfDays">
|
||||
<source>Number of days until removing files</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.checkAll" resname="label.checkAll">
|
||||
<source>Check/uncheck all</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.class" resname="label.class">
|
||||
<source>Class</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.cron" resname="label.cron">
|
||||
<source>Cron</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.defType" resname="label.defType">
|
||||
<source>Definition type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.defType.interval" resname="label.defType.interval">
|
||||
<source>Interval</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.defType.cron" resname="label.defType.cron">
|
||||
<source>Cron command</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.description" resname="label.description">
|
||||
<source>Description</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.email" resname="label.email">
|
||||
<source>Email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.end" resname="label.end">
|
||||
<source>End (HH:MM DD-MM-YYYY)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.executeSelected" resname="label.executeSelected">
|
||||
<source>Execute selected tasks</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.cronjobSelected" resname="label.cronjobSelected">
|
||||
<source>Execute selected tasks on next cron job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.extension" resname="label.extension">
|
||||
<source>Extension</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.frequency" resname="label.frequency">
|
||||
<source>Frequency</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.frequency.long" resname="label.frequency.long">
|
||||
<source>Frequency (seconds or cron command)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.id" resname="label.id">
|
||||
<source>ID</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.lastExecution" resname="label.lastExecution">
|
||||
<source>Last Execution</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.manual" resname="label.manual">
|
||||
<source>Manual</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.manually" resname="label.manually">
|
||||
<source>manually</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.name" resname="label.name">
|
||||
<source>Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.nextExecution" resname="label.nextExecution">
|
||||
<source>Next Execution</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.parallel" resname="label.parallel">
|
||||
<source>Parallel Execution</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.parallel.long" resname="label.parallel.long">
|
||||
<source>Allow Parallel Execution</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.serverTime" resname="label.serverTime">
|
||||
<source>Server time</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.sleepTime" resname="label.sleepTime">
|
||||
<source>Sleep time</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.start" resname="label.start">
|
||||
<source>Start (HH:MM DD-MM-YYYY)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.fileStorageIndexing.storage" resname="label.fileStorageIndexing.storage">
|
||||
<source>Storage to index</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.fileStorageExtraction.fileCount" resname="label.fileStorageExtraction.fileCount">
|
||||
<source>Number of files per run</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.fileStorageExtraction.registeredExtractors" resname="label.fileStorageExtraction.registeredExtractors">
|
||||
<source>Registered extractors</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.fileStorageExtraction.registeredExtractors.extractor" resname="label.fileStorageExtraction.registeredExtractors.extractor">
|
||||
<source>%1$s with priority %2$d</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.fileStorageExtraction.registeredExtractors.with_extractors" resname="label.fileStorageExtraction.registeredExtractors.with_extractors">
|
||||
<source>Following extractors have been registered and will be used when running this task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.fileStorageExtraction.registeredExtractors.without_extractors" resname="label.fileStorageExtraction.registeredExtractors.without_extractors">
|
||||
<source>There currently is no extractor registered. This task will have no effect!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.tableGarbageCollection.allTables" resname="label.tableGarbageCollection.allTables">
|
||||
<source>Clean all available tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.tableGarbageCollection.table" resname="label.tableGarbageCollection.table">
|
||||
<source>Table to clean up</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.tableGarbageCollection.additionalInformationTable" resname="label.tableGarbageCollection.additionalInformationTable">
|
||||
<source>Table: %s</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.tableGarbageCollection.additionalInformationAllTables" resname="label.tableGarbageCollection.additionalInformationAllTables">
|
||||
<source>All tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.tableGarbageCollection.numberOfDays" resname="label.tableGarbageCollection.numberOfDays">
|
||||
<source>Delete entries older than given number of days</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.optimizeDatabaseTables.selectTables" resname="label.optimizeDatabaseTables.selectTables">
|
||||
<source>Database tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.type" resname="label.type">
|
||||
<source>Type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.type.recurring" resname="label.type.recurring">
|
||||
<source>Recurring</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.type.single" resname="label.type.single">
|
||||
<source>Single</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.group" resname="label.group">
|
||||
<source>Task group</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.group.taskCount" resname="label.group.taskCount">
|
||||
<source>Amount of tasks in this scheduler task group</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.noGroup" resname="label.noGroup">
|
||||
<source>(no task group defined)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.noGroup.taskCount" resname="label.noGroup.taskCount">
|
||||
<source>Amount of tasks that are not assigned to any scheduler task group</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.noClass" resname="label.noClass">
|
||||
<source>Tasks without registered classes</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.noClass.taskCount" resname="label.noClass.taskCount">
|
||||
<source>Amount of tasks without found class</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.schedulableCommandName" resname="label.schedulableCommandName">
|
||||
<source><![CDATA[Schedulable Command. <em>Save and reopen to define command arguments</em>]]></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.saveAndCreateNewTask" resname="label.saveAndCreateNewTask">
|
||||
<source>Save and create new task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.addOptionToCommand" resname="label.addOptionToCommand">
|
||||
<source>Add option</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.addError" resname="msg.addError">
|
||||
<source>The task could not be added.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.addSuccess" resname="msg.addSuccess">
|
||||
<source>The task was added successfully.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.cliScript" resname="msg.cliScript">
|
||||
<source>The script to execute the Scheduler from the command line is: <strong>"%s scheduler:run"</strong>.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.cliScriptExecutable" resname="msg.cliScriptExecutable">
|
||||
<source>The webserver user is allowed execute this script.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.cliScriptNotExecutable" resname="msg.cliScriptNotExecutable">
|
||||
<source>The webserver user is not allowed to execute this script.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.delete" resname="msg.delete">
|
||||
<source>Are you sure you want to delete this task?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.deleteError" resname="msg.deleteError">
|
||||
<source>The task could not be deleted.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.deleteSuccess" resname="msg.deleteSuccess">
|
||||
<source>The task was successfully deleted.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.endDateSmallerThanStartDate" resname="msg.endDateSmallerThanStartDate">
|
||||
<source>The end date is before the start date.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.executed" resname="msg.executed">
|
||||
<source>Executed: %s</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.frequencyError" resname="msg.frequencyError">
|
||||
<source>Invalid frequency. Please enter either a number of seconds or a valid cron command. The cron parser said: %1$s [code: %2$d].</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.incompleteLastRun" resname="msg.incompleteLastRun">
|
||||
<source>The information about the last execution of the Scheduler is incomplete.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.infoScreenIntro" resname="msg.infoScreenIntro">
|
||||
<source>This is the list of all available tasks in this TYPO3 installation. Click on the icon at the far right to directly create a new scheduled task of the chosen type.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidFrequency" resname="msg.invalidFrequency">
|
||||
<source>Invalid frequency. Please enter either a number of seconds or a valid cron command.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidSleepTime" resname="msg.invalidSleepTime">
|
||||
<source>Please enter a sleep time greater or equal to 0 (zero).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.lastRun" resname="msg.lastRun">
|
||||
<source>The Scheduler was last started (%1$s) on %2$s at %3$s and ended on %4$s at %5$s.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.maynotDeleteRunningTask" resname="msg.maynotDeleteRunningTask">
|
||||
<source>A running task may not be deleted.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.maynotEditRunningTask" resname="msg.maynotEditRunningTask">
|
||||
<source>A running task may not be edited.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.maynotStopNonRunningTask" resname="msg.maynotStopNonRunningTask">
|
||||
<source>The task is not running. There are no executions to unmark.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noEmail" resname="msg.noEmail">
|
||||
<source>Please enter an email address.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noFrequency" resname="msg.noFrequency">
|
||||
<source>No frequency was defined, either as an interval or as a cron command.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noLastRun" resname="msg.noLastRun">
|
||||
<source>The Scheduler has never yet run or the information about the last run has been lost.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noTasksDefined" resname="msg.noTasksDefined">
|
||||
<source>There are currently no task classes available at all. To be able to create new tasks, you must either enable the samples provided by the Scheduler or install some other extension that relies on the Scheduler.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noTaskClassFound" resname="msg.noTaskClassFound">
|
||||
<source>The selected task class could not be found. You should probably contact the task's developers.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noTasks" resname="msg.noTasks">
|
||||
<source>No tasks defined yet.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.notExecuted" resname="msg.notExecuted">
|
||||
<source>Not executed: %s</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.executionFailed" resname="msg.executionFailed">
|
||||
<source>Execution of task "%1$s" failed with the following message: %2$s</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.executionFailureDefault" resname="msg.executionFailureDefault">
|
||||
<source>The execution failed, but the error message could not be retrieved (it was probably too large). Sorry.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.executionFailureReport" resname="msg.executionFailureReport">
|
||||
<source>Execution failed: %1$d, %2$s</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noStartDate" resname="msg.noStartDate">
|
||||
<source>Please define a start date.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidStartDate" resname="msg.invalidStartDate">
|
||||
<source>Start date is invalid.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidEndDate" resname="msg.invalidEndDate">
|
||||
<source>End date is invalid.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidNumberOfDays" resname="msg.invalidNumberOfDays">
|
||||
<source>Number of days is invalid.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.selectionOfNonExistingCacheBackends" resname="msg.selectionOfNonExistingCacheBackends">
|
||||
<source>Some of the selected cache backends do not exist. Please select valid backends only.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noCacheBackendSelected" resname="msg.noCacheBackendSelected">
|
||||
<source>Please select at least one cache backend.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidTaskClass" resname="msg.invalidTaskClass">
|
||||
<source><![CDATA[<strong>The following tasks can not be executed. No related class could be found for none of the following listed tasks.</strong> You should consider reinstalling the extension that provided them or simply deleting the tasks. Please check also if any of the listed tasks have to be recreated manually (which can be necessary after an extension update due to breaking changes or missing migration scripts).]]></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidTaskClassEdit" resname="msg.invalidTaskClassEdit">
|
||||
<source>The current class (%s) could not be found. Change it below or consider deleting this task altogether.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidTaskClass.infobox.title" resname="msg.invalidTaskClass.infobox.title">
|
||||
<source>%s registered scheduler tasks can not be executed</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.invalidTaskClass.infobox.message" resname="msg.invalidTaskClass.infobox.message">
|
||||
<source><![CDATA[<a href="#recordlist-task-group-missing">Please check the erroneous tasks!</a>]]></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.schedulerSetupCheck" resname="msg.schedulerSetupCheck">
|
||||
<source>This screen checks if the requisites for running the Scheduler as a cron job are fulfilled. It also displays information about the last run of the Scheduler.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.serverTime" resname="msg.serverTime">
|
||||
<source>Current server time is %s.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.serverTimeHelp" resname="msg.serverTimeHelp">
|
||||
<source>All dates and times in the Scheduler are measured according to the server's time, as the Scheduler is run purely on the server-side.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.stop" resname="msg.stop">
|
||||
<source>Are you sure you want to mark this task as not running? Note that this will not stop the actual script (if unsure please refer to the manual).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.stopError" resname="msg.stopError">
|
||||
<source>The task could not be marked as non-running.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.stopSuccess" resname="msg.stopSuccess">
|
||||
<source>The task was successfully marked as non-running.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.taskNotFound" resname="msg.taskNotFound">
|
||||
<source>The requested task (UID: %d) was not found.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.updateSuccess" resname="msg.updateSuccess">
|
||||
<source>The task was updated successfully.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.updateError" resname="msg.updateError">
|
||||
<source>The task could not be updated.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.selectionOfNonExistingDatabaseTables" resname="msg.selectionOfNonExistingDatabaseTables">
|
||||
<source>Some of the selected database tables do not exist. Please select only valid tables.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.noDatabaseTablesSelected" resname="msg.noDatabaseTablesSelected">
|
||||
<source>Please select at least one database table.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.unregisteredCommand" resname="msg.unregisteredCommand">
|
||||
<source>Command with identifier "%s" has not been registered.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.errorParsingArguments" resname="msg.errorParsingArguments">
|
||||
<source>Error parsing current set of arguments: "%s".</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.errorParsingOptions" resname="msg.errorParsingOptions">
|
||||
<source>Error parsing current set of options: "%s".</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="msg.mandatoryArgumentMissing" resname="msg.mandatoryArgumentMissing">
|
||||
<source>Argument "%s" is mandatory</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="none" resname="none">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status" resname="status">
|
||||
<source>Status</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status.progress" resname="status.progress">
|
||||
<source>Progress</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status.disabled" resname="status.disabled">
|
||||
<source>disabled</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status.failure" resname="status.failure">
|
||||
<source>failure</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status.late" resname="status.late">
|
||||
<source>late</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status.running" resname="status.running">
|
||||
<source>running</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="status.scheduled" resname="status.scheduled">
|
||||
<source>scheduled</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task" resname="task">
|
||||
<source>Task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="title" resname="title">
|
||||
<source>TYPO3 Scheduler administration module</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.run_task" resname="action.run_task">
|
||||
<source>Run task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="action.run_task_cron" resname="action.run_task_cron">
|
||||
<source>Run task on next cron job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="testTask.name" resname="testTask.name">
|
||||
<source>Scheduler test task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="testTask.description" resname="testTask.description">
|
||||
<source>The Scheduler test task just sends a mail to a given email address. It is designed to be used for testing purposes.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sleepTask.name" resname="sleepTask.name">
|
||||
<source>Scheduler sleep task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sleepTask.description" resname="sleepTask.description">
|
||||
<source>This task does nothing but put PHP to sleep for a number of seconds. It is designed to test multiple executions.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="cachingFrameworkGarbageCollection.name" resname="cachingFrameworkGarbageCollection.name">
|
||||
<source>Caching framework garbage collection</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="cachingFrameworkGarbageCollection.description" resname="cachingFrameworkGarbageCollection.description">
|
||||
<source>This task calls the garbage collection of configured caching framework caches which use one of the selected backends. This will free some space in cache backends which do not have an internal garbage collection. In case of the default database backend it is advisable to run this task once a day when the database is mostly idle.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="fileIndexing.name" resname="fileIndexing.name">
|
||||
<source>[OBSOLETE] File Abstraction Layer: Indexing job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="fileIndexing.description" resname="fileIndexing.description">
|
||||
<source>Runs indexing tasks based on an indexing configuration and a storage/folder information.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="fileStorageIndexing.name" resname="fileStorageIndexing.name">
|
||||
<source>File Abstraction Layer: Update storage index</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="fileStorageIndexing.description" resname="fileStorageIndexing.description">
|
||||
<source>Updates the Index/Cache Data of a Storage; only needed if changes to the storage are possible outside the backend (FTP, RemoteStorages).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="fileStorageExtraction.name" resname="fileStorageExtraction.name">
|
||||
<source>File Abstraction Layer: Extract metadata in storage</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="fileStorageExtraction.description" resname="fileStorageExtraction.description">
|
||||
<source>Extracts metadata for all files in storage which have been changed since last run.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="tableGarbageCollection.name" resname="tableGarbageCollection.name">
|
||||
<source>Table garbage collection</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="tableGarbageCollection.description" resname="tableGarbageCollection.description">
|
||||
<source>Task to delete old entries from specific tables like sys_log. Use $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['tx_scheduler_TableGarbageCollection']['options']['tables'] to configure tables. Refer to the manual for more details.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="recyclerGarbageCollection.name" resname="recyclerGarbageCollection.name">
|
||||
<source>Fileadmin garbage collection</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="recyclerGarbageCollection.description" resname="recyclerGarbageCollection.description">
|
||||
<source>This task empties all "_recycler_" folders below fileadmin. This helps free some space in the file system.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="executeSchedulableCommandTask.name" resname="executeSchedulableCommandTask.name">
|
||||
<source>Execute console commands</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="executeSchedulableCommandTask.description" resname="executeSchedulableCommandTask.description">
|
||||
<source>Allows regular console commands to be configured and executed through the scheduler framework.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="optimizeDatabaseTable.name" resname="optimizeDatabaseTable.name">
|
||||
<source>Optimize MySQL database tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="optimizeDatabaseTable.description" resname="optimizeDatabaseTable.description">
|
||||
<source>This task executes "OPTIMIZE TABLE" statements on the selected database tables. This helps to reduce storage space and improve I/O efficiency. Warning! Tables will be locked during the optimization process.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="ipAnonymization.name" resname="ipAnonymization.name">
|
||||
<source>Anonymize IP addresses in database tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="ipAnonymization.description" resname="ipAnonymization.description">
|
||||
<source>This task anonymizes the IP addresses to enforce the privacy of the persisted data.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.table" resname="label.ipAnonymization.table">
|
||||
<source>Table to anoymize</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.additionalInformationTable" resname="label.ipAnonymization.additionalInformationTable">
|
||||
<source>Table: %s after %s days</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.additionalInformationAllTables" resname="label.ipAnonymization.additionalInformationAllTables">
|
||||
<source>All tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.numberOfDays" resname="label.ipAnonymization.numberOfDays">
|
||||
<source>Handle entries older than given number of days</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.mask" resname="label.ipAnonymization.mask">
|
||||
<source>Mask level</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.mask.1" resname="label.ipAnonymization.mask.1">
|
||||
<source>Last byte for IPv4 / Interface ID for IPv6</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="label.ipAnonymization.mask.2" resname="label.ipAnonymization.mask.2">
|
||||
<source>Last 2 bytes for IPv4 / Interface & SLA ID for IPv6</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="command.example1" resname="command.example1">
|
||||
<source>Every day-of-week from Monday through Friday at 09:00 and 15:00</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="command.example2" resname="command.example2">
|
||||
<source>Every 2 hours</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="command.example3" resname="command.example3">
|
||||
<source>Every 20 minutes</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="command.example4" resname="command.example4">
|
||||
<source>Every Tuesday at 07:00</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
|
||||
<file t3:id="1415814985" source-language="en" datatype="plaintext" original="EXT:scheduler/Resources/Private/Language/locallang_csh_scheduler.xlf" date="2011-10-17T20:22:37Z" product-name="scheduler">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id=".alttitle" resname=".alttitle">
|
||||
<source>Scheduler</source>
|
||||
</trans-unit>
|
||||
<trans-unit id=".description" resname=".description">
|
||||
<source>The Scheduler provides an interface to register and manage recurring or one-time tasks.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id=".syntax" resname=".syntax">
|
||||
<source>The Scheduler uses a single cron job to handle all registered tasks. It is thus very convenient as only one cron job needs to be set up and all task frequencies can be managed from the BE module.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_class.alttitle" resname="task_class.alttitle">
|
||||
<source>Class of the task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_class.description" resname="task_class.description">
|
||||
<source>Choose a class from the list of available tasks</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_class.details" resname="task_class.details">
|
||||
<source>Some tasks are provided by the TYPO3 Core itself. Others come from extensions. See the manual to learn how to create your own tasks and register them with the Scheduler.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_disable.alttitle" resname="task_disable.alttitle">
|
||||
<source>Disable task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_disable.description" resname="task_disable.description">
|
||||
<source>Check this box to disable the automatic, scheduled execution of this task. It can still be launched manually from the BE module.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_type.alttitle" resname="task_type.alttitle">
|
||||
<source>Type of task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_type.description" resname="task_type.description">
|
||||
<source>Choose whether the task must happen a single time or will be repeated</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_start.alttitle" resname="task_start.alttitle">
|
||||
<source>Start of task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_start.description" resname="task_start.description">
|
||||
<source>Enter a start date and time for the task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_start.details" resname="task_start.details" xml:space="preserve">
|
||||
<source>Date must be entered in a standard format like HH:mm dd-mm-YYYY or YYYY-mm-dd HH:mm. Natural keywords can also be used, like "now" or "tomorrow".
|
||||
Date and time must correspond to the server's time zone.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_end.alttitle" resname="task_end.alttitle">
|
||||
<source>End of task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_end.description" resname="task_end.description">
|
||||
<source>Enter an end date and time for the task, can be blank</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_end.details" resname="task_end.details" xml:space="preserve">
|
||||
<source>Date must be entered in a standard format like HH:mm dd-mm-YYYY or YYYY-mm-dd HH:mm. Natural keywords can also be used, like "now" or "tomorrow".
|
||||
Date and time must correspond to the server's time zone.
|
||||
|
||||
Can be left blank, if task is not supposed to end at any point.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_frequency.alttitle" resname="task_frequency.alttitle">
|
||||
<source>Frequency of task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_frequency.description" resname="task_frequency.description">
|
||||
<source>Enter a frequency, either in seconds or using a cron-like syntax (minutes, hours, day of month, months and day of week)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_frequency.details" resname="task_frequency.details" xml:space="preserve">
|
||||
<source>The frequency of the task can be simply entered as a number of seconds.
|
||||
|
||||
Alternatively it is possible to use the same syntax as for cron jobs. This is simply a list of five numbers or the wildcard "*", separated by blanks. The five numbers are (in order) minutes, hours, day of month, months and day of week. Examples:
|
||||
|
||||
*/5 * * * * means every 5 minutes
|
||||
30 22 * * * means every day at 22:30
|
||||
|
||||
The Scheduler supports the full cron syntax.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_frequency.seeAlso" resname="task_frequency.seeAlso">
|
||||
<source>Wikipedia reference|http://en.wikipedia.org/wiki/Cron</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_multiple.alttitle" resname="task_multiple.alttitle">
|
||||
<source>Multiple executions</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_multiple.description" resname="task_multiple.description">
|
||||
<source>Choose whether a task can be running several times simultaneously or not</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_email.alttitle" resname="task_email.alttitle">
|
||||
<source>Email address</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_email.description" resname="task_email.description">
|
||||
<source>Enter a recipient for the mails sent by the test task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_description.alttitle" resname="task_description.alttitle">
|
||||
<source>Task description</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_description.description" resname="task_description.description">
|
||||
<source>Enter a description for this task</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_group.alttitle" resname="task_group.alttitle">
|
||||
<source>Task group</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_group.description" resname="task_group.description">
|
||||
<source>In case of a high number of different tasks, it may be useful to visually group similar tasks.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_sleepTime.alttitle" resname="task_sleepTime.alttitle">
|
||||
<source>Sleep time</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_sleepTime.description" resname="task_sleepTime.description">
|
||||
<source>Enter a number of seconds during which the task will just sleep.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_cachingFrameworkGarbageCollection_selectedBackends.alttitle" resname="task_cachingFrameworkGarbageCollection_selectedBackends.alttitle">
|
||||
<source>Select backends</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_cachingFrameworkGarbageCollection_selectedBackends.description" resname="task_cachingFrameworkGarbageCollection_selectedBackends.description">
|
||||
<source>If a cache uses one of the selected backends, the garbage collection will be called for this cache.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_tableGarbageCollection_numberOfDays.alttitle" resname="task_tableGarbageCollection_numberOfDays.alttitle">
|
||||
<source>Number of days</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_tableGarbageCollection_numberOfDays.description" resname="task_tableGarbageCollection_numberOfDays.description">
|
||||
<source>Delete rows from selected table that are older than given number of days.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_tableGarbageCollection_table.alttitle" resname="task_tableGarbageCollection_table.alttitle">
|
||||
<source>Select table</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_tableGarbageCollection_table.description" resname="task_tableGarbageCollection_table.description">
|
||||
<source>Select one of the registered tables to be cleaned up with this task.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_tableGarbageCollection_allTables.alttitle" resname="task_tableGarbageCollection_allTables.alttitle">
|
||||
<source>Clean up all tables</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_tableGarbageCollection_allTables.description" resname="task_tableGarbageCollection_allTables.description">
|
||||
<source>If selected, all configured tables are cleaned. The selection below is ignored.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_recyclerGarbageCollection_numberOfDays.alttitle" resname="task_recyclerGarbageCollection_numberOfDays.alttitle">
|
||||
<source>Number of days</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="task_recyclerGarbageCollection_numberOfDays.description" resname="task_recyclerGarbageCollection_numberOfDays.description">
|
||||
<source>Delete files in _recycler_ folders that are older than the given number of days.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
|
||||
<file t3:id="1415814986" source-language="en" datatype="plaintext" original="EXT:scheduler/Resources/Private/Language/locallang_em.xlf" date="2011-10-17T20:22:32Z" product-name="scheduler">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="scheduler.config.maxLifetime" resname="scheduler.config.maxLifetime">
|
||||
<source>Maximum lifetime: Enter the maximum lifetime (in minutes) of a scheduler task. If a task is still running after that time, it will be dropped from the execution list (but not stopped).</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scheduler.config.showSampleTasks" resname="scheduler.config.showSampleTasks">
|
||||
<source>Enable sample tasks: When turned on, you can use the sample, test tasks provided by the scheduler. Before turning this off, make sure you don't have any of those sample tasks currently scheduled. You will also need to clear the configuration cache.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
|
||||
<file t3:id="1415814987" source-language="en" datatype="plaintext" original="EXT:scheduler/Resources/Private/Language/locallang_mod.xlf" date="2011-10-17T20:22:37Z" product-name="scheduler">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="mlang_tabs_tab" resname="mlang_tabs_tab">
|
||||
<source>Scheduler</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="mlang_labels_tabdescr" resname="mlang_labels_tabdescr">
|
||||
<source>Scheduler administration module. Check all registered tasks. Add, modify or delete tasks.<br /><em>Access for 'admin' users only!</em></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="mlang_labels_tablabel" resname="mlang_labels_tablabel">
|
||||
<source>TYPO3 Scheduler</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
|
||||
<file t3:id="1415814988" source-language="en" datatype="plaintext" original="EXT:scheduler/Resources/Private/Language/locallang_tca.xlf" date="2011-10-17T20:22:37Z" product-name="scheduler">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="tx_scheduler_task_group" resname="tx_scheduler_task_group">
|
||||
<source>Scheduler task group</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="tx_scheduler_task_group.groupName" resname="tx_scheduler_task_group.groupName">
|
||||
<source>Group name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="tx_scheduler_task_group.description" resname="tx_scheduler_task_group.description">
|
||||
<source>Description</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -0,0 +1,6 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<f:be.infobox state="-1" title="{f:translate(key:'label.serverTime')}">
|
||||
<p><f:translate key="msg.serverTimeHelp" /></p>
|
||||
<p><f:translate key="msg.serverTime" arguments="{0: now}" /></p>
|
||||
</f:be.infobox>
|
||||
</html>
|
||||
@@ -0,0 +1,179 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
|
||||
<f:if condition="{missingClasses}">
|
||||
<f:be.infobox title="{f:translate(key:'msg.invalidTaskClass.infobox.title', arguments: '{0: \'{missingClasses ->f:count()}\'}')}" state="2">
|
||||
<f:format.raw><f:translate key="msg.invalidTaskClass.infobox.message" /></f:format.raw>
|
||||
</f:be.infobox>
|
||||
</f:if>
|
||||
<f:for each="{tasks}" as="taskGroup" iteration="groupIterator">
|
||||
<f:if condition="{taskGroup.tasks -> f:count()}">
|
||||
<div class="tx_scheduler_mod1_table">
|
||||
<div class="panel panel-space panel-default recordlist">
|
||||
<f:if condition="{taskGroup.groupName} == ''">
|
||||
<f:then>
|
||||
<f:if condition="{tasks -> f:count()}>1">
|
||||
<div class="panel-heading taskGroup" data-task-group-id="{groupIterator.index}">
|
||||
<span title="{f:translate(key: 'label.group')}">{f:translate(id: 'label.noGroup')}</span> (<span class="t3js-table-total-items" title="{f:translate(id: 'label.noGroup.taskCount')}">{taskGroup.tasks -> f:count()}</span>)
|
||||
<a href="#" class="pull-right t3js-toggle-recordlist" data-table="task-group-{groupIterator.index}" data-toggle="collapse" data-target="#recordlist-task-group-{groupIterator.index}">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</div>
|
||||
</f:if>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<div class="panel-heading taskGroup" data-task-group-id="{groupIterator.index}">
|
||||
<span title="{f:translate(key: 'label.group')}">{taskGroup.groupName}</span> (<span class="t3js-table-total-items" title="{f:translate(id: 'label.group.taskCount')}">{taskGroup.tasks -> f:count()}</span>)
|
||||
<a href="#" class="pull-right t3js-toggle-recordlist" data-table="task-group-{groupIterator.index}" data-toggle="collapse" data-target="#recordlist-task-group-{groupIterator.index}">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</div>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<div class="collapse in" data-state="expanded" id="recordlist-task-group-{groupIterator.index}">
|
||||
<div class="table-fit">
|
||||
<table class="table table-striped table-hover taskGroup-table">
|
||||
<thead>
|
||||
<tr class="taskGroup_{groupIterator.index}">
|
||||
<th><span class="t-span"><a class="btn btn-default checkall" href="#" id="checkall" title="{f:translate(key:'label.checkAll')}"><core:icon identifier="actions-document-select"/></a></span></th>
|
||||
<th><span class="t-span"><f:translate key="label.id"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="task"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="label.type"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="label.frequency"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="label.parallel"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="label.lastExecution"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="label.nextExecution"/></span></th>
|
||||
<th><span class="t-span"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<f:for each="{taskGroup.tasks}" as="taskRecord" iteration="taskIterator">
|
||||
<f:if condition="{taskRecord.validClass}">
|
||||
<tr class="{taskRecord.showAsDisabled} taskGroup_{groupIterator.index}">
|
||||
<td><span class="t-span"><label class="btn btn-default btn-checkbox"><input type="checkbox" name="tx_scheduler[execute][]" value="{taskRecord.uid}" id="task_{taskRecord.uid}"><span class="t3-icon fa"></span></label></span></td>
|
||||
<td class="right"><span class="t-span">{taskRecord.uid}</span></td>
|
||||
<td class="nowrap-disabled">
|
||||
<span class="t-span">
|
||||
<f:for each="{taskRecord.labels}" as="label">
|
||||
<f:if condition="{taskRecord.validClass}">
|
||||
<span class="label label-{label.class} pull-right" title="{label.description}">{label.text}</span>
|
||||
</f:if>
|
||||
</f:for>
|
||||
<span class="name">
|
||||
<a href="{moduleUri}&CMD=edit&tx_scheduler[uid]={taskRecord.uid}">
|
||||
{taskRecord.classTitle} ({taskRecord.classExtension})
|
||||
<f:if condition="{taskRecord.progress}">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{taskRecord.progress}" aria-valuemin="0" aria-valuemax="100" style="width: {taskRecord.progress}%;">{taskRecord.progress}%</div>
|
||||
</div>
|
||||
</f:if>
|
||||
<f:if condition="{taskRecord.additionalInformation}">
|
||||
<div class="additional-information"><f:format.nl2br>{taskRecord.additionalInformation}</f:format.nl2br></div>
|
||||
</f:if>
|
||||
</a>
|
||||
</span>
|
||||
<f:if condition="{taskRecord.description} != ''">
|
||||
<span class="description"><f:format.nl2br>{taskRecord.description}</f:format.nl2br></span>
|
||||
</f:if>
|
||||
</span>
|
||||
</td>
|
||||
<td><span class="t-span">{taskRecord.execType}</span></td>
|
||||
<td><span class="t-span">{taskRecord.frequency}</span></td>
|
||||
<td><span class="t-span">{taskRecord.multiple}</span></td>
|
||||
<td><span class="t-span">{taskRecord.lastExecution}</span></td>
|
||||
<td><span class="t-span {taskRecord.labels.nextexecution.class}">{taskRecord.nextDate}</span></td>
|
||||
<td class="nowrap">
|
||||
<span class="t-span">
|
||||
<f:if condition="{taskRecord.isRunning}">
|
||||
<f:then>
|
||||
<div class="btn-group" role="group">
|
||||
<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="{moduleUri}&CMD=stop&tx_scheduler[uid]={taskRecord.uid}" data-severity="warning" data-title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:stop')}" data-button-close-text="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')}" data-content="{f:translate(key:'msg.stop')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:stop')}">
|
||||
<core:icon identifier="actions-close"/>
|
||||
</a>
|
||||
</div>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<div class="btn-group" role="group">
|
||||
<a data-toggle="tooltip" data-container="body" class="btn btn-default" href="{moduleUri}&CMD=edit&tx_scheduler[uid]={taskRecord.uid}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:edit')}">
|
||||
<core:icon identifier="actions-open"/>
|
||||
</a>
|
||||
<a data-toggle="tooltip" data-container="body" class="btn btn-default" href="{moduleUri}&CMD=toggleHidden&tx_scheduler[uid]={taskRecord.uid}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:{taskRecord.translationKey}')}">
|
||||
<f:if condition="{taskRecord.translationKey} == 'enable'">
|
||||
<f:then>
|
||||
<core:icon identifier="actions-edit-unhide"/>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<core:icon identifier="actions-edit-hide"/>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</a>
|
||||
<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="{moduleUri}&CMD=delete&tx_scheduler[uid]={taskRecord.uid}" data-severity="warning" data-title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete')}" data-button-close-text="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')}" data-content="{f:translate(key:'msg.delete')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete')}">
|
||||
<core:icon identifier="actions-edit-delete"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<a class="btn btn-default" data-toggle="tooltip" data-container="body" href="{moduleUri}&CMD=setNextExecutionTime&tx_scheduler[uid]={taskRecord.uid}" title="{f:translate(key:'action.run_task_cron')}">
|
||||
<core:icon identifier="actions-clock"/>
|
||||
</a>
|
||||
<a class="btn btn-default" data-toggle="tooltip" data-container="body" href="{moduleUri}&tx_scheduler[execute][]={taskRecord.uid}" title="{f:translate(key:'action.run_task')}">
|
||||
<core:icon identifier="actions-play"/>
|
||||
</a>
|
||||
</div>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</f:if>
|
||||
</f:for>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</f:if>
|
||||
</f:for>
|
||||
|
||||
<f:if condition="{missingClasses}">
|
||||
<div class="tx_scheduler_mod1_table">
|
||||
<div class="panel panel-space panel-default recordlist">
|
||||
<div class="panel-heading taskGroup" data-task-group-id="missing">
|
||||
<f:translate key="label.noClass"/>
|
||||
(<span class="t3js-table-total-items" title="{f:translate(key: 'label.noClass.taskCount')}">{missingClasses -> f:count()}</span>)
|
||||
<a href="#" class="pull-right t3js-toggle-recordlist"
|
||||
data-table="task-group-missing" data-toggle="collapse" data-target="#recordlist-task-group-missing">
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse in" data-state="expanded" id="recordlist-task-group-missing">
|
||||
<div class="table-fit">
|
||||
<table class="table table-striped table-hover">
|
||||
<tr class="taskGroup" data-task-group-id="0">
|
||||
<td class="nowrap-disabled"><span>{f:translate(key:'msg.invalidTaskClass') -> f:format.raw()}</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table table-striped table-hover taskGroup-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><span class="t-span"><f:translate key="label.id"/></span></th>
|
||||
<th><span class="t-span"><f:translate key="task"/></span></th>
|
||||
<th><span class="t-span"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<f:for each="{missingClasses}" as="missingClass">
|
||||
<tr>
|
||||
<td class="right">{missingClass.uid}</td>
|
||||
<td><span class="label label-danger">{missingClass.class}</span></td>
|
||||
<td class="nowrap">
|
||||
<div class="btn-group" role="group">
|
||||
<a data-toggle="tooltip" data-container="body" class="btn btn-default t3js-modal-trigger" href="{moduleUri}&CMD=delete&tx_scheduler[uid]={missingClass.uid}" data-severity="warning" data-title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete')}" data-button-close-text="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:cancel')}" data-content="{f:translate(key:'msg.delete')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:delete')}">
|
||||
<core:icon identifier="actions-edit-delete"/>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</f:for>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</f:if>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
<p class="lead">
|
||||
<f:translate key="msg.schedulerSetupCheck" />
|
||||
</p>
|
||||
|
||||
<f:be.infobox title="{f:translate(key: 'hdg.lastRun')}" message="{lastRunMessage}" state="{lastRunSeverity}" />
|
||||
|
||||
<f:if condition="{composerMode}">
|
||||
<f:then>
|
||||
<f:be.infobox title="{f:translate(key: 'hdg.composerMode')}" state="-1">
|
||||
<p>{f:translate(key: 'msg.composerMode')}</p>
|
||||
</f:be.infobox>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<f:be.infobox title="{f:translate(key: 'hdg.cliScript')}" state="{isExecutableSeverity}">
|
||||
<p>{f:translate(key: 'msg.cliScript', arguments: '{0: script}') -> f:format.raw()}</p>
|
||||
{isExecutableMessage}
|
||||
</f:be.infobox>
|
||||
</f:else>
|
||||
</f:if>
|
||||
|
||||
<f:render partial="ServerTime" arguments="{now: now}" />
|
||||
@@ -0,0 +1,159 @@
|
||||
{namespace be=TYPO3\CMS\Backend\ViewHelpers}
|
||||
|
||||
<input type="hidden" name="tx_scheduler[uid]" value="{uid}" />
|
||||
<input type="hidden" name="previousCMD" value="{cmd}" />
|
||||
|
||||
<div class="form-section" id="task_disable_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_disable" label="{task_disable_label}"/>
|
||||
<div class="form-control-wrap">
|
||||
<input type="hidden" name="tx_scheduler[disable]" value="0">
|
||||
<input class="checkbox" type="checkbox" name="tx_scheduler[disable]" value="1" id="task_disable" {task_disable}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" id="task_class_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_class" label="{lang}label.class"/>
|
||||
<div class="form-control-wrap">
|
||||
<f:if condition="{task_class}">
|
||||
<f:then>
|
||||
<div>{task_class_title} ({task_class_extension})</div>
|
||||
<input type="hidden" name="tx_scheduler[class]" id="task_class" value="{task_class}">
|
||||
</f:then>
|
||||
<f:else>
|
||||
<select name="tx_scheduler[class]" id="task_class" class="form-control">
|
||||
<f:for each="{groupedClasses}" as="classGroup" key="extension">
|
||||
<optgroup label="{extension}">
|
||||
<f:for each="{classGroup}" as="classInfo" key="class">
|
||||
<option value="{class}" title="{classInfo.description}" {classInfo.selected}>{classInfo.title}</option>
|
||||
</f:for>
|
||||
</optgroup>
|
||||
</f:for>
|
||||
</select>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" id="task_type_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_type" label="{lang}label.type"/>
|
||||
<div class="form-control-wrap">
|
||||
<select name="tx_scheduler[type]" id="task_type" class="form-control">
|
||||
<option value="1" {task_type_selected_1}><f:translate key="{lang}label.type.single" /></option>
|
||||
<option value="2" {task_type_selected_2}><f:translate key="{lang}label.type.recurring" /></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" id="task_group_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_group" label="{lang}label.group"/>
|
||||
<div class="form-control-wrap">
|
||||
<div class="input-group">
|
||||
<select name="tx_scheduler[task_group]" id="task_class" class="form-control">
|
||||
<option value="0" title=""></option>
|
||||
<f:for each="{registeredTaskGroups}" as="registeredTaskGroup">
|
||||
<option value="{registeredTaskGroup.uid}" title="{registeredTaskGroup.groupName}" {registeredTaskGroup.selected}>
|
||||
{registeredTaskGroup.groupName}
|
||||
</option>
|
||||
</f:for>
|
||||
</select>
|
||||
<div class="input-group-btn" role="group">
|
||||
<be:link.newRecord class="btn btn-default" table="tx_scheduler_task_group" returnUrl="{returnUrl}">
|
||||
<core:icon identifier="actions-add" />
|
||||
</be:link.newRecord>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-6" id="task_start_col">
|
||||
<f:be.labels.csh table="{csh}" field="task_start" label="{lang}label.start"/>
|
||||
<div class="form-control-wrap">
|
||||
<div class="input-group" id="tceforms-datetimefield-task_start_row-wrapper">
|
||||
<input name="tx_scheduler[start]_hr" value="{start_value_hr}" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="datetime" data-date-offset="0" type="text" id="tceforms-datetimefield-task_start_row">
|
||||
<input name="tx_scheduler[start]" value="{start_value}" type="hidden">
|
||||
<span class="input-group-btn"><label class="btn btn-default" for="tceforms-datetimefield-task_start_row"><core:icon identifier="actions-edit-pick-date" /></label></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-6" id="task_end_col">
|
||||
<f:be.labels.csh table="{csh}" field="task_end" label="{lang}label.end"/>
|
||||
<div class="form-control-wrap">
|
||||
<div class="input-group" id="tceforms-datetimefield-task_end_row-wrapper">
|
||||
<input name="tx_scheduler[end]_hr" value="{end_value_hr}" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="datetime" data-date-offset="0" type="text" id="tceforms-datetimefield-task_end_row">
|
||||
<input name="tx_scheduler[end]" value="{end_value}" type="hidden">
|
||||
<span class="input-group-btn"><label class="btn btn-default" for="tceforms-datetimefield-task_end_row"><core:icon identifier="actions-edit-pick-date" /></label></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" id="task_frequency_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_frequency" label="{lang}label.frequency.long"/>
|
||||
<div class="form-control-wrap">
|
||||
<div class="form-wizards-wrap">
|
||||
<div class="form-wizards-element">
|
||||
<input type="text" name="tx_scheduler[frequency]" class="form-control" id="task_frequency" value="{frequency}">
|
||||
</div>
|
||||
<f:if condition="{frequencyOptions}">
|
||||
<div class="form-wizards-items-aside">
|
||||
<div class="btn-group">
|
||||
<select
|
||||
class="form-control tceforms-select tceforms-wizardselect"
|
||||
onchange="document.getElementById('task_frequency').value=this.options[this.selectedIndex].value;this.blur();this.selectedIndex=0;">
|
||||
<option></option>
|
||||
<f:for each="{frequencyOptions}" as="label" key="command">
|
||||
<option value="{command}">{f:translate(key:label,default:label)} ({command})</option>
|
||||
</f:for>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</f:if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" id="task_multiple_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_multiple" label="{lang}label.parallel.long"/>
|
||||
<div class="form-control-wrap">
|
||||
<input type="hidden" name="tx_scheduler[multiple]" value="0">
|
||||
<input class="checkbox" type="checkbox" name="tx_scheduler[multiple]" value="1" id="task_multiple" {multiple}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section" id="task_description_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{csh}" field="task_description" label="{lang}label.description"/>
|
||||
<div class="form-control-wrap">
|
||||
<textarea class="form-control" name="tx_scheduler[description]">{description}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<f:for each="{additionalFields}" as="field">
|
||||
<f:render section="additionalField" arguments="{field: field}" />
|
||||
</f:for>
|
||||
|
||||
<f:format.raw>{table}</f:format.raw>
|
||||
|
||||
|
||||
<f:render partial="ServerTime" arguments="{now: now}" />
|
||||
|
||||
<f:section name="additionalField">
|
||||
<div class="form-section extraFields extra_fields_{field.htmlClassName}" {field.additionalFieldsStyle} id="{field.fieldID}_row">
|
||||
<div class="form-group">
|
||||
<f:be.labels.csh table="{field.cshKey}" field="{field.cshLabel}" label="{field.langLabel}"/>
|
||||
<div class="form-control-wrap">
|
||||
{field.code -> f:format.raw()}
|
||||
</div>
|
||||
{field.browseButton -> f:format.raw()}
|
||||
</div>
|
||||
</div>
|
||||
</f:section>
|
||||
@@ -0,0 +1,44 @@
|
||||
{namespace s = TYPO3\CMS\Scheduler\ViewHelpers}
|
||||
|
||||
<p class="lead">
|
||||
<f:translate key="msg.infoScreenIntro" />
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<f:translate key="label.name" />
|
||||
</th>
|
||||
<th>
|
||||
<f:translate key="label.extension" />
|
||||
</th>
|
||||
<th colspan="2">
|
||||
<f:translate key="label.description" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<f:for each="{registeredClasses}" key="class" as="classInfo">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{s:moduleLink(controller:'scheduler', action: 'add', arguments: '{class: class}')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:new')}">
|
||||
{classInfo.title}
|
||||
</a>
|
||||
</td>
|
||||
<td>{classInfo.extension}</td>
|
||||
<td>{classInfo.description}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<span class="btn btn-default">
|
||||
<a href="{s:moduleLink(controller:'scheduler', action: 'add', arguments: '{class: class}')}" title="{f:translate(key:'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:new')}">
|
||||
<core:icon identifier="actions-add" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</f:for>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<f:translate key="msg.noTasksDefined" />
|
||||
@@ -0,0 +1,12 @@
|
||||
<f:render partial="TaskList" arguments="{tasks: tasks, moduleUri: moduleUri, missingClasses: missingClasses}" />
|
||||
|
||||
<button class="btn btn-default" name="go_cron" id="scheduler_executeselected">
|
||||
<core:icon identifier="actions-clock" />
|
||||
<f:translate key="label.cronjobSelected" />
|
||||
</button>
|
||||
<button class="btn btn-default" name="go" id="scheduler_executeselected">
|
||||
<core:icon identifier="actions-play" />
|
||||
<f:translate key="label.executeSelected" />
|
||||
</button>
|
||||
|
||||
<f:render partial="ServerTime" arguments="{now: now}" />
|
||||
@@ -0,0 +1 @@
|
||||
<f:be.infobox state="-1" message="{f:translate(key: 'msg.noTasks')}" />
|
||||
|
After Width: | Height: | Size: 861 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#FF5E49" d="M0 0h64v64H0V0z"/><circle fill="#FFF" cx="32" cy="32" r="13.3"/><path fill="#FF5E49" d="M39 32l-11-6.4v12.8z"/><path opacity=".5" fill="#FFF" d="M23.2 12.7l1.5 2.6c1.7-.7 3.5-1.2 5.3-1.4v-3c-2.4.2-4.7.8-6.8 1.8zM17.3 21.2c1.1-1.5 2.4-2.8 3.9-3.9l-1.5-2.6c-1.9 1.4-3.6 3.1-5 5l2.6 1.5zM13.9 30c.2-1.9.7-3.7 1.4-5.3l-2.6-1.5c-1 2.1-1.6 4.4-1.8 6.8h3zM17.3 42.8l-2.6 1.5c1.4 1.9 3.1 3.6 5 5l1.5-2.6c-1.5-1.1-2.8-2.4-3.9-3.9z"/><path fill="#FFF" d="M34 10.9v3c9.1 1 16.3 8.7 16.3 18.1S43.1 49.1 34 50.1v3C44.8 52.1 53.3 43 53.3 32S44.8 11.9 34 10.9z"/><path opacity=".5" fill="#FFF" d="M15.3 39.3c-.7-1.7-1.2-3.5-1.4-5.3h-3c.2 2.4.9 4.7 1.8 6.8l2.6-1.5zM30 50.1c-1.9-.2-3.7-.7-5.3-1.4l-1.5 2.6c2.1 1 4.4 1.6 6.8 1.8v-3z"/></svg>
|
||||
|
After Width: | Height: | Size: 808 B |
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module: TYPO3/CMS/Scheduler/PageBrowser
|
||||
* Javascript for adding links for calling the page browser pop up
|
||||
*/
|
||||
define(['jquery'], function($) {
|
||||
'use strict';
|
||||
|
||||
$(document).on('click', '.t3js-pageBrowser', function() {
|
||||
var $this = $(this),
|
||||
browserWin = "",
|
||||
pageUrl = $this.data('url');
|
||||
|
||||
browserWin = window.open(pageUrl, "Typo3WinBrowser", "height=650,width=800,status=0,menubar=0,resizable=1,scrollbars=1");
|
||||
browserWin.focus();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module: TYPO3/CMS/Scheduler/Scheduler
|
||||
*/
|
||||
define(['jquery',
|
||||
'TYPO3/CMS/Backend/SplitButtons',
|
||||
'datatables'
|
||||
], function($, SplitButtons) {
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {{}}
|
||||
* @exports TYPO3/CMS/Scheduler/Scheduler
|
||||
*/
|
||||
var Scheduler = {};
|
||||
|
||||
var allCheckedStatus = false;
|
||||
|
||||
/**
|
||||
* This method reacts on changes to the task class
|
||||
* It switches on or off the relevant extra fields
|
||||
*
|
||||
* @param {Object} theSelector
|
||||
*/
|
||||
Scheduler.actOnChangedTaskClass = function(theSelector) {
|
||||
var taskClass = theSelector.val();
|
||||
taskClass = taskClass.toLowerCase().replace(/\\/g, '-');
|
||||
|
||||
// Hide all extra fields
|
||||
$('.extraFields').hide();
|
||||
// Show only relevant extra fields
|
||||
$('.extra_fields_' + taskClass).show();
|
||||
};
|
||||
|
||||
/**
|
||||
* This method reacts on changes to the type of a task, i.e. single or recurring
|
||||
*/
|
||||
Scheduler.actOnChangedTaskType = function() {
|
||||
Scheduler.toggleFieldsByTaskType($(this).val());
|
||||
};
|
||||
|
||||
/**
|
||||
* This method reacts on field changes of all table field for table garbage collection task
|
||||
*
|
||||
* @param {Object} theCheckbox
|
||||
*/
|
||||
Scheduler.actOnChangeSchedulerTableGarbageCollectionAllTables = function(theCheckbox) {
|
||||
var $numberOfDays = $('#task_tableGarbageCollection_numberOfDays');
|
||||
var $taskTableGarbageCollectionTable = $('#task_tableGarbageCollection_table');
|
||||
if (theCheckbox.prop('checked')) {
|
||||
$taskTableGarbageCollectionTable.prop('disabled', true);
|
||||
$numberOfDays.prop('disabled', true);
|
||||
} else {
|
||||
// Get number of days for selected table
|
||||
var numberOfDays = parseInt($numberOfDays.val());
|
||||
if (numberOfDays < 1) {
|
||||
var selectedTable = $taskTableGarbageCollectionTable.val();
|
||||
if (typeof(defaultNumberOfDays[selectedTable]) !== 'undefined') {
|
||||
numberOfDays = defaultNumberOfDays[selectedTable];
|
||||
}
|
||||
}
|
||||
|
||||
$taskTableGarbageCollectionTable.prop('disabled', false);
|
||||
if (numberOfDays > 0) {
|
||||
$numberOfDays.prop('disabled', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This methods set the 'number of days' field to the default expire period
|
||||
* of the selected table
|
||||
*
|
||||
* @param {Object} theSelector
|
||||
*/
|
||||
Scheduler.actOnChangeSchedulerTableGarbageCollectionTable = function(theSelector) {
|
||||
var $numberOfDays = $('#task_tableGarbageCollection_numberOfDays');
|
||||
if (defaultNumberOfDays[theSelector.val()] > 0) {
|
||||
$numberOfDays.prop('disabled', false);
|
||||
$numberOfDays.val(defaultNumberOfDays[theSelector.val()]);
|
||||
} else {
|
||||
$numberOfDays.prop('disabled', true);
|
||||
$numberOfDays.val(0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check or uncheck all checkboxes
|
||||
*
|
||||
* @param {Object} theSelector
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Scheduler.checkOrUncheckAllCheckboxes = function(theSelector) {
|
||||
theSelector.parents('.tx_scheduler_mod1_table').find(':checkbox').prop('checked', !allCheckedStatus);
|
||||
allCheckedStatus = !allCheckedStatus;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the relevant form fields by task type
|
||||
*
|
||||
* @param {Integer} taskType
|
||||
*/
|
||||
Scheduler.toggleFieldsByTaskType = function(taskType) {
|
||||
// Single task option = 1, Recurring task option = 2
|
||||
taskType = parseInt(taskType);
|
||||
$('#task_end_col').toggle(taskType === 2);
|
||||
$('#task_frequency_row').toggle(taskType === 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the visibility of task groups by clicking anywhere on the
|
||||
* task group header
|
||||
*
|
||||
* @param {Object} theSelector
|
||||
*/
|
||||
Scheduler.toggleTaskGroups = function(theSelector) {
|
||||
var taskGroup = theSelector.data('task-group-id');
|
||||
$('#recordlist-task-group-' + taskGroup).collapse('toggle');
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers listeners
|
||||
*/
|
||||
Scheduler.initializeEvents = function() {
|
||||
$('.checkall').on('click', function() {
|
||||
Scheduler.checkOrUncheckAllCheckboxes($(this));
|
||||
});
|
||||
|
||||
$('#task_class').change(function() {
|
||||
Scheduler.actOnChangedTaskClass($(this));
|
||||
});
|
||||
|
||||
$('#task_type').change(Scheduler.actOnChangedTaskType);
|
||||
|
||||
$('#task_tableGarbageCollection_allTables').change(function() {
|
||||
Scheduler.actOnChangeSchedulerTableGarbageCollectionAllTables($(this));
|
||||
});
|
||||
|
||||
$('#task_tableGarbageCollection_table').change(function() {
|
||||
Scheduler.actOnChangeSchedulerTableGarbageCollectionTable($(this));
|
||||
});
|
||||
|
||||
$('.taskGroup').on('click', function() {
|
||||
Scheduler.toggleTaskGroups($(this));
|
||||
});
|
||||
|
||||
$('table.taskGroup-table').DataTable({
|
||||
"paging": false,
|
||||
"searching": false
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize default states
|
||||
*/
|
||||
Scheduler.initializeDefaultStates = function() {
|
||||
var $taskType = $('#task_type');
|
||||
if ($taskType.length) {
|
||||
Scheduler.toggleFieldsByTaskType($taskType.val());
|
||||
}
|
||||
var $taskClass = $('#task_class');
|
||||
if ($taskClass.length) {
|
||||
Scheduler.actOnChangedTaskClass($taskClass);
|
||||
}
|
||||
};
|
||||
|
||||
$(Scheduler.initializeEvents);
|
||||
$(Scheduler.initializeDefaultStates);
|
||||
|
||||
SplitButtons.addPreSubmitCallback(function() {
|
||||
var taskClass = $('#task_class').val();
|
||||
taskClass = taskClass.toLowerCase().replace(/\\/g, '-');
|
||||
|
||||
$('.extraFields').appendTo($('#extraFieldsHidden'));
|
||||
$('.extra_fields_' + taskClass).appendTo($('#extraFieldsSection'));
|
||||
});
|
||||
|
||||
return Scheduler;
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "typo3/cms-scheduler",
|
||||
"type": "typo3-cms-framework",
|
||||
"description": "The TYPO3 Scheduler let's you register tasks to happen at a specific time",
|
||||
"homepage": "https://typo3.org",
|
||||
"license": ["GPL-2.0-or-later"],
|
||||
"authors": [{
|
||||
"name": "TYPO3 Core Team",
|
||||
"email": "typo3cms@typo3.org",
|
||||
"role": "Developer"
|
||||
}],
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"typo3/cms-core": "9.5.31"
|
||||
},
|
||||
"conflict": {
|
||||
"typo3/cms": "*"
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "scheduler"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TYPO3\\CMS\\Scheduler\\": "Classes/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"TYPO3\\CMS\\Scheduler\\Tests\\": "Tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# cat=basic//; type=string; label=LLL:EXT:scheduler/Resources/Private/Language/locallang_em.xlf:scheduler.config.maxLifetime
|
||||
maxLifetime = 1440
|
||||
|
||||
# cat=basic//; type=boolean; label=LLL:EXT:scheduler/Resources/Private/Language/locallang_em.xlf:scheduler.config.showSampleTasks
|
||||
showSampleTasks = 1
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'Scheduler',
|
||||
'description' => 'The TYPO3 Scheduler let\'s you register tasks to happen at a specific time',
|
||||
'category' => 'misc',
|
||||
'version' => '9.5.31',
|
||||
'state' => 'stable',
|
||||
'uploadfolder' => 0,
|
||||
'createDirs' => '',
|
||||
'clearCacheOnLoad' => 0,
|
||||
'author' => 'TYPO3 Core Team',
|
||||
'author_email' => 'typo3cms@typo3.org',
|
||||
'author_company' => '',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '9.5.31',
|
||||
],
|
||||
'conflicts' => [],
|
||||
'suggests' => [],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
defined('TYPO3_MODE') or die();
|
||||
|
||||
// Get the extensions's configuration
|
||||
$showSampleTasks = (bool)\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
|
||||
\TYPO3\CMS\Core\Configuration\ExtensionConfiguration::class
|
||||
)->get('scheduler', 'showSampleTasks');
|
||||
// If sample tasks should be shown,
|
||||
// register information for the test and sleep tasks
|
||||
if ($showSampleTasks) {
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Example\TestTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:testTask.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:testTask.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Example\TestTaskAdditionalFieldProvider::class
|
||||
];
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Example\SleepTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:sleepTask.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:sleepTask.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Example\SleepTaskAdditionalFieldProvider::class
|
||||
];
|
||||
}
|
||||
unset($showSampleTasks);
|
||||
|
||||
// Add caching framework garbage collection task
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\CachingFrameworkGarbageCollectionTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:cachingFrameworkGarbageCollection.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:cachingFrameworkGarbageCollection.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\CachingFrameworkGarbageCollectionAdditionalFieldProvider::class
|
||||
];
|
||||
|
||||
// Add task to index file in a storage
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\FileStorageIndexingTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageIndexing.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageIndexing.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\FileStorageIndexingAdditionalFieldProvider::class
|
||||
];
|
||||
|
||||
// Add task for extracting metadata from files in a storage
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\FileStorageExtractionTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageExtraction.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:fileStorageExtraction.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\FileStorageExtractionAdditionalFieldProvider::class
|
||||
|
||||
];
|
||||
|
||||
// Add recycler directory cleanup task
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\RecyclerGarbageCollectionTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:recyclerGarbageCollection.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:recyclerGarbageCollection.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\RecyclerGarbageCollectionAdditionalFieldProvider::class
|
||||
];
|
||||
|
||||
// Add execute schedulable command task
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\ExecuteSchedulableCommandTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:executeSchedulableCommandTask.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:executeSchedulableCommandTask.name',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\ExecuteSchedulableCommandAdditionalFieldProvider::class
|
||||
];
|
||||
|
||||
// Save any previous option array for table garbage collection task
|
||||
// to temporary variable so it can be pre-populated by other
|
||||
// extensions and LocalConfiguration/AdditionalConfiguration
|
||||
$garbageCollectionTaskOptions = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options'] ?? [];
|
||||
$garbageCollectionTaskOptions['tables'] = $garbageCollectionTaskOptions['tables'] ?? [];
|
||||
// Add table garbage collection task
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tableGarbageCollection.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:tableGarbageCollection.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\TableGarbageCollectionAdditionalFieldProvider::class,
|
||||
'options' => $garbageCollectionTaskOptions
|
||||
];
|
||||
unset($garbageCollectionTaskOptions);
|
||||
|
||||
// Register sys_log and sys_history table in table garbage collection task
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']['sys_log'] ?? false)) {
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']['sys_log'] = [
|
||||
'dateField' => 'tstamp',
|
||||
'expirePeriod' => 180
|
||||
];
|
||||
}
|
||||
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']['sys_history'] ?? false)) {
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\TableGarbageCollectionTask::class]['options']['tables']['sys_history'] = [
|
||||
'dateField' => 'tstamp',
|
||||
'expirePeriod' => 30
|
||||
];
|
||||
}
|
||||
|
||||
// Save any previous option array for ip anonymization task
|
||||
// to temporary variable so it can be pre-populated by other
|
||||
// extensions and LocalConfiguration/AdditionalConfiguration
|
||||
$ipAnonymizeCollectionTaskOptions = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\IpAnonymizationTask::class]['options'] ?? [];
|
||||
$ipAnonymizeCollectionTaskOptions['tables'] = $ipAnonymizeCollectionTaskOptions['tables'] ?? [];
|
||||
// Add ip anonymization task
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\IpAnonymizationTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:ipAnonymization.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:ipAnonymization.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\IpAnonymizationAdditionalFieldProvider::class,
|
||||
'options' => $ipAnonymizeCollectionTaskOptions
|
||||
];
|
||||
unset($ipAnonymizeCollectionTaskOptions);
|
||||
|
||||
if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\IpAnonymizationTask::class]['options']['tables']['sys_log'] ?? false)) {
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\IpAnonymizationTask::class]['options']['tables']['sys_log'] = [
|
||||
'dateField' => 'tstamp',
|
||||
'ipField' => 'IP'
|
||||
];
|
||||
}
|
||||
|
||||
// Add task for optimizing database tables
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Scheduler\Task\OptimizeDatabaseTableTask::class] = [
|
||||
'extension' => 'scheduler',
|
||||
'title' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:optimizeDatabaseTable.name',
|
||||
'description' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:optimizeDatabaseTable.description',
|
||||
'additionalFields' => \TYPO3\CMS\Scheduler\Task\OptimizeDatabaseTableAdditionalFieldProvider::class
|
||||
|
||||
];
|
||||
|
||||
// Available frequency options
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['frequencyOptions'] = [
|
||||
'0 9,15 * * 1-5' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example1',
|
||||
'0 */2 * * *' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example2',
|
||||
'*/20 * * * *' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example3',
|
||||
'0 7 * * 2' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:command.example4',
|
||||
];
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
defined('TYPO3_MODE') or die();
|
||||
|
||||
// Add module
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
|
||||
'system',
|
||||
'txschedulerM1',
|
||||
'',
|
||||
'',
|
||||
[
|
||||
'routeTarget' => \TYPO3\CMS\Scheduler\Controller\SchedulerModuleController::class . '::mainAction',
|
||||
'access' => 'admin',
|
||||
'name' => 'system_txschedulerM1',
|
||||
'icon' => 'EXT:scheduler/Resources/Public/Icons/module-scheduler.svg',
|
||||
'labels' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf'
|
||||
]
|
||||
);
|
||||
|
||||
// Add context sensitive help (csh) to the backend module
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr(
|
||||
'_MOD_system_txschedulerM1',
|
||||
'EXT:scheduler/Resources/Private/Language/locallang_csh_scheduler.xlf'
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Table structure for table 'tx_scheduler_task'
|
||||
#
|
||||
CREATE TABLE tx_scheduler_task (
|
||||
uid int(11) unsigned NOT NULL auto_increment,
|
||||
crdate int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
disable tinyint(4) unsigned DEFAULT '0' NOT NULL,
|
||||
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
|
||||
description text,
|
||||
nextexecution int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
lastexecution_time int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
lastexecution_failure text,
|
||||
lastexecution_context char(3) DEFAULT '' NOT NULL,
|
||||
serialized_task_object mediumblob,
|
||||
serialized_executions mediumblob,
|
||||
task_group int(11) unsigned DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (uid),
|
||||
KEY index_nextexecution (nextexecution)
|
||||
);
|
||||
|
||||
#
|
||||
# Table structure for table 'tx_scheduler_task_group'
|
||||
#
|
||||
CREATE TABLE tx_scheduler_task_group (
|
||||
groupName varchar(80) DEFAULT '' NOT NULL,
|
||||
description text
|
||||
);
|
||||