[ 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\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 }
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 |