* @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\CMS\Console; use Joomla\Component\Scheduler\Administrator\Scheduler\Scheduler; use Joomla\Component\Scheduler\Administrator\Task\Status; use Joomla\Console\Command\AbstractCommand; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; // phpcs:disable PSR1.Files.SideEffects \defined('JPATH_PLATFORM') or die; // phpcs:enable PSR1.Files.SideEffects /** * Console command to run scheduled tasks. * * @since 4.1.0 */ class TasksRunCommand extends AbstractCommand { /** * The default command name * * @var string * @since 4.1.0 */ protected static $defaultName = 'scheduler:run'; /** * @var SymfonyStyle * @since 4.1.0 */ private $ioStyle; /** * @param InputInterface $input The input to inject into the command. * @param OutputInterface $output The output to inject into the command. * * @return integer The command exit code. * * @since 4.1.0 * @throws \RunTimeException * @throws InvalidArgumentException */ protected function doExecute(InputInterface $input, OutputInterface $output): int { /** * Not as a class constant because of some the autoload order doesn't let us * load the namespace when it's time to do that (why?) */ static $outTextMap = [ Status::OK => 'Task#%1$02d \'%2$s\' processed in %3$.2f seconds.', Status::WILL_RESUME => 'Task#%1$02d \'%2$s\' ran for %3$.2f seconds, will resume next time.', Status::NO_RUN => 'Task#%1$02d \'%2$s\' failed to run. Is it already running?', Status::NO_ROUTINE => 'Task#%1$02d \'%2$s\' is orphaned! Visit the backend to resolve.', 'N/A' => 'Task#%1$02d \'%2$s\' exited with code %4$d in %3$.2f seconds.', ]; $this->configureIo($input, $output); $this->ioStyle->title('Run Tasks'); $scheduler = new Scheduler(); $id = $input->getOption('id'); $all = $input->getOption('all'); if ($id) { $records[] = $scheduler->fetchTaskRecord($id); } else { $filters = $scheduler::TASK_QUEUE_FILTERS; $listConfig = $scheduler::TASK_QUEUE_LIST_CONFIG; $listConfig['limit'] = ($all ? null : 1); $records = $scheduler->fetchTaskRecords($filters, $listConfig); } if ($id && !$records[0]) { $this->ioStyle->writeln('No matching task found!'); return Status::NO_TASK; } elseif (!$records) { $this->ioStyle->writeln('No tasks due!'); return Status::NO_TASK; } $status = ['startTime' => microtime(true)]; $taskCount = \count($records); $exit = Status::OK; foreach ($records as $record) { $cStart = microtime(true); $task = $scheduler->runTask(['id' => $record->id, 'allowDisabled' => true, 'allowConcurrent' => true]); $exit = empty($task) ? Status::NO_RUN : $task->getContent()['status']; $duration = microtime(true) - $cStart; $key = (\array_key_exists($exit, $outTextMap)) ? $exit : 'N/A'; $this->ioStyle->writeln(sprintf($outTextMap[$key], $record->id, $record->title, $duration, $exit)); } $netTime = round(microtime(true) - $status['startTime'], 2); $this->ioStyle->newLine(); $this->ioStyle->writeln("Finished running $taskCount tasks in $netTime seconds."); return $taskCount === 1 ? $exit : Status::OK; } /** * Configure the IO. * * @param InputInterface $input The input to inject into the command. * @param OutputInterface $output The output to inject into the command. * * @return void * * @since 4.1.0 */ private function configureIO(InputInterface $input, OutputInterface $output) { $this->ioStyle = new SymfonyStyle($input, $output); } /** * Configure the command. * * @return void * * @since 4.1.0 */ protected function configure(): void { $this->addOption('id', 'i', InputOption::VALUE_REQUIRED, 'The id of the task to run.'); $this->addOption('all', '', InputOption::VALUE_NONE, 'Run all due tasks. Note that this is overridden if --id is used.'); $help = "%command.name% run scheduled tasks. \nUsage: php %command.full_name% [flags]"; $this->setDescription('Run one or more scheduled tasks'); $this->setHelp($help); } }