[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_scheduler/src/Model/ -> TasksModel.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\Model;
  12  
  13  use Joomla\CMS\Component\ComponentHelper;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Language\Text;
  16  use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
  17  use Joomla\CMS\MVC\Model\ListModel;
  18  use Joomla\CMS\Object\CMSObject;
  19  use Joomla\Component\Scheduler\Administrator\Helper\SchedulerHelper;
  20  use Joomla\Component\Scheduler\Administrator\Task\TaskOption;
  21  use Joomla\Database\DatabaseQuery;
  22  use Joomla\Database\ParameterType;
  23  use Joomla\Database\QueryInterface;
  24  use Joomla\Utilities\ArrayHelper;
  25  
  26  // phpcs:disable PSR1.Files.SideEffects
  27  \defined('_JEXEC') or die;
  28  // phpcs:enable PSR1.Files.SideEffects
  29  
  30  /**
  31   * The MVC Model for TasksView.
  32   * Defines methods to deal with operations concerning multiple `#__scheduler_tasks` entries.
  33   *
  34   * @since  4.1.0
  35   */
  36  class TasksModel extends ListModel
  37  {
  38      /**
  39       * Constructor.
  40       *
  41       * @param   array                     $config   An optional associative array of configuration settings.
  42       * @param   MVCFactoryInterface|null  $factory  The factory.
  43       *
  44       * @since   4.1.0
  45       * @throws  \Exception
  46       * @see     \JControllerLegacy
  47       */
  48      public function __construct($config = [], MVCFactoryInterface $factory = null)
  49      {
  50          if (empty($config['filter_fields'])) {
  51              $config['filter_fields'] = [
  52                  'id', 'a.id',
  53                  'asset_id', 'a.asset_id',
  54                  'title', 'a.title',
  55                  'type', 'a.type',
  56                  'type_title', 'j.type_title',
  57                  'state', 'a.state',
  58                  'last_exit_code', 'a.last_exit_code',
  59                  'last_execution', 'a.last_execution',
  60                  'next_execution', 'a.next_execution',
  61                  'times_executed', 'a.times_executed',
  62                  'times_failed', 'a.times_failed',
  63                  'ordering', 'a.ordering',
  64                  'priority', 'a.priority',
  65                  'note', 'a.note',
  66                  'created', 'a.created',
  67                  'created_by', 'a.created_by',
  68              ];
  69          }
  70  
  71          parent::__construct($config, $factory);
  72      }
  73  
  74      /**
  75       * Method to get a store id based on model configuration state.
  76       *
  77       * This is necessary because the model is used by the component and
  78       * different modules that might need different sets of data or different
  79       * ordering requirements.
  80       *
  81       * @param   string  $id  A prefix for the store id.
  82       *
  83       * @return string  A store id.
  84       *
  85       * @since  4.1.0
  86       */
  87      protected function getStoreId($id = ''): string
  88      {
  89          // Compile the store id.
  90          $id .= ':' . $this->getState('filter.search');
  91          $id .= ':' . $this->getState('filter.state');
  92          $id .= ':' . $this->getState('filter.type');
  93          $id .= ':' . $this->getState('filter.orphaned');
  94          $id .= ':' . $this->getState('filter.due');
  95          $id .= ':' . $this->getState('filter.locked');
  96          $id .= ':' . $this->getState('filter.trigger');
  97          $id .= ':' . $this->getState('list.select');
  98  
  99          return parent::getStoreId($id);
 100      }
 101  
 102      /**
 103       * Method to create a query for a list of items.
 104       *
 105       * @return QueryInterface
 106       *
 107       * @since  4.1.0
 108       * @throws \Exception
 109       */
 110      protected function getListQuery(): QueryInterface
 111      {
 112          // Create a new query object.
 113          $db    = $this->getDatabase();
 114          $query = $db->getQuery(true);
 115  
 116          /**
 117           * Select the required fields from the table.
 118           * ? Do we need all these defaults ?
 119           * ? Does 'list.select' exist ?
 120           */
 121          $query->select(
 122              $this->getState(
 123                  'list.select',
 124                  [
 125                      $db->quoteName('a.id'),
 126                      $db->quoteName('a.asset_id'),
 127                      $db->quoteName('a.title'),
 128                      $db->quoteName('a.type'),
 129                      $db->quoteName('a.execution_rules'),
 130                      $db->quoteName('a.state'),
 131                      $db->quoteName('a.last_exit_code'),
 132                      $db->quoteName('a.locked'),
 133                      $db->quoteName('a.last_execution'),
 134                      $db->quoteName('a.next_execution'),
 135                      $db->quoteName('a.times_executed'),
 136                      $db->quoteName('a.times_failed'),
 137                      $db->quoteName('a.priority'),
 138                      $db->quoteName('a.ordering'),
 139                      $db->quoteName('a.note'),
 140                      $db->quoteName('a.checked_out'),
 141                      $db->quoteName('a.checked_out_time'),
 142                  ]
 143              )
 144          )
 145              ->select(
 146                  [
 147                      $db->quoteName('uc.name', 'editor'),
 148                  ]
 149              )
 150              ->from($db->quoteName('#__scheduler_tasks', 'a'))
 151              ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
 152  
 153          // Filters go below
 154          $filterCount = 0;
 155  
 156          /**
 157           * Extends query if already filtered.
 158           *
 159           * @param   string  $outerGlue
 160           * @param   array   $conditions
 161           * @param   string  $innerGlue
 162           *
 163           * @since  4.1.0
 164           */
 165          $extendWhereIfFiltered = static function (
 166              string $outerGlue,
 167              array $conditions,
 168              string $innerGlue
 169          ) use (
 170              $query,
 171              &$filterCount
 172  ) {
 173              if ($filterCount++) {
 174                  $query->extendWhere($outerGlue, $conditions, $innerGlue);
 175              } else {
 176                  $query->where($conditions, $innerGlue);
 177              }
 178          };
 179  
 180          // Filter over ID, title (redundant to search, but) ---
 181          if (is_numeric($id = $this->getState('filter.id'))) {
 182              $filterCount++;
 183              $id = (int) $id;
 184              $query->where($db->qn('a.id') . ' = :id')
 185                  ->bind(':id', $id, ParameterType::INTEGER);
 186          } elseif ($title = $this->getState('filter.title')) {
 187              $filterCount++;
 188              $match = "%$title%";
 189              $query->where($db->qn('a.title') . ' LIKE :match')
 190                  ->bind(':match', $match);
 191          }
 192  
 193          // Filter orphaned (-1: exclude, 0: include, 1: only) ----
 194          $filterOrphaned = (int) $this->getState('filter.orphaned');
 195  
 196          if ($filterOrphaned !== 0) {
 197              $filterCount++;
 198              $taskOptions = SchedulerHelper::getTaskOptions();
 199  
 200              // Array of all active routine ids
 201              $activeRoutines = array_map(
 202                  static function (TaskOption $taskOption): string {
 203                      return $taskOption->id;
 204                  },
 205                  $taskOptions->options
 206              );
 207  
 208              if ($filterOrphaned === -1) {
 209                  $query->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
 210              } else {
 211                  $query->whereNotIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
 212              }
 213          }
 214  
 215          // Filter over state ----
 216          $state = $this->getState('filter.state');
 217  
 218          if ($state !== '*') {
 219              $filterCount++;
 220  
 221              if (is_numeric($state)) {
 222                  $state = (int) $state;
 223  
 224                  $query->where($db->quoteName('a.state') . ' = :state')
 225                      ->bind(':state', $state, ParameterType::INTEGER);
 226              } else {
 227                  $query->whereIn($db->quoteName('a.state'), [0, 1]);
 228              }
 229          }
 230  
 231          // Filter over type ----
 232          $typeFilter = $this->getState('filter.type');
 233  
 234          if ($typeFilter) {
 235              $filterCount++;
 236              $query->where($db->quotename('a.type') . '= :type')
 237                  ->bind(':type', $typeFilter);
 238          }
 239  
 240          // Filter over exit code ----
 241          $exitCode = $this->getState('filter.last_exit_code');
 242  
 243          if (is_numeric($exitCode)) {
 244              $filterCount++;
 245              $exitCode = (int) $exitCode;
 246              $query->where($db->quoteName('a.last_exit_code') . '= :last_exit_code')
 247                  ->bind(':last_exit_code', $exitCode, ParameterType::INTEGER);
 248          }
 249  
 250          // Filter due (-1: exclude, 0: include, 1: only) ----
 251          $due = $this->getState('filter.due');
 252  
 253          if (is_numeric($due) && $due != 0) {
 254              $now      = Factory::getDate('now', 'GMT')->toSql();
 255              $operator = $due == 1 ? ' <= ' : ' > ';
 256              $filterCount++;
 257              $query->where($db->qn('a.next_execution') . $operator . ':now')
 258                  ->bind(':now', $now);
 259          }
 260  
 261          /*
 262           * Filter locked ---
 263           * Locks can be either hard locks or soft locks. Locks that have expired (exceeded the task timeout) are soft
 264           * locks. Hard-locked tasks are assumed to be running. Soft-locked tasks are assumed to have suffered a fatal
 265           * failure.
 266           * {-2: exclude-all, -1: exclude-hard-locked, 0: include, 1: include-only-locked, 2: include-only-soft-locked}
 267           */
 268          $locked = $this->getState('filter.locked');
 269  
 270          if (is_numeric($locked) && $locked != 0) {
 271              $now              = Factory::getDate('now', 'GMT');
 272              $timeout          = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
 273              $timeout          = new \DateInterval(sprintf('PT%dS', $timeout));
 274              $timeoutThreshold = (clone $now)->sub($timeout)->toSql();
 275              $now              = $now->toSql();
 276  
 277              switch ($locked) {
 278                  case -2:
 279                      $query->where($db->qn('a.locked') . 'IS NULL');
 280                      break;
 281                  case -1:
 282                      $extendWhereIfFiltered(
 283                          'AND',
 284                          [
 285                              $db->qn('a.locked') . ' IS NULL',
 286                              $db->qn('a.locked') . ' < :threshold',
 287                          ],
 288                          'OR'
 289                      );
 290                      $query->bind(':threshold', $timeoutThreshold);
 291                      break;
 292                  case 1:
 293                      $query->where($db->qn('a.locked') . ' IS NOT NULL');
 294                      break;
 295                  case 2:
 296                      $query->where($db->qn('a.locked') . ' < :threshold')
 297                          ->bind(':threshold', $timeoutThreshold);
 298              }
 299          }
 300  
 301          // Filter over search string if set (title, type title, note, id) ----
 302          $searchStr = $this->getState('filter.search');
 303  
 304          if (!empty($searchStr)) {
 305              // Allow search by ID
 306              if (stripos($searchStr, 'id:') === 0) {
 307                  // Add array support [?]
 308                  $id = (int) substr($searchStr, 3);
 309                  $query->where($db->quoteName('a.id') . '= :id')
 310                      ->bind(':id', $id, ParameterType::INTEGER);
 311              } elseif (stripos($searchStr, 'type:') !== 0) {
 312                  // Search by type is handled exceptionally in _getList() [@todo: remove refs]
 313                  $searchStr = "%$searchStr%";
 314  
 315                  // Bind keys to query
 316                  $query->bind(':title', $searchStr)
 317                      ->bind(':note', $searchStr);
 318                  $conditions = [
 319                      $db->quoteName('a.title') . ' LIKE :title',
 320                      $db->quoteName('a.note') . ' LIKE :note',
 321                  ];
 322                  $extendWhereIfFiltered('AND', $conditions, 'OR');
 323              }
 324          }
 325  
 326          // Add list ordering clause. ----
 327          // @todo implement multi-column ordering someway
 328          $multiOrdering = $this->state->get('list.multi_ordering');
 329  
 330          if (!$multiOrdering || !\is_array($multiOrdering)) {
 331              $orderCol = $this->state->get('list.ordering', 'a.title');
 332              $orderDir = $this->state->get('list.direction', 'asc');
 333  
 334              // Type title ordering is handled exceptionally in _getList()
 335              if ($orderCol !== 'j.type_title') {
 336                  $query->order($db->quoteName($orderCol) . ' ' . $orderDir);
 337  
 338                  // If ordering by type or state, also order by title.
 339                  if (\in_array($orderCol, ['a.type', 'a.state', 'a.priority'])) {
 340                      // @todo : Test if things are working as expected
 341                      $query->order($db->quoteName('a.title') . ' ' . $orderDir);
 342                  }
 343              }
 344          } else {
 345              // @todo Should add quoting here
 346              $query->order($multiOrdering);
 347          }
 348  
 349          return $query;
 350      }
 351  
 352      /**
 353       * Overloads the parent _getList() method.
 354       * Takes care of attaching TaskOption objects and sorting by type titles.
 355       *
 356       * @param   DatabaseQuery  $query       The database query to get the list with
 357       * @param   int            $limitstart  The list offset
 358       * @param   int            $limit       Number of list items to fetch
 359       *
 360       * @return object[]
 361       *
 362       * @since  4.1.0
 363       * @throws \Exception
 364       */
 365      protected function _getList($query, $limitstart = 0, $limit = 0): array
 366      {
 367          // Get stuff from the model state
 368          $listOrder      = $this->getState('list.ordering', 'a.title');
 369          $listDirectionN = strtolower($this->getState('list.direction', 'asc')) == 'desc' ? -1 : 1;
 370  
 371          // Set limit parameters and get object list
 372          $query->setLimit($limit, $limitstart);
 373          $this->getDatabase()->setQuery($query);
 374  
 375          // Return optionally an extended class.
 376          // @todo: Use something other than CMSObject..
 377          if ($this->getState('list.customClass')) {
 378              $responseList = array_map(
 379                  static function (array $arr) {
 380                      $o = new CMSObject();
 381  
 382                      foreach ($arr as $k => $v) {
 383                          $o->{$k} = $v;
 384                      }
 385  
 386                      return $o;
 387                  },
 388                  $this->getDatabase()->loadAssocList() ?: []
 389              );
 390          } else {
 391              $responseList = $this->getDatabase()->loadObjectList();
 392          }
 393  
 394          // Attach TaskOptions objects and a safe type title
 395          $this->attachTaskOptions($responseList);
 396  
 397          // If ordering by non-db fields, we need to sort here in code
 398          if ($listOrder == 'j.type_title') {
 399              $responseList = ArrayHelper::sortObjects($responseList, 'safeTypeTitle', $listDirectionN, true, false);
 400          }
 401  
 402          return $responseList;
 403      }
 404  
 405      /**
 406       * For an array of items, attaches TaskOption objects and (safe) type titles to each.
 407       *
 408       * @param   array  $items  Array of items, passed by reference
 409       *
 410       * @return void
 411       *
 412       * @since  4.1.0
 413       * @throws \Exception
 414       */
 415      private function attachTaskOptions(array $items): void
 416      {
 417          $taskOptions = SchedulerHelper::getTaskOptions();
 418  
 419          foreach ($items as $item) {
 420              $item->taskOption    = $taskOptions->findOption($item->type);
 421              $item->safeTypeTitle = $item->taskOption->title ?? Text::_('JGLOBAL_NONAPPLICABLE');
 422          }
 423      }
 424  
 425      /**
 426       * Proxy for the parent method.
 427       * Sets ordering defaults.
 428       *
 429       * @param   string  $ordering   Field to order/sort list by
 430       * @param   string  $direction  Direction in which to sort list
 431       *
 432       * @return void
 433       * @since  4.1.0
 434       */
 435      protected function populateState($ordering = 'a.title', $direction = 'ASC'): void
 436      {
 437          // Call the parent method
 438          parent::populateState($ordering, $direction);
 439      }
 440  }


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