[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_joomlaupdate/src/Model/ -> UpdateModel.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Administrator
   5   * @subpackage  com_joomlaupdate
   6   *
   7   * @copyright   (C) 2012 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\Joomlaupdate\Administrator\Model;
  12  
  13  use Joomla\CMS\Authentication\Authentication;
  14  use Joomla\CMS\Component\ComponentHelper;
  15  use Joomla\CMS\Extension\ExtensionHelper;
  16  use Joomla\CMS\Factory;
  17  use Joomla\CMS\Filesystem\File;
  18  use Joomla\CMS\Filter\InputFilter;
  19  use Joomla\CMS\Http\Http;
  20  use Joomla\CMS\Http\HttpFactory;
  21  use Joomla\CMS\Installer\Installer;
  22  use Joomla\CMS\Language\Text;
  23  use Joomla\CMS\Log\Log;
  24  use Joomla\CMS\MVC\Model\BaseDatabaseModel;
  25  use Joomla\CMS\Plugin\PluginHelper;
  26  use Joomla\CMS\Updater\Update;
  27  use Joomla\CMS\Updater\Updater;
  28  use Joomla\CMS\User\UserHelper;
  29  use Joomla\CMS\Version;
  30  use Joomla\Database\ParameterType;
  31  use Joomla\Registry\Registry;
  32  use Joomla\Utilities\ArrayHelper;
  33  
  34  // phpcs:disable PSR1.Files.SideEffects
  35  \defined('_JEXEC') or die;
  36  // phpcs:enable PSR1.Files.SideEffects
  37  
  38  /**
  39   * Joomla! update overview Model
  40   *
  41   * @since  2.5.4
  42   */
  43  class UpdateModel extends BaseDatabaseModel
  44  {
  45      /**
  46       * @var   array  $updateInformation  null
  47       * Holds the update information evaluated in getUpdateInformation.
  48       *
  49       * @since 3.10.0
  50       */
  51      private $updateInformation = null;
  52  
  53      /**
  54       * Detects if the Joomla! update site currently in use matches the one
  55       * configured in this component. If they don't match, it changes it.
  56       *
  57       * @return  void
  58       *
  59       * @since    2.5.4
  60       */
  61      public function applyUpdateSite()
  62      {
  63          // Determine the intended update URL.
  64          $params = ComponentHelper::getParams('com_joomlaupdate');
  65  
  66          switch ($params->get('updatesource', 'nochange')) {
  67              // "Minor & Patch Release for Current version AND Next Major Release".
  68              case 'next':
  69                  $updateURL = 'https://update.joomla.org/core/sts/list_sts.xml';
  70                  break;
  71  
  72              // "Testing"
  73              case 'testing':
  74                  $updateURL = 'https://update.joomla.org/core/test/list_test.xml';
  75                  break;
  76  
  77              // "Custom"
  78              // @todo: check if the customurl is valid and not just "not empty".
  79              case 'custom':
  80                  if (trim($params->get('customurl', '')) != '') {
  81                      $updateURL = trim($params->get('customurl', ''));
  82                  } else {
  83                      Factory::getApplication()->enqueueMessage(Text::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM_ERROR'), 'error');
  84  
  85                      return;
  86                  }
  87                  break;
  88  
  89              /**
  90               * "Minor & Patch Release for Current version (recommended and default)".
  91               * The commented "case" below are for documenting where 'default' and legacy options falls
  92               * case 'default':
  93               * case 'lts':
  94               * case 'sts': (It's shown as "Default" because that option does not exist any more)
  95               * case 'nochange':
  96               */
  97              default:
  98                  $updateURL = 'https://update.joomla.org/core/list.xml';
  99          }
 100  
 101          $id = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
 102          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
 103          $query = $db->getQuery(true)
 104              ->select($db->quoteName('us') . '.*')
 105              ->from($db->quoteName('#__update_sites_extensions', 'map'))
 106              ->join(
 107                  'INNER',
 108                  $db->quoteName('#__update_sites', 'us'),
 109                  $db->quoteName('us.update_site_id') . ' = ' . $db->quoteName('map.update_site_id')
 110              )
 111              ->where($db->quoteName('map.extension_id') . ' = :id')
 112              ->bind(':id', $id, ParameterType::INTEGER);
 113          $db->setQuery($query);
 114          $update_site = $db->loadObject();
 115  
 116          if ($update_site->location != $updateURL) {
 117              // Modify the database record.
 118              $update_site->last_check_timestamp = 0;
 119              $update_site->location = $updateURL;
 120              $db->updateObject('#__update_sites', $update_site, 'update_site_id');
 121  
 122              // Remove cached updates.
 123              $query->clear()
 124                  ->delete($db->quoteName('#__updates'))
 125                  ->where($db->quoteName('extension_id') . ' = :id')
 126                  ->bind(':id', $id, ParameterType::INTEGER);
 127              $db->setQuery($query);
 128              $db->execute();
 129          }
 130      }
 131  
 132      /**
 133       * Makes sure that the Joomla! update cache is up-to-date.
 134       *
 135       * @param   boolean  $force  Force reload, ignoring the cache timeout.
 136       *
 137       * @return  void
 138       *
 139       * @since    2.5.4
 140       */
 141      public function refreshUpdates($force = false)
 142      {
 143          if ($force) {
 144              $cache_timeout = 0;
 145          } else {
 146              $update_params = ComponentHelper::getParams('com_installer');
 147              $cache_timeout = (int) $update_params->get('cachetimeout', 6);
 148              $cache_timeout = 3600 * $cache_timeout;
 149          }
 150  
 151          $updater               = Updater::getInstance();
 152          $minimumStability      = Updater::STABILITY_STABLE;
 153          $comJoomlaupdateParams = ComponentHelper::getParams('com_joomlaupdate');
 154  
 155          if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) {
 156              $minimumStability = $comJoomlaupdateParams->get('minimum_stability', Updater::STABILITY_STABLE);
 157          }
 158  
 159          $reflection = new \ReflectionObject($updater);
 160          $reflectionMethod = $reflection->getMethod('findUpdates');
 161          $methodParameters = $reflectionMethod->getParameters();
 162  
 163          if (count($methodParameters) >= 4) {
 164              // Reinstall support is available in Updater
 165              $updater->findUpdates(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id, $cache_timeout, $minimumStability, true);
 166          } else {
 167              $updater->findUpdates(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id, $cache_timeout, $minimumStability);
 168          }
 169      }
 170  
 171      /**
 172       * Makes sure that the Joomla! Update Component Update is in the database and check if there is a new version.
 173       *
 174       * @return  boolean  True if there is an update else false
 175       *
 176       * @since   4.0.0
 177       */
 178      public function getCheckForSelfUpdate()
 179      {
 180          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
 181  
 182          $query = $db->getQuery(true)
 183              ->select($db->quoteName('extension_id'))
 184              ->from($db->quoteName('#__extensions'))
 185              ->where($db->quoteName('element') . ' = ' . $db->quote('com_joomlaupdate'));
 186          $db->setQuery($query);
 187  
 188          try {
 189              // Get the component extension ID
 190              $joomlaUpdateComponentId = $db->loadResult();
 191          } catch (\RuntimeException $e) {
 192              // Something is wrong here!
 193              $joomlaUpdateComponentId = 0;
 194              Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
 195          }
 196  
 197          // Try the update only if we have an extension id
 198          if ($joomlaUpdateComponentId != 0) {
 199              // Always force to check for an update!
 200              $cache_timeout = 0;
 201  
 202              $updater = Updater::getInstance();
 203              $updater->findUpdates($joomlaUpdateComponentId, $cache_timeout, Updater::STABILITY_STABLE);
 204  
 205              // Fetch the update information from the database.
 206              $query = $db->getQuery(true)
 207                  ->select('*')
 208                  ->from($db->quoteName('#__updates'))
 209                  ->where($db->quoteName('extension_id') . ' = :id')
 210                  ->bind(':id', $joomlaUpdateComponentId, ParameterType::INTEGER);
 211              $db->setQuery($query);
 212  
 213              try {
 214                  $joomlaUpdateComponentObject = $db->loadObject();
 215              } catch (\RuntimeException $e) {
 216                  // Something is wrong here!
 217                  $joomlaUpdateComponentObject = null;
 218                  Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
 219              }
 220  
 221              return !empty($joomlaUpdateComponentObject);
 222          }
 223  
 224          return false;
 225      }
 226  
 227      /**
 228       * Returns an array with the Joomla! update information.
 229       *
 230       * @return  array
 231       *
 232       * @since   2.5.4
 233       */
 234      public function getUpdateInformation()
 235      {
 236          if ($this->updateInformation) {
 237              return $this->updateInformation;
 238          }
 239  
 240          // Initialise the return array.
 241          $this->updateInformation = array(
 242              'installed' => \JVERSION,
 243              'latest'    => null,
 244              'object'    => null,
 245              'hasUpdate' => false,
 246              'current'   => JVERSION // This is deprecated please use 'installed' or JVERSION directly
 247          );
 248  
 249          // Fetch the update information from the database.
 250          $id = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
 251          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
 252          $query = $db->getQuery(true)
 253              ->select('*')
 254              ->from($db->quoteName('#__updates'))
 255              ->where($db->quoteName('extension_id') . ' = :id')
 256              ->bind(':id', $id, ParameterType::INTEGER);
 257          $db->setQuery($query);
 258          $updateObject = $db->loadObject();
 259  
 260          if (is_null($updateObject)) {
 261              // We have not found any update in the database - we seem to be running the latest version.
 262              $this->updateInformation['latest'] = \JVERSION;
 263  
 264              return $this->updateInformation;
 265          }
 266  
 267          // Check whether this is a valid update or not
 268          if (version_compare($updateObject->version, JVERSION, '<')) {
 269              // This update points to an outdated version. We should not offer to update to this.
 270              $this->updateInformation['latest'] = JVERSION;
 271  
 272              return $this->updateInformation;
 273          }
 274  
 275          $minimumStability      = Updater::STABILITY_STABLE;
 276          $comJoomlaupdateParams = ComponentHelper::getParams('com_joomlaupdate');
 277  
 278          if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) {
 279              $minimumStability = $comJoomlaupdateParams->get('minimum_stability', Updater::STABILITY_STABLE);
 280          }
 281  
 282          // Fetch the full update details from the update details URL.
 283          $update = new Update();
 284          $update->loadFromXml($updateObject->detailsurl, $minimumStability);
 285  
 286          // Make sure we use the current information we got from the detailsurl
 287          $this->updateInformation['object'] = $update;
 288          $this->updateInformation['latest'] = $updateObject->version;
 289  
 290          // Check whether this is an update or not.
 291          if (version_compare($this->updateInformation['latest'], JVERSION, '>')) {
 292              $this->updateInformation['hasUpdate'] = true;
 293          }
 294  
 295          return $this->updateInformation;
 296      }
 297  
 298      /**
 299       * Removes all of the updates from the table and enable all update streams.
 300       *
 301       * @return  boolean  Result of operation.
 302       *
 303       * @since   3.0
 304       */
 305      public function purge()
 306      {
 307          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
 308  
 309          // Modify the database record
 310          $update_site = new \stdClass();
 311          $update_site->last_check_timestamp = 0;
 312          $update_site->enabled = 1;
 313          $update_site->update_site_id = 1;
 314          $db->updateObject('#__update_sites', $update_site, 'update_site_id');
 315  
 316          $query = $db->getQuery(true)
 317              ->delete($db->quoteName('#__updates'))
 318              ->where($db->quoteName('update_site_id') . ' = 1');
 319          $db->setQuery($query);
 320  
 321          if ($db->execute()) {
 322              $this->_message = Text::_('COM_JOOMLAUPDATE_CHECKED_UPDATES');
 323  
 324              return true;
 325          } else {
 326              $this->_message = Text::_('COM_JOOMLAUPDATE_FAILED_TO_CHECK_UPDATES');
 327  
 328              return false;
 329          }
 330      }
 331  
 332      /**
 333       * Downloads the update package to the site.
 334       *
 335       * @return  array
 336       *
 337       * @since   2.5.4
 338       */
 339      public function download()
 340      {
 341          $updateInfo = $this->getUpdateInformation();
 342          $packageURL = trim($updateInfo['object']->downloadurl->_data);
 343          $sources    = $updateInfo['object']->get('downloadSources', array());
 344  
 345          // We have to manually follow the redirects here so we set the option to false.
 346          $httpOptions = new Registry();
 347          $httpOptions->set('follow_location', false);
 348  
 349          try {
 350              $head = HttpFactory::getHttp($httpOptions)->head($packageURL);
 351          } catch (\RuntimeException $e) {
 352              // Passing false here -> download failed message
 353              $response['basename'] = false;
 354  
 355              return $response;
 356          }
 357  
 358          // Follow the Location headers until the actual download URL is known
 359          while (isset($head->headers['location'])) {
 360              $packageURL = (string) $head->headers['location'][0];
 361  
 362              try {
 363                  $head = HttpFactory::getHttp($httpOptions)->head($packageURL);
 364              } catch (\RuntimeException $e) {
 365                  // Passing false here -> download failed message
 366                  $response['basename'] = false;
 367  
 368                  return $response;
 369              }
 370          }
 371  
 372          // Remove protocol, path and query string from URL
 373          $basename = basename($packageURL);
 374  
 375          if (strpos($basename, '?') !== false) {
 376              $basename = substr($basename, 0, strpos($basename, '?'));
 377          }
 378  
 379          // Find the path to the temp directory and the local package.
 380          $tempdir  = (string) InputFilter::getInstance(
 381              [],
 382              [],
 383              InputFilter::ONLY_BLOCK_DEFINED_TAGS,
 384              InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
 385          )
 386              ->clean(Factory::getApplication()->get('tmp_path'), 'path');
 387          $target   = $tempdir . '/' . $basename;
 388          $response = [];
 389  
 390          // Do we have a cached file?
 391          $exists = File::exists($target);
 392  
 393          if (!$exists) {
 394              // Not there, let's fetch it.
 395              $mirror = 0;
 396  
 397              while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) {
 398                  $name       = $sources[$mirror];
 399                  $packageURL = trim($name->url);
 400                  $mirror++;
 401              }
 402  
 403              $response['basename'] = $download;
 404          } else {
 405              // Is it a 0-byte file? If so, re-download please.
 406              $filesize = @filesize($target);
 407  
 408              if (empty($filesize)) {
 409                  $mirror = 0;
 410  
 411                  while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) {
 412                      $name       = $sources[$mirror];
 413                      $packageURL = trim($name->url);
 414                      $mirror++;
 415                  }
 416  
 417                  $response['basename'] = $download;
 418              }
 419  
 420              // Yes, it's there, skip downloading.
 421              $response['basename'] = $basename;
 422          }
 423  
 424          $response['check'] = $this->isChecksumValid($target, $updateInfo['object']);
 425  
 426          return $response;
 427      }
 428  
 429      /**
 430       * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest
 431       *
 432       * @param   string  $packagefile   Location of the package to be installed
 433       * @param   Update  $updateObject  The Update Object
 434       *
 435       * @return  boolean  False in case the validation did not work; true in any other case.
 436       *
 437       * @note    This method has been forked from (JInstallerHelper::isChecksumValid) so it
 438       *          does not depend on an up-to-date InstallerHelper at the update time
 439       *
 440       * @since   3.9.0
 441       */
 442      private function isChecksumValid($packagefile, $updateObject)
 443      {
 444          $hashes = array('sha256', 'sha384', 'sha512');
 445  
 446          foreach ($hashes as $hash) {
 447              if ($updateObject->get($hash, false)) {
 448                  $hashPackage = hash_file($hash, $packagefile);
 449                  $hashRemote  = $updateObject->$hash->_data;
 450  
 451                  if ($hashPackage !== $hashRemote) {
 452                      // Return false in case the hash did not match
 453                      return false;
 454                  }
 455              }
 456          }
 457  
 458          // Well nothing was provided or all worked
 459          return true;
 460      }
 461  
 462      /**
 463       * Downloads a package file to a specific directory
 464       *
 465       * @param   string  $url     The URL to download from
 466       * @param   string  $target  The directory to store the file
 467       *
 468       * @return  boolean True on success
 469       *
 470       * @since   2.5.4
 471       */
 472      protected function downloadPackage($url, $target)
 473      {
 474          try {
 475              Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), Log::INFO, 'Update');
 476          } catch (\RuntimeException $exception) {
 477              // Informational log only
 478          }
 479  
 480          // Make sure the target does not exist.
 481          File::delete($target);
 482  
 483          // Download the package
 484          try {
 485              $result = HttpFactory::getHttp([], ['curl', 'stream'])->get($url);
 486          } catch (\RuntimeException $e) {
 487              return false;
 488          }
 489  
 490          if (!$result || ($result->code != 200 && $result->code != 310)) {
 491              return false;
 492          }
 493  
 494          // Write the file to disk
 495          File::write($target, $result->body);
 496  
 497          return basename($target);
 498      }
 499  
 500      /**
 501       * Backwards compatibility. Use createUpdateFile() instead.
 502       *
 503       * @param   null  $basename The basename of the file to create
 504       *
 505       * @return  boolean
 506       * @since   2.5.1
 507       * @deprecated 5.0
 508       */
 509      public function createRestorationFile($basename = null): bool
 510      {
 511          return $this->createUpdateFile($basename);
 512      }
 513  
 514      /**
 515       * Create the update.php file and trigger onJoomlaBeforeUpdate event.
 516       *
 517       * The onJoomlaBeforeUpdate event stores the core files for which overrides have been defined.
 518       * This will be compared in the onJoomlaAfterUpdate event with the current filesystem state,
 519       * thereby determining how many and which overrides need to be checked and possibly updated
 520       * after Joomla installed an update.
 521       *
 522       * @param   string  $basename  Optional base path to the file.
 523       *
 524       * @return  boolean True if successful; false otherwise.
 525       *
 526       * @since  2.5.4
 527       */
 528      public function createUpdateFile($basename = null): bool
 529      {
 530          // Load overrides plugin.
 531          PluginHelper::importPlugin('installer');
 532  
 533          // Get a password
 534          $password = UserHelper::genRandomPassword(32);
 535          $app = Factory::getApplication();
 536  
 537          // Trigger event before joomla update.
 538          $app->triggerEvent('onJoomlaBeforeUpdate');
 539  
 540          // Get the absolute path to site's root.
 541          $siteroot = JPATH_SITE;
 542  
 543          // If the package name is not specified, get it from the update info.
 544          if (empty($basename)) {
 545              $updateInfo = $this->getUpdateInformation();
 546              $packageURL = $updateInfo['object']->downloadurl->_data;
 547              $basename = basename($packageURL);
 548          }
 549  
 550          // Get the package name.
 551          $config  = $app->getConfig();
 552          $tempdir = $config->get('tmp_path');
 553          $file    = $tempdir . '/' . $basename;
 554  
 555          $filesize = @filesize($file);
 556          $app->setUserState('com_joomlaupdate.password', $password);
 557          $app->setUserState('com_joomlaupdate.filesize', $filesize);
 558  
 559          $data = "<?php\ndefined('_JOOMLA_UPDATE') or die('Restricted access');\n";
 560          $data .= '$extractionSetup = [' . "\n";
 561          $data .= <<<ENDDATA
 562      'security.password' => '$password',
 563      'setup.sourcefile' => '$file',
 564      'setup.destdir' => '$siteroot',
 565  ENDDATA;
 566  
 567          $data .= '];';
 568  
 569          // Remove the old file, if it's there...
 570          $configpath = JPATH_COMPONENT_ADMINISTRATOR . '/update.php';
 571  
 572          if (File::exists($configpath)) {
 573              if (!File::delete($configpath)) {
 574                  File::invalidateFileCache($configpath);
 575                  @unlink($configpath);
 576              }
 577          }
 578  
 579          // Write new file. First try with File.
 580          $result = File::write($configpath, $data);
 581  
 582          // In case File used FTP but direct access could help.
 583          if (!$result) {
 584              if (function_exists('file_put_contents')) {
 585                  $result = @file_put_contents($configpath, $data);
 586  
 587                  if ($result !== false) {
 588                      $result = true;
 589                  }
 590              } else {
 591                  $fp = @fopen($configpath, 'wt');
 592  
 593                  if ($fp !== false) {
 594                      $result = @fwrite($fp, $data);
 595  
 596                      if ($result !== false) {
 597                          $result = true;
 598                      }
 599  
 600                      @fclose($fp);
 601                  }
 602              }
 603          }
 604  
 605          return $result;
 606      }
 607  
 608      /**
 609       * Finalise the upgrade.
 610       *
 611       * This method will do the following:
 612       * * Run the schema update SQL files.
 613       * * Run the Joomla post-update script.
 614       * * Update the manifest cache and #__extensions entry for Joomla itself.
 615       *
 616       * It performs essentially the same function as InstallerFile::install() without the file copy.
 617       *
 618       * @return  boolean True on success.
 619       *
 620       * @since   2.5.4
 621       */
 622      public function finaliseUpgrade()
 623      {
 624          $installer = Installer::getInstance();
 625  
 626          $manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml');
 627  
 628          if ($manifest === false) {
 629              $installer->abort(Text::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
 630  
 631              return false;
 632          }
 633  
 634          $installer->manifest = $manifest;
 635  
 636          $installer->setUpgrade(true);
 637          $installer->setOverwrite(true);
 638  
 639          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
 640          $installer->extension = new \Joomla\CMS\Table\Extension($db);
 641          $installer->extension->load(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id);
 642  
 643          $installer->setAdapter($installer->extension->type);
 644  
 645          $installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml');
 646          $installer->setPath('source', JPATH_MANIFESTS . '/files');
 647          $installer->setPath('extension_root', JPATH_ROOT);
 648  
 649          // Run the script file.
 650          \JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php');
 651  
 652          $manifestClass = new \JoomlaInstallerScript();
 653  
 654          ob_start();
 655          ob_implicit_flush(false);
 656  
 657          if ($manifestClass && method_exists($manifestClass, 'preflight')) {
 658              if ($manifestClass->preflight('update', $installer) === false) {
 659                  $installer->abort(
 660                      Text::sprintf(
 661                          'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
 662                          Text::_('JLIB_INSTALLER_INSTALL')
 663                      )
 664                  );
 665  
 666                  return false;
 667              }
 668          }
 669  
 670          // Create msg object; first use here.
 671          $msg = ob_get_contents();
 672          ob_end_clean();
 673  
 674          // Get a database connector object.
 675          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
 676  
 677          /*
 678           * Check to see if a file extension by the same name is already installed.
 679           * If it is, then update the table because if the files aren't there
 680           * we can assume that it was (badly) uninstalled.
 681           * If it isn't, add an entry to extensions.
 682           */
 683          $query = $db->getQuery(true)
 684              ->select($db->quoteName('extension_id'))
 685              ->from($db->quoteName('#__extensions'))
 686              ->where($db->quoteName('type') . ' = ' . $db->quote('file'))
 687              ->where($db->quoteName('element') . ' = ' . $db->quote('joomla'));
 688          $db->setQuery($query);
 689  
 690          try {
 691              $db->execute();
 692          } catch (\RuntimeException $e) {
 693              // Install failed, roll back changes.
 694              $installer->abort(
 695                  Text::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', Text::_('JLIB_INSTALLER_UPDATE'), $e->getMessage())
 696              );
 697  
 698              return false;
 699          }
 700  
 701          $id = $db->loadResult();
 702          $row = new \Joomla\CMS\Table\Extension($db);
 703  
 704          if ($id) {
 705              // Load the entry and update the manifest_cache.
 706              $row->load($id);
 707  
 708              // Update name.
 709              $row->set('name', 'files_joomla');
 710  
 711              // Update manifest.
 712              $row->manifest_cache = $installer->generateManifestCache();
 713  
 714              if (!$row->store()) {
 715                  // Install failed, roll back changes.
 716                  $installer->abort(
 717                      Text::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', Text::_('JLIB_INSTALLER_UPDATE'), $row->getError())
 718                  );
 719  
 720                  return false;
 721              }
 722          } else {
 723              // Add an entry to the extension table with a whole heap of defaults.
 724              $row->set('name', 'files_joomla');
 725              $row->set('type', 'file');
 726              $row->set('element', 'joomla');
 727  
 728              // There is no folder for files so leave it blank.
 729              $row->set('folder', '');
 730              $row->set('enabled', 1);
 731              $row->set('protected', 0);
 732              $row->set('access', 0);
 733              $row->set('client_id', 0);
 734              $row->set('params', '');
 735              $row->set('manifest_cache', $installer->generateManifestCache());
 736  
 737              if (!$row->store()) {
 738                  // Install failed, roll back changes.
 739                  $installer->abort(Text::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError()));
 740  
 741                  return false;
 742              }
 743  
 744              // Set the insert id.
 745              $row->set('extension_id', $db->insertid());
 746  
 747              // Since we have created a module item, we add it to the installation step stack
 748              // so that if we have to rollback the changes we can undo it.
 749              $installer->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id));
 750          }
 751  
 752          $result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id);
 753  
 754          if ($result === false) {
 755              // Install failed, rollback changes (message already logged by the installer).
 756              $installer->abort();
 757  
 758              return false;
 759          }
 760  
 761          // Reinitialise the installer's extensions table's properties.
 762          $installer->extension->getFields(true);
 763  
 764          // Start Joomla! 1.6.
 765          ob_start();
 766          ob_implicit_flush(false);
 767  
 768          if ($manifestClass && method_exists($manifestClass, 'update')) {
 769              if ($manifestClass->update($installer) === false) {
 770                  // Install failed, rollback changes.
 771                  $installer->abort(
 772                      Text::sprintf(
 773                          'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
 774                          Text::_('JLIB_INSTALLER_INSTALL')
 775                      )
 776                  );
 777  
 778                  return false;
 779              }
 780          }
 781  
 782          // Append messages.
 783          $msg .= ob_get_contents();
 784          ob_end_clean();
 785  
 786          // Clobber any possible pending updates.
 787          $update = new \Joomla\CMS\Table\Update($db);
 788          $uid = $update->find(
 789              array('element' => 'joomla', 'type' => 'file', 'client_id' => '0', 'folder' => '')
 790          );
 791  
 792          if ($uid) {
 793              $update->delete($uid);
 794          }
 795  
 796          // And now we run the postflight.
 797          ob_start();
 798          ob_implicit_flush(false);
 799  
 800          if ($manifestClass && method_exists($manifestClass, 'postflight')) {
 801              $manifestClass->postflight('update', $installer);
 802          }
 803  
 804          // Append messages.
 805          $msg .= ob_get_contents();
 806          ob_end_clean();
 807  
 808          if ($msg != '') {
 809              $installer->set('extension_message', $msg);
 810          }
 811  
 812          // Refresh versionable assets cache.
 813          Factory::getApplication()->flushAssets();
 814  
 815          return true;
 816      }
 817  
 818      /**
 819       * Removes the extracted package file and trigger onJoomlaAfterUpdate event.
 820       *
 821       * The onJoomlaAfterUpdate event compares the stored list of files previously overridden with
 822       * the updated core files, finding out which files have changed during the update, thereby
 823       * determining how many and which override files need to be checked and possibly updated after
 824       * the Joomla update.
 825       *
 826       * @return  void
 827       *
 828       * @since   2.5.4
 829       */
 830      public function cleanUp()
 831      {
 832          // Load overrides plugin.
 833          PluginHelper::importPlugin('installer');
 834  
 835          $app = Factory::getApplication();
 836  
 837          // Trigger event after joomla update.
 838          $app->triggerEvent('onJoomlaAfterUpdate');
 839  
 840          // Remove the update package.
 841          $tempdir = $app->get('tmp_path');
 842  
 843          $file = $app->getUserState('com_joomlaupdate.file', null);
 844          File::delete($tempdir . '/' . $file);
 845  
 846          // Remove the update.php file used in Joomla 4.0.3 and later.
 847          if (File::exists(JPATH_COMPONENT_ADMINISTRATOR . '/update.php')) {
 848              File::delete(JPATH_COMPONENT_ADMINISTRATOR . '/update.php');
 849          }
 850  
 851          // Remove the legacy restoration.php file (when updating from Joomla 4.0.2 and earlier).
 852          if (File::exists(JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php')) {
 853              File::delete(JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php');
 854          }
 855  
 856          // Remove the legacy restore_finalisation.php file used in Joomla 4.0.2 and earlier.
 857          if (File::exists(JPATH_COMPONENT_ADMINISTRATOR . '/restore_finalisation.php')) {
 858              File::delete(JPATH_COMPONENT_ADMINISTRATOR . '/restore_finalisation.php');
 859          }
 860  
 861          // Remove joomla.xml from the site's root.
 862          if (File::exists(JPATH_ROOT . '/joomla.xml')) {
 863              File::delete(JPATH_ROOT . '/joomla.xml');
 864          }
 865  
 866          // Unset the update filename from the session.
 867          $app = Factory::getApplication();
 868          $app->setUserState('com_joomlaupdate.file', null);
 869          $oldVersion = $app->getUserState('com_joomlaupdate.oldversion');
 870  
 871          // Trigger event after joomla update.
 872          $app->triggerEvent('onJoomlaAfterUpdate', array($oldVersion));
 873          $app->setUserState('com_joomlaupdate.oldversion', null);
 874      }
 875  
 876      /**
 877       * Uploads what is presumably an update ZIP file under a mangled name in the temporary directory.
 878       *
 879       * @return  void
 880       *
 881       * @since   3.6.0
 882       */
 883      public function upload()
 884      {
 885          // Get the uploaded file information.
 886          $input = Factory::getApplication()->input;
 887  
 888          // Do not change the filter type 'raw'. We need this to let files containing PHP code to upload. See \JInputFiles::get.
 889          $userfile = $input->files->get('install_package', null, 'raw');
 890  
 891          // Make sure that file uploads are enabled in php.
 892          if (!(bool) ini_get('file_uploads')) {
 893              throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLFILE'), 500);
 894          }
 895  
 896          // Make sure that zlib is loaded so that the package can be unpacked.
 897          if (!extension_loaded('zlib')) {
 898              throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLZLIB'), 500);
 899          }
 900  
 901          // If there is no uploaded file, we have a problem...
 902          if (!is_array($userfile)) {
 903              throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_NO_FILE_SELECTED'), 500);
 904          }
 905  
 906          // Is the PHP tmp directory missing?
 907          if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_NO_TMP_DIR)) {
 908              throw new \RuntimeException(
 909                  Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br>' .
 910                  Text::_('COM_INSTALLER_MSG_WARNINGS_PHPUPLOADNOTSET'),
 911                  500
 912              );
 913          }
 914  
 915          // Is the max upload size too small in php.ini?
 916          if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_INI_SIZE)) {
 917              throw new \RuntimeException(
 918                  Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br>' . Text::_('COM_INSTALLER_MSG_WARNINGS_SMALLUPLOADSIZE'),
 919                  500
 920              );
 921          }
 922  
 923          // Check if there was a different problem uploading the file.
 924          if ($userfile['error'] || $userfile['size'] < 1) {
 925              throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500);
 926          }
 927  
 928          // Build the appropriate paths.
 929          $tmp_dest = tempnam(Factory::getApplication()->get('tmp_path'), 'ju');
 930          $tmp_src  = $userfile['tmp_name'];
 931  
 932          // Move uploaded file.
 933          $result = File::upload($tmp_src, $tmp_dest, false, true);
 934  
 935          if (!$result) {
 936              throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500);
 937          }
 938  
 939          Factory::getApplication()->setUserState('com_joomlaupdate.temp_file', $tmp_dest);
 940      }
 941  
 942      /**
 943       * Checks the super admin credentials are valid for the currently logged in users
 944       *
 945       * @param   array  $credentials  The credentials to authenticate the user with
 946       *
 947       * @return  boolean
 948       *
 949       * @since   3.6.0
 950       */
 951      public function captiveLogin($credentials)
 952      {
 953          // Make sure the username matches
 954          $username = $credentials['username'] ?? null;
 955          $user     = Factory::getUser();
 956  
 957          if (strtolower($user->username) != strtolower($username)) {
 958              return false;
 959          }
 960  
 961          // Make sure the user is authorised
 962          if (!$user->authorise('core.admin')) {
 963              return false;
 964          }
 965  
 966          // Get the global Authentication object.
 967          $authenticate = Authentication::getInstance();
 968          $response     = $authenticate->authenticate($credentials);
 969  
 970          if ($response->status !== Authentication::STATUS_SUCCESS) {
 971              return false;
 972          }
 973  
 974          return true;
 975      }
 976  
 977      /**
 978       * Does the captive (temporary) file we uploaded before still exist?
 979       *
 980       * @return  boolean
 981       *
 982       * @since   3.6.0
 983       */
 984      public function captiveFileExists()
 985      {
 986          $file = Factory::getApplication()->getUserState('com_joomlaupdate.temp_file', null);
 987  
 988          if (empty($file) || !File::exists($file)) {
 989              return false;
 990          }
 991  
 992          return true;
 993      }
 994  
 995      /**
 996       * Remove the captive (temporary) file we uploaded before and the .
 997       *
 998       * @return  void
 999       *
1000       * @since   3.6.0
1001       */
1002      public function removePackageFiles()
1003      {
1004          $files = array(
1005              Factory::getApplication()->getUserState('com_joomlaupdate.temp_file', null),
1006              Factory::getApplication()->getUserState('com_joomlaupdate.file', null),
1007          );
1008  
1009          foreach ($files as $file) {
1010              if ($file !== null && File::exists($file)) {
1011                  File::delete($file);
1012              }
1013          }
1014      }
1015  
1016      /**
1017       * Gets PHP options.
1018       * @todo: Outsource, build common code base for pre install and pre update check
1019       *
1020       * @return array Array of PHP config options
1021       *
1022       * @since   3.10.0
1023       */
1024      public function getPhpOptions()
1025      {
1026          $options = array();
1027  
1028          /*
1029           * Check the PHP Version. It is already checked in Update.
1030           * A Joomla! Update which is not supported by current PHP
1031           * version is not shown. So this check is actually unnecessary.
1032           */
1033          $option         = new \stdClass();
1034          $option->label  = Text::sprintf('INSTL_PHP_VERSION_NEWER', $this->getTargetMinimumPHPVersion());
1035          $option->state  = $this->isPhpVersionSupported();
1036          $option->notice = null;
1037          $options[]      = $option;
1038  
1039          // Check for zlib support.
1040          $option         = new \stdClass();
1041          $option->label  = Text::_('INSTL_ZLIB_COMPRESSION_SUPPORT');
1042          $option->state  = extension_loaded('zlib');
1043          $option->notice = null;
1044          $options[]      = $option;
1045  
1046          // Check for XML support.
1047          $option         = new \stdClass();
1048          $option->label  = Text::_('INSTL_XML_SUPPORT');
1049          $option->state  = extension_loaded('xml');
1050          $option->notice = null;
1051          $options[]      = $option;
1052  
1053          // Check for mbstring options.
1054          if (extension_loaded('mbstring')) {
1055              // Check for default MB language.
1056              $option = new \stdClass();
1057              $option->label  = Text::_('INSTL_MB_LANGUAGE_IS_DEFAULT');
1058              $option->state  = strtolower(ini_get('mbstring.language')) === 'neutral';
1059              $option->notice = $option->state ? null : Text::_('INSTL_NOTICEMBLANGNOTDEFAULT');
1060              $options[] = $option;
1061  
1062              // Check for MB function overload.
1063              $option = new \stdClass();
1064              $option->label  = Text::_('INSTL_MB_STRING_OVERLOAD_OFF');
1065              $option->state  = ini_get('mbstring.func_overload') == 0;
1066              $option->notice = $option->state ? null : Text::_('INSTL_NOTICEMBSTRINGOVERLOAD');
1067              $options[] = $option;
1068          }
1069  
1070          // Check for a missing native parse_ini_file implementation.
1071          $option = new \stdClass();
1072          $option->label  = Text::_('INSTL_PARSE_INI_FILE_AVAILABLE');
1073          $option->state  = $this->getIniParserAvailability();
1074          $option->notice = null;
1075          $options[] = $option;
1076  
1077          // Check for missing native json_encode / json_decode support.
1078          $option = new \stdClass();
1079          $option->label  = Text::_('INSTL_JSON_SUPPORT_AVAILABLE');
1080          $option->state  = function_exists('json_encode') && function_exists('json_decode');
1081          $option->notice = null;
1082          $options[] = $option;
1083          $updateInformation = $this->getUpdateInformation();
1084  
1085          // Check if configured database is compatible with the next major version of Joomla
1086          $nextMajorVersion = Version::MAJOR_VERSION + 1;
1087  
1088          if (version_compare($updateInformation['latest'], (string) $nextMajorVersion, '>=')) {
1089              $option = new \stdClass();
1090              $option->label  = Text::sprintf('INSTL_DATABASE_SUPPORTED', $this->getConfiguredDatabaseType());
1091              $option->state  = $this->isDatabaseTypeSupported();
1092              $option->notice = null;
1093              $options[]      = $option;
1094          }
1095  
1096          // Check if database structure is up to date
1097          $option = new \stdClass();
1098          $option->label  = Text::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_TITLE');
1099          $option->state  = $this->getDatabaseSchemaCheck();
1100          $option->notice = $option->state ? null : Text::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_NOTICE');
1101          $options[] = $option;
1102  
1103          return $options;
1104      }
1105  
1106      /**
1107       * Gets PHP Settings.
1108       * @todo: Outsource, build common code base for pre install and pre update check
1109       *
1110       * @return  array
1111       *
1112       * @since   3.10.0
1113       */
1114      public function getPhpSettings()
1115      {
1116          $settings = array();
1117  
1118          // Check for display errors.
1119          $setting = new \stdClass();
1120          $setting->label = Text::_('INSTL_DISPLAY_ERRORS');
1121          $setting->state = (bool) ini_get('display_errors');
1122          $setting->recommended = false;
1123          $settings[] = $setting;
1124  
1125          // Check for file uploads.
1126          $setting = new \stdClass();
1127          $setting->label = Text::_('INSTL_FILE_UPLOADS');
1128          $setting->state = (bool) ini_get('file_uploads');
1129          $setting->recommended = true;
1130          $settings[] = $setting;
1131  
1132          // Check for output buffering.
1133          $setting = new \stdClass();
1134          $setting->label = Text::_('INSTL_OUTPUT_BUFFERING');
1135          $setting->state = (int) ini_get('output_buffering') !== 0;
1136          $setting->recommended = false;
1137          $settings[] = $setting;
1138  
1139          // Check for session auto-start.
1140          $setting = new \stdClass();
1141          $setting->label = Text::_('INSTL_SESSION_AUTO_START');
1142          $setting->state = (bool) ini_get('session.auto_start');
1143          $setting->recommended = false;
1144          $settings[] = $setting;
1145  
1146          // Check for native ZIP support.
1147          $setting = new \stdClass();
1148          $setting->label = Text::_('INSTL_ZIP_SUPPORT_AVAILABLE');
1149          $setting->state = function_exists('zip_open') && function_exists('zip_read');
1150          $setting->recommended = true;
1151          $settings[] = $setting;
1152  
1153          // Check for GD support
1154          $setting = new \stdClass();
1155          $setting->label = Text::sprintf('INSTL_EXTENSION_AVAILABLE', 'GD');
1156          $setting->state = extension_loaded('gd');
1157          $setting->recommended = true;
1158          $settings[] = $setting;
1159  
1160          // Check for iconv support
1161          $setting = new \stdClass();
1162          $setting->label = Text::sprintf('INSTL_EXTENSION_AVAILABLE', 'iconv');
1163          $setting->state = function_exists('iconv');
1164          $setting->recommended = true;
1165          $settings[] = $setting;
1166  
1167          // Check for intl support
1168          $setting = new \stdClass();
1169          $setting->label = Text::sprintf('INSTL_EXTENSION_AVAILABLE', 'intl');
1170          $setting->state = function_exists('transliterator_transliterate');
1171          $setting->recommended = true;
1172          $settings[] = $setting;
1173  
1174          return $settings;
1175      }
1176  
1177      /**
1178       * Returns the configured database type id (mysqli or sqlsrv or ...)
1179       *
1180       * @return string
1181       *
1182       * @since 3.10.0
1183       */
1184      private function getConfiguredDatabaseType()
1185      {
1186          return Factory::getApplication()->get('dbtype');
1187      }
1188  
1189      /**
1190       * Returns true, if J! version is < 4 or current configured
1191       * database type is compatible with the update.
1192       *
1193       * @return boolean
1194       *
1195       * @since 3.10.0
1196       */
1197      public function isDatabaseTypeSupported()
1198      {
1199          $updateInformation = $this->getUpdateInformation();
1200          $nextMajorVersion  = Version::MAJOR_VERSION + 1;
1201  
1202          // Check if configured database is compatible with Joomla 4
1203          if (version_compare($updateInformation['latest'], (string) $nextMajorVersion, '>=')) {
1204              $unsupportedDatabaseTypes = array('sqlsrv', 'sqlazure');
1205              $currentDatabaseType = $this->getConfiguredDatabaseType();
1206  
1207              return !in_array($currentDatabaseType, $unsupportedDatabaseTypes);
1208          }
1209  
1210          return true;
1211      }
1212  
1213  
1214      /**
1215       * Returns true, if current installed php version is compatible with the update.
1216       *
1217       * @return boolean
1218       *
1219       * @since 3.10.0
1220       */
1221      public function isPhpVersionSupported()
1222      {
1223          return version_compare(PHP_VERSION, $this->getTargetMinimumPHPVersion(), '>=');
1224      }
1225  
1226      /**
1227       * Returns the PHP minimum version for the update.
1228       * Returns JOOMLA_MINIMUM_PHP, if there is no information given.
1229       *
1230       * @return string
1231       *
1232       * @since 3.10.0
1233       */
1234      private function getTargetMinimumPHPVersion()
1235      {
1236          $updateInformation = $this->getUpdateInformation();
1237  
1238          return isset($updateInformation['object']->php_minimum) ?
1239              $updateInformation['object']->php_minimum->_data :
1240              JOOMLA_MINIMUM_PHP;
1241      }
1242  
1243      /**
1244       * Checks the availability of the parse_ini_file and parse_ini_string functions.
1245       * @todo: Outsource, build common code base for pre install and pre update check
1246       *
1247       * @return  boolean  True if the method exists.
1248       *
1249       * @since   3.10.0
1250       */
1251      public function getIniParserAvailability()
1252      {
1253          $disabledFunctions = ini_get('disable_functions');
1254  
1255          if (!empty($disabledFunctions)) {
1256              // Attempt to detect them in the PHP INI disable_functions variable.
1257              $disabledFunctions = explode(',', trim($disabledFunctions));
1258              $numberOfDisabledFunctions = count($disabledFunctions);
1259  
1260              for ($i = 0; $i < $numberOfDisabledFunctions; $i++) {
1261                  $disabledFunctions[$i] = trim($disabledFunctions[$i]);
1262              }
1263  
1264              $result = !in_array('parse_ini_string', $disabledFunctions);
1265          } else {
1266              // Attempt to detect their existence; even pure PHP implementations of them will trigger a positive response, though.
1267              $result = function_exists('parse_ini_string');
1268          }
1269  
1270          return $result;
1271      }
1272  
1273  
1274      /**
1275       * Check if database structure is up to date
1276       *
1277       * @return  boolean  True if ok, false if not.
1278       *
1279       * @since   3.10.0
1280       */
1281      private function getDatabaseSchemaCheck(): bool
1282      {
1283          $mvcFactory = $this->bootComponent('com_installer')->getMVCFactory();
1284  
1285          /** @var \Joomla\Component\Installer\Administrator\Model\DatabaseModel $model */
1286          $model = $mvcFactory->createModel('Database', 'Administrator');
1287  
1288          // Check if no default text filters found
1289          if (!$model->getDefaultTextFilters()) {
1290              return false;
1291          }
1292  
1293          $coreExtensionInfo = \Joomla\CMS\Extension\ExtensionHelper::getExtensionRecord('joomla', 'file');
1294          $cache = new \Joomla\Registry\Registry($coreExtensionInfo->manifest_cache);
1295  
1296          $updateVersion = $cache->get('version');
1297  
1298          // Check if database update version does not match CMS version
1299          if (version_compare($updateVersion, JVERSION) != 0) {
1300              return false;
1301          }
1302  
1303          // Ensure we only get information for core
1304          $model->setState('filter.extension_id', $coreExtensionInfo->extension_id);
1305  
1306          // We're filtering by a single extension which must always exist - so can safely access this through
1307          // element 0 of the array
1308          $changeInformation = $model->getItems()[0];
1309  
1310          // Check if schema errors found
1311          if ($changeInformation['errorsCount'] !== 0) {
1312              return false;
1313          }
1314  
1315          // Check if database schema version does not match CMS version
1316          if ($model->getSchemaVersion($coreExtensionInfo->extension_id) != $changeInformation['schema']) {
1317              return false;
1318          }
1319  
1320          // No database problems found
1321          return true;
1322      }
1323  
1324      /**
1325       * Gets an array containing all installed extensions, that are not core extensions.
1326       *
1327       * @return  array  name,version,updateserver
1328       *
1329       * @since   3.10.0
1330       */
1331      public function getNonCoreExtensions()
1332      {
1333          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
1334          $query = $db->getQuery(true);
1335  
1336          $query->select(
1337              [
1338                  $db->quoteName('ex.name'),
1339                  $db->quoteName('ex.extension_id'),
1340                  $db->quoteName('ex.manifest_cache'),
1341                  $db->quoteName('ex.type'),
1342                  $db->quoteName('ex.folder'),
1343                  $db->quoteName('ex.element'),
1344                  $db->quoteName('ex.client_id'),
1345              ]
1346          )
1347              ->from($db->quoteName('#__extensions', 'ex'))
1348              ->where($db->quoteName('ex.package_id') . ' = 0')
1349              ->whereNotIn($db->quoteName('ex.extension_id'), ExtensionHelper::getCoreExtensionIds());
1350  
1351          $db->setQuery($query);
1352          $rows = $db->loadObjectList();
1353  
1354          foreach ($rows as $extension) {
1355              $decode = json_decode($extension->manifest_cache);
1356  
1357              // Remove unused fields so they do not cause javascript errors during pre-update check
1358              unset($decode->description);
1359              unset($decode->copyright);
1360              unset($decode->creationDate);
1361  
1362              $this->translateExtensionName($extension);
1363              $extension->version
1364                  = isset($decode->version) ? $decode->version : Text::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION');
1365              unset($extension->manifest_cache);
1366              $extension->manifest_cache = $decode;
1367          }
1368  
1369          return $rows;
1370      }
1371  
1372      /**
1373       * Gets an array containing all installed and enabled plugins, that are not core plugins.
1374       *
1375       * @param   array  $folderFilter  Limit the list of plugins to a specific set of folder values
1376       *
1377       * @return  array  name,version,updateserver
1378       *
1379       * @since   3.10.0
1380       */
1381      public function getNonCorePlugins($folderFilter = ['system','user','authentication','actionlog','multifactorauth'])
1382      {
1383          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
1384          $query = $db->getQuery(true);
1385  
1386          $query->select(
1387              $db->qn('ex.name') . ', ' .
1388              $db->qn('ex.extension_id') . ', ' .
1389              $db->qn('ex.manifest_cache') . ', ' .
1390              $db->qn('ex.type') . ', ' .
1391              $db->qn('ex.folder') . ', ' .
1392              $db->qn('ex.element') . ', ' .
1393              $db->qn('ex.client_id') . ', ' .
1394              $db->qn('ex.package_id')
1395          )->from(
1396              $db->qn('#__extensions', 'ex')
1397          )->where(
1398              $db->qn('ex.type') . ' = ' . $db->quote('plugin')
1399          )->where(
1400              $db->qn('ex.enabled') . ' = 1'
1401          )->whereNotIn(
1402              $db->quoteName('ex.extension_id'),
1403              ExtensionHelper::getCoreExtensionIds()
1404          );
1405  
1406          if (count($folderFilter) > 0) {
1407              $folderFilter = array_map(array($db, 'quote'), $folderFilter);
1408  
1409              $query->where($db->qn('folder') . ' IN (' . implode(',', $folderFilter) . ')');
1410          }
1411  
1412          $db->setQuery($query);
1413          $rows = $db->loadObjectList();
1414  
1415          foreach ($rows as $plugin) {
1416              $decode = json_decode($plugin->manifest_cache);
1417  
1418              // Remove unused fields so they do not cause javascript errors during pre-update check
1419              unset($decode->description);
1420              unset($decode->copyright);
1421              unset($decode->creationDate);
1422  
1423              $this->translateExtensionName($plugin);
1424              $plugin->version = $decode->version ?? Text::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION');
1425              unset($plugin->manifest_cache);
1426              $plugin->manifest_cache = $decode;
1427          }
1428  
1429          return $rows;
1430      }
1431  
1432      /**
1433       * Called by controller's fetchExtensionCompatibility, which is called via AJAX.
1434       *
1435       * @param   string  $extensionID          The ID of the checked extension
1436       * @param   string  $joomlaTargetVersion  Target version of Joomla
1437       *
1438       * @return object
1439       *
1440       * @since 3.10.0
1441       */
1442      public function fetchCompatibility($extensionID, $joomlaTargetVersion)
1443      {
1444          $updateSites = $this->getUpdateSitesInfo($extensionID);
1445  
1446          if (empty($updateSites)) {
1447              return (object) array('state' => 2);
1448          }
1449  
1450          foreach ($updateSites as $updateSite) {
1451              if ($updateSite['type'] === 'collection') {
1452                  $updateFileUrls = $this->getCollectionDetailsUrls($updateSite, $joomlaTargetVersion);
1453  
1454                  foreach ($updateFileUrls as $updateFileUrl) {
1455                      $compatibleVersions = $this->checkCompatibility($updateFileUrl, $joomlaTargetVersion);
1456  
1457                      // Return the compatible versions
1458                      return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions);
1459                  }
1460              } else {
1461                  $compatibleVersions = $this->checkCompatibility($updateSite['location'], $joomlaTargetVersion);
1462  
1463                  // Return the compatible versions
1464                  return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions);
1465              }
1466          }
1467  
1468          // In any other case we mark this extension as not compatible
1469          return (object) array('state' => 0);
1470      }
1471  
1472      /**
1473       * Returns records with update sites and extension information for a given extension ID.
1474       *
1475       * @param   int  $extensionID  The extension ID
1476       *
1477       * @return  array
1478       *
1479       * @since 3.10.0
1480       */
1481      private function getUpdateSitesInfo($extensionID)
1482      {
1483          $id = (int) $extensionID;
1484          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
1485          $query = $db->getQuery(true);
1486  
1487          $query->select(
1488              $db->qn('us.type') . ', ' .
1489              $db->qn('us.location') . ', ' .
1490              $db->qn('e.element') . ' AS ' . $db->qn('ext_element') . ', ' .
1491              $db->qn('e.type') . ' AS ' . $db->qn('ext_type') . ', ' .
1492              $db->qn('e.folder') . ' AS ' . $db->qn('ext_folder')
1493          )
1494              ->from($db->quoteName('#__update_sites', 'us'))
1495              ->join(
1496                  'LEFT',
1497                  $db->quoteName('#__update_sites_extensions', 'ue'),
1498                  $db->quoteName('ue.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
1499              )
1500              ->join(
1501                  'LEFT',
1502                  $db->quoteName('#__extensions', 'e'),
1503                  $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('ue.extension_id')
1504              )
1505              ->where($db->quoteName('e.extension_id') . ' = :id')
1506              ->bind(':id', $id, ParameterType::INTEGER);
1507  
1508          $db->setQuery($query);
1509  
1510          $result = $db->loadAssocList();
1511  
1512          if (!is_array($result)) {
1513              return array();
1514          }
1515  
1516          return $result;
1517      }
1518  
1519      /**
1520       * Method to get details URLs from a collection update site for given extension and Joomla target version.
1521       *
1522       * @param   array   $updateSiteInfo       The update site and extension information record to process
1523       * @param   string  $joomlaTargetVersion  The Joomla! version to test against,
1524       *
1525       * @return  array  An array of URLs.
1526       *
1527       * @since   3.10.0
1528       */
1529      private function getCollectionDetailsUrls($updateSiteInfo, $joomlaTargetVersion)
1530      {
1531          $return = array();
1532  
1533          $http = new Http();
1534  
1535          try {
1536              $response = $http->get($updateSiteInfo['location']);
1537          } catch (\RuntimeException $e) {
1538              $response = null;
1539          }
1540  
1541          if ($response === null || $response->code !== 200) {
1542              return $return;
1543          }
1544  
1545          $updateSiteXML = simplexml_load_string($response->body);
1546  
1547          foreach ($updateSiteXML->extension as $extension) {
1548              $attribs = new \stdClass();
1549  
1550              $attribs->element               = '';
1551              $attribs->type                  = '';
1552              $attribs->folder                = '';
1553              $attribs->targetplatformversion = '';
1554  
1555              foreach ($extension->attributes() as $key => $value) {
1556                  $attribs->$key = (string) $value;
1557              }
1558  
1559              if (
1560                  $attribs->element === $updateSiteInfo['ext_element']
1561                  && $attribs->type === $updateSiteInfo['ext_type']
1562                  && $attribs->folder === $updateSiteInfo['ext_folder']
1563                  && preg_match('/^' . $attribs->targetplatformversion . '/', $joomlaTargetVersion)
1564              ) {
1565                  $return[] = (string) $extension['detailsurl'];
1566              }
1567          }
1568  
1569          return $return;
1570      }
1571  
1572      /**
1573       * Method to check non core extensions for compatibility.
1574       *
1575       * @param   string  $updateFileUrl        The items update XML url.
1576       * @param   string  $joomlaTargetVersion  The Joomla! version to test against
1577       *
1578       * @return  array  An array of strings with compatible version numbers
1579       *
1580       * @since   3.10.0
1581       */
1582      private function checkCompatibility($updateFileUrl, $joomlaTargetVersion)
1583      {
1584          $minimumStability = ComponentHelper::getParams('com_installer')->get('minimum_stability', Updater::STABILITY_STABLE);
1585  
1586          $update = new Update();
1587          $update->set('jversion.full', $joomlaTargetVersion);
1588          $update->loadFromXml($updateFileUrl, $minimumStability);
1589  
1590          $compatibleVersions = $update->get('compatibleVersions');
1591  
1592          // Check if old version of the updater library
1593          if (!isset($compatibleVersions)) {
1594              $downloadUrl = $update->get('downloadurl');
1595              $updateVersion = $update->get('version');
1596  
1597              return empty($downloadUrl) || empty($downloadUrl->_data) || empty($updateVersion) ? array() : array($updateVersion->_data);
1598          }
1599  
1600          usort($compatibleVersions, 'version_compare');
1601  
1602          return $compatibleVersions;
1603      }
1604  
1605      /**
1606       * Translates an extension name
1607       *
1608       * @param   object  &$item  The extension of which the name needs to be translated
1609       *
1610       * @return  void
1611       *
1612       * @since   3.10.0
1613       */
1614      protected function translateExtensionName(&$item)
1615      {
1616          // @todo: Cleanup duplicated code. from com_installer/models/extension.php
1617          $lang = Factory::getLanguage();
1618          $path = $item->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;
1619  
1620          $extension = $item->element;
1621          $source = JPATH_SITE;
1622  
1623          switch ($item->type) {
1624              case 'component':
1625                  $extension = $item->element;
1626                  $source = $path . '/components/' . $extension;
1627                  break;
1628              case 'module':
1629                  $extension = $item->element;
1630                  $source = $path . '/modules/' . $extension;
1631                  break;
1632              case 'file':
1633                  $extension = 'files_' . $item->element;
1634                  break;
1635              case 'library':
1636                  $extension = 'lib_' . $item->element;
1637                  break;
1638              case 'plugin':
1639                  $extension = 'plg_' . $item->folder . '_' . $item->element;
1640                  $source = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element;
1641                  break;
1642              case 'template':
1643                  $extension = 'tpl_' . $item->element;
1644                  $source = $path . '/templates/' . $item->element;
1645          }
1646  
1647          $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
1648          || $lang->load("$extension.sys", $source);
1649          $lang->load($extension, JPATH_ADMINISTRATOR)
1650          || $lang->load($extension, $source);
1651  
1652          // Translate the extension name if possible
1653          $item->name = strip_tags(Text::_($item->name));
1654      }
1655  
1656      /**
1657       * Checks whether a given template is active
1658       *
1659       * @param   string  $template  The template name to be checked
1660       *
1661       * @return  boolean
1662       *
1663       * @since   3.10.4
1664       */
1665      public function isTemplateActive($template)
1666      {
1667          $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
1668          $query = $db->getQuery(true);
1669  
1670          $query->select(
1671              $db->qn(
1672                  array(
1673                      'id',
1674                      'home'
1675                  )
1676              )
1677          )->from(
1678              $db->qn('#__template_styles')
1679          )->where(
1680              $db->qn('template') . ' = :template'
1681          )->bind(':template', $template, ParameterType::STRING);
1682  
1683          $templates = $db->setQuery($query)->loadObjectList();
1684  
1685          $home = array_filter(
1686              $templates,
1687              function ($value) {
1688                  return $value->home > 0;
1689              }
1690          );
1691  
1692          $ids = ArrayHelper::getColumn($templates, 'id');
1693  
1694          $menu = false;
1695  
1696          if (count($ids)) {
1697              $query = $db->getQuery(true);
1698  
1699              $query->select(
1700                  'COUNT(*)'
1701              )->from(
1702                  $db->qn('#__menu')
1703              )->whereIn(
1704                  $db->qn('template_style_id'),
1705                  $ids
1706              );
1707  
1708              $menu = $db->setQuery($query)->loadResult() > 0;
1709          }
1710  
1711          return $home || $menu;
1712      }
1713  }


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