[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Installer/ -> InstallerHelper.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2007 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\Installer;
  11  
  12  use Joomla\Archive\Archive;
  13  use Joomla\CMS\Factory;
  14  use Joomla\CMS\Filesystem\File;
  15  use Joomla\CMS\Filesystem\Folder;
  16  use Joomla\CMS\Filesystem\Path;
  17  use Joomla\CMS\Http\HttpFactory;
  18  use Joomla\CMS\Language\Text;
  19  use Joomla\CMS\Log\Log;
  20  use Joomla\CMS\Plugin\PluginHelper;
  21  use Joomla\CMS\Updater\Update;
  22  use Joomla\CMS\Version;
  23  
  24  // phpcs:disable PSR1.Files.SideEffects
  25  \defined('JPATH_PLATFORM') or die;
  26  // phpcs:enable PSR1.Files.SideEffects
  27  
  28  /**
  29   * Installer helper class
  30   *
  31   * @since  3.1
  32   */
  33  abstract class InstallerHelper
  34  {
  35      /**
  36       * Hash not validated identifier.
  37       *
  38       * @var    integer
  39       * @since  3.9.0
  40       */
  41      public const HASH_NOT_VALIDATED = 0;
  42  
  43      /**
  44       * Hash validated identifier.
  45       *
  46       * @var    integer
  47       * @since  3.9.0
  48       */
  49      public const HASH_VALIDATED = 1;
  50  
  51      /**
  52       * Hash not provided identifier.
  53       *
  54       * @var    integer
  55       * @since  3.9.0
  56       */
  57      public const HASH_NOT_PROVIDED = 2;
  58  
  59      /**
  60       * Downloads a package
  61       *
  62       * @param   string       $url     URL of file to download
  63       * @param   string|bool  $target  Download target filename or false to get the filename from the URL
  64       *
  65       * @return  string|boolean  Path to downloaded package or boolean false on failure
  66       *
  67       * @since   3.1
  68       */
  69      public static function downloadPackage($url, $target = false)
  70      {
  71          // Capture PHP errors
  72          $track_errors = ini_get('track_errors');
  73          ini_set('track_errors', true);
  74  
  75          // Set user agent
  76          $version = new Version();
  77          ini_set('user_agent', $version->getUserAgent('Installer'));
  78  
  79          // Load installer plugins, and allow URL and headers modification
  80          $headers = array();
  81          PluginHelper::importPlugin('installer');
  82          Factory::getApplication()->triggerEvent('onInstallerBeforePackageDownload', array(&$url, &$headers));
  83  
  84          try {
  85              $response = HttpFactory::getHttp()->get($url, $headers);
  86          } catch (\RuntimeException $exception) {
  87              Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT', $exception->getMessage()), Log::WARNING, 'jerror');
  88  
  89              return false;
  90          }
  91  
  92          // Convert keys of headers to lowercase, to accommodate for case variations
  93          $headers = array_change_key_case($response->headers, CASE_LOWER);
  94  
  95          if (302 == $response->code && !empty($headers['location'])) {
  96              return self::downloadPackage($headers['location']);
  97          } elseif (200 != $response->code) {
  98              Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT', $response->code), Log::WARNING, 'jerror');
  99  
 100              return false;
 101          }
 102  
 103          // Parse the Content-Disposition header to get the file name
 104          if (
 105              !empty($headers['content-disposition'])
 106              && preg_match("/\s*filename\s?=\s?(.*)/", $headers['content-disposition'][0], $parts)
 107          ) {
 108              $flds = explode(';', $parts[1]);
 109              $target = trim($flds[0], '"');
 110          }
 111  
 112          $tmpPath = Factory::getApplication()->get('tmp_path');
 113  
 114          // Set the target path if not given
 115          if (!$target) {
 116              $target = $tmpPath . '/' . self::getFilenameFromUrl($url);
 117          } else {
 118              $target = $tmpPath . '/' . basename($target);
 119          }
 120  
 121          // Write buffer to file
 122          File::write($target, $response->body);
 123  
 124          // Restore error tracking to what it was before
 125          ini_set('track_errors', $track_errors);
 126  
 127          // Bump the max execution time because not using built in php zip libs are slow
 128          @set_time_limit(ini_get('max_execution_time'));
 129  
 130          // Return the name of the downloaded package
 131          return basename($target);
 132      }
 133  
 134      /**
 135       * Unpacks a file and verifies it as a Joomla element package
 136       * Supports .gz .tar .tar.gz and .zip
 137       *
 138       * @param   string   $packageFilename    The uploaded package filename or install directory
 139       * @param   boolean  $alwaysReturnArray  If should return false (and leave garbage behind) or return $retval['type']=false
 140       *
 141       * @return  array|boolean  Array on success or boolean false on failure
 142       *
 143       * @since   3.1
 144       */
 145      public static function unpack($packageFilename, $alwaysReturnArray = false)
 146      {
 147          // Path to the archive
 148          $archivename = $packageFilename;
 149  
 150          // Temporary folder to extract the archive into
 151          $tmpdir = uniqid('install_');
 152  
 153          // Clean the paths to use for archive extraction
 154          $extractdir = Path::clean(\dirname($packageFilename) . '/' . $tmpdir);
 155          $archivename = Path::clean($archivename);
 156  
 157          // Do the unpacking of the archive
 158          try {
 159              $archive = new Archive(array('tmp_path' => Factory::getApplication()->get('tmp_path')));
 160              $extract = $archive->extract($archivename, $extractdir);
 161          } catch (\Exception $e) {
 162              if ($alwaysReturnArray) {
 163                  return array(
 164                      'extractdir'  => null,
 165                      'packagefile' => $archivename,
 166                      'type'        => false,
 167                  );
 168              }
 169  
 170              return false;
 171          }
 172  
 173          if (!$extract) {
 174              if ($alwaysReturnArray) {
 175                  return array(
 176                      'extractdir'  => null,
 177                      'packagefile' => $archivename,
 178                      'type'        => false,
 179                  );
 180              }
 181  
 182              return false;
 183          }
 184  
 185          /*
 186           * Let's set the extraction directory and package file in the result array so we can
 187           * cleanup everything properly later on.
 188           */
 189          $retval['extractdir'] = $extractdir;
 190          $retval['packagefile'] = $archivename;
 191  
 192          /*
 193           * Try to find the correct install directory.  In case the package is inside a
 194           * subdirectory detect this and set the install directory to the correct path.
 195           *
 196           * List all the items in the installation directory.  If there is only one, and
 197           * it is a folder, then we will set that folder to be the installation folder.
 198           */
 199          $dirList = array_merge((array) Folder::files($extractdir, ''), (array) Folder::folders($extractdir, ''));
 200  
 201          if (\count($dirList) === 1) {
 202              if (Folder::exists($extractdir . '/' . $dirList[0])) {
 203                  $extractdir = Path::clean($extractdir . '/' . $dirList[0]);
 204              }
 205          }
 206  
 207          /*
 208           * We have found the install directory so lets set it and then move on
 209           * to detecting the extension type.
 210           */
 211          $retval['dir'] = $extractdir;
 212  
 213          /*
 214           * Get the extension type and return the directory/type array on success or
 215           * false on fail.
 216           */
 217          $retval['type'] = self::detectType($extractdir);
 218  
 219          if ($alwaysReturnArray || $retval['type']) {
 220              return $retval;
 221          } else {
 222              return false;
 223          }
 224      }
 225  
 226      /**
 227       * Method to detect the extension type from a package directory
 228       *
 229       * @param   string  $packageDirectory  Path to package directory
 230       *
 231       * @return  mixed  Extension type string or boolean false on fail
 232       *
 233       * @since   3.1
 234       */
 235      public static function detectType($packageDirectory)
 236      {
 237          // Search the install dir for an XML file
 238          $files = Folder::files($packageDirectory, '\.xml$', 1, true);
 239  
 240          if (!$files || !\count($files)) {
 241              Log::add(Text::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), Log::WARNING, 'jerror');
 242  
 243              return false;
 244          }
 245  
 246          foreach ($files as $file) {
 247              $xml = simplexml_load_file($file);
 248  
 249              if (!$xml) {
 250                  continue;
 251              }
 252  
 253              if ($xml->getName() !== 'extension') {
 254                  unset($xml);
 255                  continue;
 256              }
 257  
 258              $type = (string) $xml->attributes()->type;
 259  
 260              // Free up memory
 261              unset($xml);
 262  
 263              return $type;
 264          }
 265  
 266          Log::add(Text::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), Log::WARNING, 'jerror');
 267  
 268          return false;
 269      }
 270  
 271      /**
 272       * Gets a file name out of a url
 273       *
 274       * @param   string  $url  URL to get name from
 275       *
 276       * @return  string  Clean version of the filename or a unique id
 277       *
 278       * @since   3.1
 279       */
 280      public static function getFilenameFromUrl($url)
 281      {
 282          $default = uniqid();
 283  
 284          if (!\is_string($url) || strpos($url, '/') === false) {
 285              return $default;
 286          }
 287  
 288          // Get last part of the url (after the last slash).
 289          $parts    = explode('/', $url);
 290          $filename = array_pop($parts);
 291  
 292          // Replace special characters with underscores.
 293          $filename = preg_replace('/[^a-z0-9\_\-\.]/i', '_', $filename);
 294  
 295          // Replace multiple underscores with just one.
 296          $filename = preg_replace('/__+/', '_', trim($filename, '_'));
 297  
 298          // Return the cleaned filename or, if it is empty, a unique id.
 299          return $filename ?: $default;
 300      }
 301  
 302      /**
 303       * Clean up temporary uploaded package and unpacked extension
 304       *
 305       * @param   string  $package    Path to the uploaded package file
 306       * @param   string  $resultdir  Path to the unpacked extension
 307       *
 308       * @return  boolean  True on success
 309       *
 310       * @since   3.1
 311       */
 312      public static function cleanupInstall($package, $resultdir)
 313      {
 314          // Does the unpacked extension directory exist?
 315          if ($resultdir && is_dir($resultdir)) {
 316              Folder::delete($resultdir);
 317          }
 318  
 319          // Is the package file a valid file?
 320          if (is_file($package)) {
 321              File::delete($package);
 322          } elseif (is_file(Path::clean(Factory::getApplication()->get('tmp_path') . '/' . $package))) {
 323              // It might also be just a base filename
 324              File::delete(Path::clean(Factory::getApplication()->get('tmp_path') . '/' . $package));
 325          }
 326      }
 327  
 328      /**
 329       * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest
 330       *
 331       * @param   string  $packagefile   Location of the package to be installed
 332       * @param   Update  $updateObject  The Update Object
 333       *
 334       * @return  integer  one if the hashes match, zero if hashes doesn't match, two if hashes not found
 335       *
 336       * @since   3.9.0
 337       */
 338      public static function isChecksumValid($packagefile, $updateObject)
 339      {
 340          $hashes     = array('sha256', 'sha384', 'sha512');
 341          $hashOnFile = false;
 342  
 343          foreach ($hashes as $hash) {
 344              if ($updateObject->get($hash, false)) {
 345                  $hashPackage = hash_file($hash, $packagefile);
 346                  $hashRemote  = $updateObject->$hash->_data;
 347                  $hashOnFile  = true;
 348  
 349                  if ($hashPackage !== strtolower($hashRemote)) {
 350                      return self::HASH_NOT_VALIDATED;
 351                  }
 352              }
 353          }
 354  
 355          if ($hashOnFile) {
 356              return self::HASH_VALIDATED;
 357          }
 358  
 359          return self::HASH_NOT_PROVIDED;
 360      }
 361  }


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