[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugins 5 * @subpackage System.Tasknotification 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 * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace 11 */ 12 13 use Joomla\CMS\Application\CMSApplication; 14 use Joomla\CMS\Factory; 15 use Joomla\CMS\Filesystem\File; 16 use Joomla\CMS\Filesystem\Path; 17 use Joomla\CMS\Form\Form; 18 use Joomla\CMS\Language\Text; 19 use Joomla\CMS\Log\Log; 20 use Joomla\CMS\Mail\MailTemplate; 21 use Joomla\CMS\Plugin\CMSPlugin; 22 use Joomla\CMS\User\UserFactoryInterface; 23 use Joomla\Component\Scheduler\Administrator\Task\Status; 24 use Joomla\Component\Scheduler\Administrator\Task\Task; 25 use Joomla\Database\DatabaseInterface; 26 use Joomla\Event\Event; 27 use Joomla\Event\EventInterface; 28 use Joomla\Event\SubscriberInterface; 29 use PHPMailer\PHPMailer\Exception as MailerException; 30 31 // phpcs:disable PSR1.Files.SideEffects 32 \defined('_JEXEC') or die; 33 // phpcs:enable PSR1.Files.SideEffects 34 35 /** 36 * This plugin implements email notification functionality for Tasks configured through the Scheduler component. 37 * Notification configuration is supported on a per-task basis, which can be set-up through the Task item form, made 38 * possible by injecting the notification fields into the item form with a `onContentPrepareForm` listener.<br/> 39 * 40 * Notifications can be set-up on: task success, failure, fatal failure (task running too long or crashing the request), 41 * or on _orphaned_ task routines (missing parent plugin - either uninstalled, disabled or no longer offering a routine 42 * with the same ID). 43 * 44 * @since 4.1.0 45 */ 46 class PlgSystemTasknotification extends CMSPlugin implements SubscriberInterface 47 { 48 /** 49 * The task notification form. This form is merged into the task item form by {@see 50 * injectTaskNotificationFieldset()}. 51 * 52 * @var string 53 * @since 4.1.0 54 */ 55 private const TASK_NOTIFICATION_FORM = 'task_notification'; 56 57 /** 58 * @var CMSApplication 59 * @since 4.1.0 60 */ 61 protected $app; 62 63 /** 64 * @var DatabaseInterface 65 * @since 4.1.0 66 */ 67 protected $db; 68 69 /** 70 * @var boolean 71 * @since 4.1.0 72 */ 73 protected $autoloadLanguage = true; 74 75 76 /** 77 * @inheritDoc 78 * 79 * @return array 80 * 81 * @since 4.1.0 82 */ 83 public static function getSubscribedEvents(): array 84 { 85 return [ 86 'onContentPrepareForm' => 'injectTaskNotificationFieldset', 87 'onTaskExecuteSuccess' => 'notifySuccess', 88 'onTaskExecuteFailure' => 'notifyFailure', 89 'onTaskRoutineNotFound' => 'notifyOrphan', 90 'onTaskRecoverFailure' => 'notifyFatalRecovery', 91 ]; 92 } 93 94 /** 95 * Inject fields to support configuration of post-execution notifications into the task item form. 96 * 97 * @param EventInterface $event The onContentPrepareForm event. 98 * 99 * @return boolean True if successful. 100 * 101 * @since 4.1.0 102 */ 103 public function injectTaskNotificationFieldset(EventInterface $event): bool 104 { 105 /** @var Form $form */ 106 $form = $event->getArgument('0'); 107 108 if ($form->getName() !== 'com_scheduler.task') { 109 return true; 110 } 111 112 $formFile = __DIR__ . "/forms/" . self::TASK_NOTIFICATION_FORM . '.xml'; 113 114 try { 115 $formFile = Path::check($formFile); 116 } catch (Exception $e) { 117 // Log? 118 return false; 119 } 120 121 if (!File::exists($formFile)) { 122 return false; 123 } 124 125 return $form->loadFile($formFile); 126 } 127 128 /** 129 * Send out email notifications on Task execution failure if task configuration allows it. 130 * 131 * @param Event $event The onTaskExecuteFailure event. 132 * 133 * @return void 134 * 135 * @since 4.1.0 136 * @throws Exception 137 */ 138 public function notifyFailure(Event $event): void 139 { 140 /** @var Task $task */ 141 $task = $event->getArgument('subject'); 142 143 if (!(int) $task->get('params.notifications.failure_mail', 1)) { 144 return; 145 } 146 147 // @todo safety checks, multiple files [?] 148 $outFile = $event->getArgument('subject')->snapshot['output_file'] ?? ''; 149 $data = $this->getDataFromTask($event->getArgument('subject')); 150 $this->sendMail('plg_system_tasknotification.failure_mail', $data, $outFile); 151 } 152 153 /** 154 * Send out email notifications on orphaned task if task configuration allows.<br/> 155 * A task is `orphaned` if the task's parent plugin has been removed/disabled, or no longer offers a task 156 * with the same routine ID. 157 * 158 * @param Event $event The onTaskRoutineNotFound event. 159 * 160 * @return void 161 * 162 * @since 4.1.0 163 * @throws Exception 164 */ 165 public function notifyOrphan(Event $event): void 166 { 167 /** @var Task $task */ 168 $task = $event->getArgument('subject'); 169 170 if (!(int) $task->get('params.notifications.orphan_mail', 1)) { 171 return; 172 } 173 174 $data = $this->getDataFromTask($event->getArgument('subject')); 175 $this->sendMail('plg_system_tasknotification.orphan_mail', $data); 176 } 177 178 /** 179 * Send out email notifications on Task execution success if task configuration allows. 180 * 181 * @param Event $event The onTaskExecuteSuccess event. 182 * 183 * @return void 184 * 185 * @since 4.1.0 186 * @throws Exception 187 */ 188 public function notifySuccess(Event $event): void 189 { 190 /** @var Task $task */ 191 $task = $event->getArgument('subject'); 192 193 if (!(int) $task->get('params.notifications.success_mail', 0)) { 194 return; 195 } 196 197 // @todo safety checks, multiple files [?] 198 $outFile = $event->getArgument('subject')->snapshot['output_file'] ?? ''; 199 $data = $this->getDataFromTask($event->getArgument('subject')); 200 $this->sendMail('plg_system_tasknotification.success_mail', $data, $outFile); 201 } 202 203 /** 204 * Send out email notifications on fatal recovery of task execution if task configuration allows.<br/> 205 * Fatal recovery indicated that the task either crashed the parent process or its execution lasted longer 206 * than the global task timeout (this is configurable through the Scheduler component configuration). 207 * In the latter case, the global task timeout should be adjusted so that this false positive can be avoided. 208 * This stands as a limitation of the Scheduler's current task execution implementation, which doesn't involve 209 * keeping track of the parent PHP process which could enable keeping track of the task's status. 210 * 211 * @param Event $event The onTaskRecoverFailure event. 212 * 213 * @return void 214 * 215 * @since 4.1.0 216 * @throws Exception 217 */ 218 public function notifyFatalRecovery(Event $event): void 219 { 220 /** @var Task $task */ 221 $task = $event->getArgument('subject'); 222 223 if (!(int) $task->get('params.notifications.fatal_failure_mail', 1)) { 224 return; 225 } 226 227 $data = $this->getDataFromTask($event->getArgument('subject')); 228 $this->sendMail('plg_system_tasknotification.fatal_recovery_mail', $data); 229 } 230 231 /** 232 * @param Task $task A task object 233 * 234 * @return array An array of data to bind to a mail template. 235 * 236 * @since 4.1.0 237 */ 238 private function getDataFromTask(Task $task): array 239 { 240 $lockOrExecTime = Factory::getDate($task->get('locked') ?? $task->get('last_execution'))->format(Text::_('DATE_FORMAT_LC2')); 241 242 return [ 243 'TASK_ID' => $task->get('id'), 244 'TASK_TITLE' => $task->get('title'), 245 'EXIT_CODE' => $task->getContent()['status'] ?? Status::NO_EXIT, 246 'EXEC_DATE_TIME' => $lockOrExecTime, 247 'TASK_OUTPUT' => $task->getContent()['output_body'] ?? '', 248 ]; 249 } 250 251 /** 252 * @param string $template The mail template. 253 * @param array $data The data to bind to the mail template. 254 * @param string $attachment The attachment to send with the mail (@todo multiple) 255 * 256 * @return void 257 * 258 * @since 4.1.0 259 * @throws Exception 260 */ 261 private function sendMail(string $template, array $data, string $attachment = ''): void 262 { 263 $app = $this->app; 264 $db = $this->db; 265 266 /** @var UserFactoryInterface $userFactory */ 267 $userFactory = Factory::getContainer()->get('user.factory'); 268 269 // Get all users who are not blocked and have opted in for system mails. 270 $query = $db->getQuery(true); 271 272 $query->select($db->qn(['name', 'email', 'sendEmail', 'id'])) 273 ->from($db->quoteName('#__users')) 274 ->where($db->quoteName('sendEmail') . ' = 1') 275 ->where($db->quoteName('block') . ' = 0'); 276 277 $db->setQuery($query); 278 279 try { 280 $users = $db->loadObjectList(); 281 } catch (RuntimeException $e) { 282 return; 283 } 284 285 if ($users === null) { 286 Log::add(Text::_('PLG_SYSTEM_TASK_NOTIFICATION_USER_FETCH_FAIL'), Log::ERROR); 287 288 return; 289 } 290 291 $mailSent = false; 292 293 // Mail all matching users who also have the `core.manage` privilege for com_scheduler. 294 foreach ($users as $user) { 295 $user = $userFactory->loadUserById($user->id); 296 297 if ($user->authorise('core.manage', 'com_scheduler')) { 298 try { 299 $mailer = new MailTemplate($template, $app->getLanguage()->getTag()); 300 $mailer->addTemplateData($data); 301 $mailer->addRecipient($user->email); 302 303 if ( 304 !empty($attachment) 305 && File::exists($attachment) 306 && is_file($attachment) 307 ) { 308 // @todo we allow multiple files [?] 309 $attachName = pathinfo($attachment, PATHINFO_BASENAME); 310 $mailer->addAttachment($attachName, $attachment); 311 } 312 313 $mailer->send(); 314 $mailSent = true; 315 } catch (MailerException $exception) { 316 Log::add(Text::_('PLG_SYSTEM_TASK_NOTIFICATION_NOTIFY_SEND_EMAIL_FAIL'), Log::ERROR); 317 } 318 } 319 } 320 321 if (!$mailSent) { 322 Log::add(Text::_('PLG_SYSTEM_TASK_NOTIFICATION_NO_MAIL_SENT'), Log::WARNING); 323 } 324 } 325 }
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 |