first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user