[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Installer/Adapter/ -> ComponentAdapter.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\Factory;
  14  use Joomla\CMS\Filesystem\Folder;
  15  use Joomla\CMS\Filesystem\Path;
  16  use Joomla\CMS\Installer\Installer;
  17  use Joomla\CMS\Installer\InstallerAdapter;
  18  use Joomla\CMS\Language\Text;
  19  use Joomla\CMS\Log\Log;
  20  use Joomla\CMS\Table\Asset;
  21  use Joomla\CMS\Table\Extension;
  22  use Joomla\CMS\Table\Table;
  23  use Joomla\CMS\Table\Update;
  24  use Joomla\Database\ParameterType;
  25  use Joomla\Registry\Registry;
  26  
  27  // phpcs:disable PSR1.Files.SideEffects
  28  \defined('JPATH_PLATFORM') or die;
  29  // phpcs:enable PSR1.Files.SideEffects
  30  
  31  /**
  32   * Component installer
  33   *
  34   * @since  3.1
  35   */
  36  class ComponentAdapter extends InstallerAdapter
  37  {
  38      /**
  39       * The list of current files for the Joomla! CMS administrator that are installed and is read
  40       * from the manifest on disk in the update area to handle doing a diff
  41       * and deleting files that are in the old files list and not in the new
  42       * files list.
  43       *
  44       * @var    array
  45       * @since  3.1
  46       * */
  47      protected $oldAdminFiles = null;
  48  
  49      /**
  50       * The list of current files for the Joomla! CMS API that are installed and is read
  51       * from the manifest on disk in the update area to handle doing a diff
  52       * and deleting files that are in the old files list and not in the new
  53       * files list.
  54       *
  55       * @var    array
  56       * @since  4.0.0
  57       * */
  58      protected $oldApiFiles = null;
  59  
  60      /**
  61       * The list of current files that are installed and is read
  62       * from the manifest on disk in the update area to handle doing a diff
  63       * and deleting files that are in the old files list and not in the new
  64       * files list.
  65       *
  66       * @var    array
  67       * @since  3.1
  68       * */
  69      protected $oldFiles = null;
  70  
  71      /**
  72       * A path to the PHP file that the scriptfile declaration in
  73       * the manifest refers to.
  74       *
  75       * @var    string
  76       * @since  3.1
  77       * */
  78      protected $manifest_script = null;
  79  
  80      /**
  81       * For legacy installations this is a path to the PHP file that the scriptfile declaration in the
  82       * manifest refers to.
  83       *
  84       * @var    string
  85       * @since  3.1
  86       * */
  87      protected $install_script = null;
  88  
  89      /**
  90       * Method to check if the extension is present in the filesystem
  91       *
  92       * @return  boolean
  93       *
  94       * @since   3.4
  95       * @throws  \RuntimeException
  96       */
  97      protected function checkExtensionInFilesystem()
  98      {
  99          /*
 100           * If the component site or admin directory already exists, then we will assume that the component is already
 101           * installed or another component is using that directory.
 102           */
 103          if (
 104              file_exists($this->parent->getPath('extension_site'))
 105              || file_exists($this->parent->getPath('extension_administrator'))
 106              || file_exists($this->parent->getPath('extension_api'))
 107          ) {
 108              // Look for an update function or update tag
 109              $updateElement = $this->getManifest()->update;
 110  
 111              // Upgrade manually set or update function available or update tag detected
 112              if (
 113                  $updateElement || $this->parent->isUpgrade()
 114                  || ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update'))
 115              ) {
 116                  // If there is a matching extension mark this as an update
 117                  $this->setRoute('update');
 118              } elseif (!$this->parent->isOverwrite()) {
 119                  // We didn't have overwrite set, find an update function or find an update tag so lets call it safe
 120                  if (file_exists($this->parent->getPath('extension_site'))) {
 121                      // If the site exists say so.
 122                      throw new \RuntimeException(
 123                          Text::sprintf(
 124                              'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_SITE',
 125                              $this->parent->getPath('extension_site')
 126                          )
 127                      );
 128                  }
 129  
 130                  if (file_exists($this->parent->getPath('extension_administrator'))) {
 131                      // If the admin exists say so
 132                      throw new \RuntimeException(
 133                          Text::sprintf(
 134                              'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_ADMIN',
 135                              $this->parent->getPath('extension_administrator')
 136                          )
 137                      );
 138                  }
 139  
 140                  // If the API exists say so
 141                  throw new \RuntimeException(
 142                      Text::sprintf(
 143                          'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_API',
 144                          $this->parent->getPath('extension_api')
 145                      )
 146                  );
 147              }
 148          }
 149  
 150          return false;
 151      }
 152  
 153      /**
 154       * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
 155       *
 156       * @return  void
 157       *
 158       * @since   3.4
 159       * @throws  \RuntimeException
 160       */
 161      protected function copyBaseFiles()
 162      {
 163          // Copy site files
 164          if ($this->getManifest()->files) {
 165              if ($this->route === 'update') {
 166                  $result = $this->parent->parseFiles($this->getManifest()->files, 0, $this->oldFiles);
 167              } else {
 168                  $result = $this->parent->parseFiles($this->getManifest()->files);
 169              }
 170  
 171              if ($result === false) {
 172                  throw new \RuntimeException(
 173                      Text::sprintf(
 174                          'JLIB_INSTALLER_ABORT_COMP_FAIL_SITE_FILES',
 175                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 176                      )
 177                  );
 178              }
 179          }
 180  
 181          // Copy admin files
 182          if ($this->getManifest()->administration->files) {
 183              if ($this->route === 'update') {
 184                  $result = $this->parent->parseFiles($this->getManifest()->administration->files, 1, $this->oldAdminFiles);
 185              } else {
 186                  $result = $this->parent->parseFiles($this->getManifest()->administration->files, 1);
 187              }
 188  
 189              if ($result === false) {
 190                  throw new \RuntimeException(
 191                      Text::sprintf(
 192                          'JLIB_INSTALLER_ABORT_COMP_FAIL_ADMIN_FILES',
 193                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 194                      )
 195                  );
 196              }
 197          }
 198  
 199          // Copy API files
 200          if ($this->getManifest()->api->files) {
 201              if ($this->route === 'update') {
 202                  $result = $this->parent->parseFiles($this->getManifest()->api->files, 3, $this->oldApiFiles);
 203              } else {
 204                  $result = $this->parent->parseFiles($this->getManifest()->api->files, 3);
 205              }
 206  
 207              if ($result === false) {
 208                  throw new \RuntimeException(
 209                      Text::sprintf(
 210                          'JLIB_INSTALLER_ABORT_COMP_FAIL_API_FILES',
 211                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 212                      )
 213                  );
 214              }
 215          }
 216  
 217          // If there is a manifest script, let's copy it.
 218          if ($this->manifest_script) {
 219              $path['src']  = $this->parent->getPath('source') . '/' . $this->manifest_script;
 220              $path['dest'] = $this->parent->getPath('extension_administrator') . '/' . $this->manifest_script;
 221  
 222              if ($this->parent->isOverwrite() || !file_exists($path['dest'])) {
 223                  if (!$this->parent->copyFiles(array($path))) {
 224                      throw new \RuntimeException(
 225                          Text::sprintf(
 226                              'JLIB_INSTALLER_ABORT_MANIFEST',
 227                              Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 228                          )
 229                      );
 230                  }
 231              }
 232          }
 233      }
 234  
 235      /**
 236       * Method to create the extension root path if necessary
 237       *
 238       * @return  void
 239       *
 240       * @since   3.4
 241       * @throws  \RuntimeException
 242       */
 243      protected function createExtensionRoot()
 244      {
 245          // If the component directory does not exist, let's create it
 246          $created = false;
 247  
 248          if (!file_exists($this->parent->getPath('extension_site'))) {
 249              if (!$created = Folder::create($this->parent->getPath('extension_site'))) {
 250                  throw new \RuntimeException(
 251                      Text::sprintf(
 252                          'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
 253                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route)),
 254                          $this->parent->getPath('extension_site')
 255                      )
 256                  );
 257              }
 258          }
 259  
 260          /*
 261           * Since we created the component directory and we will want to remove it if we have to roll back
 262           * the installation, let's add it to the installation step stack
 263           */
 264          if ($created) {
 265              $this->parent->pushStep(
 266                  array(
 267                      'type' => 'folder',
 268                      'path' => $this->parent->getPath('extension_site'),
 269                  )
 270              );
 271          }
 272  
 273          // If the component admin directory does not exist, let's create it
 274          $created = false;
 275  
 276          if (!file_exists($this->parent->getPath('extension_administrator'))) {
 277              if (!$created = Folder::create($this->parent->getPath('extension_administrator'))) {
 278                  throw new \RuntimeException(
 279                      Text::sprintf(
 280                          'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
 281                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route)),
 282                          $this->parent->getPath('extension_administrator')
 283                      )
 284                  );
 285              }
 286          }
 287  
 288          /*
 289           * Since we created the component admin directory and we will want to remove it if we have to roll
 290           * back the installation, let's add it to the installation step stack
 291           */
 292          if ($created) {
 293              $this->parent->pushStep(
 294                  array(
 295                      'type' => 'folder',
 296                      'path' => $this->parent->getPath('extension_administrator'),
 297                  )
 298              );
 299          }
 300  
 301          // If the component API directory does not exist, let's create it
 302          $created = false;
 303  
 304          if (!file_exists($this->parent->getPath('extension_api'))) {
 305              if (!$created = Folder::create($this->parent->getPath('extension_api'))) {
 306                  throw new \RuntimeException(
 307                      Text::sprintf(
 308                          'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
 309                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route)),
 310                          $this->parent->getPath('extension_api')
 311                      )
 312                  );
 313              }
 314          }
 315  
 316          /*
 317           * Since we created the component API directory and we will want to remove it if we have to roll
 318           * back the installation, let's add it to the installation step stack
 319           */
 320          if ($created) {
 321              $this->parent->pushStep(
 322                  array(
 323                      'type' => 'folder',
 324                      'path' => $this->parent->getPath('extension_api'),
 325                  )
 326              );
 327          }
 328      }
 329  
 330      /**
 331       * Method to finalise the installation processing
 332       *
 333       * @return  void
 334       *
 335       * @since   3.4
 336       * @throws  \RuntimeException
 337       */
 338      protected function finaliseInstall()
 339      {
 340          /** @var Update $update */
 341          $update = Table::getInstance('update');
 342  
 343          // Clobber any possible pending updates
 344          $uid = $update->find(
 345              array(
 346                  'element'   => $this->element,
 347                  'type'      => $this->extension->type,
 348                  'client_id' => 1,
 349              )
 350          );
 351  
 352          if ($uid) {
 353              $update->delete($uid);
 354          }
 355  
 356          // We will copy the manifest file to its appropriate place.
 357          if ($this->route !== 'discover_install') {
 358              if (!$this->parent->copyManifest()) {
 359                  // Install failed, roll back changes
 360                  throw new \RuntimeException(
 361                      Text::sprintf(
 362                          'JLIB_INSTALLER_ABORT_COPY_SETUP',
 363                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route))
 364                      )
 365                  );
 366              }
 367          }
 368  
 369          // Time to build the admin menus
 370          if (!$this->_buildAdminMenus($this->extension->extension_id)) {
 371              Log::add(Text::_('JLIB_INSTALLER_ABORT_COMP_BUILDADMINMENUS_FAILED'), Log::WARNING, 'jerror');
 372          }
 373  
 374          // Make sure that menu items pointing to the component have correct component id assigned to them.
 375          // Prevents message "Component 'com_extension' does not exist." after uninstalling / re-installing component.
 376          if (!$this->_updateMenus($this->extension->extension_id)) {
 377              Log::add(Text::_('JLIB_INSTALLER_ABORT_COMP_UPDATESITEMENUS_FAILED'), Log::WARNING, 'jerror');
 378          }
 379  
 380          /** @var Asset $asset */
 381          $asset = Table::getInstance('Asset');
 382  
 383          // Check if an asset already exists for this extension and create it if not
 384          if (!$asset->loadByName($this->extension->element)) {
 385              // Register the component container just under root in the assets table.
 386              $asset->name      = $this->extension->element;
 387              $asset->parent_id = 1;
 388              $asset->rules     = '{}';
 389              $asset->title     = $this->extension->name;
 390              $asset->setLocation(1, 'last-child');
 391  
 392              if (!$asset->store()) {
 393                  // Install failed, roll back changes
 394                  throw new \RuntimeException(
 395                      Text::sprintf(
 396                          'JLIB_INSTALLER_ABORT_ROLLBACK',
 397                          Text::_('JLIB_INSTALLER_' . strtoupper($this->route)),
 398                          $this->extension->getError()
 399                      )
 400                  );
 401              }
 402          }
 403      }
 404  
 405      /**
 406       * Method to finalise the uninstallation processing
 407       *
 408       * @return  boolean
 409       *
 410       * @since   4.0.0
 411       * @throws  \RuntimeException
 412       */
 413      protected function finaliseUninstall(): bool
 414      {
 415          $extensionId = $this->extension->extension_id;
 416  
 417          $db = $this->getDatabase();
 418  
 419          // Remove the schema version
 420          $query = $db->getQuery(true)
 421              ->delete($db->quoteName('#__schemas'))
 422              ->where($db->quoteName('extension_id') . ' = :extension_id')
 423              ->bind(':extension_id', $extensionId, ParameterType::INTEGER);
 424          $db->setQuery($query);
 425          $db->execute();
 426  
 427          // Remove the component container in the assets table.
 428          $asset = Table::getInstance('Asset');
 429  
 430          if ($asset->loadByName($this->getElement())) {
 431              $asset->delete();
 432          }
 433  
 434          $extensionName = $this->element;
 435          $extensionNameWithWildcard = $extensionName . '.%';
 436  
 437          // Remove categories for this component
 438          $query = $db->getQuery(true)
 439              ->delete($db->quoteName('#__categories'))
 440              ->where(
 441                  [
 442                      $db->quoteName('extension') . ' = :extension',
 443                      $db->quoteName('extension') . ' LIKE :wildcard',
 444                  ],
 445                  'OR'
 446              )
 447              ->bind(':extension', $extensionName)
 448              ->bind(':wildcard', $extensionNameWithWildcard);
 449          $db->setQuery($query);
 450          $db->execute();
 451  
 452          // Rebuild the categories for correct lft/rgt
 453          Table::getInstance('category')->rebuild();
 454  
 455          // Clobber any possible pending updates
 456          $update = Table::getInstance('update');
 457          $uid = $update->find(
 458              array(
 459                  'element'   => $this->extension->element,
 460                  'type'      => 'component',
 461                  'client_id' => 1,
 462                  'folder'    => '',
 463              )
 464          );
 465  
 466          if ($uid) {
 467              $update->delete($uid);
 468          }
 469  
 470          // Now we need to delete the installation directories. This is the final step in uninstalling the component.
 471          if (trim($this->extension->element)) {
 472              $retval = true;
 473  
 474              // Delete the component site directory
 475              if (is_dir($this->parent->getPath('extension_site'))) {
 476                  if (!Folder::delete($this->parent->getPath('extension_site'))) {
 477                      Log::add(Text::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_SITE'), Log::WARNING, 'jerror');
 478                      $retval = false;
 479                  }
 480              }
 481  
 482              // Delete the component admin directory
 483              if (is_dir($this->parent->getPath('extension_administrator'))) {
 484                  if (!Folder::delete($this->parent->getPath('extension_administrator'))) {
 485                      Log::add(Text::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_ADMIN'), Log::WARNING, 'jerror');
 486                      $retval = false;
 487                  }
 488              }
 489  
 490              // Delete the component API directory
 491              if (is_dir($this->parent->getPath('extension_api'))) {
 492                  if (!Folder::delete($this->parent->getPath('extension_api'))) {
 493                      Log::add(Text::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_API'), Log::WARNING, 'jerror');
 494                      $retval = false;
 495                  }
 496              }
 497  
 498              // Now we will no longer need the extension object, so let's delete it
 499              $this->extension->delete($this->extension->extension_id);
 500  
 501              return $retval;
 502          }
 503  
 504          // No component option defined... cannot delete what we don't know about
 505          Log::add(Text::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_NO_OPTION'), Log::WARNING, 'jerror');
 506  
 507          return false;
 508      }
 509  
 510      /**
 511       * Get the filtered extension element from the manifest
 512       *
 513       * @param   string  $element  Optional element name to be converted
 514       *
 515       * @return  string  The filtered element
 516       *
 517       * @since   3.4
 518       */
 519      public function getElement($element = null)
 520      {
 521          $element = parent::getElement($element);
 522  
 523          if (strpos($element, 'com_') !== 0) {
 524              $element = 'com_' . $element;
 525          }
 526  
 527          return $element;
 528      }
 529  
 530      /**
 531       * Custom loadLanguage method
 532       *
 533       * @param   string  $path  The path language files are on.
 534       *
 535       * @return  void
 536       *
 537       * @since   3.1
 538       */
 539      public function loadLanguage($path = null)
 540      {
 541          $source = $this->parent->getPath('source');
 542  
 543          switch ($this->parent->extension->client_id) {
 544              case 0:
 545                  $client = JPATH_SITE;
 546  
 547                  break;
 548  
 549              case 1:
 550                  $client = JPATH_ADMINISTRATOR;
 551  
 552                  break;
 553  
 554              case 3:
 555                  $client = JPATH_API;
 556  
 557                  break;
 558  
 559              default:
 560                  throw new \InvalidArgumentException(
 561                      sprintf(
 562                          'Unsupported client ID %d for component %s',
 563                          $this->parent->extension->client_id,
 564                          $this->parent->extension->element
 565                      )
 566                  );
 567          }
 568  
 569          if (!$source) {
 570              $this->parent->setPath('source', $client . '/components/' . $this->parent->extension->element);
 571          }
 572  
 573          $extension = $this->getElement();
 574          $source    = $path ?: $client . '/components/' . $extension;
 575  
 576          if ($this->getManifest()->administration->files) {
 577              $element = $this->getManifest()->administration->files;
 578          } elseif ($this->getManifest()->api->files) {
 579              $element = $this->getManifest()->api->files;
 580          } elseif ($this->getManifest()->files) {
 581              $element = $this->getManifest()->files;
 582          } else {
 583              $element = null;
 584          }
 585  
 586          if ($element) {
 587              $folder = (string) $element->attributes()->folder;
 588  
 589              if ($folder && file_exists($path . '/' . $folder)) {
 590                  $source = $path . '/' . $folder;
 591              }
 592          }
 593  
 594          $this->doLoadLanguage($extension, $source);
 595      }
 596  
 597      /**
 598       * Method to parse optional tags in the manifest
 599       *
 600       * @return  void
 601       *
 602       * @since   3.4
 603       */
 604      protected function parseOptionalTags()
 605      {
 606          // Parse optional tags
 607          $this->parent->parseMedia($this->getManifest()->media);
 608          $this->parent->parseLanguages($this->getManifest()->languages);
 609          $this->parent->parseLanguages($this->getManifest()->administration->languages, 1);
 610      }
 611  
 612      /**
 613       * Method to parse the queries specified in the `<sql>` tags
 614       *
 615       * @return  void
 616       *
 617       * @since   4.0.0
 618       * @throws  \RuntimeException
 619       */
 620      protected function parseQueries()
 621      {
 622          parent::parseQueries();
 623  
 624          // We have extra tasks to run for the uninstall path
 625          if ($this->route === 'uninstall') {
 626              $this->_removeAdminMenus($this->extension->extension_id);
 627          }
 628      }
 629  
 630      /**
 631       * Prepares the adapter for a discover_install task
 632       *
 633       * @return  void
 634       *
 635       * @since   3.4
 636       * @throws  \RuntimeException
 637       */
 638      public function prepareDiscoverInstall()
 639      {
 640          // Need to find to find where the XML file is since we don't store this normally
 641          $client = ApplicationHelper::getClientInfo($this->extension->client_id);
 642          $short_element = str_replace('com_', '', $this->extension->element);
 643          $manifestPath = $client->path . '/components/' . $this->extension->element . '/' . $short_element . '.xml';
 644          $this->parent->manifest = $this->parent->isManifest($manifestPath);
 645          $this->parent->setPath('manifest', $manifestPath);
 646          $this->parent->setPath('source', $client->path . '/components/' . $this->extension->element);
 647          $this->parent->setPath('extension_root', $this->parent->getPath('source'));
 648          $this->setManifest($this->parent->getManifest());
 649  
 650          $manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
 651          $this->extension->manifest_cache = json_encode($manifest_details);
 652          $this->extension->state = 0;
 653          $this->extension->name = $manifest_details['name'];
 654          $this->extension->enabled = 1;
 655          $this->extension->params = $this->parent->getParams();
 656  
 657          $stored = false;
 658  
 659          try {
 660              $this->extension->store();
 661              $stored = true;
 662          } catch (\RuntimeException $e) {
 663              $name = $this->extension->name;
 664              $type = $this->extension->type;
 665              $element = $this->extension->element;
 666  
 667              // Try to delete existing failed records before retrying
 668              $db = $this->getDatabase();
 669  
 670              $query = $db->getQuery(true)
 671                  ->select($db->quoteName('extension_id'))
 672                  ->from($db->quoteName('#__extensions'))
 673                  ->where(
 674                      [
 675                          $db->quoteName('name') . ' = :name',
 676                          $db->quoteName('type') . ' = :type',
 677                          $db->quoteName('element') . ' = :element',
 678                      ]
 679                  )
 680                  ->bind(':name', $name)
 681                  ->bind(':type', $type)
 682                  ->bind(':element', $element);
 683  
 684              $db->setQuery($query);
 685  
 686              $extension_ids = $db->loadColumn();
 687  
 688              if (!empty($extension_ids)) {
 689                  foreach ($extension_ids as $eid) {
 690                      // Remove leftover admin menus for this extension ID
 691                      $this->_removeAdminMenus($eid);
 692  
 693                      // Remove the extension record itself
 694                      /** @var Extension $extensionTable */
 695                      $extensionTable = Table::getInstance('extension');
 696                      $extensionTable->delete($eid);
 697                  }
 698              }
 699          }
 700  
 701          if (!$stored) {
 702              try {
 703                  $this->extension->store();
 704              } catch (\RuntimeException $e) {
 705                  throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'), $e->getCode(), $e);
 706              }
 707          }
 708      }
 709  
 710      /**
 711       * Removes this extension's files
 712       *
 713       * @return  void
 714       *
 715       * @since   4.0.0
 716       * @throws  \RuntimeException
 717       */
 718      protected function removeExtensionFiles()
 719      {
 720          // Let's remove those language files and media in the JROOT/images/ folder that are associated with the component we are uninstalling
 721          $this->parent->removeFiles($this->getManifest()->media);
 722          $this->parent->removeFiles($this->getManifest()->languages);
 723          $this->parent->removeFiles($this->getManifest()->administration->languages, 1);
 724      }
 725  
 726      /**
 727       * Method to do any prechecks and setup the install paths for the extension
 728       *
 729       * @return  void
 730       *
 731       * @since   3.4
 732       * @throws  \RuntimeException
 733       */
 734      protected function setupInstallPaths()
 735      {
 736          // Set the installation target paths
 737          $this->parent->setPath('extension_site', Path::clean(JPATH_SITE . '/components/' . $this->element));
 738          $this->parent->setPath('extension_administrator', Path::clean(JPATH_ADMINISTRATOR . '/components/' . $this->element));
 739          $this->parent->setPath('extension_api', Path::clean(JPATH_API . '/components/' . $this->element));
 740  
 741          // Copy the admin path as it's used as a common base
 742          $this->parent->setPath('extension_root', $this->parent->getPath('extension_administrator'));
 743  
 744          // Make sure that we have an admin element
 745          if (!$this->getManifest()->administration) {
 746              throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT'));
 747          }
 748      }
 749  
 750      /**
 751       * Method to do any prechecks and setup the uninstall job
 752       *
 753       * @return  void
 754       *
 755       * @since   4.0.0
 756       */
 757      protected function setupUninstall()
 758      {
 759          // Get the admin and site paths for the component
 760          $this->parent->setPath('extension_administrator', Path::clean(JPATH_ADMINISTRATOR . '/components/' . $this->extension->element));
 761          $this->parent->setPath('extension_api', Path::clean(JPATH_API . '/components/' . $this->extension->element));
 762          $this->parent->setPath('extension_site', Path::clean(JPATH_SITE . '/components/' . $this->extension->element));
 763  
 764          // Copy the admin path as it's used as a common base
 765          $this->parent->setPath('extension_root', $this->parent->getPath('extension_administrator'));
 766  
 767          // Find and load the XML install file for the component
 768          $this->parent->setPath('source', $this->parent->getPath('extension_administrator'));
 769  
 770          // Get the package manifest object
 771          // We do findManifest to avoid problem when uninstalling a list of extension: getManifest cache its manifest file
 772          $this->parent->findManifest();
 773          $this->setManifest($this->parent->getManifest());
 774  
 775          if (!$this->getManifest()) {
 776              // Make sure we delete the folders if no manifest exists
 777              Folder::delete($this->parent->getPath('extension_administrator'));
 778              Folder::delete($this->parent->getPath('extension_api'));
 779              Folder::delete($this->parent->getPath('extension_site'));
 780  
 781              // Remove the menu
 782              $this->_removeAdminMenus($this->extension->extension_id);
 783  
 784              // Raise a warning
 785              throw new \RuntimeException(Text::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORREMOVEMANUALLY'));
 786          }
 787  
 788          // Attempt to load the admin language file; might have uninstall strings
 789          $this->loadLanguage(JPATH_ADMINISTRATOR . '/components/' . $this->extension->element);
 790      }
 791  
 792      /**
 793       * Method to setup the update routine for the adapter
 794       *
 795       * @return  void
 796       *
 797       * @since   3.4
 798       */
 799      protected function setupUpdates()
 800      {
 801          // Hunt for the original XML file
 802          $old_manifest = null;
 803  
 804          // Use a temporary instance due to side effects; start in the administrator first
 805          $tmpInstaller = new Installer();
 806          $tmpInstaller->setDatabase($this->getDatabase());
 807          $tmpInstaller->setPath('source', $this->parent->getPath('extension_administrator'));
 808  
 809          if (!$tmpInstaller->findManifest()) {
 810              // Then the site
 811              $tmpInstaller->setPath('source', $this->parent->getPath('extension_site'));
 812  
 813              if ($tmpInstaller->findManifest()) {
 814                  $old_manifest = $tmpInstaller->getManifest();
 815              }
 816          } else {
 817              $old_manifest = $tmpInstaller->getManifest();
 818          }
 819  
 820          if ($old_manifest) {
 821              $this->oldAdminFiles = $old_manifest->administration->files;
 822              $this->oldApiFiles = $old_manifest->api->files;
 823              $this->oldFiles = $old_manifest->files;
 824          }
 825      }
 826  
 827      /**
 828       * Method to store the extension to the database
 829       *
 830       * @param   bool  $deleteExisting  Should I try to delete existing records of the same component?
 831       *
 832       * @return  void
 833       *
 834       * @since   3.4
 835       * @throws  \RuntimeException
 836       */
 837      protected function storeExtension($deleteExisting = false)
 838      {
 839          // The extension is stored during prepareDiscoverInstall for discover installs
 840          if ($this->route === 'discover_install') {
 841              return;
 842          }
 843  
 844          // Add or update an entry to the extension table
 845          $this->extension->name         = $this->name;
 846          $this->extension->type         = 'component';
 847          $this->extension->element      = $this->element;
 848          $this->extension->changelogurl = $this->changelogurl;
 849  
 850          // If we are told to delete existing extension entries then do so.
 851          if ($deleteExisting) {
 852              $name = $this->extension->name;
 853              $type = $this->extension->type;
 854              $element = $this->extension->element;
 855  
 856              // Try to delete existing failed records before retrying
 857              $db = $this->getDatabase();
 858  
 859              $query = $db->getQuery(true)
 860                  ->select($db->quoteName('extension_id'))
 861                  ->from($db->quoteName('#__extensions'))
 862                  ->where(
 863                      [
 864                          $db->quoteName('name') . ' = :name',
 865                          $db->quoteName('type') . ' = :type',
 866                          $db->quoteName('element') . ' = :element',
 867                      ]
 868                  )
 869                  ->bind(':name', $name)
 870                  ->bind(':type', $type)
 871                  ->bind(':element', $element);
 872  
 873              $db->setQuery($query);
 874  
 875              $extension_ids = $db->loadColumn();
 876  
 877              if (!empty($extension_ids)) {
 878                  foreach ($extension_ids as $eid) {
 879                      // Remove leftover admin menus for this extension ID
 880                      $this->_removeAdminMenus($eid);
 881  
 882                      // Remove the extension record itself
 883                      /** @var Extension $extensionTable */
 884                      $extensionTable = Table::getInstance('extension');
 885                      $extensionTable->delete($eid);
 886                  }
 887              }
 888          }
 889  
 890          // Namespace is optional
 891          if (isset($this->manifest->namespace)) {
 892              $this->extension->namespace = (string) $this->manifest->namespace;
 893          }
 894  
 895          // If there is not already a row, generate a heap of defaults
 896          if (!$this->currentExtensionId) {
 897              $this->extension->folder    = '';
 898              $this->extension->enabled   = 1;
 899              $this->extension->protected = 0;
 900              $this->extension->access    = 0;
 901              $this->extension->client_id = 1;
 902              $this->extension->params    = $this->parent->getParams();
 903          }
 904  
 905          $this->extension->manifest_cache = $this->parent->generateManifestCache();
 906  
 907          $couldStore = $this->extension->store();
 908  
 909          if (!$couldStore && $deleteExisting) {
 910              // Install failed, roll back changes
 911              throw new \RuntimeException(
 912                  Text::sprintf(
 913                      'JLIB_INSTALLER_ABORT_COMP_INSTALL_ROLLBACK',
 914                      $this->extension->getError()
 915                  )
 916              );
 917          }
 918  
 919          if (!$couldStore && !$deleteExisting) {
 920              // Maybe we have a failed installation (e.g. timeout). Let's retry after deleting old records.
 921              $this->storeExtension(true);
 922          }
 923      }
 924  
 925      /**
 926       * Method to build menu database entries for a component
 927       *
 928       * @param   int|null  $componentId  The component ID for which I'm building menus
 929       *
 930       * @return  boolean  True if successful
 931       *
 932       * @since   3.1
 933       */
 934      protected function _buildAdminMenus($componentId = null)
 935      {
 936          $db     = $this->getDatabase();
 937          $option = $this->element;
 938  
 939          // If a component exists with this option in the table within the protected menutype 'main' then we don't need to add menus
 940          $query = $db->getQuery(true)
 941              ->select(
 942                  [
 943                      $db->quoteName('m.id'),
 944                      $db->quoteName('e.extension_id'),
 945                  ]
 946              )
 947              ->from($db->quoteName('#__menu', 'm'))
 948              ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id'))
 949              ->where(
 950                  [
 951                      $db->quoteName('m.parent_id') . ' = 1',
 952                      $db->quoteName('m.client_id') . ' = 1',
 953                      $db->quoteName('m.menutype') . ' = ' . $db->quote('main'),
 954                      $db->quoteName('e.element') . ' = :element',
 955                  ]
 956              )
 957              ->bind(':element', $option);
 958  
 959          $db->setQuery($query);
 960  
 961          // In case of a failed installation (e.g. timeout error) we may have duplicate menu item and extension records.
 962          $componentrows = $db->loadObjectList();
 963  
 964          // Check if menu items exist
 965          if (!empty($componentrows)) {
 966              // Don't do anything if overwrite has not been enabled
 967              if (!$this->parent->isOverwrite()) {
 968                  return true;
 969              }
 970  
 971              // Remove all menu items
 972              foreach ($componentrows as $componentrow) {
 973                  // Remove existing menu items if overwrite has been enabled
 974                  if ($option) {
 975                      // If something goes wrong, there's no way to rollback @todo: Search for better solution
 976                      $this->_removeAdminMenus($componentrow->extension_id);
 977                  }
 978              }
 979          }
 980  
 981          // Only try to detect the component ID if it's not provided
 982          if (empty($componentId)) {
 983              // Lets find the extension id
 984              $query->clear()
 985                  ->select($db->quoteName('e.extension_id'))
 986                  ->from($db->quoteName('#__extensions', 'e'))
 987                  ->where(
 988                      [
 989                          $db->quoteName('e.type') . ' = ' . $db->quote('component'),
 990                          $db->quoteName('e.element') . ' = :element',
 991                      ]
 992                  )
 993                  ->bind(':element', $option);
 994  
 995              $db->setQuery($query);
 996              $componentId = $db->loadResult();
 997          }
 998  
 999          // Ok, now its time to handle the menus.  Start with the component root menu, then handle submenus.
1000          $menuElement = $this->getManifest()->administration->menu;
1001  
1002          // Just do not create the menu if $menuElement not exist
1003          if (!$menuElement) {
1004              return true;
1005          }
1006  
1007          // If the menu item is hidden do nothing more, just return
1008          if (\in_array((string) $menuElement['hidden'], array('true', 'hidden'))) {
1009              return true;
1010          }
1011  
1012          // Let's figure out what the menu item data should look like
1013          $data = array();
1014  
1015          // I have a menu element, use this information
1016          $data['menutype']     = 'main';
1017          $data['client_id']    = 1;
1018          $data['title']        = (string) trim($menuElement);
1019          $data['alias']        = (string) $menuElement;
1020          $data['type']         = 'component';
1021          $data['published']    = 1;
1022          $data['parent_id']    = 1;
1023          $data['component_id'] = $componentId;
1024          $data['img']          = ((string) $menuElement->attributes()->img) ?: 'class:component';
1025          $data['home']         = 0;
1026          $data['path']         = '';
1027          $data['params']       = '';
1028  
1029          if ($params = $menuElement->params) {
1030              // Pass $params through Registry to convert to JSON.
1031              $params = new Registry($params);
1032              $data['params'] = $params->toString();
1033          }
1034  
1035          // Set the menu link
1036          $request = [];
1037  
1038          if ((string) $menuElement->attributes()->task) {
1039              $request[] = 'task=' . $menuElement->attributes()->task;
1040          }
1041  
1042          if ((string) $menuElement->attributes()->view) {
1043              $request[] = 'view=' . $menuElement->attributes()->view;
1044          }
1045  
1046          $qstring = \count($request) ? '&' . implode('&', $request) : '';
1047          $data['link'] = 'index.php?option=' . $option . $qstring;
1048  
1049          // Try to create the menu item in the database
1050          $parent_id = $this->_createAdminMenuItem($data, 1);
1051  
1052          if ($parent_id === false) {
1053              return false;
1054          }
1055  
1056          /*
1057           * Process SubMenus
1058           */
1059  
1060          if (!$this->getManifest()->administration->submenu) {
1061              // No submenu? We're done.
1062              return true;
1063          }
1064  
1065          foreach ($this->getManifest()->administration->submenu->menu as $child) {
1066              $data                 = array();
1067              $data['menutype']     = 'main';
1068              $data['client_id']    = 1;
1069              $data['title']        = (string) trim($child);
1070              $data['alias']        = (string) $child;
1071              $data['type']         = 'component';
1072              $data['published']    = 1;
1073              $data['parent_id']    = $parent_id;
1074              $data['component_id'] = $componentId;
1075              $data['img']          = ((string) $child->attributes()->img) ?: 'class:component';
1076              $data['home']         = 0;
1077              $data['params']       = '';
1078  
1079              if ($params = $child->params) {
1080                  // Pass $params through Registry to convert to JSON.
1081                  $params = new Registry($params);
1082                  $data['params'] = $params->toString();
1083              }
1084  
1085              // Set the sub menu link
1086              if ((string) $child->attributes()->link) {
1087                  $data['link'] = 'index.php?' . $child->attributes()->link;
1088              } else {
1089                  $request = array();
1090  
1091                  if ((string) $child->attributes()->act) {
1092                      $request[] = 'act=' . $child->attributes()->act;
1093                  }
1094  
1095                  if ((string) $child->attributes()->task) {
1096                      $request[] = 'task=' . $child->attributes()->task;
1097                  }
1098  
1099                  if ((string) $child->attributes()->controller) {
1100                      $request[] = 'controller=' . $child->attributes()->controller;
1101                  }
1102  
1103                  if ((string) $child->attributes()->view) {
1104                      $request[] = 'view=' . $child->attributes()->view;
1105                  }
1106  
1107                  if ((string) $child->attributes()->layout) {
1108                      $request[] = 'layout=' . $child->attributes()->layout;
1109                  }
1110  
1111                  if ((string) $child->attributes()->sub) {
1112                      $request[] = 'sub=' . $child->attributes()->sub;
1113                  }
1114  
1115                  $qstring      = \count($request) ? '&' . implode('&', $request) : '';
1116                  $data['link'] = 'index.php?option=' . $option . $qstring;
1117              }
1118  
1119              $submenuId = $this->_createAdminMenuItem($data, $parent_id);
1120  
1121              if ($submenuId === false) {
1122                  return false;
1123              }
1124  
1125              /*
1126               * Since we have created a menu item, we add it to the installation step stack
1127               * so that if we have to rollback the changes we can undo it.
1128               */
1129              $this->parent->pushStep(array('type' => 'menu', 'id' => $componentId));
1130          }
1131  
1132          return true;
1133      }
1134  
1135      /**
1136       * Method to remove admin menu references to a component
1137       *
1138       * @param   int  $id  The ID of the extension whose admin menus will be removed
1139       *
1140       * @return  boolean  True if successful.
1141       *
1142       * @throws  \Exception
1143       *
1144       * @since   3.1
1145       */
1146      protected function _removeAdminMenus($id)
1147      {
1148          $db = $this->getDatabase();
1149  
1150          /** @var  \Joomla\CMS\Table\Menu  $table */
1151          $table = Table::getInstance('menu');
1152  
1153          // Get the ids of the menu items
1154          $query = $db->getQuery(true)
1155              ->select($db->quoteName('id'))
1156              ->from($db->quoteName('#__menu'))
1157              ->where(
1158                  [
1159                      $db->quoteName('client_id') . ' = 1',
1160                      $db->quoteName('menutype') . ' = ' . $db->quote('main'),
1161                      $db->quoteName('component_id') . ' = :id',
1162                  ]
1163              )
1164              ->bind(':id', $id, ParameterType::INTEGER);
1165  
1166          $db->setQuery($query);
1167  
1168          $ids    = $db->loadColumn();
1169          $result = true;
1170  
1171          // Check for error
1172          if (!empty($ids)) {
1173              // Iterate the items to delete each one.
1174              foreach ($ids as $menuid) {
1175                  if (!$table->delete((int) $menuid, false)) {
1176                      Factory::getApplication()->enqueueMessage($table->getError(), 'error');
1177  
1178                      $result = false;
1179                  }
1180              }
1181  
1182              // Rebuild the whole tree
1183              $table->rebuild();
1184          }
1185  
1186          return $result;
1187      }
1188  
1189      /**
1190       * Method to update menu database entries for a component in case the component has been uninstalled before.
1191       * NOTE: This will not update admin menus. Use _updateMenus() instead to update admin menus ase well.
1192       *
1193       * @param   int|null  $componentId  The component ID.
1194       *
1195       * @return  boolean  True if successful
1196       *
1197       * @since   3.4.2
1198       */
1199      protected function _updateSiteMenus($componentId = null)
1200      {
1201          return $this->_updateMenus($componentId, 0);
1202      }
1203  
1204      /**
1205       * Method to update menu database entries for a component in case if the component has been uninstalled before.
1206       *
1207       * @param   int|null  $componentId  The component ID.
1208       * @param   int       $clientId     The client id
1209       *
1210       * @return  boolean  True if successful
1211       *
1212       * @since   3.7.0
1213       */
1214      protected function _updateMenus($componentId, $clientId = null)
1215      {
1216          $db        = $this->getDatabase();
1217          $option    = $this->element;
1218          $link      = 'index.php?option=' . $option;
1219          $linkMatch = 'index.php?option=' . $option . '&%';
1220  
1221          // Update all menu items which contain 'index.php?option=com_extension' or 'index.php?option=com_extension&...'
1222          // to use the new component id.
1223          $query = $db->getQuery(true)
1224              ->update($db->quoteName('#__menu'))
1225              ->set($db->quoteName('component_id') . ' = :componentId')
1226              ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
1227              ->extendWhere(
1228                  'AND',
1229                  [
1230                      $db->quoteName('link') . ' LIKE :link',
1231                      $db->quoteName('link') . ' LIKE :linkMatch',
1232                  ],
1233                  'OR'
1234              )
1235              ->bind(':componentId', $componentId, ParameterType::INTEGER)
1236              ->bind(':link', $link)
1237              ->bind(':linkMatch', $linkMatch);
1238  
1239          if (isset($clientId)) {
1240              $query->where($db->quoteName('client_id') . ' = :clientId')
1241                  ->bind(':clientId', $clientId, ParameterType::INTEGER);
1242          }
1243  
1244          try {
1245              $db->setQuery($query);
1246              $db->execute();
1247          } catch (\RuntimeException $e) {
1248              return false;
1249          }
1250  
1251          return true;
1252      }
1253  
1254      /**
1255       * Custom rollback method
1256       * - Roll back the component menu item
1257       *
1258       * @param   array  $step  Installation step to rollback.
1259       *
1260       * @return  boolean  True on success
1261       *
1262       * @throws  \Exception
1263       *
1264       * @since   3.1
1265       */
1266      protected function _rollback_menu($step)
1267      {
1268          return $this->_removeAdminMenus($step['id']);
1269      }
1270  
1271      /**
1272       * Discover unregistered extensions.
1273       *
1274       * @return  array  A list of extensions.
1275       *
1276       * @since   3.1
1277       */
1278      public function discover()
1279      {
1280          $results          = array();
1281          $site_components  = Folder::folders(JPATH_SITE . '/components');
1282          $admin_components = Folder::folders(JPATH_ADMINISTRATOR . '/components');
1283          $api_components = Folder::folders(JPATH_API . '/components');
1284  
1285          foreach ($site_components as $component) {
1286              if (file_exists(JPATH_SITE . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml')) {
1287                  $manifest_details = Installer::parseXMLInstallFile(
1288                      JPATH_SITE . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'
1289                  );
1290                  $extension = Table::getInstance('extension');
1291                  $extension->set('type', 'component');
1292                  $extension->set('client_id', 0);
1293                  $extension->set('element', $component);
1294                  $extension->set('folder', '');
1295                  $extension->set('name', $component);
1296                  $extension->set('state', -1);
1297                  $extension->set('manifest_cache', json_encode($manifest_details));
1298                  $extension->set('params', '{}');
1299  
1300                  $results[] = $extension;
1301              }
1302          }
1303  
1304          foreach ($admin_components as $component) {
1305              if (file_exists(JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml')) {
1306                  $manifest_details = Installer::parseXMLInstallFile(
1307                      JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'
1308                  );
1309                  $extension = Table::getInstance('extension');
1310                  $extension->set('type', 'component');
1311                  $extension->set('client_id', 1);
1312                  $extension->set('element', $component);
1313                  $extension->set('folder', '');
1314                  $extension->set('name', $component);
1315                  $extension->set('state', -1);
1316                  $extension->set('manifest_cache', json_encode($manifest_details));
1317                  $extension->set('params', '{}');
1318                  $results[] = $extension;
1319              }
1320          }
1321  
1322          foreach ($api_components as $component) {
1323              if (file_exists(JPATH_API . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml')) {
1324                  $manifest_details = Installer::parseXMLInstallFile(
1325                      JPATH_API . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'
1326                  );
1327                  $extension = Table::getInstance('extension');
1328                  $extension->set('type', 'component');
1329                  $extension->set('client_id', 3);
1330                  $extension->set('element', $component);
1331                  $extension->set('folder', '');
1332                  $extension->set('name', $component);
1333                  $extension->set('state', -1);
1334                  $extension->set('manifest_cache', json_encode($manifest_details));
1335                  $extension->set('params', '{}');
1336                  $results[] = $extension;
1337              }
1338          }
1339  
1340          return $results;
1341      }
1342  
1343      /**
1344       * Refreshes the extension table cache
1345       *
1346       * @return  boolean  Result of operation, true if updated, false on failure
1347       *
1348       * @since   3.1
1349       */
1350      public function refreshManifestCache()
1351      {
1352          // Need to find to find where the XML file is since we don't store this normally
1353          $client                 = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
1354          $short_element          = str_replace('com_', '', $this->parent->extension->element);
1355          $manifestPath           = $client->path . '/components/' . $this->parent->extension->element . '/' . $short_element . '.xml';
1356          $this->parent->manifest = $this->parent->isManifest($manifestPath);
1357          $this->parent->setPath('manifest', $manifestPath);
1358  
1359          $manifest_details                        = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
1360          $this->parent->extension->manifest_cache = json_encode($manifest_details);
1361          $this->parent->extension->name           = $manifest_details['name'];
1362  
1363          // Namespace is optional
1364          if (isset($manifest_details['namespace'])) {
1365              $this->parent->extension->namespace = $manifest_details['namespace'];
1366          }
1367  
1368          try {
1369              return $this->parent->extension->store();
1370          } catch (\RuntimeException $e) {
1371              Log::add(Text::_('JLIB_INSTALLER_ERROR_COMP_REFRESH_MANIFEST_CACHE'), Log::WARNING, 'jerror');
1372  
1373              return false;
1374          }
1375      }
1376  
1377      /**
1378       * Creates the menu item in the database. If the item already exists it tries to remove it and create it afresh.
1379       *
1380       * @param   array    &$data     The menu item data to create
1381       * @param   integer  $parentId  The parent menu item ID
1382       *
1383       * @return  boolean|integer  Menu item ID on success, false on failure
1384       *
1385       * @throws  \Exception
1386       *
1387       * @since   3.1
1388       */
1389      protected function _createAdminMenuItem(array &$data, $parentId)
1390      {
1391          $db = $this->getDatabase();
1392  
1393          /** @var  \Joomla\CMS\Table\Menu  $table */
1394          $table  = Table::getInstance('menu');
1395  
1396          try {
1397              $table->setLocation($parentId, 'last-child');
1398          } catch (\InvalidArgumentException $e) {
1399              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1400  
1401              return false;
1402          }
1403  
1404          if (!$table->bind($data) || !$table->check() || !$table->store()) {
1405              $menutype     = $data['menutype'];
1406              $link         = $data['link'];
1407              $type         = $data['type'];
1408              $menuParentId = $data['parent_id'];
1409              $home         = $data['home'];
1410  
1411              // The menu item already exists. Delete it and retry instead of throwing an error.
1412              $query = $db->getQuery(true)
1413                  ->select($db->quoteName('id'))
1414                  ->from($db->quoteName('#__menu'))
1415                  ->where(
1416                      [
1417                          $db->quoteName('menutype') . ' = :menutype',
1418                          $db->quoteName('client_id') . ' = 1',
1419                          $db->quoteName('link') . ' = :link',
1420                          $db->quoteName('type') . ' = :type',
1421                          $db->quoteName('parent_id') . ' = :parent_id',
1422                          $db->quoteName('home') . ' = :home',
1423                      ]
1424                  )
1425                  ->bind(':menutype', $menutype)
1426                  ->bind(':link', $link)
1427                  ->bind(':type', $type)
1428                  ->bind(':parent_id', $menuParentId, ParameterType::INTEGER)
1429                  ->bind(':home', $home, ParameterType::BOOLEAN);
1430  
1431              $db->setQuery($query);
1432              $menu_id = $db->loadResult();
1433  
1434              if (!$menu_id) {
1435                  // Oops! Could not get the menu ID. Go back and rollback changes.
1436                  Factory::getApplication()->enqueueMessage($table->getError(), 'error');
1437  
1438                  return false;
1439              } else {
1440                  /** @var  \Joomla\CMS\Table\Menu $temporaryTable */
1441                  $temporaryTable = Table::getInstance('menu');
1442                  $temporaryTable->delete($menu_id, true);
1443                  $temporaryTable->load($parentId);
1444                  $temporaryTable->rebuild($parentId, $temporaryTable->lft, $temporaryTable->level, $temporaryTable->path);
1445  
1446                  // Retry creating the menu item
1447                  $table->setLocation($parentId, 'last-child');
1448  
1449                  if (!$table->bind($data) || !$table->check() || !$table->store()) {
1450                      // Install failed, warn user and rollback changes
1451                      Factory::getApplication()->enqueueMessage($table->getError(), 'error');
1452  
1453                      return false;
1454                  }
1455              }
1456          }
1457  
1458          return $table->id;
1459      }
1460  }


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