[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Installer/Adapter/ -> FileAdapter.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\Installer\Adapter;
  11  
  12  use Joomla\CMS\Filesystem\File;
  13  use Joomla\CMS\Filesystem\Folder;
  14  use Joomla\CMS\Filesystem\Path;
  15  use Joomla\CMS\Installer\Installer;
  16  use Joomla\CMS\Installer\InstallerAdapter;
  17  use Joomla\CMS\Language\Text;
  18  use Joomla\CMS\Log\Log;
  19  use Joomla\CMS\Table\Table;
  20  use Joomla\Database\ParameterType;
  21  
  22  // phpcs:disable PSR1.Files.SideEffects
  23  \defined('JPATH_PLATFORM') or die;
  24  // phpcs:enable PSR1.Files.SideEffects
  25  
  26  /**
  27   * File installer
  28   *
  29   * @since  3.1
  30   */
  31  class FileAdapter extends InstallerAdapter
  32  {
  33      /**
  34       * `<scriptfile>` element of the extension manifest
  35       *
  36       * @var    object
  37       * @since  3.1
  38       */
  39      protected $scriptElement = null;
  40  
  41      /**
  42       * Flag if the adapter supports discover installs
  43       *
  44       * Adapters should override this and set to false if discover install is unsupported
  45       *
  46       * @var    boolean
  47       * @since  3.4
  48       */
  49      protected $supportsDiscoverInstall = false;
  50  
  51      /**
  52       * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
  53       *
  54       * @return  void
  55       *
  56       * @since   3.4
  57       * @throws  \RuntimeException
  58       */
  59      protected function copyBaseFiles()
  60      {
  61          // Populate File and Folder List to copy
  62          $this->populateFilesAndFolderList();
  63  
  64          // Now that we have folder list, lets start creating them
  65          foreach ($this->folderList as $folder) {
  66              if (!Folder::exists($folder)) {
  67                  if (!$created = Folder::create($folder)) {
  68                      throw new \RuntimeException(
  69                          Text::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY', $folder)
  70                      );
  71                  }
  72  
  73                  // Since we created a directory and will want to remove it if we have to roll back.
  74                  // The installation due to some errors, let's add it to the installation step stack.
  75                  if ($created) {
  76                      $this->parent->pushStep(array('type' => 'folder', 'path' => $folder));
  77                  }
  78              }
  79          }
  80  
  81          // Now that we have file list, let's start copying them
  82          $this->parent->copyFiles($this->fileList);
  83      }
  84  
  85      /**
  86       * Method to finalise the installation processing
  87       *
  88       * @return  void
  89       *
  90       * @since   3.4
  91       * @throws  \RuntimeException
  92       */
  93      protected function finaliseInstall()
  94      {
  95          // Clobber any possible pending updates
  96          $update = Table::getInstance('update');
  97  
  98          $uid = $update->find(
  99              array(
 100                  'element' => $this->element,
 101                  'type' => $this->type,
 102              )
 103          );
 104  
 105          if ($uid) {
 106              $update->delete($uid);
 107          }
 108  
 109          // Lastly, we will copy the manifest file to its appropriate place.
 110          $manifest = array();
 111          $manifest['src'] = $this->parent->getPath('manifest');
 112          $manifest['dest'] = JPATH_MANIFESTS . '/files/' . basename($this->parent->getPath('manifest'));
 113  
 114          if (!$this->parent->copyFiles(array($manifest), true)) {
 115              // Install failed, rollback changes
 116              throw new \RuntimeException(
 117                  Text::sprintf(
 118                      'JLIB_INSTALLER_ABORT_COPY_SETUP',
 119                      Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 120                  )
 121              );
 122          }
 123  
 124          // If there is a manifest script, let's copy it.
 125          if ($this->manifest_script) {
 126              // First, we have to create a folder for the script if one isn't present
 127              if (!file_exists($this->parent->getPath('extension_root'))) {
 128                  Folder::create($this->parent->getPath('extension_root'));
 129              }
 130  
 131              $path['src'] = $this->parent->getPath('source') . '/' . $this->manifest_script;
 132              $path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;
 133  
 134              if ($this->parent->isOverwrite() || !file_exists($path['dest'])) {
 135                  if (!$this->parent->copyFiles(array($path))) {
 136                      // Install failed, rollback changes
 137                      throw new \RuntimeException(
 138                          Text::sprintf(
 139                              'JLIB_INSTALLER_ABORT_MANIFEST',
 140                              Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 141                          )
 142                      );
 143                  }
 144              }
 145          }
 146      }
 147  
 148      /**
 149       * Method to finalise the uninstallation processing
 150       *
 151       * @return  boolean
 152       *
 153       * @since   4.0.0
 154       * @throws  \RuntimeException
 155       */
 156      protected function finaliseUninstall(): bool
 157      {
 158          File::delete(JPATH_MANIFESTS . '/files/' . $this->extension->element . '.xml');
 159  
 160          $extensionId = $this->extension->extension_id;
 161  
 162          $db = $this->getDatabase();
 163  
 164          // Remove the schema version
 165          $query = $db->getQuery(true)
 166              ->delete('#__schemas')
 167              ->where('extension_id = :extension_id')
 168              ->bind(':extension_id', $extensionId, ParameterType::INTEGER);
 169          $db->setQuery($query);
 170          $db->execute();
 171  
 172          // Clobber any possible pending updates
 173          $update = Table::getInstance('update');
 174          $uid    = $update->find(
 175              [
 176                  'element' => $this->extension->element,
 177                  'type'    => $this->type,
 178              ]
 179          );
 180  
 181          if ($uid) {
 182              $update->delete($uid);
 183          }
 184  
 185          $this->extension->delete();
 186  
 187          return true;
 188      }
 189  
 190      /**
 191       * Get the filtered extension element from the manifest
 192       *
 193       * @param   string  $element  Optional element name to be converted
 194       *
 195       * @return  string  The filtered element
 196       *
 197       * @since   3.4
 198       */
 199      public function getElement($element = null)
 200      {
 201          if (!$element) {
 202              $manifestPath = Path::clean($this->parent->getPath('manifest'));
 203              $element = preg_replace('/\.xml/', '', basename($manifestPath));
 204          }
 205  
 206          return $element;
 207      }
 208  
 209      /**
 210       * Custom loadLanguage method
 211       *
 212       * @param   string  $path  The path on which to find language files.
 213       *
 214       * @return  void
 215       *
 216       * @since   3.1
 217       */
 218      public function loadLanguage($path)
 219      {
 220          $extension = 'files_' . strtolower(str_replace('files_', '', $this->getElement()));
 221  
 222          $this->doLoadLanguage($extension, $path, JPATH_SITE);
 223      }
 224  
 225      /**
 226       * Method to parse optional tags in the manifest
 227       *
 228       * @return  void
 229       *
 230       * @since   3.4
 231       */
 232      protected function parseOptionalTags()
 233      {
 234          // Parse optional tags
 235          $this->parent->parseLanguages($this->getManifest()->languages);
 236      }
 237  
 238      /**
 239       * Removes this extension's files
 240       *
 241       * @return  void
 242       *
 243       * @since   4.0.0
 244       * @throws  \RuntimeException
 245       */
 246      protected function removeExtensionFiles()
 247      {
 248          // Loop through all elements and get list of files and folders
 249          foreach ($this->getManifest()->fileset->files as $eFiles) {
 250              $target = (string) $eFiles->attributes()->target;
 251  
 252              // Create folder path
 253              if (empty($target)) {
 254                  $targetFolder = JPATH_ROOT;
 255              } else {
 256                  $targetFolder = JPATH_ROOT . '/' . $target;
 257              }
 258  
 259              $folderList = [];
 260  
 261              // Check if all children exists
 262              if (\count($eFiles->children()) > 0) {
 263                  // Loop through all filenames elements
 264                  foreach ($eFiles->children() as $eFileName) {
 265                      if ($eFileName->getName() === 'folder') {
 266                          $folderList[] = $targetFolder . '/' . $eFileName;
 267                      } else {
 268                          $fileName = $targetFolder . '/' . $eFileName;
 269                          File::delete($fileName);
 270                      }
 271                  }
 272              }
 273  
 274              // Delete any folders that don't have any content in them.
 275              foreach ($folderList as $folder) {
 276                  $files = Folder::files($folder);
 277  
 278                  if ($files !== false && !\count($files)) {
 279                      Folder::delete($folder);
 280                  }
 281              }
 282          }
 283  
 284          // Lastly, remove the extension_root
 285          $folder = $this->parent->getPath('extension_root');
 286  
 287          if (Folder::exists($folder)) {
 288              Folder::delete($folder);
 289          }
 290  
 291          $this->parent->removeFiles($this->getManifest()->languages);
 292      }
 293  
 294      /**
 295       * Method to do any prechecks and setup the install paths for the extension
 296       *
 297       * @return  void
 298       *
 299       * @since   3.4
 300       */
 301      protected function setupInstallPaths()
 302      {
 303          // Set the file root path
 304          if ($this->name === 'files_joomla') {
 305              // If we are updating the Joomla core, set the root path to the root of Joomla
 306              $this->parent->setPath('extension_root', JPATH_ROOT);
 307          } else {
 308              $this->parent->setPath('extension_root', JPATH_MANIFESTS . '/files/' . $this->element);
 309          }
 310      }
 311  
 312      /**
 313       * Method to do any prechecks and setup the uninstall job
 314       *
 315       * @return  void
 316       *
 317       * @since   4.0.0
 318       */
 319      protected function setupUninstall()
 320      {
 321          $manifestFile = JPATH_MANIFESTS . '/files/' . $this->extension->element . '.xml';
 322  
 323          // Because libraries may not have their own folders we cannot use the standard method of finding an installation manifest
 324          if (!file_exists($manifestFile)) {
 325              // Remove this row entry since its invalid
 326              $this->extension->delete($this->extension->extension_id);
 327  
 328              throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_NOTFOUND_MANIFEST'));
 329          }
 330  
 331          // Set the files root path
 332          $this->parent->setPath('extension_root', JPATH_MANIFESTS . '/files/' . $this->extension->element);
 333  
 334          // Set the source path for compatibility with the API
 335          $this->parent->setPath('source', $this->parent->getPath('extension_root'));
 336  
 337          $xml = simplexml_load_file($manifestFile);
 338  
 339          // If we cannot load the XML file return null
 340          if (!$xml) {
 341              throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_MANIFEST'));
 342          }
 343  
 344          // Check for a valid XML root tag.
 345          if ($xml->getName() !== 'extension') {
 346              throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_MANIFEST'));
 347          }
 348  
 349          $this->setManifest($xml);
 350  
 351          // Attempt to load the language file; might have uninstall strings
 352          $this->loadLanguage(JPATH_MANIFESTS . '/files');
 353      }
 354  
 355      /**
 356       * Method to store the extension to the database
 357       *
 358       * @return  void
 359       *
 360       * @since   3.4
 361       * @throws  \RuntimeException
 362       */
 363      protected function storeExtension()
 364      {
 365          if ($this->currentExtensionId) {
 366              // Load the entry and update the manifest_cache
 367              $this->extension->load($this->currentExtensionId);
 368  
 369              // Update name
 370              $this->extension->name = $this->name;
 371  
 372              // Update manifest
 373              $this->extension->manifest_cache = $this->parent->generateManifestCache();
 374  
 375              if (!$this->extension->store()) {
 376                  // Install failed, roll back changes
 377                  throw new \RuntimeException(
 378                      Text::sprintf(
 379                          'JLIB_INSTALLER_ABORT_ROLLBACK',
 380                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route)),
 381                          $this->extension->getError()
 382                      )
 383                  );
 384              }
 385          } else {
 386              // Add an entry to the extension table with a whole heap of defaults
 387              $this->extension->name         = $this->name;
 388              $this->extension->type         = 'file';
 389              $this->extension->element      = $this->element;
 390              $this->extension->changelogurl = $this->changelogurl;
 391  
 392              // There is no folder for files so leave it blank
 393              $this->extension->folder    = '';
 394              $this->extension->enabled   = 1;
 395              $this->extension->protected = 0;
 396              $this->extension->access    = 0;
 397              $this->extension->client_id = 0;
 398              $this->extension->params    = '';
 399  
 400              // Update the manifest cache for the entry
 401              $this->extension->manifest_cache = $this->parent->generateManifestCache();
 402  
 403              if (!$this->extension->store()) {
 404                  // Install failed, roll back changes
 405                  throw new \RuntimeException(
 406                      Text::sprintf(
 407                          'JLIB_INSTALLER_ABORT_ROLLBACK',
 408                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route)),
 409                          $this->extension->getError()
 410                      )
 411                  );
 412              }
 413  
 414              // Since we have created a module item, we add it to the installation step stack
 415              // so that if we have to rollback the changes we can undo it.
 416              $this->parent->pushStep(array('type' => 'extension', 'extension_id' => $this->extension->extension_id));
 417          }
 418      }
 419  
 420      /**
 421       * Function used to check if extension is already installed
 422       *
 423       * @param   string  $extension  The element name of the extension to install
 424       *
 425       * @return  boolean  True if extension exists
 426       *
 427       * @since   3.1
 428       */
 429      protected function extensionExistsInSystem($extension = null)
 430      {
 431          // Get a database connector object
 432          $db = $this->getDatabase();
 433  
 434          $query = $db->getQuery(true)
 435              ->select($db->quoteName('extension_id'))
 436              ->from($db->quoteName('#__extensions'))
 437              ->where($db->quoteName('type') . ' = ' . $db->quote('file'))
 438              ->where($db->quoteName('element') . ' = :extension')
 439              ->bind(':extension', $extension);
 440          $db->setQuery($query);
 441  
 442          try {
 443              $db->execute();
 444          } catch (\RuntimeException $e) {
 445              // Install failed, rollback changes - error logged by the installer
 446              return false;
 447          }
 448  
 449          $id = $db->loadResult();
 450  
 451          if (empty($id)) {
 452              return false;
 453          }
 454  
 455          return true;
 456      }
 457  
 458      /**
 459       * Function used to populate files and folder list
 460       *
 461       * @return  boolean  none
 462       *
 463       * @since   3.1
 464       */
 465      protected function populateFilesAndFolderList()
 466      {
 467          // Initialise variable
 468          $this->folderList = array();
 469          $this->fileList = array();
 470  
 471          // Set root folder names
 472          $packagePath = $this->parent->getPath('source');
 473          $jRootPath = Path::clean(JPATH_ROOT);
 474  
 475          // Loop through all elements and get list of files and folders
 476          foreach ($this->getManifest()->fileset->files as $eFiles) {
 477              // Check if the element is files element
 478              $folder = (string) $eFiles->attributes()->folder;
 479              $target = (string) $eFiles->attributes()->target;
 480  
 481              // Split folder names into array to get folder names. This will help in creating folders
 482              $arrList = preg_split("#/|\\/#", $target);
 483  
 484              $folderName = $jRootPath;
 485  
 486              foreach ($arrList as $dir) {
 487                  if (empty($dir)) {
 488                      continue;
 489                  }
 490  
 491                  $folderName .= '/' . $dir;
 492  
 493                  // Check if folder exists, if not then add to the array for folder creation
 494                  if (!Folder::exists($folderName)) {
 495                      $this->folderList[] = $folderName;
 496                  }
 497              }
 498  
 499              // Create folder path
 500              $sourceFolder = empty($folder) ? $packagePath : $packagePath . '/' . $folder;
 501              $targetFolder = empty($target) ? $jRootPath : $jRootPath . '/' . $target;
 502  
 503              // Check if source folder exists
 504              if (!Folder::exists($sourceFolder)) {
 505                  Log::add(Text::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY', $sourceFolder), Log::WARNING, 'jerror');
 506  
 507                  // If installation fails, rollback
 508                  $this->parent->abort();
 509  
 510                  return false;
 511              }
 512  
 513              // Check if all children exists
 514              if (\count($eFiles->children())) {
 515                  // Loop through all filenames elements
 516                  foreach ($eFiles->children() as $eFileName) {
 517                      $path['src'] = $sourceFolder . '/' . $eFileName;
 518                      $path['dest'] = $targetFolder . '/' . $eFileName;
 519                      $path['type'] = 'file';
 520  
 521                      if ($eFileName->getName() === 'folder') {
 522                          $folderName         = $targetFolder . '/' . $eFileName;
 523                          $this->folderList[] = $folderName;
 524                          $path['type']       = 'folder';
 525                      }
 526  
 527                      $this->fileList[] = $path;
 528                  }
 529              } else {
 530                  $files = Folder::files($sourceFolder);
 531  
 532                  foreach ($files as $file) {
 533                      $path['src'] = $sourceFolder . '/' . $file;
 534                      $path['dest'] = $targetFolder . '/' . $file;
 535  
 536                      $this->fileList[] = $path;
 537                  }
 538              }
 539          }
 540      }
 541  
 542      /**
 543       * Refreshes the extension table cache
 544       *
 545       * @return  boolean result of operation, true if updated, false on failure
 546       *
 547       * @since   3.1
 548       */
 549      public function refreshManifestCache()
 550      {
 551          // Need to find to find where the XML file is since we don't store this normally
 552          $manifestPath = JPATH_MANIFESTS . '/files/' . $this->parent->extension->element . '.xml';
 553          $this->parent->manifest = $this->parent->isManifest($manifestPath);
 554          $this->parent->setPath('manifest', $manifestPath);
 555  
 556          $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
 557          $this->parent->extension->manifest_cache = json_encode($manifest_details);
 558          $this->parent->extension->name = $manifest_details['name'];
 559  
 560          try {
 561              return $this->parent->extension->store();
 562          } catch (\RuntimeException $e) {
 563              Log::add(Text::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'), Log::WARNING, 'jerror');
 564  
 565              return false;
 566          }
 567      }
 568  }


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