[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Updater/ -> Updater.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer