[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_scheduler/src/Scheduler/ -> Scheduler.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Administrator
   5   * @subpackage  com_scheduler
   6   *
   7   * @copyright   (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9   */
  10  
  11  namespace Joomla\Component\Scheduler\Administrator\Scheduler;
  12  
  13  use Joomla\CMS\Application\CMSApplication;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Language\Text;
  16  use Joomla\CMS\Log\Log;
  17  use Joomla\Component\Scheduler\Administrator\Extension\SchedulerComponent;
  18  use Joomla\Component\Scheduler\Administrator\Model\TaskModel;
  19  use Joomla\Component\Scheduler\Administrator\Model\TasksModel;
  20  use Joomla\Component\Scheduler\Administrator\Task\Status;
  21  use Joomla\Component\Scheduler\Administrator\Task\Task;
  22  use Symfony\Component\OptionsResolver\Exception\AccessException;
  23  use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
  24  use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
  25  use Symfony\Component\OptionsResolver\OptionsResolver;
  26  
  27  // phpcs:disable PSR1.Files.SideEffects
  28  \defined('_JEXEC') or die;
  29  // phpcs:enable PSR1.Files.SideEffects
  30  
  31  /**
  32   * The Scheduler class provides the core functionality of ComScheduler.
  33   * Currently, this includes fetching scheduled tasks from the database
  34   * and execution of any or the next due task.
  35   * It is planned that this class is extended with C[R]UD methods for
  36   * scheduled tasks.
  37   *
  38   * @since 4.1.0
  39   * @todo  A global instance?
  40   */
  41  class Scheduler
  42  {
  43      private const LOG_TEXT = [
  44          Status::OK          => 'COM_SCHEDULER_SCHEDULER_TASK_COMPLETE',
  45          Status::WILL_RESUME => 'COM_SCHEDULER_SCHEDULER_TASK_WILL_RESUME',
  46          Status::NO_LOCK     => 'COM_SCHEDULER_SCHEDULER_TASK_LOCKED',
  47          Status::NO_RUN      => 'COM_SCHEDULER_SCHEDULER_TASK_UNLOCKED',
  48          Status::NO_ROUTINE  => 'COM_SCHEDULER_SCHEDULER_TASK_ROUTINE_NA',
  49      ];
  50  
  51      /**
  52       * Filters for the task queue. Can be used with fetchTaskRecords().
  53       *
  54       * @since 4.1.0
  55       * @todo  remove?
  56       */
  57      public const TASK_QUEUE_FILTERS = [
  58          'due'    => 1,
  59          'locked' => -1,
  60      ];
  61  
  62      /**
  63       * List config for the task queue. Can be used with fetchTaskRecords().
  64       *
  65       * @since 4.1.0
  66       * @todo  remove?
  67       */
  68      public const TASK_QUEUE_LIST_CONFIG = [
  69          'multi_ordering' => ['a.priority DESC ', 'a.next_execution ASC'],
  70      ];
  71  
  72      /**
  73       * Run a scheduled task.
  74       * Runs a single due task from the task queue by default if $id and $title are not passed.
  75       *
  76       * @param   array  $options  Array with options to configure the method's behavior. Supports:
  77       *                           1. `id`: (Optional) ID of the task to run.
  78       *                           2. `allowDisabled`: Allow running disabled tasks.
  79       *                           3. `allowConcurrent`: Allow concurrent execution, i.e., running the task when another
  80       *                           task may be running.
  81       *
  82       * @return ?Task  The task executed or null if not exists
  83       *
  84       * @since 4.1.0
  85       * @throws \RuntimeException
  86       */
  87      public function runTask(array $options): ?Task
  88      {
  89          $resolver = new OptionsResolver();
  90  
  91          try {
  92              $this->configureTaskRunnerOptions($resolver);
  93          } catch (\Exception $e) {
  94          }
  95  
  96          try {
  97              $options = $resolver->resolve($options);
  98          } catch (\Exception $e) {
  99              if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException) {
 100                  throw $e;
 101              }
 102          }
 103  
 104          /** @var CMSApplication $app */
 105          $app = Factory::getApplication();
 106  
 107          // ? Sure about inferring scheduling bypass?
 108          $task = $this->getTask(
 109              [
 110                  'id'                  => (int) $options['id'],
 111                  'allowDisabled'       => $options['allowDisabled'],
 112                  'bypassScheduling'    => (int) $options['id'] !== 0,
 113                  'allowConcurrent'     => $options['allowConcurrent'],
 114                  'includeCliExclusive' => ($app->isClient('cli')),
 115              ]
 116          );
 117  
 118          // ? Should this be logged? (probably, if an ID is passed?)
 119          if (empty($task)) {
 120              return null;
 121          }
 122  
 123          $app->getLanguage()->load('com_scheduler', JPATH_ADMINISTRATOR);
 124  
 125          $options['text_entry_format'] = '{DATE}    {TIME}    {PRIORITY}    {MESSAGE}';
 126          $options['text_file']         = 'joomla_scheduler.php';
 127          Log::addLogger($options, Log::ALL, $task->logCategory);
 128  
 129          $taskId    = $task->get('id');
 130          $taskTitle = $task->get('title');
 131  
 132          $task->log(Text::sprintf('COM_SCHEDULER_SCHEDULER_TASK_START', $taskId, $taskTitle), 'info');
 133  
 134          // Let's try to avoid time-outs
 135          if (\function_exists('set_time_limit')) {
 136              set_time_limit(0);
 137          }
 138  
 139          try {
 140              $task->run();
 141          } catch (\Exception $e) {
 142              // We suppress the exception here, it's still accessible with `$task->getContent()['exception']`.
 143          }
 144  
 145          $executionSnapshot = $task->getContent();
 146          $exitCode          = $executionSnapshot['status'] ?? Status::NO_EXIT;
 147          $netDuration       = $executionSnapshot['netDuration'] ?? 0;
 148          $duration          = $executionSnapshot['duration'] ?? 0;
 149  
 150          if (\array_key_exists($exitCode, self::LOG_TEXT)) {
 151              $level = in_array($exitCode, [Status::OK, Status::WILL_RESUME]) ? 'info' : 'warning';
 152              $task->log(Text::sprintf(self::LOG_TEXT[$exitCode], $taskId, $duration, $netDuration), $level);
 153  
 154              return $task;
 155          }
 156  
 157          $task->log(
 158              Text::sprintf('COM_SCHEDULER_SCHEDULER_TASK_UNKNOWN_EXIT', $taskId, $duration, $netDuration, $exitCode),
 159              'warning'
 160          );
 161  
 162          return $task;
 163      }
 164  
 165      /**
 166       * Set up an {@see OptionsResolver} to resolve options compatible with {@see runTask}.
 167       *
 168       * @param   OptionsResolver  $resolver  The {@see OptionsResolver} instance to set up.
 169       *
 170       * @return void
 171       *
 172       * @since 4.1.0
 173       * @throws AccessException
 174       */
 175      protected function configureTaskRunnerOptions(OptionsResolver $resolver): void
 176      {
 177          $resolver->setDefaults(
 178              [
 179                  'id' => 0,
 180                  'allowDisabled' => false,
 181                  'allowConcurrent' => false,
 182              ]
 183          )
 184              ->setAllowedTypes('id', 'numeric')
 185              ->setAllowedTypes('allowDisabled', 'bool')
 186              ->setAllowedTypes('allowConcurrent', 'bool');
 187      }
 188  
 189      /**
 190       * Get the next task which is due to run, limit to a specific task when ID is given
 191       *
 192       * @param   array  $options  Options for the getter, see {@see TaskModel::getTask()}.
 193       *                           ! should probably also support a non-locking getter.
 194       *
 195       * @return  Task $task The task to execute
 196       *
 197       * @since 4.1.0
 198       * @throws \RuntimeException
 199       */
 200      public function getTask(array $options = []): ?Task
 201      {
 202          $resolver = new OptionsResolver();
 203  
 204          try {
 205              TaskModel::configureTaskGetterOptions($resolver);
 206          } catch (\Exception $e) {
 207          }
 208  
 209          try {
 210              $options = $resolver->resolve($options);
 211          } catch (\Exception $e) {
 212              if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException) {
 213                  throw $e;
 214              }
 215          }
 216  
 217          try {
 218              /** @var SchedulerComponent $component */
 219              $component = Factory::getApplication()->bootComponent('com_scheduler');
 220  
 221              /** @var TaskModel $model */
 222              $model = $component->getMVCFactory()->createModel('Task', 'Administrator', ['ignore_request' => true]);
 223          } catch (\Exception $e) {
 224          }
 225  
 226          if (!isset($model)) {
 227              throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
 228          }
 229  
 230          $task = $model->getTask($options);
 231  
 232          if (empty($task)) {
 233              return null;
 234          }
 235  
 236          return new Task($task);
 237      }
 238  
 239      /**
 240       * Fetches a single scheduled task in a Task instance.
 241       * If no id or title is specified, a due task is returned.
 242       *
 243       * @param   int   $id             The task ID.
 244       * @param   bool  $allowDisabled  Allow disabled/trashed tasks?
 245       *
 246       * @return ?object  A matching task record, if it exists
 247       *
 248       * @since 4.1.0
 249       * @throws \RuntimeException
 250       */
 251      public function fetchTaskRecord(int $id = 0, bool $allowDisabled = false): ?object
 252      {
 253          $filters    = [];
 254          $listConfig = ['limit' => 1];
 255  
 256          if ($id > 0) {
 257              $filters['id'] = $id;
 258          } else {
 259              // Filters and list config for scheduled task queue
 260              $filters['due']               = 1;
 261              $filters['locked']            = -1;
 262              $listConfig['multi_ordering'] = [
 263                  'a.priority DESC',
 264                  'a.next_execution ASC',
 265              ];
 266          }
 267  
 268          if ($allowDisabled) {
 269              $filters['state'] = '';
 270          }
 271  
 272          return $this->fetchTaskRecords($filters, $listConfig)[0] ?? null;
 273      }
 274  
 275      /**
 276       * @param   array  $filters     The filters to set to the model
 277       * @param   array  $listConfig  The list config (ordering, etc.) to set to the model
 278       *
 279       * @return array
 280       *
 281       * @since 4.1.0
 282       * @throws \RunTimeException
 283       */
 284      public function fetchTaskRecords(array $filters, array $listConfig): array
 285      {
 286          $model = null;
 287  
 288          try {
 289              /** @var SchedulerComponent $component */
 290              $component = Factory::getApplication()->bootComponent('com_scheduler');
 291  
 292              /** @var TasksModel $model */
 293              $model = $component->getMVCFactory()
 294                  ->createModel('Tasks', 'Administrator', ['ignore_request' => true]);
 295          } catch (\Exception $e) {
 296          }
 297  
 298          if (!$model) {
 299              throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
 300          }
 301  
 302          $model->setState('list.select', 'a.*');
 303  
 304          // Default to only enabled tasks
 305          if (!isset($filters['state'])) {
 306              $model->setState('filter.state', 1);
 307          }
 308  
 309          // Default to including orphaned tasks
 310          $model->setState('filter.orphaned', 0);
 311  
 312          // Default to ordering by ID
 313          $model->setState('list.ordering', 'a.id');
 314          $model->setState('list.direction', 'ASC');
 315  
 316          // List options
 317          foreach ($listConfig as $key => $value) {
 318              $model->setState('list.' . $key, $value);
 319          }
 320  
 321          // Filter options
 322          foreach ($filters as $type => $filter) {
 323              $model->setState('filter.' . $type, $filter);
 324          }
 325  
 326          return $model->getItems() ?: [];
 327      }
 328  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer