[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |