[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_scheduler/src/Traits/ -> TaskPluginTrait.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\Traits;
  12  
  13  use Joomla\CMS\Factory;
  14  use Joomla\CMS\Filesystem\Path;
  15  use Joomla\CMS\Form\Form;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Log\Log;
  18  use Joomla\CMS\Plugin\CMSPlugin;
  19  use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
  20  use Joomla\Component\Scheduler\Administrator\Task\Status;
  21  use Joomla\Event\EventInterface;
  22  use Joomla\Utilities\ArrayHelper;
  23  
  24  // phpcs:disable PSR1.Files.SideEffects
  25  \defined('_JEXEC') or die;
  26  // phpcs:enable PSR1.Files.SideEffects
  27  
  28  /**
  29   * Utility trait for plugins that offer `com_scheduler` compatible task routines. This trait defines a lot
  30   * of handy methods that make it really simple to support task routines in a J4.x plugin. This trait includes standard
  31   * methods to broadcast routines {@see TaskPluginTrait::advertiseRoutines()}, enhance task forms
  32   * {@see TaskPluginTrait::enhanceTaskItemForm()} and call routines
  33   * {@see TaskPluginTrait::standardRoutineHandler()}. With standard cookie-cutter behaviour, a task plugin may only need
  34   * to include this trait, and define methods corresponding to each routine along with the `TASKS_MAP` class constant to
  35   * declare supported routines and related properties.
  36   *
  37   * @since  4.1.0
  38   */
  39  trait TaskPluginTrait
  40  {
  41      /**
  42       * A snapshot of the routine state.
  43       *
  44       * @var array
  45       * @since  4.1.0
  46       */
  47      protected $snapshot = [];
  48  
  49      /**
  50       * Set information to {@see $snapshot} when initializing a routine.
  51       *
  52       * @param   ExecuteTaskEvent  $event  The onExecuteTask event.
  53       *
  54       * @return void
  55       *
  56       * @since  4.1.0
  57       */
  58      protected function startRoutine(ExecuteTaskEvent $event): void
  59      {
  60          if (!$this instanceof CMSPlugin) {
  61              return;
  62          }
  63  
  64          $this->snapshot['logCategory'] = $event->getArgument('subject')->logCategory;
  65          $this->snapshot['plugin']      = $this->_name;
  66          $this->snapshot['startTime']   = microtime(true);
  67          $this->snapshot['status']      = Status::RUNNING;
  68      }
  69  
  70      /**
  71       * Set information to {@see $snapshot} when ending a routine. This information includes the routine exit code and
  72       * timing information.
  73       *
  74       * @param   ExecuteTaskEvent  $event     The event
  75       * @param   ?int              $exitCode  The task exit code
  76       *
  77       * @return void
  78       *
  79       * @since  4.1.0
  80       * @throws \Exception
  81       */
  82      protected function endRoutine(ExecuteTaskEvent $event, int $exitCode): void
  83      {
  84          if (!$this instanceof CMSPlugin) {
  85              return;
  86          }
  87  
  88          $this->snapshot['endTime']  = $endTime = microtime(true);
  89          $this->snapshot['duration'] = $endTime - $this->snapshot['startTime'];
  90          $this->snapshot['status']   = $exitCode ?? Status::OK;
  91          $event->setResult($this->snapshot);
  92      }
  93  
  94      /**
  95       * Enhance the task form with routine-specific fields from an XML file declared through the TASKS_MAP constant.
  96       * If a plugin only supports the task form and does not need additional logic, this method can be mapped to the
  97       * `onContentPrepareForm` event through {@see SubscriberInterface::getSubscribedEvents()} and will take care
  98       * of injecting the fields without additional logic in the plugin class.
  99       *
 100       * @param   EventInterface|Form  $context  The onContentPrepareForm event or the Form object.
 101       * @param   mixed                $data     The form data, required when $context is a {@see Form} instance.
 102       *
 103       * @return boolean  True if the form was successfully enhanced or the context was not relevant.
 104       *
 105       * @since  4.1.0
 106       * @throws \Exception
 107       */
 108      public function enhanceTaskItemForm($context, $data = null): bool
 109      {
 110          if ($context instanceof EventInterface) {
 111              /** @var Form $form */
 112              $form = $context->getArgument('0');
 113              $data = $context->getArgument('1');
 114          } elseif ($context instanceof Form) {
 115              $form = $context;
 116          } else {
 117              throw new \InvalidArgumentException(
 118                  sprintf(
 119                      'Argument 0 of %1$s must be an instance of %2$s or %3$s',
 120                      __METHOD__,
 121                      EventInterface::class,
 122                      Form::class
 123                  )
 124              );
 125          }
 126  
 127          if ($form->getName() !== 'com_scheduler.task') {
 128              return true;
 129          }
 130  
 131          $routineId           = $this->getRoutineId($form, $data);
 132          $isSupported         = \array_key_exists($routineId, self::TASKS_MAP);
 133          $enhancementFormName = self::TASKS_MAP[$routineId]['form'] ?? '';
 134  
 135          // Return if routine is not supported by the plugin or the routine does not have a form linked in TASKS_MAP.
 136          if (!$isSupported || \strlen($enhancementFormName) === 0) {
 137              return true;
 138          }
 139  
 140          // We expect the form XML in "{PLUGIN_PATH}/forms/{FORM_NAME}.xml"
 141          $path                = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name;
 142          $enhancementFormFile = $path . '/forms/' . $enhancementFormName . '.xml';
 143  
 144          try {
 145              $enhancementFormFile = Path::check($enhancementFormFile);
 146          } catch (\Exception $e) {
 147              return false;
 148          }
 149  
 150          if (is_file($enhancementFormFile)) {
 151              return $form->loadFile($enhancementFormFile);
 152          }
 153  
 154          return false;
 155      }
 156  
 157      /**
 158       * Advertise the task routines supported by the plugin. This method should be mapped to the `onTaskOptionsList`,
 159       * enabling the plugin to advertise its routines without any custom logic.<br/>
 160       * **Note:** This method expects the `TASKS_MAP` class constant to have relevant information.
 161       *
 162       * @param   EventInterface  $event  onTaskOptionsList Event
 163       *
 164       * @return void
 165       *
 166       * @since  4.1.0
 167       */
 168      public function advertiseRoutines(EventInterface $event): void
 169      {
 170          $options = [];
 171  
 172          foreach (self::TASKS_MAP as $routineId => $details) {
 173              // Sanity check against non-compliant plugins
 174              if (isset($details['langConstPrefix'])) {
 175                  $options[$routineId] = $details['langConstPrefix'];
 176              }
 177          }
 178  
 179          $subject = $event->getArgument('subject');
 180          $subject->addOptions($options);
 181      }
 182  
 183      /**
 184       * Get the relevant task routine ID in the context of a form event, e.g., the `onContentPrepareForm` event.
 185       *
 186       * @param   Form   $form  The form
 187       * @param   mixed  $data  The data
 188       *
 189       * @return  string
 190       *
 191       * @since  4.1.0
 192       * @throws  \Exception
 193       */
 194      protected function getRoutineId(Form $form, $data): string
 195      {
 196          /*
 197           * Depending on when the form is loaded, the ID may either be in $data or the data already bound to the form.
 198           * $data can also either be an object or an array.
 199           */
 200          $routineId = $data->taskOption->id ?? $data->type ?? $data['type'] ?? $form->getValue('type') ?? $data['taskOption']->id ?? '';
 201  
 202          // If we're unable to find a routineId, it might be in the form input.
 203          if (empty($routineId)) {
 204              $app       = $this->getApplication() ?? ($this->app ?? Factory::getApplication());
 205              $form      = $app->getInput()->get('jform', []);
 206              $routineId = ArrayHelper::getValue($form, 'type', '', 'STRING');
 207          }
 208  
 209          return $routineId;
 210      }
 211  
 212      /**
 213       * Add a log message to the task log.
 214       *
 215       * @param   string  $message   The log message
 216       * @param   string  $priority  The log message priority
 217       *
 218       * @return void
 219       *
 220       * @since  4.1.0
 221       * @throws \Exception
 222       * @todo   : use dependency injection here (starting from the Task & Scheduler classes).
 223       */
 224      protected function logTask(string $message, string $priority = 'info'): void
 225      {
 226          static $langLoaded;
 227          static $priorityMap = [
 228              'debug'   => Log::DEBUG,
 229              'error'   => Log::ERROR,
 230              'info'    => Log::INFO,
 231              'notice'  => Log::NOTICE,
 232              'warning' => Log::WARNING,
 233          ];
 234  
 235          if (!$langLoaded) {
 236              $app = $this->getApplication() ?? ($this->app ?? Factory::getApplication());
 237              $app->getLanguage()->load('com_scheduler', JPATH_ADMINISTRATOR);
 238              $langLoaded = true;
 239          }
 240  
 241          $category = $this->snapshot['logCategory'];
 242  
 243          Log::add(Text::_('COM_SCHEDULER_ROUTINE_LOG_PREFIX') . $message, $priorityMap[$priority] ?? Log::INFO, $category);
 244      }
 245  
 246      /**
 247       * Handler for *standard* task routines. Standard routines are mapped to valid class methods 'method' through
 248       * `static::TASKS_MAP`. These methods are expected to take a single argument (the Event) and return an integer
 249       * return status (see {@see Status}). For a plugin that maps each of its task routines to valid methods and does
 250       * not need non-standard handling, this method can be mapped to the `onExecuteTask` event through
 251       * {@see SubscriberInterface::getSubscribedEvents()}, which would allow it to then check if the event wants to
 252       * execute a routine offered by the parent plugin, call the routine and do some other housework without any code
 253       * in the parent classes.<br/>
 254       * **Compatible routine method signature:**&nbsp;&nbsp; ({@see ExecuteTaskEvent::class}, ...): int
 255       *
 256       * @param   ExecuteTaskEvent  $event  The `onExecuteTask` event.
 257       *
 258       * @return void
 259       *
 260       * @since 4.1.0
 261       * @throws \Exception
 262       */
 263      public function standardRoutineHandler(ExecuteTaskEvent $event): void
 264      {
 265          if (!\array_key_exists($event->getRoutineId(), self::TASKS_MAP)) {
 266              return;
 267          }
 268  
 269          $this->startRoutine($event);
 270          $routineId  = $event->getRoutineId();
 271          $methodName = (string) self::TASKS_MAP[$routineId]['method'] ?? '';
 272          $exitCode   = Status::NO_EXIT;
 273  
 274          // We call the mapped method if it exists and confirms to the ($event) -> int signature.
 275          if (!empty($methodName) && ($staticReflection = new \ReflectionClass($this))->hasMethod($methodName)) {
 276              $method = $staticReflection->getMethod($methodName);
 277  
 278              // Might need adjustments here for PHP8 named parameters.
 279              if (
 280                  !($method->getNumberOfRequiredParameters() === 1)
 281                  || !$method->getParameters()[0]->hasType()
 282                  || $method->getParameters()[0]->getType()->getName() !== ExecuteTaskEvent::class
 283                  || !$method->hasReturnType()
 284                  || $method->getReturnType()->getName() !== 'int'
 285              ) {
 286                  $this->logTask(
 287                      sprintf(
 288                          'Incorrect routine method signature for %1$s(). See checks in %2$s()',
 289                          $method->getName(),
 290                          __METHOD__
 291                      ),
 292                      'error'
 293                  );
 294  
 295                  return;
 296              }
 297  
 298              try {
 299                  // Enable invocation of private/protected methods.
 300                  $method->setAccessible(true);
 301                  $exitCode = $method->invoke($this, $event);
 302              } catch (\ReflectionException $e) {
 303                  // @todo replace with language string (?)
 304                  $this->logTask('Exception when calling routine: ' . $e->getMessage(), 'error');
 305                  $exitCode = Status::NO_RUN;
 306              }
 307          } else {
 308              $this->logTask(
 309                  sprintf(
 310                      'Incorrectly configured TASKS_MAP in class %s. Missing valid method for `routine_id` %s',
 311                      static::class,
 312                      $routineId
 313                  ),
 314                  'error'
 315              );
 316          }
 317  
 318          /**
 319           * Closure to validate a status against {@see Status}
 320           *
 321           * @since 4.1.0
 322           */
 323          $validateStatus = static function (int $statusCode): bool {
 324              return \in_array(
 325                  $statusCode,
 326                  (new \ReflectionClass(Status::class))->getConstants()
 327              );
 328          };
 329  
 330          // Validate the exit code.
 331          if (!\is_int($exitCode) || !$validateStatus($exitCode)) {
 332              $exitCode = Status::INVALID_EXIT;
 333          }
 334  
 335          $this->endRoutine($event, $exitCode);
 336      }
 337  }


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