[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2008 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\Updater; 11 12 use Joomla\CMS\Adapter\Adapter; 13 use Joomla\CMS\Factory; 14 use Joomla\CMS\Table\Table; 15 use Joomla\Database\ParameterType; 16 17 // phpcs:disable PSR1.Files.SideEffects 18 \defined('JPATH_PLATFORM') or die; 19 // phpcs:enable PSR1.Files.SideEffects 20 21 /** 22 * Updater Class 23 * 24 * @since 1.7.0 25 */ 26 class Updater extends Adapter 27 { 28 /** 29 * Development snapshots, nightly builds, pre-release versions and so on 30 * 31 * @var integer 32 * @since 3.4 33 */ 34 public const STABILITY_DEV = 0; 35 36 /** 37 * Alpha versions (work in progress, things are likely to be broken) 38 * 39 * @var integer 40 * @since 3.4 41 */ 42 public const STABILITY_ALPHA = 1; 43 44 /** 45 * Beta versions (major functionality in place, show-stopper bugs are likely to be present) 46 * 47 * @var integer 48 * @since 3.4 49 */ 50 public const STABILITY_BETA = 2; 51 52 /** 53 * Release Candidate versions (almost stable, minor bugs might be present) 54 * 55 * @var integer 56 * @since 3.4 57 */ 58 public const STABILITY_RC = 3; 59 60 /** 61 * Stable versions (production quality code) 62 * 63 * @var integer 64 * @since 3.4 65 */ 66 public const STABILITY_STABLE = 4; 67 68 /** 69 * Updater instance container. 70 * 71 * @var Updater 72 * @since 1.7.3 73 */ 74 protected static $instance; 75 76 /** 77 * Constructor 78 * 79 * @param string $basepath Base Path of the adapters 80 * @param string $classprefix Class prefix of adapters 81 * @param string $adapterfolder Name of folder to append to base path 82 * 83 * @since 3.1 84 */ 85 public function __construct($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Updater\\Adapter', $adapterfolder = 'Adapter') 86 { 87 parent::__construct($basepath, $classprefix, $adapterfolder); 88 } 89 90 /** 91 * Returns a reference to the global Installer object, only creating it 92 * if it doesn't already exist. 93 * 94 * @return Updater An installer object 95 * 96 * @since 1.7.0 97 */ 98 public static function getInstance() 99 { 100 if (!isset(self::$instance)) { 101 self::$instance = new static(); 102 } 103 104 return self::$instance; 105 } 106 107 /** 108 * Finds the update for an extension. Any discovered updates are stored in the #__updates table. 109 * 110 * @param int|array $eid Extension Identifier or list of Extension Identifiers; if zero use all 111 * sites 112 * @param integer $cacheTimeout How many seconds to cache update information; if zero, force reload the 113 * update information 114 * @param integer $minimumStability Minimum stability for the updates; 0=dev, 1=alpha, 2=beta, 3=rc, 115 * 4=stable 116 * @param boolean $includeCurrent Should I include the current version in the results? 117 * 118 * @return boolean True if there are updates 119 * 120 * @since 1.7.0 121 */ 122 public function findUpdates($eid = 0, $cacheTimeout = 0, $minimumStability = self::STABILITY_STABLE, $includeCurrent = false) 123 { 124 $retval = false; 125 126 $results = $this->getUpdateSites($eid); 127 128 if (empty($results)) { 129 return $retval; 130 } 131 132 $now = time(); 133 $earliestTime = $now - $cacheTimeout; 134 $sitesWithUpdates = array(); 135 136 if ($cacheTimeout > 0) { 137 $sitesWithUpdates = $this->getSitesWithUpdates($earliestTime); 138 } 139 140 foreach ($results as $result) { 141 /** 142 * If we have already checked for updates within the cache timeout period we will report updates available 143 * only if there are update records matching this update site. Then we skip processing of the update site 144 * since it's already processed within the cache timeout period. 145 */ 146 if ( 147 ($cacheTimeout > 0) 148 && isset($result['last_check_timestamp']) 149 && ($result['last_check_timestamp'] >= $earliestTime) 150 ) { 151 $retval = $retval || \in_array($result['update_site_id'], $sitesWithUpdates); 152 153 continue; 154 } 155 156 // Make sure there is no update left over in the database. 157 $db = $this->getDbo(); 158 $query = $db->getQuery(true) 159 ->delete($db->quoteName('#__updates')) 160 ->where($db->quoteName('update_site_id') . ' = :id') 161 ->bind(':id', $result['update_site_id'], ParameterType::INTEGER); 162 $db->setQuery($query); 163 $db->execute(); 164 165 $updateObjects = $this->getUpdateObjectsForSite($result, $minimumStability, $includeCurrent); 166 167 if (!empty($updateObjects)) { 168 $retval = true; 169 170 /** @var \Joomla\CMS\Table\Update $update */ 171 foreach ($updateObjects as $update) { 172 $update->check(); 173 $update->store(); 174 } 175 } 176 177 // Finally, update the last update check timestamp 178 $this->updateLastCheckTimestamp($result['update_site_id']); 179 } 180 181 return $retval; 182 } 183 184 /** 185 * Returns the update site records for an extension with ID $eid. If $eid is zero all enabled update sites records 186 * will be returned. 187 * 188 * @param int $eid The extension ID to fetch. 189 * 190 * @return array 191 * 192 * @since 3.6.0 193 */ 194 private function getUpdateSites($eid = 0) 195 { 196 $db = $this->getDbo(); 197 $query = $db->getQuery(true); 198 199 $query->select( 200 [ 201 'DISTINCT ' . $db->quoteName('a.update_site_id'), 202 $db->quoteName('a.type'), 203 $db->quoteName('a.location'), 204 $db->quoteName('a.last_check_timestamp'), 205 $db->quoteName('a.extra_query'), 206 ] 207 ) 208 ->from($db->quoteName('#__update_sites', 'a')) 209 ->where($db->quoteName('a.enabled') . ' = 1'); 210 211 if ($eid) { 212 $query->join( 213 'INNER', 214 $db->quoteName('#__update_sites_extensions', 'b'), 215 $db->quoteName('a.update_site_id') . ' = ' . $db->quoteName('b.update_site_id') 216 ); 217 218 if (\is_array($eid)) { 219 $query->whereIn($db->quoteName('b.extension_id'), $eid); 220 } elseif ($eid = (int) $eid) { 221 $query->where($db->quoteName('b.extension_id') . ' = :eid') 222 ->bind(':eid', $eid, ParameterType::INTEGER); 223 } 224 } 225 226 $db->setQuery($query); 227 228 $result = $db->loadAssocList(); 229 230 if (!\is_array($result)) { 231 return array(); 232 } 233 234 return $result; 235 } 236 237 /** 238 * Loads the contents of an update site record $updateSite and returns the update objects 239 * 240 * @param array $updateSite The update site record to process 241 * @param int $minimumStability Minimum stability for the returned update records 242 * @param bool $includeCurrent Should I also include the current version? 243 * 244 * @return array The update records. Empty array if no updates are found. 245 * 246 * @since 3.6.0 247 */ 248 private function getUpdateObjectsForSite($updateSite, $minimumStability = self::STABILITY_STABLE, $includeCurrent = false) 249 { 250 $retVal = array(); 251 252 $this->setAdapter($updateSite['type']); 253 254 if (!isset($this->_adapters[$updateSite['type']])) { 255 // Ignore update sites requiring adapters we don't have installed 256 return $retVal; 257 } 258 259 $updateSite['minimum_stability'] = $minimumStability; 260 261 // Get the update information from the remote update XML document 262 /** @var UpdateAdapter $adapter */ 263 $adapter = $this->_adapters[ $updateSite['type']]; 264 $update_result = $adapter->findUpdate($updateSite); 265 266 // Version comparison operator. 267 $operator = $includeCurrent ? 'ge' : 'gt'; 268 269 if (\is_array($update_result)) { 270 // If we have additional update sites in the remote (collection) update XML document, parse them 271 if (\array_key_exists('update_sites', $update_result) && \count($update_result['update_sites'])) { 272 $thisUrl = trim($updateSite['location']); 273 $thisId = (int) $updateSite['update_site_id']; 274 275 foreach ($update_result['update_sites'] as $extraUpdateSite) { 276 $extraUrl = trim($extraUpdateSite['location']); 277 $extraId = (int) $extraUpdateSite['update_site_id']; 278 279 // Do not try to fetch the same update site twice 280 if (($thisId == $extraId) || ($thisUrl == $extraUrl)) { 281 continue; 282 } 283 284 $extraUpdates = $this->getUpdateObjectsForSite($extraUpdateSite, $minimumStability); 285 286 if (\count($extraUpdates)) { 287 $retVal = array_merge($retVal, $extraUpdates); 288 } 289 } 290 } 291 292 if (\array_key_exists('updates', $update_result) && \count($update_result['updates'])) { 293 /** @var \Joomla\CMS\Table\Update $current_update */ 294 foreach ($update_result['updates'] as $current_update) { 295 $current_update->extra_query = $updateSite['extra_query']; 296 297 /** @var \Joomla\CMS\Table\Update $update */ 298 $update = Table::getInstance('update'); 299 300 /** @var \Joomla\CMS\Table\Extension $extension */ 301 $extension = Table::getInstance('extension'); 302 303 $uid = $update 304 ->find( 305 array( 306 'element' => $current_update->get('element'), 307 'type' => $current_update->get('type'), 308 'client_id' => $current_update->get('client_id'), 309 'folder' => $current_update->get('folder'), 310 ) 311 ); 312 313 $eid = $extension 314 ->find( 315 array( 316 'element' => $current_update->get('element'), 317 'type' => $current_update->get('type'), 318 'client_id' => $current_update->get('client_id'), 319 'folder' => $current_update->get('folder'), 320 ) 321 ); 322 323 if (!$uid) { 324 // Set the extension id 325 if ($eid) { 326 // We have an installed extension, check the update is actually newer 327 $extension->load($eid); 328 $data = json_decode($extension->manifest_cache, true); 329 330 if (version_compare($current_update->version, $data['version'], $operator) == 1) { 331 $current_update->extension_id = $eid; 332 $retVal[] = $current_update; 333 } 334 } else { 335 // A potentially new extension to be installed 336 $retVal[] = $current_update; 337 } 338 } else { 339 $update->load($uid); 340 341 // We already have an update in the database lets check whether it has an extension_id 342 if ((int) $update->extension_id === 0 && $eid) { 343 // The current update does not have an extension_id but we found one. Let's use it. 344 $current_update->extension_id = $eid; 345 } 346 347 // If there is an update, check that the version is newer then replaces 348 if (version_compare($current_update->version, $update->version, $operator) == 1) { 349 $retVal[] = $current_update; 350 } 351 } 352 } 353 } 354 } 355 356 return $retVal; 357 } 358 359 /** 360 * Returns the IDs of the update sites with cached updates 361 * 362 * @param int $timestamp Optional. If set, only update sites checked before $timestamp will be taken into 363 * account. 364 * 365 * @return array The IDs of the update sites with cached updates 366 * 367 * @since 3.6.0 368 */ 369 private function getSitesWithUpdates($timestamp = 0) 370 { 371 $db = Factory::getDbo(); 372 $timestamp = (int) $timestamp; 373 374 $query = $db->getQuery(true) 375 ->select('DISTINCT ' . $db->quoteName('update_site_id')) 376 ->from($db->quoteName('#__updates')); 377 378 if ($timestamp) { 379 $subQuery = $db->getQuery(true) 380 ->select($db->quoteName('update_site_id')) 381 ->from($db->quoteName('#__update_sites')) 382 ->where( 383 [ 384 $db->quoteName('last_check_timestamp') . ' IS NULL', 385 $db->quoteName('last_check_timestamp') . ' <= :timestamp', 386 ], 387 'OR' 388 ); 389 390 $query->where($db->quoteName('update_site_id') . ' IN (' . $subQuery . ')') 391 ->bind(':timestamp', $timestamp, ParameterType::INTEGER); 392 } 393 394 $retVal = $db->setQuery($query)->loadColumn(0); 395 396 if (empty($retVal)) { 397 return array(); 398 } 399 400 return $retVal; 401 } 402 403 /** 404 * Update the last check timestamp of an update site 405 * 406 * @param int $updateSiteId The update site ID to mark as just checked 407 * 408 * @return void 409 * 410 * @since 3.6.0 411 */ 412 private function updateLastCheckTimestamp($updateSiteId) 413 { 414 $timestamp = time(); 415 $db = Factory::getDbo(); 416 $updateSiteId = (int) $updateSiteId; 417 418 $query = $db->getQuery(true) 419 ->update($db->quoteName('#__update_sites')) 420 ->set($db->quoteName('last_check_timestamp') . ' = :timestamp') 421 ->where($db->quoteName('update_site_id') . ' = :id') 422 ->bind(':timestamp', $timestamp, ParameterType::INTEGER) 423 ->bind(':id', $updateSiteId, ParameterType::INTEGER); 424 $db->setQuery($query); 425 $db->execute(); 426 } 427 }
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 |