From 20e78faf8cffd949db00b8f461fc6689dfdf9a39 Mon Sep 17 00:00:00 2001 From: Claus Due Date: Sat, 26 Nov 2011 20:46:54 +0100 Subject: [PATCH] [FEATURE] Run CommandController commands through Scheduler This feature allows any Extbase CommandController command to be executed through the scheduler. Argument values are fully supported and both action and argument names can be translated through locallang. Task name and arguments are displayed in the Task overview. Change-Id: I91cf580db97da50fb34d8f9c64b453d96b21f303 Resolves: #32107 --- Classes/MVC/Controller/CommandController.php | 8 +- Classes/Scheduler/FieldProvider.php | 313 +++++++++++++++++++ Classes/Scheduler/Task.php | 155 +++++++++ Classes/Scheduler/TaskExecutor.php | 147 +++++++++ Resources/Private/Language/locallang_db.xlf | 9 + ext_tables.php | 7 + 6 files changed, 635 insertions(+), 4 deletions(-) create mode 100644 Classes/Scheduler/FieldProvider.php create mode 100644 Classes/Scheduler/Task.php create mode 100644 Classes/Scheduler/TaskExecutor.php diff --git a/Classes/MVC/Controller/CommandController.php b/Classes/MVC/Controller/CommandController.php index 5285df62..9a3097e3 100644 --- a/Classes/MVC/Controller/CommandController.php +++ b/Classes/MVC/Controller/CommandController.php @@ -96,10 +96,10 @@ public function canProcessRequest(Tx_Extbase_MVC_RequestInterface $request) { /** * Processes a command line request. * - * @param \TYPO3\FLOW3\MVC\RequestInterface $request The request object - * @param \TYPO3\FLOW3\MVC\ResponseInterface $response The response, modified by this controller + * @param Tx_Extbase_MVC_RequestInterface $request The request object + * @param Tx_Extbase_MVC_ResponseInterface $response The response, modified by this controller * @return void - * @throws \TYPO3\FLOW3\MVC\Exception\UnsupportedRequestTypeException if the controller doesn't support the current request type + * @throws Tx_Extbase_MVC_Exception_UnsupportedRequestTypeException if the controller doesn't support the current request type * @author Robert Lemke * @api */ @@ -171,7 +171,7 @@ protected function mapRequestArgumentsToControllerArguments() { $argument->setValue($this->request->getArgument($argumentName)); } elseif ($argument->isRequired()) { $exception = new Tx_Extbase_MVC_Exception_Command('Required argument "' . $argumentName . '" is not set.', 1306755520); - $this->forward('error', 'TYPO3\FLOW3\Command\HelpCommandController', array('exception' => $exception)); + $this->forward('error', 'Tx_Extbase_Command_HelpCommandController', array('exception' => $exception)); } } } diff --git a/Classes/Scheduler/FieldProvider.php b/Classes/Scheduler/FieldProvider.php new file mode 100644 index 00000000..4bfb4880 --- /dev/null +++ b/Classes/Scheduler/FieldProvider.php @@ -0,0 +1,313 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +/** + * Field provider for Extbase CommandController Scheduler task + * + * @package Extbase + * @subpackage Scheduler + */ +class Tx_Extbase_Scheduler_FieldProvider implements Tx_Scheduler_AdditionalFieldProvider { + + /** + * @var Tx_Extbase_MVC_CLI_CommandManager + */ + protected $commandManager; + + /** + * @var Tx_Extbase_Object_ObjectManagerInterface + */ + protected $objectManager; + + /** + * @var Tx_Extbase_Reflection_Service + */ + protected $reflectionService; + + /** + * @var Tx_Extbase_Scheduler_Task + */ + protected $task; + + /** + * Constructor + * + * @param Tx_Extbase_Object_ObjectManagerInterface $objectManager + * @param Tx_Extbase_MVC_CLI_CommandManager $commandManager + * @param Tx_Extbase_Reflection_Service $reflectionService + */ + public function __construct(Tx_Extbase_Object_ObjectManagerInterface $objectManager = NULL, Tx_Extbase_MVC_CLI_CommandManager $commandManager = NULL, Tx_Extbase_Reflection_Service $reflectionService = NULL) { + $this->objectManager = $objectManager !== NULL ? $objectManager : t3lib_div::makeInstance('Tx_Extbase_Object_ObjectManager'); + $this->commandManager = $commandManager !== NULL ? $commandManager : $this->objectManager->get('Tx_Extbase_MVC_CLI_CommandManager'); + $this->reflectionService = $reflectionService !== NULL ? $reflectionService : $this->objectManager->get('Tx_Extbase_Reflection_Service'); + } + + /** + * Render additional information fields within the scheduler backend. + * + * @param array $taskInfo Array information of task to return + * @param mixed $task Tx_Extbase_Scheduler_Task or tx_scheduler_Execution instance + * @param Tx_Scheduler_Module $schedulerModule Reference to the calling object (BE module of the Scheduler) + * @return array Additional fields + * @see interfaces/tx_scheduler_AdditionalFieldProvider#getAdditionalFields($taskInfo, $task, $schedulerModule) + */ + public function getAdditionalFields(array &$taskInfo, $task, Tx_Scheduler_Module $schedulerModule) { + $this->task = $task; + if ($this->task !== NULL) { + $this->task->setScheduler(); + } + $fields = array(); + $fields['action'] = $this->getCommandControllerActionField(); + if ($this->task !== NULL && $this->task->getCommandIdentifier()) { + $command = $this->commandManager->getCommandByIdentifier($this->task->getCommandIdentifier()); + $fields['description'] = $this->getCommandControllerActionDescriptionField(); + $argumentFields = $this->getCommandControllerActionArgumentFields($command->getArgumentDefinitions()); + $fields = array_merge($fields, $argumentFields); + $this->task->save(); + } + return $fields; + } + + /** + * Validates additional selected fields + * + * @param array $submittedData + * @param Tx_Scheduler_Module $schedulerModule + * @return boolean + */ + public function validateAdditionalFields(array &$submittedData, Tx_Scheduler_Module $schedulerModule) { + return TRUE; + } + + /** + * Saves additional field values + * + * @param array $submittedData + * @param Tx_Scheduler_Task $task + * @return boolean + */ + public function saveAdditionalFields(array $submittedData, Tx_Scheduler_Task $task) { + $task->setCommandIdentifier($submittedData['task_extbase']['action']); + $task->setArguments($submittedData['task_extbase']['arguments']); + return TRUE; + } + + /** + * Get description of selected command + * + * @return string + */ + protected function getCommandControllerActionDescriptionField() { + $command = $this->commandManager->getCommandByIdentifier($this->task->getCommandIdentifier()); + return array( + 'code' => '', + 'label' => '' . $command->getDescription() . '' + ); + } + + /** + * Gets a select field containing all possible CommandController actions + * + * @return array + */ + protected function getCommandControllerActionField() { + $commands = $this->commandManager->getAvailableCommands(); + $options = array(); + foreach ($commands as $command) { + if ($command->isInternal() === FALSE) { + $classNameParts = explode('_', $command->getControllerClassName()); + $identifier = $command->getCommandIdentifier(); + $options[$identifier] = $classNameParts[1] . ' ' . str_replace('CommandController', '', $classNameParts[3]) . ': ' . $command->getControllerCommandName(); + } + } + $name = "action"; + $currentlySelectedCommand = $this->task !== NULL ? $this->task->getCommandIdentifier() : NULL; + return array( + 'code' => $this->renderSelectField($name, $options, $currentlySelectedCommand), + 'label' => $this->getActionLabel() + ); + } + + /** + * Gets a set of fields covering arguments which must be sent to $currentControllerAction. + * Also registers the default values of those fields with the Task, allowing + * them to be read upon execution. + * + * @param array $argumentDefinitions + * @return array + */ + protected function getCommandControllerActionArgumentFields(array $argumentDefinitions) { + $fields = array(); + $argumentValues = $this->task->getArguments(); + foreach ($argumentDefinitions as $index=>$argument) { + $name = $argument->getName(); + $defaultValue = $this->getDefaultArgumentValue($argument); + $this->task->addDefaultValue($name, $defaultValue); + $value = isset($argumentValues[$name]) ? $argumentValues[$name] : $defaultValue; + $fields[$name] = array( + 'code' => $this->renderField($argument, $value), + 'label' => $this->getArgumentLabel($argument) + ); + } + return $fields; + } + + /** + * Gets a label for $key based on either provided extension or currently + * selected CommandController extension,ยด + * + * @param string $localLanguageKey + * @param string $extensionName + * @return string + */ + protected function getLanguageLabel($localLanguageKey, $extensionName = NULL) { + if (!$extensionName) { + list ($extensionName, $commandControllerName, $commandName) = explode(':', $this->task->getCommandIdentifier()); + } + $label = Tx_Extbase_Utility_Localization::translate($localLanguageKey, $extensionName); + return $label; + } + + /** + * Gets the data type required for the argument value + * + * @param Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument + * @return string the argument type + */ + protected function getArgumentType(Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument) { + $command = $this->commandManager->getCommandByIdentifier($this->task->getCommandIdentifier()); + $controllerClassName = $command->getControllerClassName(); + $methodName = $command->getControllerCommandName() . 'Command'; + $tags = $this->reflectionService->getMethodTagsValues($controllerClassName, $methodName); + foreach ($tags['param'] as $tag) { + list ($argumentType, $argumentVariableName) = explode(' ', $tag); + if (substr($argumentVariableName, 1) === $argument->getName()) { + return $argumentType; + } + } + } + + /** + * Get a human-readable label for a command argument + * + * @param Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument + * @return string + */ + protected function getArgumentLabel(Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument) { + $argumentName = $argument->getName(); + list ($extensionName, $commandControllerName, $commandName) = explode(':', $this->task->getCommandIdentifier()); + $path = array('command', $commandControllerName, $commandName, 'arguments', $argumentName); + $labelNameIndex = implode('.', $path); + $label = $this->getLanguageLabel($labelNameIndex); + if (!$label) { + $label = 'Argument: ' . $argumentName; + } + $descriptionIndex = $labelNameIndex . '.description'; + $description = $this->getLanguageLabel($descriptionIndex); + if (strlen($description) === 0) { + $description = $argument->getDescription(); + } + if (strlen($description) > 0) { + $label .= '. ' . htmlspecialchars($description) . ''; + } + return $label; + } + + /** + * Gets the default value of argument + * + * @param Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument + * @return mixed + */ + protected function getDefaultArgumentValue(Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument) { + $type = $this->getArgumentType($argument); + $argumentName = $argument->getName(); + $command = $this->commandManager->getCommandByIdentifier($this->task->getCommandIdentifier()); + $argumentReflection = $this->reflectionService->getMethodParameters($command->getControllerClassName(), $command->getControllerCommandName() . 'Command'); + $defaultValue = $argumentReflection[$argumentName]['defaultValue']; + if ($type === 'boolean') { + $defaultValue = ((bool) $defaultValue) ? 1 : 0; + } + return $defaultValue; + } + + /** + * Get a human-readable label for the action field + * + * @return string + */ + protected function getActionLabel() { + $index = 'task.action'; + $label = $this->getLanguageLabel($index, 'extbase'); + if (!$label) { + $label = 'CommandController Command. Save and reopen to define command arguments'; + } + return $label; + } + + /** + * Render a select field with name $name and options $options + * + * @param string $name + * @param array $options + * @param string $selectedOptionValue + * @return string + */ + protected function renderSelectField($name, array $options, $selectedOptionValue) { + $html = array( + ''); + return implode(LF, $html); + } + + /** + * Renders a field for defining an argument's value + * + * @param Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument + * @param mixed $currentValue + * @return string + */ + protected function renderField(Tx_Extbase_MVC_CLI_CommandArgumentDefinition $argument, $currentValue) { + $type = $this->getArgumentType($argument); + $name = $argument->getName(); + $fieldName = 'tx_scheduler[task_extbase][arguments][' . htmlspecialchars($name) . ']'; + if ($type === 'boolean') { + // checkbox field for boolean values. + $html = ''; + $html .= ''; + } else { + // regular string, also the default field type + $html = ' '; + } + return $html; + } + +} + +?> diff --git a/Classes/Scheduler/Task.php b/Classes/Scheduler/Task.php new file mode 100644 index 00000000..5f697939 --- /dev/null +++ b/Classes/Scheduler/Task.php @@ -0,0 +1,155 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +/** + * Scheduler task to execute CommandController commands + * + * @package Extbase + * @subpackage Scheduler + */ +class Tx_Extbase_Scheduler_Task extends Tx_Scheduler_Task { + + /** + * @var string + */ + protected $commandIdentifier; + + /** + * @var array + */ + protected $arguments = array(); + + /** + * @var array + */ + protected $defaults = array(); + + /** + * @var Tx_Extbase_Object_ObjectManagerInterface + */ + protected $objectManager; + + /** + * @var Tx_Extbase_MVC_CLI_CommandManager + */ + protected $commandManager; + + /** + * @var Tx_Extbase_Scheduler_TaskExecutor + */ + protected $taskExecutor; + + /** + * Function execute from the Scheduler + * + * @return boolean TRUE on successful execution, FALSE on error + */ + public function execute() { + $this->objectManager = t3lib_div::makeInstance('Tx_Extbase_Object_ObjectManager'); + $this->commandManager = $this->objectManager->get('Tx_Extbase_MVC_CLI_CommandManager'); + $this->taskExecutor = $this->objectManager->get('Tx_Extbase_Scheduler_TaskExecutor'); + try { + $this->taskExecutor->execute($this); + return TRUE; + } catch (Exception $e) { + t3lib_div::sysLog($e->getMessage(), $this->commandIdentifier, 3); + return FALSE; + } + } + + /** + * @param string $commandIdentifier + */ + public function setCommandIdentifier($commandIdentifier) { + $this->commandIdentifier = $commandIdentifier; + } + + /** + * @return string + */ + public function getCommandIdentifier() { + return $this->commandIdentifier; + } + + /** + * @param array $arguments + */ + public function setArguments($arguments) { + $this->arguments = $arguments; + } + + /** + * @return array + */ + public function getArguments() { + return $this->arguments; + } + + /** + * @param array $defaults + */ + public function setDefaults(array $defaults) { + $this->defaults = $defaults; + } + + /** + * @return array + */ + public function getDefaults() { + return $this->defaults; + } + + /** + * @param string $argumentName + * @param mixed $argumentValue + */ + public function addDefaultValue($argumentName, $argumentValue) { + if (is_bool($argumentValue)) { + $argumentValue = intval($argumentValue); + } + $this->defaults[$argumentName] = $argumentValue; + } + + /** + * Return a text representation of the selected command and arguments + * + * @return string Information to display + */ + public function getAdditionalInformation() { + $label = $this->commandIdentifier; + if (count($this->arguments) > 0) { + $arguments = array(); + foreach ($this->arguments as $argumentName=>$argumentValue) { + if ($argumentValue != $this->defaults[$argumentName]) { + array_push($arguments, $argumentName . '=' . $argumentValue); + } + } + $label .= ' ' . implode(', ', $arguments); + } + return $label; + } + +} + +?> \ No newline at end of file diff --git a/Classes/Scheduler/TaskExecutor.php b/Classes/Scheduler/TaskExecutor.php new file mode 100644 index 00000000..5c0341b8 --- /dev/null +++ b/Classes/Scheduler/TaskExecutor.php @@ -0,0 +1,147 @@ + +* All rights reserved +* +* This script is part of the TYPO3 project. The TYPO3 project is +* free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* The GNU General Public License can be found at +* http://www.gnu.org/copyleft/gpl.html. +* +* This script is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* This copyright notice MUST APPEAR in all copies of the script! +***************************************************************/ + +/** + * Task Executor + * + * Takes a Tx_Extbase_Scheduler_Task and executes the CommandController command + * defined therein. + * + * @package Extbase + * @subpackage Scheduler + */ +class Tx_Extbase_Scheduler_TaskExecutor implements t3lib_Singleton { + + /** + * @var Tx_Extbase_Object_ObjectManagerInterface + */ + protected $objectManager; + + /** + * @var Tx_Extbase_MVC_CLI_CommandManager + */ + protected $commandManager; + + /** + * @var Tx_Extbase_Configuration_ConfigurationManagerInterface + */ + protected $configurationManager; + + /** + * @param Tx_Extbase_Object_ObjectManager $objectManager + */ + public function injectObjectManager(Tx_Extbase_Object_ObjectManager $objectManager) { + $this->objectManager = $objectManager; + } + + /** + * @param Tx_Extbase_MVC_CLI_CommandManager $commandManager + */ + public function injectCommandManager(Tx_Extbase_MVC_CLI_CommandManager $commandManager) { + $this->commandManager = $commandManager; + } + + /** + * @param Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager + */ + public function injectConfigurationManager(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) { + $this->configurationManager = $configurationManager; + } + + /** + * Initializes configuration manager, object container and reflection service + * + * @param array $configuration + * @return void + */ + protected function initialize(array $configuration) { + // initialize configuration + $this->configurationManager->setContentObject(t3lib_div::makeInstance('tslib_cObj')); + $this->configurationManager->setConfiguration($configuration); + + // configure object container + $typoScriptSetup = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT); + if (isset($typoScriptSetup['config.']['tx_extbase.']['objects.'])) { + $objectContainer = t3lib_div::makeInstance('Tx_Extbase_Object_Container_Container'); + foreach ($typoScriptSetup['config.']['tx_extbase.']['objects.'] as $classNameWithDot => $classConfiguration) { + if (isset($classConfiguration['className'])) { + $originalClassName = rtrim($classNameWithDot, '.'); + $objectContainer->registerImplementation($originalClassName, $classConfiguration['className']); + } + } + } + + // initialize reflection + $reflectionService = $this->objectManager->get('Tx_Extbase_Reflection_Service'); + $reflectionService->setDataCache($GLOBALS['typo3CacheManager']->getCache('extbase_reflection')); + if (!$reflectionService->isInitialized()) { + $reflectionService->initialize(); + } + } + + /** + * Execute Task + * + * If errors occur during Task execution they are thrown as Exceptions which + * must be caught manually if you manually execute Tasks through your code. + * + * @param Tx_Extbase_Scheduler_Task $task the task to execute + * @return void + */ + public function execute(Tx_Extbase_Scheduler_Task $task) { + $commandIdentifier = $task->getCommandIdentifier(); + list ($extensionKey, $controllerName, $commandName) = explode(':', $commandIdentifier); + $extensionName = t3lib_div::underscoredToUpperCamelCase($extensionKey); + $this->initialize(array('extensionName' => $extensionName)); + + $request = $this->objectManager->create('Tx_Extbase_MVC_CLI_Request'); + $dispatcher = $this->objectManager->get('Tx_Extbase_MVC_Dispatcher'); + $response = $this->objectManager->create('Tx_Extbase_MVC_CLI_Response'); + + // execute command + $command = $this->commandManager->getCommandByIdentifier($commandIdentifier); + $request->setControllerObjectName($command->getControllerClassName()); + $request->setControllerCommandName($command->getControllerCommandName()); + $request->setArguments($task->getArguments()); + $dispatcher->dispatch($request, $response); + + $this->shutdown(); + } + + /** + * Resets framework singletons + * + * @return void + */ + protected function shutdown() { + // shutdown + $persistenceManager = $this->objectManager->get('Tx_Extbase_Persistence_Manager'); + $persistenceManager->persistAll(); + $reflectionService = $this->objectManager->get('Tx_Extbase_Reflection_Service'); + $reflectionService->shutdown(); + } + +} + +?> \ No newline at end of file diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf index f98d8a4a..3a569762 100644 --- a/Resources/Private/Language/locallang_db.xlf +++ b/Resources/Private/Language/locallang_db.xlf @@ -21,6 +21,15 @@ Tx_Extbase_Domain_Model_FrontendUserGroup + + CommandController Command + + + Extbase CommandController Task + + + Allows Extbase CommandController commands to be configured and executed through the scheduler framework. + diff --git a/ext_tables.php b/ext_tables.php index f9c12aac..9297d015 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -56,4 +56,11 @@ } $TCA['fe_groups']['types']['Tx_Extbase_Domain_Model_FrontendUserGroup'] = $TCA['fe_groups']['types']['0']; +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks']['Tx_Extbase_Scheduler_Task'] = array( + 'extension' => $_EXTKEY, + 'title' => 'LLL:EXT:extbase/Resources/Private/Language/locallang_db.xml:task.name', + 'description' => 'LLL:EXT:extbase/Resources/Private/Language/locallang_db.xml:task.description', + 'additionalFields' => 'Tx_Extbase_Scheduler_FieldProvider' +); + ?> \ No newline at end of file