[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugin 5 * @subpackage System.updatenotification 6 * 7 * @copyright (C) 2015 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\Access\Access; 14 use Joomla\CMS\Cache\Cache; 15 use Joomla\CMS\Component\ComponentHelper; 16 use Joomla\CMS\Extension\ExtensionHelper; 17 use Joomla\CMS\Language\Text; 18 use Joomla\CMS\Log\Log; 19 use Joomla\CMS\Mail\Exception\MailDisabledException; 20 use Joomla\CMS\Mail\MailTemplate; 21 use Joomla\CMS\Plugin\CMSPlugin; 22 use Joomla\CMS\Table\Table; 23 use Joomla\CMS\Updater\Updater; 24 use Joomla\CMS\Uri\Uri; 25 use Joomla\CMS\Version; 26 use Joomla\Database\ParameterType; 27 use PHPMailer\PHPMailer\Exception as phpMailerException; 28 29 // phpcs:disable PSR1.Files.SideEffects 30 \defined('_JEXEC') or die; 31 // phpcs:enable PSR1.Files.SideEffects 32 33 // Uncomment the following line to enable debug mode (update notification email sent every single time) 34 // define('PLG_SYSTEM_UPDATENOTIFICATION_DEBUG', 1); 35 36 /** 37 * Joomla! Update Notification plugin 38 * 39 * Sends out an email to all Super Users or a predefined list of email addresses of Super Users when a new 40 * Joomla! version is available. 41 * 42 * This plugin is a direct adaptation of the corresponding plugin in Akeeba Ltd's Admin Tools. The author has 43 * consented to relicensing their plugin's code under GPLv2 or later (the original version was licensed under 44 * GPLv3 or later) to allow its inclusion in the Joomla! CMS. 45 * 46 * @since 3.5 47 */ 48 class PlgSystemUpdatenotification extends CMSPlugin 49 { 50 /** 51 * Application object 52 * 53 * @var \Joomla\CMS\Application\CMSApplication 54 * @since 4.0.0 55 */ 56 protected $app; 57 58 /** 59 * Database driver 60 * 61 * @var \Joomla\Database\DatabaseInterface 62 * @since 4.0.0 63 */ 64 protected $db; 65 66 /** 67 * Load plugin language files automatically 68 * 69 * @var boolean 70 * @since 3.6.3 71 */ 72 protected $autoloadLanguage = true; 73 74 /** 75 * The update check and notification email code is triggered after the page has fully rendered. 76 * 77 * @return void 78 * 79 * @since 3.5 80 */ 81 public function onAfterRender() 82 { 83 // Get the timeout for Joomla! updates, as configured in com_installer's component parameters 84 $component = ComponentHelper::getComponent('com_installer'); 85 86 /** @var \Joomla\Registry\Registry $params */ 87 $params = $component->getParams(); 88 $cache_timeout = (int) $params->get('cachetimeout', 6); 89 $cache_timeout = 3600 * $cache_timeout; 90 91 // Do we need to run? Compare the last run timestamp stored in the plugin's options with the current 92 // timestamp. If the difference is greater than the cache timeout we shall not execute again. 93 $now = time(); 94 $last = (int) $this->params->get('lastrun', 0); 95 96 if (!defined('PLG_SYSTEM_UPDATENOTIFICATION_DEBUG') && (abs($now - $last) < $cache_timeout)) { 97 return; 98 } 99 100 // Update last run status 101 // If I have the time of the last run, I can update, otherwise insert 102 $this->params->set('lastrun', $now); 103 104 $db = $this->db; 105 $paramsJson = $this->params->toString('JSON'); 106 107 $query = $db->getQuery(true) 108 ->update($db->quoteName('#__extensions')) 109 ->set($db->quoteName('params') . ' = :params') 110 ->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) 111 ->where($db->quoteName('folder') . ' = ' . $db->quote('system')) 112 ->where($db->quoteName('element') . ' = ' . $db->quote('updatenotification')) 113 ->bind(':params', $paramsJson); 114 115 try { 116 // Lock the tables to prevent multiple plugin executions causing a race condition 117 $db->lockTable('#__extensions'); 118 } catch (Exception $e) { 119 // If we can't lock the tables it's too risky to continue execution 120 return; 121 } 122 123 try { 124 // Update the plugin parameters 125 $result = $db->setQuery($query)->execute(); 126 127 $this->clearCacheGroups(['com_plugins']); 128 } catch (Exception $exc) { 129 // If we failed to execute 130 $db->unlockTables(); 131 $result = false; 132 } 133 134 try { 135 // Unlock the tables after writing 136 $db->unlockTables(); 137 } catch (Exception $e) { 138 // If we can't lock the tables assume we have somehow failed 139 $result = false; 140 } 141 142 // Abort on failure 143 if (!$result) { 144 return; 145 } 146 147 // This is the extension ID for Joomla! itself 148 $eid = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id; 149 150 // Get any available updates 151 $updater = Updater::getInstance(); 152 $results = $updater->findUpdates([$eid], $cache_timeout); 153 154 // If there are no updates our job is done. We need BOTH this check AND the one below. 155 if (!$results) { 156 return; 157 } 158 159 // Get the update model and retrieve the Joomla! core updates 160 $model = $this->app->bootComponent('com_installer') 161 ->getMVCFactory()->createModel('Update', 'Administrator', ['ignore_request' => true]); 162 $model->setState('filter.extension_id', $eid); 163 $updates = $model->getItems(); 164 165 // If there are no updates we don't have to notify anyone about anything. This is NOT a duplicate check. 166 if (empty($updates)) { 167 return; 168 } 169 170 // Get the available update 171 $update = array_pop($updates); 172 173 // Check the available version. If it's the same or less than the installed version we have no updates to notify about. 174 if (version_compare($update->version, JVERSION, 'le')) { 175 return; 176 } 177 178 // If we're here, we have updates. First, get a link to the Joomla! Update component. 179 $baseURL = Uri::base(); 180 $baseURL = rtrim($baseURL, '/'); 181 $baseURL .= (substr($baseURL, -13) !== 'administrator') ? '/administrator/' : '/'; 182 $baseURL .= 'index.php?option=com_joomlaupdate'; 183 $uri = new Uri($baseURL); 184 185 /** 186 * Some third party security solutions require a secret query parameter to allow log in to the administrator 187 * backend of the site. The link generated above will be invalid and could probably block the user out of their 188 * site, confusing them (they can't understand the third party security solution is not part of Joomla! proper). 189 * So, we're calling the onBuildAdministratorLoginURL system plugin event to let these third party solutions 190 * add any necessary secret query parameters to the URL. The plugins are supposed to have a method with the 191 * signature: 192 * 193 * public function onBuildAdministratorLoginURL(Uri &$uri); 194 * 195 * The plugins should modify the $uri object directly and return null. 196 */ 197 $this->app->triggerEvent('onBuildAdministratorLoginURL', [&$uri]); 198 199 // Let's find out the email addresses to notify 200 $superUsers = []; 201 $specificEmail = $this->params->get('email', ''); 202 203 if (!empty($specificEmail)) { 204 $superUsers = $this->getSuperUsers($specificEmail); 205 } 206 207 if (empty($superUsers)) { 208 $superUsers = $this->getSuperUsers(); 209 } 210 211 if (empty($superUsers)) { 212 return; 213 } 214 215 /* 216 * Load the appropriate language. We try to load English (UK), the current user's language and the forced 217 * language preference, in this order. This ensures that we'll never end up with untranslated strings in the 218 * update email which would make Joomla! seem bad. So, please, if you don't fully understand what the 219 * following code does DO NOT TOUCH IT. It makes the difference between a hobbyist CMS and a professional 220 * solution! 221 */ 222 $jLanguage = $this->app->getLanguage(); 223 $jLanguage->load('plg_system_updatenotification', JPATH_ADMINISTRATOR, 'en-GB', true, true); 224 $jLanguage->load('plg_system_updatenotification', JPATH_ADMINISTRATOR, null, true, false); 225 226 // Then try loading the preferred (forced) language 227 $forcedLanguage = $this->params->get('language_override', ''); 228 229 if (!empty($forcedLanguage)) { 230 $jLanguage->load('plg_system_updatenotification', JPATH_ADMINISTRATOR, $forcedLanguage, true, false); 231 } 232 233 // Replace merge codes with their values 234 $newVersion = $update->version; 235 236 $jVersion = new Version(); 237 $currentVersion = $jVersion->getShortVersion(); 238 239 $sitename = $this->app->get('sitename'); 240 241 $substitutions = [ 242 'newversion' => $newVersion, 243 'curversion' => $currentVersion, 244 'sitename' => $sitename, 245 'url' => Uri::base(), 246 'link' => $uri->toString(), 247 'releasenews' => 'https://www.joomla.org/announcements/release-news/', 248 ]; 249 250 // Send the emails to the Super Users 251 foreach ($superUsers as $superUser) { 252 try { 253 $mailer = new MailTemplate('plg_system_updatenotification.mail', $jLanguage->getTag()); 254 $mailer->addRecipient($superUser->email); 255 $mailer->addTemplateData($substitutions); 256 $mailer->send(); 257 } catch (MailDisabledException | phpMailerException $exception) { 258 try { 259 Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror'); 260 } catch (\RuntimeException $exception) { 261 $this->app->enqueueMessage(Text::_($exception->errorMessage()), 'warning'); 262 } 263 } 264 } 265 } 266 267 /** 268 * Returns the Super Users email information. If you provide a comma separated $email list 269 * we will check that these emails do belong to Super Users and that they have not blocked 270 * system emails. 271 * 272 * @param null|string $email A list of Super Users to email 273 * 274 * @return array The list of Super User emails 275 * 276 * @since 3.5 277 */ 278 private function getSuperUsers($email = null) 279 { 280 $db = $this->db; 281 $emails = []; 282 283 // Convert the email list to an array 284 if (!empty($email)) { 285 $temp = explode(',', $email); 286 287 foreach ($temp as $entry) { 288 $emails[] = trim($entry); 289 } 290 291 $emails = array_unique($emails); 292 } 293 294 // Get a list of groups which have Super User privileges 295 $ret = []; 296 297 try { 298 $rootId = Table::getInstance('Asset')->getRootId(); 299 $rules = Access::getAssetRules($rootId)->getData(); 300 $rawGroups = $rules['core.admin']->getData(); 301 $groups = []; 302 303 if (empty($rawGroups)) { 304 return $ret; 305 } 306 307 foreach ($rawGroups as $g => $enabled) { 308 if ($enabled) { 309 $groups[] = $g; 310 } 311 } 312 313 if (empty($groups)) { 314 return $ret; 315 } 316 } catch (Exception $exc) { 317 return $ret; 318 } 319 320 // Get the user IDs of users belonging to the SA groups 321 try { 322 $query = $db->getQuery(true) 323 ->select($db->quoteName('user_id')) 324 ->from($db->quoteName('#__user_usergroup_map')) 325 ->whereIn($db->quoteName('group_id'), $groups); 326 327 $db->setQuery($query); 328 $userIDs = $db->loadColumn(0); 329 330 if (empty($userIDs)) { 331 return $ret; 332 } 333 } catch (Exception $exc) { 334 return $ret; 335 } 336 337 // Get the user information for the Super Administrator users 338 try { 339 $query = $db->getQuery(true) 340 ->select($db->quoteName(['id', 'username', 'email'])) 341 ->from($db->quoteName('#__users')) 342 ->whereIn($db->quoteName('id'), $userIDs) 343 ->where($db->quoteName('block') . ' = 0') 344 ->where($db->quoteName('sendEmail') . ' = 1'); 345 346 if (!empty($emails)) { 347 $lowerCaseEmails = array_map('strtolower', $emails); 348 $query->whereIn('LOWER(' . $db->quoteName('email') . ')', $lowerCaseEmails, ParameterType::STRING); 349 } 350 351 $db->setQuery($query); 352 $ret = $db->loadObjectList(); 353 } catch (Exception $exc) { 354 return $ret; 355 } 356 357 return $ret; 358 } 359 360 /** 361 * Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp. 362 * 363 * @param array $clearGroups The cache groups to clean 364 * 365 * @return void 366 * 367 * @since 3.5 368 */ 369 private function clearCacheGroups(array $clearGroups) 370 { 371 foreach ($clearGroups as $group) { 372 try { 373 $options = [ 374 'defaultgroup' => $group, 375 'cachebase' => $this->app->get('cache_path', JPATH_CACHE), 376 ]; 377 378 $cache = Cache::getInstance('callback', $options); 379 $cache->clean(); 380 } catch (Exception $e) { 381 // Ignore it 382 } 383 } 384 } 385 }
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 |