diff --git a/cli/Commands/Cronjobs/CronjobExecute.php b/cli/Commands/Cronjobs/CronjobExecute.php index 23592a1bac9af9decf84b55c97a0697984744182..d4789ea84ba58af141e52b8912612b8982b4f85c 100644 --- a/cli/Commands/Cronjobs/CronjobExecute.php +++ b/cli/Commands/Cronjobs/CronjobExecute.php @@ -5,7 +5,11 @@ namespace Studip\Cli\Commands\Cronjobs; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; class CronjobExecute extends Command { @@ -15,28 +19,115 @@ class CronjobExecute extends Command { $this->setDescription('Execute cronjob task.'); $this->setHelp('This command will execute a cronjob task.'); - $this->addArgument('task_id', InputArgument::REQUIRED, 'Id of the desired cron job'); + + $this->addArgument( + 'task_id', + InputArgument::OPTIONAL, + 'Id of the desired cron job' + ); + + $this->addOption( + 'input', + 'i', + InputOption::VALUE_NONE, + 'Interactively input values (defaults to true if no task_id is given)' + ); } protected function execute(InputInterface $input, OutputInterface $output): int { + $helper = $this->getHelper('question'); $task_id = $input->getArgument('task_id'); + + $input_values = $task_id === null || $input->getOption('input'); + + if ($task_id === null) { + $question = new ChoiceQuestion( + "\nWhich cronjob should be executed:\n", + $this->getCronjobTaskList() + ); + $task_id = $helper->ask($input, $output, $question); + } + $task = \CronjobTask::find($task_id); if (!$task) { $output->writeln('<error>Unknown task id</error>'); return Command::FAILURE; } - if (!file_exists($GLOBALS['STUDIP_BASE_PATH'] . '/' . $task->filename)) { - $output->writeln(sprintf('<error>Invalid task, unknown filename %s</error>', $task->filename)); + if (!$task->valid) { + $output->writeln(sprintf( + '<error>Invalid task, unknown filename %s or invalid class %s</error>', + $task->filename, + $task->class + )); return Command::FAILURE; } - require_once $GLOBALS['STUDIP_BASE_PATH'] . '/' . $task->filename; - if (!class_exists('\\' . $task->class)) { - fwrite(STDOUT, 'Invalid task, unknown class "' . $task->class . '"' . PHP_EOL); - $output->writeln(sprintf('<error>Invalid task, unknown class %s</error>', $task->class)); - return Command::FAILURE; + + $parameters = $this->getDefaultTaskParameters($task); + + if ($input_values && count($parameters) > 0) { + $output->writeln("\nParameters:\n"); + + foreach ($task->parameters as $key => $definition) { + $description = trim($definition['description'], ' ?'); + $default = trim(json_encode($definition['default'] ?? null), "'"); + $label = " > {$description} [<comment>{$default}</comment>] : "; + + if ($definition['type'] === 'boolean') { + $question = new ConfirmationQuestion( + $label, + $definition['default'], + '/^(y|j|1)/i' + ); + } elseif ($definition['type'] === 'select' && !empty($definition['values'])) { + $question = new ChoiceQuestion( + $label, + $definition['values'] + ); + } else { + $question = new Question( + $label, + $definition['default'] + ); + if ($definition['type'] === 'integer') { + $question->setNormalizer(function ($value) { + return $value ? trim($value) : ''; + })->setValidator(function ($value): int { + if (strlen($value) && !ctype_digit($value)) { + throw new \RuntimeException('Number is invalid.'); + } + return $value; + }); + } + } + $parameters[$key] = $helper->ask($input, $output, $question); + } } - $task->engage(''); + + $task->engage('', $parameters); + return Command::SUCCESS; } + + protected function getCronjobTaskList(): array + { + $result = []; + \CronjobTask::findEachBySQL( + function (\CronjobTask $task) use (&$result): void + { + $result[$task->id] = $task->name; + }, + '1' + ); + return $result; + } + + private function getDefaultTaskParameters(\CronjobTask $task): array + { + $parameters = []; + foreach ($task->parameters as $key => $definition) { + $parameters[$key] = $definition['default'] ?? null; + } + return $parameters; + } } diff --git a/cli/Commands/Cronjobs/CronjobList.php b/cli/Commands/Cronjobs/CronjobList.php index 4afa637b12a8ba0caf139a9224f242c43a264587..3506276bcde4ffbe031e2161571961e5c9dc8ed9 100644 --- a/cli/Commands/Cronjobs/CronjobList.php +++ b/cli/Commands/Cronjobs/CronjobList.php @@ -27,7 +27,7 @@ class CronjobList extends Command $table->setStyle('compact'); $table->setHeaders(['Task-ID', 'Description']); foreach ($tasks as $task) { - if (!class_exists($task->class)) { + if (!$task->valid) { continue; } $description = call_user_func(['\\' . $task->class, 'getDescription']); diff --git a/lib/models/CronjobTask.class.php b/lib/models/CronjobTask.class.php index 754041660b80cc02812668e6b8f53df73e35b37f..5b5350f8c0368839d03154f21c849dcd5e5d80f9 100644 --- a/lib/models/CronjobTask.class.php +++ b/lib/models/CronjobTask.class.php @@ -35,6 +35,10 @@ * @property string execution_count database column * @property string assigned_count database column * @property SimpleORMapCollection schedules has_many CronjobSchedule + * + * @property string $description + * @property string $name + * @property array $parameters */ class CronjobTask extends SimpleORMap {