[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

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

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2014 Open Source Matters, Inc. <https://www.joomla.org>
   7   * @license    GNU General Public License version 2 or later; see LICENSE.txt
   8   */
   9  
  10  namespace Joomla\CMS\Installer;
  11  
  12  use Joomla\CMS\Factory;
  13  use Joomla\CMS\Filesystem\Folder;
  14  use Joomla\CMS\Filter\InputFilter;
  15  use Joomla\CMS\Installer\Manifest\PackageManifest;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Log\Log;
  18  use Joomla\CMS\Table\Extension;
  19  use Joomla\CMS\Table\Table;
  20  use Joomla\CMS\Table\TableInterface;
  21  use Joomla\Database\DatabaseAwareInterface;
  22  use Joomla\Database\DatabaseAwareTrait;
  23  use Joomla\Database\DatabaseDriver;
  24  use Joomla\DI\Container;
  25  use Joomla\DI\ContainerAwareInterface;
  26  use Joomla\DI\ContainerAwareTrait;
  27  use Joomla\DI\Exception\ContainerNotFoundException;
  28  use Joomla\DI\ServiceProviderInterface;
  29  
  30  // phpcs:disable PSR1.Files.SideEffects
  31  \defined('JPATH_PLATFORM') or die;
  32  // phpcs:enable PSR1.Files.SideEffects
  33  
  34  /**
  35   * Abstract adapter for the installer.
  36   *
  37   * @since  3.4
  38   */
  39  abstract class InstallerAdapter implements ContainerAwareInterface, DatabaseAwareInterface
  40  {
  41      use ContainerAwareTrait;
  42      use DatabaseAwareTrait;
  43  
  44      /**
  45       * Changelog URL of extensions
  46       *
  47       * @var    string
  48       * @since  4.0.0
  49       * */
  50      protected $changelogurl = null;
  51  
  52      /**
  53       * ID for the currently installed extension if present
  54       *
  55       * @var    integer
  56       * @since  3.4
  57       */
  58      protected $currentExtensionId = null;
  59  
  60      /**
  61       * The unique identifier for the extension (e.g. mod_login)
  62       *
  63       * @var    string
  64       * @since  3.4
  65       * */
  66      protected $element = null;
  67  
  68      /**
  69       * Extension object.
  70       *
  71       * @var    Extension
  72       * @since  3.4
  73       * */
  74      protected $extension = null;
  75  
  76      /**
  77       * Messages rendered by custom scripts
  78       *
  79       * @var    string
  80       * @since  3.4
  81       */
  82      protected $extensionMessage = '';
  83  
  84      /**
  85       * Copy of the XML manifest file.
  86       *
  87       * Making this object public allows extensions to customize the manifest in custom scripts.
  88       *
  89       * @var    \SimpleXMLElement
  90       * @since  3.4
  91       */
  92      public $manifest = null;
  93  
  94      /**
  95       * A path to the PHP file that the scriptfile declaration in the manifest refers to.
  96       *
  97       * @var    string
  98       * @since  3.4
  99       */
 100      protected $manifest_script = null;
 101  
 102      /**
 103       * Name of the extension
 104       *
 105       * @var    string
 106       * @since  3.4
 107       */
 108      protected $name = null;
 109  
 110      /**
 111       * Installer used with this adapter
 112       *
 113       * @var    Installer
 114       * @since  4.0.0
 115       */
 116      protected $parent = null;
 117  
 118      /**
 119       * Install function routing
 120       *
 121       * @var    string
 122       * @since  3.4
 123       */
 124      protected $route = 'install';
 125  
 126      /**
 127       * Flag if the adapter supports discover installs
 128       *
 129       * Adapters should override this and set to false if discover install is unsupported
 130       *
 131       * @var    boolean
 132       * @since  3.4
 133       */
 134      protected $supportsDiscoverInstall = true;
 135  
 136      /**
 137       * The type of adapter in use
 138       *
 139       * @var    string
 140       * @since  3.4
 141       */
 142      protected $type;
 143  
 144      /**
 145       * Constructor
 146       *
 147       * @param   Installer       $parent   Parent object
 148       * @param   DatabaseDriver  $db       Database object
 149       * @param   array           $options  Configuration Options
 150       *
 151       * @since   3.4
 152       */
 153      public function __construct(Installer $parent, DatabaseDriver $db, array $options = array())
 154      {
 155          $this->parent = $parent;
 156          $this->setDatabase($db);
 157  
 158          foreach ($options as $key => $value) {
 159              if (property_exists($this, $key)) {
 160                  $this->$key = $value;
 161              }
 162          }
 163  
 164          // Get a generic TableExtension instance for use if not already loaded
 165          if (!($this->extension instanceof TableInterface)) {
 166              $this->extension = Table::getInstance('extension');
 167          }
 168  
 169          // Sanity check, make sure the type is set by taking the adapter name from the class name
 170          if (!$this->type) {
 171              // This assumes the adapter short class name in its namespace is `<foo>Adapter`, replace this logic in subclasses if needed
 172              $reflection = new \ReflectionClass(\get_called_class());
 173              $this->type = str_replace('Adapter', '', $reflection->getShortName());
 174          }
 175  
 176          // Extension type is stored as lowercase in the database
 177          $this->type = strtolower($this->type);
 178      }
 179  
 180      /**
 181       * Check if a package extension allows its child extensions to be uninstalled individually
 182       *
 183       * @param   integer  $packageId  The extension ID of the package to check
 184       *
 185       * @return  boolean
 186       *
 187       * @since   3.7.0
 188       * @note    This method defaults to true to emulate the behavior of 3.6 and earlier which did not support this lookup
 189       */
 190      protected function canUninstallPackageChild($packageId)
 191      {
 192          $package = Table::getInstance('extension');
 193  
 194          // If we can't load this package ID, we have a corrupt database
 195          if (!$package->load((int) $packageId)) {
 196              return true;
 197          }
 198  
 199          $manifestFile = JPATH_MANIFESTS . '/packages/' . $package->element . '.xml';
 200  
 201          $xml = $this->parent->isManifest($manifestFile);
 202  
 203          // If the manifest doesn't exist, we've got some major issues
 204          if (!$xml) {
 205              return true;
 206          }
 207  
 208          $manifest = new PackageManifest($manifestFile);
 209  
 210          return $manifest->blockChildUninstall === false;
 211      }
 212  
 213      /**
 214       * Method to check if the extension is already present in the database
 215       *
 216       * @return  void
 217       *
 218       * @since   3.4
 219       * @throws  \RuntimeException
 220       */
 221      protected function checkExistingExtension()
 222      {
 223          try {
 224              $this->currentExtensionId = $this->extension->find(
 225                  array('element' => $this->element, 'type' => $this->type)
 226              );
 227  
 228              // If it does exist, load it
 229              if ($this->currentExtensionId) {
 230                  $this->extension->load(array('element' => $this->element, 'type' => $this->type));
 231              }
 232          } catch (\RuntimeException $e) {
 233              // Install failed, roll back changes
 234              throw new \RuntimeException(
 235                  Text::sprintf(
 236                      'JLIB_INSTALLER_ABORT_ROLLBACK',
 237                      Text::_('JLIB_INSTALLER_' . $this->route),
 238                      $e->getMessage()
 239                  ),
 240                  $e->getCode(),
 241                  $e
 242              );
 243          }
 244      }
 245  
 246      /**
 247       * Method to check if the extension is present in the filesystem, flags the route as update if so
 248       *
 249       * @return  void
 250       *
 251       * @since   3.4
 252       * @throws  \RuntimeException
 253       */
 254      protected function checkExtensionInFilesystem()
 255      {
 256          if (file_exists($this->parent->getPath('extension_root')) && (!$this->parent->isOverwrite() || $this->parent->isUpgrade())) {
 257              // Look for an update function or update tag
 258              $updateElement = $this->getManifest()->update;
 259  
 260              // Upgrade manually set or update function available or update tag detected
 261              if (
 262                  $updateElement || $this->parent->isUpgrade()
 263                  || ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update'))
 264              ) {
 265                  // Force this one
 266                  $this->parent->setOverwrite(true);
 267                  $this->parent->setUpgrade(true);
 268  
 269                  if ($this->currentExtensionId) {
 270                      // If there is a matching extension mark this as an update
 271                      $this->setRoute('update');
 272                  }
 273              } elseif (!$this->parent->isOverwrite()) {
 274                  // We didn't have overwrite set, find an update function or find an update tag so lets call it safe
 275                  throw new \RuntimeException(
 276                      Text::sprintf(
 277                          'JLIB_INSTALLER_ABORT_DIRECTORY',
 278                          Text::_('JLIB_INSTALLER_' . $this->route),
 279                          $this->type,
 280                          $this->parent->getPath('extension_root')
 281                      )
 282                  );
 283              }
 284          }
 285      }
 286  
 287      /**
 288       * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
 289       *
 290       * @return  void
 291       *
 292       * @since   3.4
 293       * @throws  \RuntimeException
 294       */
 295      abstract protected function copyBaseFiles();
 296  
 297      /**
 298       * Method to create the extension root path if necessary
 299       *
 300       * @return  void
 301       *
 302       * @since   3.4
 303       * @throws  \RuntimeException
 304       */
 305      protected function createExtensionRoot()
 306      {
 307          // If the extension directory does not exist, lets create it
 308          $created = false;
 309  
 310          if (!file_exists($this->parent->getPath('extension_root'))) {
 311              if (!$created = Folder::create($this->parent->getPath('extension_root'))) {
 312                  throw new \RuntimeException(
 313                      Text::sprintf(
 314                          'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
 315                          Text::_('JLIB_INSTALLER_' . $this->route),
 316                          $this->parent->getPath('extension_root')
 317                      )
 318                  );
 319              }
 320          }
 321  
 322          /*
 323           * Since we created the extension directory and will want to remove it if
 324           * we have to roll back the installation, let's add it to the
 325           * installation step stack
 326           */
 327  
 328          if ($created) {
 329              $this->parent->pushStep(
 330                  array(
 331                      'type' => 'folder',
 332                      'path' => $this->parent->getPath('extension_root'),
 333                  )
 334              );
 335          }
 336      }
 337  
 338      /**
 339       * Generic discover_install method for extensions
 340       *
 341       * @return  boolean  True on success
 342       *
 343       * @since   3.4
 344       */
 345      public function discover_install()
 346      {
 347          // Get the extension's description
 348          $description = (string) $this->getManifest()->description;
 349  
 350          if ($description) {
 351              $this->parent->message = Text::_($description);
 352          } else {
 353              $this->parent->message = '';
 354          }
 355  
 356          // Set the extension's name and element
 357          $this->name    = $this->getName();
 358          $this->element = $this->getElement();
 359  
 360          /*
 361           * ---------------------------------------------------------------------------------------------
 362           * Extension Precheck and Setup Section
 363           * ---------------------------------------------------------------------------------------------
 364           */
 365  
 366          // Setup the install paths and perform other prechecks as necessary
 367          try {
 368              $this->setupInstallPaths();
 369          } catch (\RuntimeException $e) {
 370              // Install failed, roll back changes
 371              $this->parent->abort($e->getMessage());
 372  
 373              return false;
 374          }
 375  
 376          /*
 377           * ---------------------------------------------------------------------------------------------
 378           * Installer Trigger Loading
 379           * ---------------------------------------------------------------------------------------------
 380           */
 381  
 382          $this->setupScriptfile();
 383  
 384          try {
 385              $this->triggerManifestScript('preflight');
 386          } catch (\RuntimeException $e) {
 387              // Install failed, roll back changes
 388              $this->parent->abort($e->getMessage());
 389  
 390              return false;
 391          }
 392  
 393          /*
 394           * ---------------------------------------------------------------------------------------------
 395           * Database Processing Section
 396           * ---------------------------------------------------------------------------------------------
 397           */
 398  
 399          try {
 400              $this->storeExtension();
 401          } catch (\RuntimeException $e) {
 402              // Install failed, roll back changes
 403              $this->parent->abort($e->getMessage());
 404  
 405              return false;
 406          }
 407  
 408          try {
 409              $this->parseQueries();
 410          } catch (\RuntimeException $e) {
 411              // Install failed, roll back changes
 412              $this->parent->abort($e->getMessage());
 413  
 414              return false;
 415          }
 416  
 417          // Run the custom install method
 418          try {
 419              $this->triggerManifestScript('install');
 420          } catch (\RuntimeException $e) {
 421              // Install failed, roll back changes
 422              $this->parent->abort($e->getMessage());
 423  
 424              return false;
 425          }
 426  
 427          /*
 428           * ---------------------------------------------------------------------------------------------
 429           * Finalization and Cleanup Section
 430           * ---------------------------------------------------------------------------------------------
 431           */
 432  
 433          try {
 434              $this->finaliseInstall();
 435          } catch (\RuntimeException $e) {
 436              // Install failed, roll back changes
 437              $this->parent->abort($e->getMessage());
 438  
 439              return false;
 440          }
 441  
 442          // And now we run the postflight
 443          try {
 444              $this->triggerManifestScript('postflight');
 445          } catch (\RuntimeException $e) {
 446              // Install failed, roll back changes
 447              $this->parent->abort($e->getMessage());
 448  
 449              return false;
 450          }
 451  
 452          return $this->extension->extension_id;
 453      }
 454  
 455      /**
 456       * Method to handle database transactions for the installer
 457       *
 458       * @return  boolean  True on success
 459       *
 460       * @since   3.4
 461       * @throws  \RuntimeException
 462       */
 463      protected function doDatabaseTransactions()
 464      {
 465          $route = $this->route === 'discover_install' ? 'install' : $this->route;
 466  
 467          // Let's run the install queries for the component
 468          if (isset($this->getManifest()->{$route}->sql)) {
 469              $result = $this->parent->parseSQLFiles($this->getManifest()->{$route}->sql);
 470  
 471              if ($result === false) {
 472                  // Only rollback if installing
 473                  if ($route === 'install') {
 474                      throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_INSTALL_ABORTED'));
 475                  }
 476  
 477                  return false;
 478              }
 479  
 480              // If installing with success and there is an uninstall script, add an installer rollback step to rollback if needed
 481              if ($route === 'install' && isset($this->getManifest()->uninstall->sql)) {
 482                  $this->parent->pushStep(array('type' => 'query', 'script' => $this->getManifest()->uninstall->sql));
 483              }
 484          }
 485  
 486          return true;
 487      }
 488  
 489      /**
 490       * Load language files
 491       *
 492       * @param   string  $extension  The name of the extension
 493       * @param   string  $source     Path to the extension
 494       * @param   string  $base       Base path for the extension language
 495       *
 496       * @return  void
 497       *
 498       * @since   3.4
 499       */
 500      protected function doLoadLanguage($extension, $source, $base = JPATH_ADMINISTRATOR)
 501      {
 502          $lang = Factory::getLanguage();
 503          $lang->load($extension . '.sys', $source) || $lang->load($extension . '.sys', $base);
 504      }
 505  
 506      /**
 507       * Method to finalise the installation processing
 508       *
 509       * @return  void
 510       *
 511       * @since   4.0.0
 512       * @throws  \RuntimeException
 513       */
 514      abstract protected function finaliseInstall();
 515  
 516      /**
 517       * Method to finalise the uninstallation processing
 518       *
 519       * @return  boolean
 520       *
 521       * @since   4.0.0
 522       * @throws  \RuntimeException
 523       */
 524      abstract protected function finaliseUninstall(): bool;
 525  
 526      /**
 527       * Checks if the adapter supports discover_install
 528       *
 529       * @return  boolean
 530       *
 531       * @since   3.4
 532       */
 533      public function getDiscoverInstallSupported()
 534      {
 535          return $this->supportsDiscoverInstall;
 536      }
 537  
 538      /**
 539       * Get the filtered extension element from the manifest
 540       *
 541       * @param   string  $element  Optional element name to be converted
 542       *
 543       * @return  string  The filtered element
 544       *
 545       * @since   3.4
 546       */
 547      public function getElement($element = null)
 548      {
 549          if (!$element) {
 550              // Ensure the element is a string
 551              $element = (string) $this->getManifest()->element;
 552          }
 553  
 554          if (!$element) {
 555              $element = $this->getName();
 556          }
 557  
 558          // Filter the name for illegal characters
 559          return strtolower(InputFilter::getInstance()->clean($element, 'cmd'));
 560      }
 561  
 562      /**
 563       * Get the manifest object.
 564       *
 565       * @return  \SimpleXMLElement  Manifest object
 566       *
 567       * @since   3.4
 568       */
 569      public function getManifest()
 570      {
 571          return $this->manifest;
 572      }
 573  
 574      /**
 575       * Get the filtered component name from the manifest
 576       *
 577       * @return  string  The filtered name
 578       *
 579       * @since   3.4
 580       */
 581      public function getName()
 582      {
 583          // Ensure the name is a string
 584          $name = (string) $this->getManifest()->name;
 585  
 586          // Filter the name for illegal characters
 587          $name = InputFilter::getInstance()->clean($name, 'string');
 588  
 589          return $name;
 590      }
 591  
 592      /**
 593       * Retrieves the parent installer
 594       *
 595       * @return  Installer
 596       *
 597       * @since   4.0.0
 598       */
 599      public function getParent()
 600      {
 601          return $this->parent;
 602      }
 603  
 604      /**
 605       * Get the install route being followed
 606       *
 607       * @return  string  The install route
 608       *
 609       * @since   3.4
 610       */
 611      public function getRoute()
 612      {
 613          return $this->route;
 614      }
 615  
 616      /**
 617       * Get the class name for the install adapter script.
 618       *
 619       * @return  string  The class name.
 620       *
 621       * @since   3.4
 622       */
 623      protected function getScriptClassName()
 624      {
 625          // Support element names like 'en-GB'
 626          $className = InputFilter::getInstance()->clean($this->element, 'cmd') . 'InstallerScript';
 627  
 628          // Cannot have - in class names
 629          $className = str_replace('-', '', $className);
 630  
 631          return $className;
 632      }
 633  
 634      /**
 635       * Generic install method for extensions
 636       *
 637       * @return  boolean|integer  The extension ID on success, boolean false on failure
 638       *
 639       * @since   3.4
 640       */
 641      public function install()
 642      {
 643          // Get the extension's description
 644          $description           = (string) $this->getManifest()->description;
 645          $this->parent->message = '';
 646  
 647          if ($description) {
 648              $this->parent->message = Text::_($description);
 649          }
 650  
 651          // Set the extension's name and element
 652          $this->name         = $this->getName();
 653          $this->element      = $this->getElement();
 654          $this->changelogurl = (string) $this->getManifest()->changelogurl;
 655  
 656          /*
 657           * ---------------------------------------------------------------------------------------------
 658           * Extension Precheck and Setup Section
 659           * ---------------------------------------------------------------------------------------------
 660           */
 661  
 662          // Setup the install paths and perform other prechecks as necessary
 663          try {
 664              $this->setupInstallPaths();
 665          } catch (\RuntimeException $e) {
 666              // Install failed, roll back changes
 667              $this->parent->abort($e->getMessage());
 668  
 669              return false;
 670          }
 671  
 672          // Check to see if an extension by the same name is already installed.
 673          try {
 674              $this->checkExistingExtension();
 675          } catch (\RuntimeException $e) {
 676              // Install failed, roll back changes
 677              $this->parent->abort($e->getMessage());
 678  
 679              return false;
 680          }
 681  
 682          // Check if the extension is present in the filesystem
 683          try {
 684              $this->checkExtensionInFilesystem();
 685          } catch (\RuntimeException $e) {
 686              // Install failed, roll back changes
 687              $this->parent->abort($e->getMessage());
 688  
 689              return false;
 690          }
 691  
 692          // If we are on the update route, run any custom setup routines
 693          if ($this->route === 'update') {
 694              try {
 695                  $this->setupUpdates();
 696              } catch (\RuntimeException $e) {
 697                  // Install failed, roll back changes
 698                  $this->parent->abort($e->getMessage());
 699  
 700                  return false;
 701              }
 702          }
 703  
 704          /*
 705           * ---------------------------------------------------------------------------------------------
 706           * Installer Trigger Loading
 707           * ---------------------------------------------------------------------------------------------
 708           */
 709  
 710          $this->setupScriptfile();
 711  
 712          try {
 713              $this->triggerManifestScript('preflight');
 714          } catch (\RuntimeException $e) {
 715              // Install failed, roll back changes
 716              $this->parent->abort($e->getMessage());
 717  
 718              return false;
 719          }
 720  
 721          /*
 722           * ---------------------------------------------------------------------------------------------
 723           * Filesystem Processing Section
 724           * ---------------------------------------------------------------------------------------------
 725           */
 726  
 727          // If the extension directory does not exist, lets create it
 728          try {
 729              $this->createExtensionRoot();
 730          } catch (\RuntimeException $e) {
 731              // Install failed, roll back changes
 732              $this->parent->abort($e->getMessage());
 733  
 734              return false;
 735          }
 736  
 737          // Copy all necessary files
 738          try {
 739              $this->copyBaseFiles();
 740          } catch (\RuntimeException $e) {
 741              // Install failed, roll back changes
 742              $this->parent->abort($e->getMessage());
 743  
 744              return false;
 745          }
 746  
 747          // Parse optional tags
 748          $this->parseOptionalTags();
 749  
 750          /*
 751           * ---------------------------------------------------------------------------------------------
 752           * Database Processing Section
 753           * ---------------------------------------------------------------------------------------------
 754           */
 755  
 756          try {
 757              $this->storeExtension();
 758          } catch (\RuntimeException $e) {
 759              // Install failed, roll back changes
 760              $this->parent->abort($e->getMessage());
 761  
 762              return false;
 763          }
 764  
 765          try {
 766              $this->parseQueries();
 767          } catch (\RuntimeException $e) {
 768              // Install failed, roll back changes
 769              $this->parent->abort($e->getMessage());
 770  
 771              return false;
 772          }
 773  
 774          // Run the custom method based on the route
 775          try {
 776              $this->triggerManifestScript($this->route);
 777          } catch (\RuntimeException $e) {
 778              // Install failed, roll back changes
 779              $this->parent->abort($e->getMessage());
 780  
 781              return false;
 782          }
 783  
 784          /*
 785           * ---------------------------------------------------------------------------------------------
 786           * Finalization and Cleanup Section
 787           * ---------------------------------------------------------------------------------------------
 788           */
 789  
 790          try {
 791              $this->finaliseInstall();
 792          } catch (\RuntimeException $e) {
 793              // Install failed, roll back changes
 794              $this->parent->abort($e->getMessage());
 795  
 796              return false;
 797          }
 798  
 799          // And now we run the postflight
 800          try {
 801              $this->triggerManifestScript('postflight');
 802          } catch (\RuntimeException $e) {
 803              // Install failed, roll back changes
 804              $this->parent->abort($e->getMessage());
 805  
 806              return false;
 807          }
 808  
 809          return $this->extension->extension_id;
 810      }
 811  
 812      /**
 813       * Method to parse the queries specified in the `<sql>` tags
 814       *
 815       * @return  void
 816       *
 817       * @since   3.4
 818       * @throws  \RuntimeException
 819       */
 820      protected function parseQueries()
 821      {
 822          // Let's run the queries for the extension
 823          if (\in_array($this->route, array('install', 'discover_install', 'uninstall'))) {
 824              // This method may throw an exception, but it is caught by the parent caller
 825              if (!$this->doDatabaseTransactions()) {
 826                  throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_INSTALL_ABORTED'));
 827              }
 828  
 829              // Set the schema version to be the latest update version
 830              if ($this->getManifest()->update) {
 831                  $this->parent->setSchemaVersion($this->getManifest()->update->schemas, $this->extension->extension_id);
 832              }
 833          } elseif ($this->route === 'update') {
 834              if ($this->getManifest()->update) {
 835                  $result = $this->parent->parseSchemaUpdates($this->getManifest()->update->schemas, $this->extension->extension_id);
 836  
 837                  if ($result === false) {
 838                      // Install failed, rollback changes
 839                      throw new \RuntimeException(Text::_('JLIB_INSTALLER_ABORT_INSTALL_ABORTED'));
 840                  }
 841              }
 842          }
 843      }
 844  
 845      /**
 846       * Method to parse optional tags in the manifest
 847       *
 848       * @return  void
 849       *
 850       * @since   3.1
 851       */
 852      protected function parseOptionalTags()
 853      {
 854          // Some extensions may not have optional tags
 855      }
 856  
 857      /**
 858       * Prepares the adapter for a discover_install task
 859       *
 860       * @return  void
 861       *
 862       * @since   3.4
 863       */
 864      public function prepareDiscoverInstall()
 865      {
 866          // Adapters may not support discover install or may have overridden the default task and aren't using this
 867      }
 868  
 869      /**
 870       * Removes this extension's files
 871       *
 872       * @return  void
 873       *
 874       * @since   4.0.0
 875       * @throws  \RuntimeException
 876       */
 877      abstract protected function removeExtensionFiles();
 878  
 879      /**
 880       * Set the manifest object.
 881       *
 882       * @param   object  $manifest  The manifest object
 883       *
 884       * @return  InstallerAdapter  Instance of this class to support chaining
 885       *
 886       * @since   3.4
 887       */
 888      public function setManifest($manifest)
 889      {
 890          $this->manifest = $manifest;
 891  
 892          return $this;
 893      }
 894  
 895      /**
 896       * Set the install route being followed
 897       *
 898       * @param   string  $route  The install route being followed
 899       *
 900       * @return  InstallerAdapter  Instance of this class to support chaining
 901       *
 902       * @since   3.4
 903       */
 904      public function setRoute($route)
 905      {
 906          $this->route = $route;
 907  
 908          return $this;
 909      }
 910  
 911      /**
 912       * Method to do any prechecks and setup the install paths for the extension
 913       *
 914       * @return  void
 915       *
 916       * @since   3.4
 917       */
 918      abstract protected function setupInstallPaths();
 919  
 920      /**
 921       * Setup the manifest script file for those adapters that use it.
 922       *
 923       * @return  void
 924       *
 925       * @since   3.4
 926       */
 927      protected function setupScriptfile()
 928      {
 929          // If there is a manifest class file, lets load it; we'll copy it later (don't have dest yet)
 930          $manifestScript = (string) $this->getManifest()->scriptfile;
 931  
 932          // When no script file, do nothing
 933          if (!$manifestScript) {
 934              return;
 935          }
 936  
 937          // Build a child container, so we do not overwrite the global one
 938          // and start from scratch when multiple extensions are installed
 939          try {
 940              $container = new Container($this->getContainer());
 941          } catch (ContainerNotFoundException $e) {
 942              @trigger_error('Container must be set.', E_USER_DEPRECATED);
 943  
 944              // Fallback to the global container
 945              $container = new Container(Factory::getContainer());
 946          }
 947  
 948          // The real location of the file
 949          $manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;
 950  
 951          $installer = null;
 952  
 953          // Load the installer from the file
 954          if (!file_exists($manifestScriptFile)) {
 955              @trigger_error(
 956                  'Installer file must exist when defined. In version 5.0 this will crash.',
 957                  E_USER_DEPRECATED
 958              );
 959  
 960              return;
 961          }
 962  
 963          require_once $manifestScriptFile;
 964  
 965          // When the instance is a service provider, then register the container with it
 966          if ($installer instanceof ServiceProviderInterface) {
 967              $installer->register($container);
 968          }
 969  
 970          // When the returned object is an installer instance, use it directly
 971          if ($installer instanceof InstallerScriptInterface) {
 972              $container->set(InstallerScriptInterface::class, $installer);
 973          }
 974  
 975          // When none is set, then use the legacy way
 976          if (!$container->has(InstallerScriptInterface::class)) {
 977              @trigger_error(
 978                  'Legacy installer files are deprecated and will be removed in 6.0. Use a service provider instead.',
 979                  E_USER_DEPRECATED
 980              );
 981  
 982              $classname = $this->getScriptClassName();
 983  
 984              \JLoader::register($classname, $manifestScriptFile);
 985  
 986              if (!class_exists($classname)) {
 987                  return;
 988              }
 989  
 990              $container->set(
 991                  InstallerScriptInterface::class,
 992                  function (Container $container) use ($classname) {
 993                      return new LegacyInstallerScript(new $classname($this));
 994                  }
 995              );
 996          }
 997  
 998          // Create a new instance
 999          $this->parent->manifestClass = $container->get(InstallerScriptInterface::class);
1000  
1001          // And set this so we can copy it later
1002          $this->manifest_script = $manifestScript;
1003      }
1004  
1005      /**
1006       * Method to do any prechecks and setup the uninstall job
1007       *
1008       * @return  void
1009       *
1010       * @since   4.0.0
1011       */
1012      abstract protected function setupUninstall();
1013  
1014      /**
1015       * Method to setup the update routine for the adapter
1016       *
1017       * @return  void
1018       *
1019       * @since   3.4
1020       */
1021      protected function setupUpdates()
1022      {
1023          // Some extensions may not have custom setup routines for updates
1024      }
1025  
1026      /**
1027       * Method to store the extension to the database
1028       *
1029       * @return  void
1030       *
1031       * @since   3.4
1032       * @throws  \RuntimeException
1033       */
1034      abstract protected function storeExtension();
1035  
1036      /**
1037       * Executes a custom install script method
1038       *
1039       * @param   string  $method  The install method to execute
1040       *
1041       * @return  boolean  True on success
1042       *
1043       * @since   3.4
1044       * @throws  \RuntimeException
1045       */
1046      protected function triggerManifestScript($method)
1047      {
1048          ob_start();
1049          ob_implicit_flush(false);
1050  
1051          if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $method)) {
1052              switch ($method) {
1053                  // The preflight and postflight take the route as a param
1054                  case 'preflight':
1055                  case 'postflight':
1056                      if ($this->parent->manifestClass->$method($this->route, $this) === false) {
1057                          if ($method !== 'postflight') {
1058                              // Clean and close the output buffer
1059                              ob_end_clean();
1060  
1061                              // The script failed, rollback changes
1062                              throw new \RuntimeException(
1063                                  Text::sprintf(
1064                                      'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
1065                                      Text::_('JLIB_INSTALLER_' . $this->route)
1066                                  )
1067                              );
1068                          }
1069                      }
1070                      break;
1071  
1072                  // The install, uninstall, and update methods only pass this object as a param
1073                  case 'install':
1074                  case 'uninstall':
1075                  case 'update':
1076                      if ($this->parent->manifestClass->$method($this) === false) {
1077                          if ($method !== 'uninstall') {
1078                              // Clean and close the output buffer
1079                              ob_end_clean();
1080  
1081                              // The script failed, rollback changes
1082                              throw new \RuntimeException(
1083                                  Text::sprintf(
1084                                      'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
1085                                      Text::_('JLIB_INSTALLER_' . $this->route)
1086                                  )
1087                              );
1088                          }
1089                      }
1090                      break;
1091              }
1092          }
1093  
1094          // Append to the message object
1095          $this->extensionMessage .= ob_get_clean();
1096  
1097          // If in postflight or uninstall, set the message for display
1098          if (($method === 'uninstall' || $method === 'postflight') && $this->extensionMessage !== '') {
1099              $this->parent->set('extension_message', $this->extensionMessage);
1100          }
1101  
1102          return true;
1103      }
1104  
1105      /**
1106       * Generic update method for extensions
1107       *
1108       * @param   integer  $id  The extension ID
1109       *
1110       * @return  boolean  True on success
1111       *
1112       * @since   4.0.0
1113       */
1114      public function uninstall($id)
1115      {
1116          if (!$this->extension->load((int) $id)) {
1117              Log::add(Text::_('JLIB_INSTALLER_ERROR_UNKNOWN_EXTENSION'), Log::WARNING, 'jerror');
1118  
1119              return false;
1120          }
1121  
1122          // Joomla 4: Locked extensions cannot be removed.
1123          if (isset($this->extension->locked) && $this->extension->locked) {
1124              Log::add(Text::_('JLIB_INSTALLER_ERROR_UNINSTALL_LOCKED_EXTENSION'), Log::WARNING, 'jerror');
1125  
1126              return false;
1127          } elseif (!isset($this->extension->locked) && $this->extension->protected) {
1128              // Joomla 3 ('locked' property does not exist yet): Protected extensions cannot be removed.
1129              Log::add(Text::_('JLIB_INSTALLER_ERROR_UNINSTALL_PROTECTED_EXTENSION'), Log::WARNING, 'jerror');
1130  
1131              return false;
1132          }
1133  
1134          /*
1135           * Does this extension have a parent package?
1136           * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
1137           */
1138          if ($this->extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($this->extension->package_id)) {
1139              Log::add(
1140                  Text::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $this->extension->name),
1141                  Log::WARNING,
1142                  'jerror'
1143              );
1144  
1145              return false;
1146          }
1147  
1148          // Setup the uninstall job as required
1149          try {
1150              $this->setupUninstall();
1151          } catch (\RuntimeException $e) {
1152              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1153  
1154              return false;
1155          }
1156  
1157          // Set the extension's name and element
1158          $this->name    = $this->getName();
1159          $this->element = $this->getElement();
1160  
1161          /*
1162           * ---------------------------------------------------------------------------------------------
1163           * Installer Trigger Loading and Uninstall
1164           * ---------------------------------------------------------------------------------------------
1165           */
1166  
1167          $this->setupScriptfile();
1168  
1169          try {
1170              $this->triggerManifestScript('preflight');
1171          } catch (\RuntimeException $e) {
1172              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1173  
1174              return false;
1175          }
1176  
1177          try {
1178              $this->triggerManifestScript('uninstall');
1179          } catch (\RuntimeException $e) {
1180              // Ignore errors for now
1181          }
1182  
1183          // Tasks from here may fail but we will still attempt to finish the uninstall process
1184          $retval = true;
1185  
1186          /*
1187           * ---------------------------------------------------------------------------------------------
1188           * Database Processing Section
1189           * ---------------------------------------------------------------------------------------------
1190           */
1191  
1192          try {
1193              $this->parseQueries();
1194          } catch (\RuntimeException $e) {
1195              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1196  
1197              $retval = false;
1198          }
1199  
1200          /*
1201           * ---------------------------------------------------------------------------------------------
1202           * Filesystem Processing Section
1203           * ---------------------------------------------------------------------------------------------
1204           */
1205  
1206          try {
1207              $this->removeExtensionFiles();
1208          } catch (\RuntimeException $e) {
1209              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1210  
1211              $retval = false;
1212          }
1213  
1214          /*
1215           * ---------------------------------------------------------------------------------------------
1216           * Finalization and Cleanup Section
1217           * ---------------------------------------------------------------------------------------------
1218           */
1219  
1220          try {
1221              $retval |= $this->finaliseUninstall();
1222          } catch (\RuntimeException $e) {
1223              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1224  
1225              $retval = false;
1226          }
1227  
1228          // And now we run the postflight
1229          try {
1230              $this->triggerManifestScript('postflight');
1231          } catch (\RuntimeException $e) {
1232              Log::add($e->getMessage(), Log::WARNING, 'jerror');
1233  
1234              $retval = false;
1235          }
1236  
1237          return $retval;
1238      }
1239  
1240      /**
1241       * Generic update method for extensions
1242       *
1243       * @return  boolean|integer  The extension ID on success, boolean false on failure
1244       *
1245       * @since   3.4
1246       */
1247      public function update()
1248      {
1249          // Set the overwrite setting
1250          $this->parent->setOverwrite(true);
1251          $this->parent->setUpgrade(true);
1252  
1253          // And make sure the route is set correctly
1254          $this->setRoute('update');
1255  
1256          // Now jump into the install method to run the update
1257          return $this->install();
1258      }
1259  
1260      /**
1261       * Proxy for db variable.
1262       *
1263       * @param   string  $name  The name of the element
1264       *
1265       * @return  mixed  The value of the element if set, null otherwise
1266       *
1267       * @since   4.2.0
1268       *
1269       * @deprecated  5.0 Use getDatabase() instead of directly accessing db
1270       */
1271      public function __get($name)
1272      {
1273          if ($name === 'db') {
1274              return $this->getDatabase();
1275          }
1276  
1277          // Default the variable
1278          if (!isset($this->$name)) {
1279              $this->$name = null;
1280          }
1281  
1282          return $this->$name;
1283      }
1284  }


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