[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Installer/Adapter/ -> ModuleAdapter.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2005 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\Application\ApplicationHelper;
  13  use Joomla\CMS\Filesystem\Folder;
  14  use Joomla\CMS\Installer\Installer;
  15  use Joomla\CMS\Installer\InstallerAdapter;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Log\Log;
  18  use Joomla\CMS\Table\Table;
  19  use Joomla\Database\ParameterType;
  20  use Joomla\Utilities\ArrayHelper;
  21  
  22  // phpcs:disable PSR1.Files.SideEffects
  23  \defined('JPATH_PLATFORM') or die;
  24  // phpcs:enable PSR1.Files.SideEffects
  25  
  26  /**
  27   * Module installer
  28   *
  29   * @since  3.1
  30   */
  31  class ModuleAdapter extends InstallerAdapter
  32  {
  33      /**
  34       * The install client ID
  35       *
  36       * @var    integer
  37       * @since  3.4
  38       */
  39      protected $clientId;
  40  
  41      /**
  42       * `<scriptfile>` element of the extension manifest
  43       *
  44       * @var    object
  45       * @since  3.1
  46       */
  47      protected $scriptElement = null;
  48  
  49      /**
  50       * Method to check if the extension is already present in the database
  51       *
  52       * @return  void
  53       *
  54       * @since   3.4
  55       * @throws  \RuntimeException
  56       */
  57      protected function checkExistingExtension()
  58      {
  59          try {
  60              $this->currentExtensionId = $this->extension->find(
  61                  array(
  62                      'element'   => $this->element,
  63                      'type'      => $this->type,
  64                      'client_id' => $this->clientId,
  65                  )
  66              );
  67          } catch (\RuntimeException $e) {
  68              // Install failed, roll back changes
  69              throw new \RuntimeException(
  70                  Text::sprintf(
  71                      'JLIB_INSTALLER_ABORT_ROLLBACK',
  72                      Text::_('JLIB_INSTALLER_' . $this->route),
  73                      $e->getMessage()
  74                  ),
  75                  $e->getCode(),
  76                  $e
  77              );
  78          }
  79      }
  80  
  81      /**
  82       * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
  83       *
  84       * @return  void
  85       *
  86       * @since   3.4
  87       * @throws  \RuntimeException
  88       */
  89      protected function copyBaseFiles()
  90      {
  91          // Copy all necessary files
  92          if ($this->parent->parseFiles($this->getManifest()->files, -1) === false) {
  93              throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_MOD_COPY_FILES'));
  94          }
  95  
  96          // If there is a manifest script, let's copy it.
  97          if ($this->manifest_script) {
  98              $path['src']  = $this->parent->getPath('source') . '/' . $this->manifest_script;
  99              $path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;
 100  
 101              if ($this->parent->isOverwrite() || !file_exists($path['dest'])) {
 102                  if (!$this->parent->copyFiles(array($path))) {
 103                      // Install failed, rollback changes
 104                      throw new \RuntimeException(
 105                          Text::sprintf(
 106                              'JLIB_INSTALLER_ABORT_MANIFEST',
 107                              Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 108                          )
 109                      );
 110                  }
 111              }
 112          }
 113      }
 114  
 115      /**
 116       * Custom discover method
 117       *
 118       * @return  array  Extension list of extensions available
 119       *
 120       * @since   3.1
 121       */
 122      public function discover()
 123      {
 124          $results = array();
 125          $site_list = Folder::folders(JPATH_SITE . '/modules');
 126          $admin_list = Folder::folders(JPATH_ADMINISTRATOR . '/modules');
 127          $site_info = ApplicationHelper::getClientInfo('site', true);
 128          $admin_info = ApplicationHelper::getClientInfo('administrator', true);
 129  
 130          foreach ($site_list as $module) {
 131              if (file_exists(JPATH_SITE . "/modules/$module/$module.xml")) {
 132                  $manifest_details = Installer::parseXMLInstallFile(JPATH_SITE . "/modules/$module/$module.xml");
 133                  $extension = Table::getInstance('extension');
 134                  $extension->set('type', 'module');
 135                  $extension->set('client_id', $site_info->id);
 136                  $extension->set('element', $module);
 137                  $extension->set('folder', '');
 138                  $extension->set('name', $module);
 139                  $extension->set('state', -1);
 140                  $extension->set('manifest_cache', json_encode($manifest_details));
 141                  $extension->set('params', '{}');
 142                  $results[] = clone $extension;
 143              }
 144          }
 145  
 146          foreach ($admin_list as $module) {
 147              if (file_exists(JPATH_ADMINISTRATOR . "/modules/$module/$module.xml")) {
 148                  $manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR . "/modules/$module/$module.xml");
 149                  $extension = Table::getInstance('extension');
 150                  $extension->set('type', 'module');
 151                  $extension->set('client_id', $admin_info->id);
 152                  $extension->set('element', $module);
 153                  $extension->set('folder', '');
 154                  $extension->set('name', $module);
 155                  $extension->set('state', -1);
 156                  $extension->set('manifest_cache', json_encode($manifest_details));
 157                  $extension->set('params', '{}');
 158                  $results[] = clone $extension;
 159              }
 160          }
 161  
 162          return $results;
 163      }
 164  
 165      /**
 166       * Method to finalise the installation processing
 167       *
 168       * @return  void
 169       *
 170       * @since   3.4
 171       * @throws  \RuntimeException
 172       */
 173      protected function finaliseInstall()
 174      {
 175          // Clobber any possible pending updates
 176          $update = Table::getInstance('update');
 177          $uid    = $update->find(
 178              array(
 179                  'element'   => $this->element,
 180                  'type'      => 'module',
 181                  'client_id' => $this->clientId,
 182              )
 183          );
 184  
 185          if ($uid) {
 186              $update->delete($uid);
 187          }
 188  
 189          // Lastly, we will copy the manifest file to its appropriate place.
 190          if ($this->route !== 'discover_install') {
 191              if (!$this->parent->copyManifest(-1)) {
 192                  // Install failed, rollback changes
 193                  throw new \RuntimeException(
 194                      Text::sprintf(
 195                          'JLIB_INSTALLER_ABORT_COPY_SETUP',
 196                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 197                      )
 198                  );
 199              }
 200          }
 201      }
 202  
 203      /**
 204       * Method to finalise the uninstallation processing
 205       *
 206       * @return  boolean
 207       *
 208       * @since   4.0.0
 209       * @throws  \RuntimeException
 210       */
 211      protected function finaliseUninstall(): bool
 212      {
 213          $extensionId = $this->extension->extension_id;
 214  
 215          $db     = $this->getDatabase();
 216          $retval = true;
 217  
 218          // Remove the schema version
 219          $query = $db->getQuery(true)
 220              ->delete('#__schemas')
 221              ->where('extension_id = :extension_id')
 222              ->bind(':extension_id', $extensionId, ParameterType::INTEGER);
 223          $db->setQuery($query);
 224          $db->execute();
 225  
 226          $element  = $this->extension->element;
 227          $clientId = $this->extension->client_id;
 228  
 229          // Let's delete all the module copies for the type we are uninstalling
 230          $query->clear()
 231              ->select($db->quoteName('id'))
 232              ->from($db->quoteName('#__modules'))
 233              ->where($db->quoteName('module') . ' = :element')
 234              ->where($db->quoteName('client_id') . ' = :client_id')
 235              ->bind(':element', $element)
 236              ->bind(':client_id', $clientId, ParameterType::INTEGER);
 237          $db->setQuery($query);
 238  
 239          try {
 240              $modules = $db->loadColumn();
 241          } catch (\RuntimeException $e) {
 242              $modules = [];
 243          }
 244  
 245          // Do we have any module copies?
 246          if (\count($modules)) {
 247              // Ensure the list is sane
 248              $modules = ArrayHelper::toInteger($modules);
 249              $modID = implode(',', $modules);
 250  
 251              // Wipe out any items assigned to menus
 252              $query = $db->getQuery(true)
 253                  ->delete($db->quoteName('#__modules_menu'))
 254                  ->where($db->quoteName('moduleid') . ' IN (' . $modID . ')');
 255              $db->setQuery($query);
 256  
 257              try {
 258                  $db->execute();
 259              } catch (\RuntimeException $e) {
 260                  Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION', $e->getMessage()), Log::WARNING, 'jerror');
 261                  $retval = false;
 262              }
 263  
 264              // Wipe out any instances in the modules table
 265              /** @var \Joomla\CMS\Table\Module $module */
 266              $module = Table::getInstance('Module');
 267  
 268              foreach ($modules as $modInstanceId) {
 269                  $module->load($modInstanceId);
 270  
 271                  if (!$module->delete()) {
 272                      Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION', $module->getError()), Log::WARNING, 'jerror');
 273                      $retval = false;
 274                  }
 275              }
 276          }
 277  
 278          // Now we will no longer need the module object, so let's delete it and free up memory
 279          $this->extension->delete($this->extension->extension_id);
 280          $query = $db->getQuery(true)
 281              ->delete($db->quoteName('#__modules'))
 282              ->where($db->quoteName('module') . ' = :element')
 283              ->where($db->quoteName('client_id') . ' = :client_id')
 284              ->bind(':element', $element)
 285              ->bind(':client_id', $clientId, ParameterType::INTEGER);
 286          $db->setQuery($query);
 287  
 288          try {
 289              // Clean up any other ones that might exist as well
 290              $db->execute();
 291          } catch (\RuntimeException $e) {
 292              // Ignore the error...
 293          }
 294  
 295          // Remove the installation folder
 296          if (!Folder::delete($this->parent->getPath('extension_root'))) {
 297              // Folder should raise an error
 298              $retval = false;
 299          }
 300  
 301          return $retval;
 302      }
 303  
 304      /**
 305       * Get the filtered extension element from the manifest
 306       *
 307       * @param   string  $element  Optional element name to be converted
 308       *
 309       * @return  string|null  The filtered element
 310       *
 311       * @since   3.4
 312       */
 313      public function getElement($element = null)
 314      {
 315          if ($element) {
 316              return $element;
 317          }
 318  
 319          // Joomla 4 Module.
 320          if ((string) $this->getManifest()->element) {
 321              return (string) $this->getManifest()->element;
 322          }
 323  
 324          if (!\count($this->getManifest()->files->children())) {
 325              return $element;
 326          }
 327  
 328          foreach ($this->getManifest()->files->children() as $file) {
 329              if ((string) $file->attributes()->module) {
 330                  // Joomla 3 (legacy) Module.
 331                  return strtolower((string) $file->attributes()->module);
 332              }
 333          }
 334  
 335          return $element;
 336      }
 337  
 338      /**
 339       * Custom loadLanguage method
 340       *
 341       * @param   string  $path  The path where we find language files
 342       *
 343       * @return  void
 344       *
 345       * @since   3.4
 346       */
 347      public function loadLanguage($path = null)
 348      {
 349          $source = $this->parent->getPath('source');
 350          $client = $this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;
 351  
 352          if (!$source) {
 353              $this->parent->setPath('source', $client . '/modules/' . $this->parent->extension->element);
 354          }
 355  
 356          $this->setManifest($this->parent->getManifest());
 357  
 358          if ($this->getManifest()->files) {
 359              $extension = $this->getElement();
 360  
 361              if ($extension) {
 362                  $source = $path ?: ($this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $extension;
 363                  $folder = (string) $this->getManifest()->files->attributes()->folder;
 364  
 365                  if ($folder && file_exists($path . '/' . $folder)) {
 366                      $source = $path . '/' . $folder;
 367                  }
 368  
 369                  $client = (string) $this->getManifest()->attributes()->client;
 370                  $this->doLoadLanguage($extension, $source, \constant('JPATH_' . strtoupper($client)));
 371              }
 372          }
 373      }
 374  
 375      /**
 376       * Method to parse optional tags in the manifest
 377       *
 378       * @return  void
 379       *
 380       * @since   3.4
 381       */
 382      protected function parseOptionalTags()
 383      {
 384          // Parse optional tags
 385          $this->parent->parseMedia($this->getManifest()->media, $this->clientId);
 386          $this->parent->parseLanguages($this->getManifest()->languages, $this->clientId);
 387      }
 388  
 389      /**
 390       * Prepares the adapter for a discover_install task
 391       *
 392       * @return  void
 393       *
 394       * @since   3.4
 395       */
 396      public function prepareDiscoverInstall()
 397      {
 398          $client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
 399          $manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml';
 400          $this->parent->manifest = $this->parent->isManifest($manifestPath);
 401          $this->parent->setPath('manifest', $manifestPath);
 402          $this->setManifest($this->parent->getManifest());
 403      }
 404  
 405      /**
 406       * Refreshes the extension table cache
 407       *
 408       * @return  boolean  Result of operation, true if updated, false on failure.
 409       *
 410       * @since   3.1
 411       */
 412      public function refreshManifestCache()
 413      {
 414          $client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
 415          $manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml';
 416          $this->parent->manifest = $this->parent->isManifest($manifestPath);
 417          $this->parent->setPath('manifest', $manifestPath);
 418          $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
 419          $this->parent->extension->manifest_cache = json_encode($manifest_details);
 420          $this->parent->extension->name = $manifest_details['name'];
 421  
 422          if ($this->parent->extension->store()) {
 423              return true;
 424          } else {
 425              Log::add(Text::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'), Log::WARNING, 'jerror');
 426  
 427              return false;
 428          }
 429      }
 430  
 431      /**
 432       * Removes this extension's files
 433       *
 434       * @return  void
 435       *
 436       * @since   4.0.0
 437       * @throws  \RuntimeException
 438       */
 439      protected function removeExtensionFiles()
 440      {
 441          $this->parent->removeFiles($this->getManifest()->media);
 442          $this->parent->removeFiles($this->getManifest()->languages, $this->extension->client_id);
 443      }
 444  
 445      /**
 446       * Method to do any prechecks and setup the install paths for the extension
 447       *
 448       * @return  void
 449       *
 450       * @since   3.4
 451       * @throws  \RuntimeException
 452       */
 453      protected function setupInstallPaths()
 454      {
 455          // Get the target application
 456          $cname = (string) $this->getManifest()->attributes()->client;
 457  
 458          if ($cname) {
 459              // Attempt to map the client to a base path
 460              $client = ApplicationHelper::getClientInfo($cname, true);
 461  
 462              if ($client === false) {
 463                  throw new \RuntimeException(
 464                      Text::sprintf(
 465                          'JLIB_INSTALLER_ABORT_MOD_UNKNOWN_CLIENT',
 466                          Text::_('JLIB_INSTALLER_' . $this->route),
 467                          $client->name
 468                      )
 469                  );
 470              }
 471  
 472              $basePath = $client->path;
 473              $this->clientId = $client->id;
 474          } else {
 475              // No client attribute was found so we assume the site as the client
 476              $basePath = JPATH_SITE;
 477              $this->clientId = 0;
 478          }
 479  
 480          // Set the installation path
 481          if (empty($this->element)) {
 482              throw new \RuntimeException(
 483                  Text::sprintf(
 484                      'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
 485                      Text::_('JLIB_INSTALLER_' . $this->route)
 486                  )
 487              );
 488          }
 489  
 490          $this->parent->setPath('extension_root', $basePath . '/modules/' . $this->element);
 491      }
 492  
 493      /**
 494       * Method to do any prechecks and setup the uninstall job
 495       *
 496       * @return  void
 497       *
 498       * @since   4.0.0
 499       */
 500      protected function setupUninstall()
 501      {
 502          // Get the extension root path
 503          $element = $this->extension->element;
 504          $client  = ApplicationHelper::getClientInfo($this->extension->client_id);
 505  
 506          if ($client === false) {
 507              throw new \RuntimeException(
 508                  Text::sprintf(
 509                      'JLIB_INSTALLER_ERROR_MOD_UNINSTALL_UNKNOWN_CLIENT',
 510                      $this->extension->client_id
 511                  )
 512              );
 513          }
 514  
 515          $this->parent->setPath('extension_root', $client->path . '/modules/' . $element);
 516  
 517          $this->parent->setPath('source', $this->parent->getPath('extension_root'));
 518  
 519          // Get the module's manifest object
 520          // We do findManifest to avoid problem when uninstalling a list of extensions: getManifest cache its manifest file.
 521          $this->parent->findManifest();
 522          $this->setManifest($this->parent->getManifest());
 523  
 524          // Attempt to load the language file; might have uninstall strings
 525          $this->loadLanguage(($this->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $element);
 526      }
 527  
 528      /**
 529       * Method to store the extension to the database
 530       *
 531       * @return  void
 532       *
 533       * @since   3.4
 534       * @throws  \RuntimeException
 535       */
 536      protected function storeExtension()
 537      {
 538          // Discover installs are stored a little differently
 539          if ($this->route === 'discover_install') {
 540              $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
 541  
 542              $this->extension->manifest_cache = json_encode($manifest_details);
 543              $this->extension->state = 0;
 544              $this->extension->name = $manifest_details['name'];
 545              $this->extension->enabled = 1;
 546              $this->extension->params = $this->parent->getParams();
 547  
 548              if (!$this->extension->store()) {
 549                  // Install failed, roll back changes
 550                  throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_MOD_DISCOVER_STORE_DETAILS'));
 551              }
 552  
 553              return;
 554          }
 555  
 556          // Was there a module already installed with the same name?
 557          if ($this->currentExtensionId) {
 558              if (!$this->parent->isOverwrite()) {
 559                  // Install failed, roll back changes
 560                  throw new \RuntimeException(
 561                      Text::sprintf(
 562                          'JLIB_INSTALLER_ABORT_MOD_INSTALL_ALLREADY_EXISTS',
 563                          Text::_('JLIB_INSTALLER_' . $this->route),
 564                          $this->name
 565                      )
 566                  );
 567              }
 568  
 569              // Load the entry and update the manifest_cache
 570              $this->extension->load($this->currentExtensionId);
 571  
 572              // Update name
 573              $this->extension->name = $this->name;
 574  
 575              // Update namespace
 576              $this->extension->namespace = (string) $this->manifest->namespace;
 577  
 578              // Update changelogurl
 579              $this->extension->changelogurl = (string) $this->manifest->changelogurl;
 580  
 581              // Update manifest
 582              $this->extension->manifest_cache = $this->parent->generateManifestCache();
 583  
 584              if (!$this->extension->store()) {
 585                  // Install failed, roll back changes
 586                  throw new \RuntimeException(
 587                      Text::sprintf(
 588                          'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
 589                          Text::_('JLIB_INSTALLER_' . $this->route),
 590                          $this->extension->getError()
 591                      )
 592                  );
 593              }
 594          } else {
 595              $this->extension->name         = $this->name;
 596              $this->extension->type         = 'module';
 597              $this->extension->element      = $this->element;
 598              $this->extension->namespace    = (string) $this->manifest->namespace;
 599              $this->extension->changelogurl = $this->changelogurl;
 600  
 601              // There is no folder for modules
 602              $this->extension->folder    = '';
 603              $this->extension->enabled   = 1;
 604              $this->extension->protected = 0;
 605              $this->extension->access    = $this->clientId == 1 ? 2 : 0;
 606              $this->extension->client_id = $this->clientId;
 607              $this->extension->params    = $this->parent->getParams();
 608  
 609              // Update the manifest cache for the entry
 610              $this->extension->manifest_cache = $this->parent->generateManifestCache();
 611  
 612              if (!$this->extension->store()) {
 613                  // Install failed, roll back changes
 614                  throw new \RuntimeException(
 615                      Text::sprintf(
 616                          'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
 617                          Text::_('JLIB_INSTALLER_' . $this->route),
 618                          $this->extension->getError()
 619                      )
 620                  );
 621              }
 622  
 623              // Since we have created a module item, we add it to the installation step stack
 624              // so that if we have to rollback the changes we can undo it.
 625              $this->parent->pushStep(
 626                  array(
 627                      'type' => 'extension',
 628                      'extension_id' => $this->extension->extension_id,
 629                  )
 630              );
 631  
 632              // Create unpublished module
 633              $name = preg_replace('#[\*?]#', '', Text::_($this->name));
 634  
 635              /** @var \Joomla\CMS\Table\Module $module */
 636              $module            = Table::getInstance('module');
 637              $module->title     = $name;
 638              $module->content   = '';
 639              $module->module    = $this->element;
 640              $module->access    = '1';
 641              $module->showtitle = '1';
 642              $module->params    = '';
 643              $module->client_id = $this->clientId;
 644              $module->language  = '*';
 645  
 646              $module->store();
 647          }
 648      }
 649  
 650      /**
 651       * Custom rollback method
 652       * - Roll back the menu item
 653       *
 654       * @param   array  $arg  Installation step to rollback
 655       *
 656       * @return  boolean  True on success
 657       *
 658       * @since   3.1
 659       */
 660      protected function _rollback_menu($arg)
 661      {
 662          // Get database connector object
 663          $db = $this->getDatabase();
 664  
 665          $moduleId = $arg['id'];
 666  
 667          // Remove the entry from the #__modules_menu table
 668          $query = $db->getQuery(true)
 669              ->delete($db->quoteName('#__modules_menu'))
 670              ->where($db->quoteName('moduleid') . ' = :module_id')
 671              ->bind(':module_id', $moduleId, ParameterType::INTEGER);
 672          $db->setQuery($query);
 673  
 674          try {
 675              return $db->execute();
 676          } catch (\RuntimeException $e) {
 677              return false;
 678          }
 679      }
 680  
 681      /**
 682       * Custom rollback method
 683       * - Roll back the module item
 684       *
 685       * @param   array  $arg  Installation step to rollback
 686       *
 687       * @return  boolean  True on success
 688       *
 689       * @since   3.1
 690       */
 691      protected function _rollback_module($arg)
 692      {
 693          // Get database connector object
 694          $db = $this->getDatabase();
 695  
 696          $moduleId = $arg['id'];
 697  
 698          // Remove the entry from the #__modules table
 699          $query = $db->getQuery(true)
 700              ->delete($db->quoteName('#__modules'))
 701              ->where($db->quoteName('id') . ' = :module_id')
 702              ->bind(':module_id', $moduleId, ParameterType::INTEGER);
 703          $db->setQuery($query);
 704  
 705          try {
 706              return $db->execute();
 707          } catch (\RuntimeException $e) {
 708              return false;
 709          }
 710      }
 711  }


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