[ 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\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:** ({@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 }
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 |