first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
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();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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]);
|
||||
}
|
||||
}
|
||||
+143
@@ -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';
|
||||
}
|
||||
+451
@@ -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);
|
||||
}
|
||||
}
|
||||
+100
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+265
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user