[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Installer/ -> Installer.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;
  11  
  12  use Joomla\CMS\Adapter\Adapter;
  13  use Joomla\CMS\Application\ApplicationHelper;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Filesystem\File;
  16  use Joomla\CMS\Filesystem\Folder;
  17  use Joomla\CMS\Filesystem\Path;
  18  use Joomla\CMS\Language\Text;
  19  use Joomla\CMS\Log\Log;
  20  use Joomla\CMS\Plugin\PluginHelper;
  21  use Joomla\CMS\Table\Extension;
  22  use Joomla\CMS\Table\Table;
  23  use Joomla\Database\DatabaseAwareInterface;
  24  use Joomla\Database\DatabaseAwareTrait;
  25  use Joomla\Database\DatabaseDriver;
  26  use Joomla\Database\DatabaseInterface;
  27  use Joomla\Database\Exception\ExecutionFailureException;
  28  use Joomla\Database\Exception\PrepareStatementFailureException;
  29  use Joomla\Database\ParameterType;
  30  use Joomla\DI\ContainerAwareInterface;
  31  
  32  // phpcs:disable PSR1.Files.SideEffects
  33  \defined('JPATH_PLATFORM') or die;
  34  // phpcs:enable PSR1.Files.SideEffects
  35  
  36  /**
  37   * Joomla base installer class
  38   *
  39   * @since  3.1
  40   */
  41  class Installer extends Adapter implements DatabaseAwareInterface
  42  {
  43      use DatabaseAwareTrait;
  44  
  45      /**
  46       * Array of paths needed by the installer
  47       *
  48       * @var    array
  49       * @since  3.1
  50       */
  51      protected $paths = array();
  52  
  53      /**
  54       * True if package is an upgrade
  55       *
  56       * @var    boolean
  57       * @since  3.1
  58       */
  59      protected $upgrade = null;
  60  
  61      /**
  62       * The manifest trigger class
  63       *
  64       * @var    object
  65       * @since  3.1
  66       */
  67      public $manifestClass = null;
  68  
  69      /**
  70       * True if existing files can be overwritten
  71       *
  72       * @var    boolean
  73       * @since  3.0.0
  74       */
  75      protected $overwrite = false;
  76  
  77      /**
  78       * Stack of installation steps
  79       * - Used for installation rollback
  80       *
  81       * @var    array
  82       * @since  3.1
  83       */
  84      protected $stepStack = array();
  85  
  86      /**
  87       * Extension Table Entry
  88       *
  89       * @var    Extension
  90       * @since  3.1
  91       */
  92      public $extension = null;
  93  
  94      /**
  95       * The output from the install/uninstall scripts
  96       *
  97       * @var    string
  98       * @since  3.1
  99       * */
 100      public $message = null;
 101  
 102      /**
 103       * The installation manifest XML object
 104       *
 105       * @var    object
 106       * @since  3.1
 107       */
 108      public $manifest = null;
 109  
 110      /**
 111       * The extension message that appears
 112       *
 113       * @var    string
 114       * @since  3.1
 115       */
 116      protected $extension_message = null;
 117  
 118      /**
 119       * The redirect URL if this extension (can be null if no redirect)
 120       *
 121       * @var    string
 122       * @since  3.1
 123       */
 124      protected $redirect_url = null;
 125  
 126      /**
 127       * Flag if the uninstall process was triggered by uninstalling a package
 128       *
 129       * @var    boolean
 130       * @since  3.7.0
 131       */
 132      protected $packageUninstall = false;
 133  
 134      /**
 135       * Backup extra_query during update_sites rebuild
 136       *
 137       * @var    string
 138       * @since  3.9.26
 139       */
 140      public $extraQuery = '';
 141  
 142      /**
 143       * JInstaller instances container.
 144       *
 145       * @var    Installer[]
 146       * @since  3.4
 147       */
 148      protected static $instances;
 149  
 150      /**
 151       * A comment marker to indicate that an update SQL query may fail without triggering an update error.
 152       *
 153       * @since  4.2.0
 154       */
 155      protected const CAN_FAIL_MARKER = '/** CAN FAIL **/';
 156  
 157      /**
 158       * The length of the CAN_FAIL_MARKER string
 159       *
 160       * @since  4.2.0
 161       */
 162      protected const CAN_FAIL_MARKER_LENGTH = 16;
 163  
 164      /**
 165       * Constructor
 166       *
 167       * @param   string  $basepath       Base Path of the adapters
 168       * @param   string  $classprefix    Class prefix of adapters
 169       * @param   string  $adapterfolder  Name of folder to append to base path
 170       *
 171       * @since   3.1
 172       */
 173      public function __construct($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder = 'Adapter')
 174      {
 175          parent::__construct($basepath, $classprefix, $adapterfolder);
 176  
 177          $this->extension = Table::getInstance('extension');
 178      }
 179  
 180      /**
 181       * Returns the global Installer object, only creating it if it doesn't already exist.
 182       *
 183       * @param   string  $basepath       Base Path of the adapters
 184       * @param   string  $classprefix    Class prefix of adapters
 185       * @param   string  $adapterfolder  Name of folder to append to base path
 186       *
 187       * @return  Installer  An installer object
 188       *
 189       * @since   3.1
 190       */
 191      public static function getInstance($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder = 'Adapter')
 192      {
 193          if (!isset(self::$instances[$basepath])) {
 194              self::$instances[$basepath] = new static($basepath, $classprefix, $adapterfolder);
 195              self::$instances[$basepath]->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
 196          }
 197  
 198          return self::$instances[$basepath];
 199      }
 200  
 201      /**
 202       * Splits a string of multiple queries into an array of individual queries.
 203       *
 204       * This is different than DatabaseDriver::splitSql. It supports the special CAN FAIL comment
 205       * marker which indicates that a SQL statement could fail without raising an error during the
 206       * installation.
 207       *
 208       * @param   string|null  $sql  Input SQL string with which to split into individual queries.
 209       *
 210       * @return  array
 211       *
 212       * @since   4.2.0
 213       */
 214      public static function splitSql(?string $sql): array
 215      {
 216          if (empty($sql)) {
 217              return [];
 218          }
 219  
 220          $start     = 0;
 221          $open      = false;
 222          $comment   = false;
 223          $endString = '';
 224          $end       = \strlen($sql);
 225          $queries   = [];
 226          $query     = '';
 227  
 228          for ($i = 0; $i < $end; $i++) {
 229              $current      = substr($sql, $i, 1);
 230              $current2     = substr($sql, $i, 2);
 231              $current3     = substr($sql, $i, 3);
 232              $lenEndString = \strlen($endString);
 233              $testEnd      = substr($sql, $i, $lenEndString);
 234  
 235              if (
 236                  $current === '"' || $current === "'" || $current2 === '--'
 237                  || ($current2 === '/*' && $current3 !== '/*!' && $current3 !== '/*+')
 238                  || ($current === '#' && $current3 !== '#__')
 239                  || ($comment && $testEnd === $endString)
 240              ) {
 241                  // Check if quoted with previous backslash
 242                  $n = 2;
 243  
 244                  while (substr($sql, $i - $n + 1, 1) === '\\' && $n < $i) {
 245                      $n++;
 246                  }
 247  
 248                  // Not quoted
 249                  if ($n % 2 === 0) {
 250                      if ($open) {
 251                          if ($testEnd === $endString) {
 252                              if ($comment) {
 253                                  $comment = false;
 254  
 255                                  if ($lenEndString > 1) {
 256                                      $i += ($lenEndString - 1);
 257                                      $current = substr($sql, $i, 1);
 258                                  }
 259  
 260                                  $start = $i + 1;
 261                              }
 262  
 263                              $open      = false;
 264                              $endString = '';
 265                          }
 266                      } else {
 267                          $open = true;
 268  
 269                          if ($current2 === '--') {
 270                              $endString = "\n";
 271                              $comment   = true;
 272                          } elseif ($current2 === '/*') {
 273                              $endString = '*/';
 274                              $comment   = true;
 275                          } elseif ($current === '#') {
 276                              $endString = "\n";
 277                              $comment   = true;
 278                          } else {
 279                              $endString = $current;
 280                          }
 281  
 282                          if ($comment && $start < $i) {
 283                              $query .= substr($sql, $start, $i - $start);
 284                          }
 285                      }
 286                  }
 287              }
 288  
 289              if ($comment) {
 290                  $start = $i + 1;
 291              }
 292  
 293              if (($current === ';' && !$open) || $i === $end - 1) {
 294                  if ($current === ';' && !$open && $start <= $i && $start > self::CAN_FAIL_MARKER_LENGTH) {
 295                      $possibleMarker = substr($sql, $start - self::CAN_FAIL_MARKER_LENGTH, $i - $start + self::CAN_FAIL_MARKER_LENGTH);
 296  
 297                      if (strtoupper($possibleMarker) === self::CAN_FAIL_MARKER) {
 298                          $start -= self::CAN_FAIL_MARKER_LENGTH;
 299                      }
 300                  }
 301  
 302                  if ($start <= $i) {
 303                      $query .= substr($sql, $start, $i - $start + 1);
 304                  }
 305  
 306                  $query = trim($query);
 307  
 308                  if ($query) {
 309                      if (($i === $end - 1) && ($current !== ';')) {
 310                          $query .= ';';
 311                      }
 312  
 313                      $queries[] = $query;
 314                  }
 315  
 316                  $query = '';
 317                  $start = $i + 1;
 318              }
 319  
 320              $endComment = false;
 321          }
 322  
 323          return $queries;
 324      }
 325  
 326      /**
 327       * Get the allow overwrite switch
 328       *
 329       * @return  boolean  Allow overwrite switch
 330       *
 331       * @since   3.1
 332       */
 333      public function isOverwrite()
 334      {
 335          return $this->overwrite;
 336      }
 337  
 338      /**
 339       * Set the allow overwrite switch
 340       *
 341       * @param   boolean  $state  Overwrite switch state
 342       *
 343       * @return  boolean  True it state is set, false if it is not
 344       *
 345       * @since   3.1
 346       */
 347      public function setOverwrite($state = false)
 348      {
 349          $tmp = $this->overwrite;
 350  
 351          if ($state) {
 352              $this->overwrite = true;
 353          } else {
 354              $this->overwrite = false;
 355          }
 356  
 357          return $tmp;
 358      }
 359  
 360      /**
 361       * Get the redirect location
 362       *
 363       * @return  string  Redirect location (or null)
 364       *
 365       * @since   3.1
 366       */
 367      public function getRedirectUrl()
 368      {
 369          return $this->redirect_url;
 370      }
 371  
 372      /**
 373       * Set the redirect location
 374       *
 375       * @param   string  $newurl  New redirect location
 376       *
 377       * @return  void
 378       *
 379       * @since   3.1
 380       */
 381      public function setRedirectUrl($newurl)
 382      {
 383          $this->redirect_url = $newurl;
 384      }
 385  
 386      /**
 387       * Get whether this installer is uninstalling extensions which are part of a package
 388       *
 389       * @return  boolean
 390       *
 391       * @since   3.7.0
 392       */
 393      public function isPackageUninstall()
 394      {
 395          return $this->packageUninstall;
 396      }
 397  
 398      /**
 399       * Set whether this installer is uninstalling extensions which are part of a package
 400       *
 401       * @param   boolean  $uninstall  True if a package triggered the uninstall, false otherwise
 402       *
 403       * @return  void
 404       *
 405       * @since   3.7.0
 406       */
 407      public function setPackageUninstall($uninstall)
 408      {
 409          $this->packageUninstall = $uninstall;
 410      }
 411  
 412      /**
 413       * Get the upgrade switch
 414       *
 415       * @return  boolean
 416       *
 417       * @since   3.1
 418       */
 419      public function isUpgrade()
 420      {
 421          return $this->upgrade;
 422      }
 423  
 424      /**
 425       * Set the upgrade switch
 426       *
 427       * @param   boolean  $state  Upgrade switch state
 428       *
 429       * @return  boolean  True if upgrade, false otherwise
 430       *
 431       * @since   3.1
 432       */
 433      public function setUpgrade($state = false)
 434      {
 435          $tmp = $this->upgrade;
 436  
 437          if ($state) {
 438              $this->upgrade = true;
 439          } else {
 440              $this->upgrade = false;
 441          }
 442  
 443          return $tmp;
 444      }
 445  
 446      /**
 447       * Get the installation manifest object
 448       *
 449       * @return  \SimpleXMLElement  Manifest object
 450       *
 451       * @since   3.1
 452       */
 453      public function getManifest()
 454      {
 455          if (!\is_object($this->manifest)) {
 456              $this->findManifest();
 457          }
 458  
 459          return $this->manifest;
 460      }
 461  
 462      /**
 463       * Get an installer path by name
 464       *
 465       * @param   string  $name     Path name
 466       * @param   string  $default  Default value
 467       *
 468       * @return  string  Path
 469       *
 470       * @since   3.1
 471       */
 472      public function getPath($name, $default = null)
 473      {
 474          return (!empty($this->paths[$name])) ? $this->paths[$name] : $default;
 475      }
 476  
 477      /**
 478       * Sets an installer path by name
 479       *
 480       * @param   string  $name   Path name
 481       * @param   string  $value  Path
 482       *
 483       * @return  void
 484       *
 485       * @since   3.1
 486       */
 487      public function setPath($name, $value)
 488      {
 489          $this->paths[$name] = $value;
 490      }
 491  
 492      /**
 493       * Pushes a step onto the installer stack for rolling back steps
 494       *
 495       * @param   array  $step  Installer step
 496       *
 497       * @return  void
 498       *
 499       * @since   3.1
 500       */
 501      public function pushStep($step)
 502      {
 503          $this->stepStack[] = $step;
 504      }
 505  
 506      /**
 507       * Installation abort method
 508       *
 509       * @param   string  $msg   Abort message from the installer
 510       * @param   string  $type  Package type if defined
 511       *
 512       * @return  boolean  True if successful
 513       *
 514       * @since   3.1
 515       */
 516      public function abort($msg = null, $type = null)
 517      {
 518          $retval = true;
 519          $step = array_pop($this->stepStack);
 520  
 521          // Raise abort warning
 522          if ($msg) {
 523              Log::add($msg, Log::WARNING, 'jerror');
 524          }
 525  
 526          while ($step != null) {
 527              switch ($step['type']) {
 528                  case 'file':
 529                      // Remove the file
 530                      $stepval = File::delete($step['path']);
 531                      break;
 532  
 533                  case 'folder':
 534                      // Remove the folder
 535                      $stepval = Folder::delete($step['path']);
 536                      break;
 537  
 538                  case 'query':
 539                      // Execute the query.
 540                      $stepval = $this->parseSQLFiles($step['script']);
 541                      break;
 542  
 543                  case 'extension':
 544                      // Get database connector object
 545                      $db     = $this->getDatabase();
 546                      $query  = $db->getQuery(true);
 547                      $stepId = (int) $step['id'];
 548  
 549                      // Remove the entry from the #__extensions table
 550                      $query->delete($db->quoteName('#__extensions'))
 551                          ->where($db->quoteName('extension_id') . ' = :step_id')
 552                          ->bind(':step_id', $stepId, ParameterType::INTEGER);
 553                      $db->setQuery($query);
 554  
 555                      try {
 556                          $db->execute();
 557  
 558                          $stepval = true;
 559                      } catch (ExecutionFailureException $e) {
 560                          // The database API will have already logged the error it caught, we just need to alert the user to the issue
 561                          Log::add(Text::_('JLIB_INSTALLER_ABORT_ERROR_DELETING_EXTENSIONS_RECORD'), Log::WARNING, 'jerror');
 562  
 563                          $stepval = false;
 564                      }
 565  
 566                      break;
 567  
 568                  default:
 569                      if ($type && \is_object($this->_adapters[$type])) {
 570                          // Build the name of the custom rollback method for the type
 571                          $method = '_rollback_' . $step['type'];
 572  
 573                          // Custom rollback method handler
 574                          if (method_exists($this->_adapters[$type], $method)) {
 575                              $stepval = $this->_adapters[$type]->$method($step);
 576                          }
 577                      } else {
 578                          // Set it to false
 579                          $stepval = false;
 580                      }
 581                      break;
 582              }
 583  
 584              // Only set the return value if it is false
 585              if ($stepval === false) {
 586                  $retval = false;
 587              }
 588  
 589              // Get the next step and continue
 590              $step = array_pop($this->stepStack);
 591          }
 592  
 593          return $retval;
 594      }
 595  
 596      // Adapter functions
 597  
 598      /**
 599       * Package installation method
 600       *
 601       * @param   string  $path  Path to package source folder
 602       *
 603       * @return  boolean  True if successful
 604       *
 605       * @since   3.1
 606       */
 607      public function install($path = null)
 608      {
 609          if ($path && Folder::exists($path)) {
 610              $this->setPath('source', $path);
 611          } else {
 612              $this->abort(Text::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));
 613  
 614              return false;
 615          }
 616  
 617          if (!$adapter = $this->setupInstall('install', true)) {
 618              $this->abort(Text::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
 619  
 620              return false;
 621          }
 622  
 623          if (!\is_object($adapter)) {
 624              return false;
 625          }
 626  
 627          // Add the languages from the package itself
 628          if (method_exists($adapter, 'loadLanguage')) {
 629              $adapter->loadLanguage($path);
 630          }
 631  
 632          // Fire the onExtensionBeforeInstall event.
 633          PluginHelper::importPlugin('extension');
 634          Factory::getApplication()->triggerEvent(
 635              'onExtensionBeforeInstall',
 636              array(
 637                  'method' => 'install',
 638                  'type' => $this->manifest->attributes()->type,
 639                  'manifest' => $this->manifest,
 640                  'extension' => 0,
 641              )
 642          );
 643  
 644          // Run the install
 645          $result = $adapter->install();
 646  
 647          // Make sure Joomla can figure out what has changed
 648          clearstatcache();
 649  
 650          // Fire the onExtensionAfterInstall
 651          Factory::getApplication()->triggerEvent(
 652              'onExtensionAfterInstall',
 653              array('installer' => clone $this, 'eid' => $result)
 654          );
 655  
 656          if ($result !== false) {
 657              // Refresh versionable assets cache
 658              Factory::getApplication()->flushAssets();
 659  
 660              return true;
 661          }
 662  
 663          return false;
 664      }
 665  
 666      /**
 667       * Discovered package installation method
 668       *
 669       * @param   integer  $eid  Extension ID
 670       *
 671       * @return  boolean  True if successful
 672       *
 673       * @since   3.1
 674       */
 675      public function discover_install($eid = null)
 676      {
 677          if (!$eid) {
 678              $this->abort(Text::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));
 679  
 680              return false;
 681          }
 682  
 683          if (!$this->extension->load($eid)) {
 684              $this->abort(Text::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
 685  
 686              return false;
 687          }
 688  
 689          if ($this->extension->state != -1) {
 690              $this->abort(Text::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));
 691  
 692              return false;
 693          }
 694  
 695          // Load the adapter(s) for the install manifest
 696          $type   = $this->extension->type;
 697          $params = array('extension' => $this->extension, 'route' => 'discover_install');
 698  
 699          $adapter = $this->loadAdapter($type, $params);
 700  
 701          if (!\is_object($adapter)) {
 702              return false;
 703          }
 704  
 705          if (!method_exists($adapter, 'discover_install') || !$adapter->getDiscoverInstallSupported()) {
 706              $this->abort(Text::sprintf('JLIB_INSTALLER_ERROR_DISCOVER_INSTALL_UNSUPPORTED', $type));
 707  
 708              return false;
 709          }
 710  
 711          // The adapter needs to prepare itself
 712          if (method_exists($adapter, 'prepareDiscoverInstall')) {
 713              try {
 714                  $adapter->prepareDiscoverInstall();
 715              } catch (\RuntimeException $e) {
 716                  $this->abort($e->getMessage());
 717  
 718                  return false;
 719              }
 720          }
 721  
 722          // Add the languages from the package itself
 723          if (method_exists($adapter, 'loadLanguage')) {
 724              $adapter->loadLanguage();
 725          }
 726  
 727          // Fire the onExtensionBeforeInstall event.
 728          PluginHelper::importPlugin('extension');
 729          Factory::getApplication()->triggerEvent(
 730              'onExtensionBeforeInstall',
 731              array(
 732                  'method' => 'discover_install',
 733                  'type' => $this->extension->get('type'),
 734                  'manifest' => null,
 735                  'extension' => $this->extension->get('extension_id'),
 736              )
 737          );
 738  
 739          // Run the install
 740          $result = $adapter->discover_install();
 741  
 742          // Fire the onExtensionAfterInstall
 743          Factory::getApplication()->triggerEvent(
 744              'onExtensionAfterInstall',
 745              array('installer' => clone $this, 'eid' => $result)
 746          );
 747  
 748          if ($result !== false) {
 749              // Refresh versionable assets cache
 750              Factory::getApplication()->flushAssets();
 751  
 752              return true;
 753          }
 754  
 755          return false;
 756      }
 757  
 758      /**
 759       * Extension discover method
 760       *
 761       * Asks each adapter to find extensions
 762       *
 763       * @return  InstallerExtension[]
 764       *
 765       * @since   3.1
 766       */
 767      public function discover()
 768      {
 769          $results = array();
 770  
 771          foreach ($this->getAdapters() as $adapter) {
 772              $instance = $this->loadAdapter($adapter);
 773  
 774              // Joomla! 1.5 installation adapter legacy support
 775              if (method_exists($instance, 'discover')) {
 776                  $tmp = $instance->discover();
 777  
 778                  // If its an array and has entries
 779                  if (\is_array($tmp) && \count($tmp)) {
 780                      // Merge it into the system
 781                      $results = array_merge($results, $tmp);
 782                  }
 783              }
 784          }
 785  
 786          return $results;
 787      }
 788  
 789      /**
 790       * Package update method
 791       *
 792       * @param   string  $path  Path to package source folder
 793       *
 794       * @return  boolean  True if successful
 795       *
 796       * @since   3.1
 797       */
 798      public function update($path = null)
 799      {
 800          if ($path && Folder::exists($path)) {
 801              $this->setPath('source', $path);
 802          } else {
 803              $this->abort(Text::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));
 804  
 805              return false;
 806          }
 807  
 808          if (!$adapter = $this->setupInstall('update', true)) {
 809              $this->abort(Text::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
 810  
 811              return false;
 812          }
 813  
 814          if (!\is_object($adapter)) {
 815              return false;
 816          }
 817  
 818          // Add the languages from the package itself
 819          if (method_exists($adapter, 'loadLanguage')) {
 820              $adapter->loadLanguage($path);
 821          }
 822  
 823          // Fire the onExtensionBeforeUpdate event.
 824          PluginHelper::importPlugin('extension');
 825          Factory::getApplication()->triggerEvent(
 826              'onExtensionBeforeUpdate',
 827              array('type' => $this->manifest->attributes()->type, 'manifest' => $this->manifest)
 828          );
 829  
 830          // Run the update
 831          $result = $adapter->update();
 832  
 833          // Fire the onExtensionAfterUpdate
 834          Factory::getApplication()->triggerEvent(
 835              'onExtensionAfterUpdate',
 836              array('installer' => clone $this, 'eid' => $result)
 837          );
 838  
 839          if ($result !== false) {
 840              return true;
 841          }
 842  
 843          return false;
 844      }
 845  
 846      /**
 847       * Package uninstallation method
 848       *
 849       * @param   string  $type        Package type
 850       * @param   mixed   $identifier  Package identifier for adapter
 851       *
 852       * @return  boolean  True if successful
 853       *
 854       * @since   3.1
 855       */
 856      public function uninstall($type, $identifier)
 857      {
 858          $params = array('extension' => $this->extension, 'route' => 'uninstall');
 859  
 860          $adapter = $this->loadAdapter($type, $params);
 861  
 862          if (!\is_object($adapter)) {
 863              return false;
 864          }
 865  
 866          // We don't load languages here, we get the extension adapter to work it out
 867          // Fire the onExtensionBeforeUninstall event.
 868          PluginHelper::importPlugin('extension');
 869          Factory::getApplication()->triggerEvent(
 870              'onExtensionBeforeUninstall',
 871              array('eid' => $identifier)
 872          );
 873  
 874          // Run the uninstall
 875          $result = $adapter->uninstall($identifier);
 876  
 877          // Fire the onExtensionAfterInstall
 878          Factory::getApplication()->triggerEvent(
 879              'onExtensionAfterUninstall',
 880              array('installer' => clone $this, 'eid' => $identifier, 'removed' => $result)
 881          );
 882  
 883          // Refresh versionable assets cache
 884          Factory::getApplication()->flushAssets();
 885  
 886          return $result;
 887      }
 888  
 889      /**
 890       * Refreshes the manifest cache stored in #__extensions
 891       *
 892       * @param   integer  $eid  Extension ID
 893       *
 894       * @return  boolean
 895       *
 896       * @since   3.1
 897       */
 898      public function refreshManifestCache($eid)
 899      {
 900          if ($eid) {
 901              if (!$this->extension->load($eid)) {
 902                  $this->abort(Text::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
 903  
 904                  return false;
 905              }
 906  
 907              if ($this->extension->state == -1) {
 908                  $this->abort(Text::sprintf('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE', $this->extension->name));
 909  
 910                  return false;
 911              }
 912  
 913              // Fetch the adapter
 914              $adapter = $this->loadAdapter($this->extension->type);
 915  
 916              if (!\is_object($adapter)) {
 917                  return false;
 918              }
 919  
 920              if (!method_exists($adapter, 'refreshManifestCache')) {
 921                  $this->abort(Text::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE', $this->extension->type));
 922  
 923                  return false;
 924              }
 925  
 926              $result = $adapter->refreshManifestCache();
 927  
 928              if ($result !== false) {
 929                  return true;
 930              } else {
 931                  return false;
 932              }
 933          }
 934  
 935          $this->abort(Text::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));
 936  
 937          return false;
 938      }
 939  
 940      // Utility functions
 941  
 942      /**
 943       * Prepare for installation: this method sets the installation directory, finds
 944       * and checks the installation file and verifies the installation type.
 945       *
 946       * @param   string   $route          The install route being followed
 947       * @param   boolean  $returnAdapter  Flag to return the instantiated adapter
 948       *
 949       * @return  boolean|InstallerAdapter  InstallerAdapter object if explicitly requested otherwise boolean
 950       *
 951       * @since   3.1
 952       */
 953      public function setupInstall($route = 'install', $returnAdapter = false)
 954      {
 955          // We need to find the installation manifest file
 956          if (!$this->findManifest()) {
 957              return false;
 958          }
 959  
 960          // Load the adapter(s) for the install manifest
 961          $type   = (string) $this->manifest->attributes()->type;
 962          $params = array('route' => $route, 'manifest' => $this->getManifest());
 963  
 964          // Load the adapter
 965          $adapter = $this->loadAdapter($type, $params);
 966  
 967          if ($returnAdapter) {
 968              return $adapter;
 969          }
 970  
 971          return true;
 972      }
 973  
 974      /**
 975       * Backward compatible method to parse through a queries element of the
 976       * installation manifest file and take appropriate action.
 977       *
 978       * @param   \SimpleXMLElement  $element  The XML node to process
 979       *
 980       * @return  mixed  Number of queries processed or False on error
 981       *
 982       * @since   3.1
 983       */
 984      public function parseQueries(\SimpleXMLElement $element)
 985      {
 986          // Get the database connector object
 987          $db = & $this->_db;
 988  
 989          if (!$element || !\count($element->children())) {
 990              // Either the tag does not exist or has no children therefore we return zero files processed.
 991              return 0;
 992          }
 993  
 994          // Get the array of query nodes to process
 995          $queries = $element->children();
 996  
 997          if (\count($queries) === 0) {
 998              // No queries to process
 999              return 0;
1000          }
1001  
1002          $update_count = 0;
1003  
1004          // Process each query in the $queries array (children of $tagName).
1005          foreach ($queries as $query) {
1006              try {
1007                  $db->setQuery($query)->execute();
1008              } catch (ExecutionFailureException $e) {
1009                  Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), Log::WARNING, 'jerror');
1010  
1011                  return false;
1012              }
1013  
1014              $update_count++;
1015          }
1016  
1017          return $update_count;
1018      }
1019  
1020      /**
1021       * Method to extract the name of a discreet installation sql file from the installation manifest file.
1022       *
1023       * @param   object  $element  The XML node to process
1024       *
1025       * @return  mixed  Number of queries processed or False on error
1026       *
1027       * @since   3.1
1028       */
1029      public function parseSQLFiles($element)
1030      {
1031          if (!$element || !\count($element->children())) {
1032              // The tag does not exist.
1033              return 0;
1034          }
1035  
1036          $db          = &$this->_db;
1037          $dbDriver    = $db->getServerType();
1038          $updateCount = 0;
1039  
1040          // Get the name of the sql file to process
1041          foreach ($element->children() as $file) {
1042              $fCharset = strtolower($file->attributes()->charset) === 'utf8' ? 'utf8' : '';
1043              $fDriver  = strtolower($file->attributes()->driver);
1044  
1045              if ($fDriver === 'mysqli' || $fDriver === 'pdomysql') {
1046                  $fDriver = 'mysql';
1047              } elseif ($fDriver === 'pgsql') {
1048                  $fDriver = 'postgresql';
1049              }
1050  
1051              if ($fCharset !== 'utf8' || $fDriver != $dbDriver) {
1052                  continue;
1053              }
1054  
1055              $sqlfile = $this->getPath('extension_root') . '/' . trim($file);
1056  
1057              // Check that sql files exists before reading. Otherwise raise error for rollback
1058              if (!file_exists($sqlfile)) {
1059                  Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_SQL_FILENOTFOUND', $sqlfile), Log::WARNING, 'jerror');
1060  
1061                  return false;
1062              }
1063  
1064              $buffer = file_get_contents($sqlfile);
1065  
1066              // Graceful exit and rollback if read not successful
1067              if ($buffer === false) {
1068                  Log::add(Text::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), Log::WARNING, 'jerror');
1069  
1070                  return false;
1071              }
1072  
1073              // Create an array of queries from the sql file
1074              $queries = self::splitSql($buffer);
1075  
1076              if (\count($queries) === 0) {
1077                  // No queries to process
1078                  continue;
1079              }
1080  
1081              // Process each query in the $queries array (split out of sql file).
1082              foreach ($queries as $query) {
1083                  $canFail = strlen($query) > self::CAN_FAIL_MARKER_LENGTH + 1 &&
1084                      strtoupper(substr($query, -self::CAN_FAIL_MARKER_LENGTH - 1)) === (self::CAN_FAIL_MARKER . ';');
1085                  $query   = $canFail ? (substr($query, 0, -self::CAN_FAIL_MARKER_LENGTH - 1) . ';') : $query;
1086  
1087                  try {
1088                      $db->setQuery($query)->execute();
1089                  } catch (ExecutionFailureException $e) {
1090                      if (!$canFail) {
1091                          Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), Log::WARNING, 'jerror');
1092  
1093                          return false;
1094                      }
1095                  }
1096  
1097                  $updateCount++;
1098              }
1099          }
1100  
1101          return $updateCount;
1102      }
1103  
1104      /**
1105       * Set the schema version for an extension by looking at its latest update
1106       *
1107       * @param   \SimpleXMLElement  $schema  Schema Tag
1108       * @param   integer            $eid     Extension ID
1109       *
1110       * @return  void
1111       *
1112       * @since   3.1
1113       */
1114      public function setSchemaVersion(\SimpleXMLElement $schema, $eid)
1115      {
1116          if ($eid && $schema) {
1117              $db          = $this->getDatabase();
1118              $schemapaths = $schema->children();
1119  
1120              if (!$schemapaths) {
1121                  return;
1122              }
1123  
1124              if (\count($schemapaths)) {
1125                  $dbDriver = $db->getServerType();
1126  
1127                  $schemapath = '';
1128  
1129                  foreach ($schemapaths as $entry) {
1130                      $attrs = $entry->attributes();
1131  
1132                      if ($attrs['type'] == $dbDriver) {
1133                          $schemapath = $entry;
1134                          break;
1135                      }
1136                  }
1137  
1138                  if ($schemapath !== '') {
1139                      $files = str_replace('.sql', '', Folder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$'));
1140                      usort($files, 'version_compare');
1141  
1142                      // Update the database
1143                      $query = $db->getQuery(true)
1144                          ->delete('#__schemas')
1145                          ->where('extension_id = :extension_id')
1146                          ->bind(':extension_id', $eid, ParameterType::INTEGER);
1147                      $db->setQuery($query);
1148  
1149                      if ($db->execute()) {
1150                          $schemaVersion = end($files);
1151  
1152                          $query->clear()
1153                              ->insert($db->quoteName('#__schemas'))
1154                              ->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')))
1155                              ->values(':extension_id, :version_id')
1156                              ->bind(':extension_id', $eid, ParameterType::INTEGER)
1157                              ->bind(':version_id', $schemaVersion);
1158                          $db->setQuery($query);
1159                          $db->execute();
1160                      }
1161                  }
1162              }
1163          }
1164      }
1165  
1166      /**
1167       * Method to process the updates for an item
1168       *
1169       * @param   \SimpleXMLElement  $schema  The XML node to process
1170       * @param   integer            $eid     Extension Identifier
1171       *
1172       * @return  boolean|int  Number of SQL updates executed; false on failure.
1173       *
1174       * @since   3.1
1175       */
1176      public function parseSchemaUpdates(\SimpleXMLElement $schema, $eid)
1177      {
1178          $updateCount = 0;
1179  
1180          // Ensure we have an XML element and a valid extension id
1181          if (!$eid || !$schema) {
1182              return $updateCount;
1183          }
1184  
1185          $db          = $this->getDatabase();
1186          $schemapaths = $schema->children();
1187  
1188          if (!\count($schemapaths)) {
1189              return $updateCount;
1190          }
1191  
1192          $dbDriver = $db->getServerType();
1193  
1194          $schemapath = '';
1195  
1196          foreach ($schemapaths as $entry) {
1197              $attrs = $entry->attributes();
1198  
1199              // Assuming that the type is a mandatory attribute but if it is not mandatory then there should be a discussion for it.
1200              $uDriver = strtolower($attrs['type']);
1201  
1202              if ($uDriver === 'mysqli' || $uDriver === 'pdomysql') {
1203                  $uDriver = 'mysql';
1204              } elseif ($uDriver === 'pgsql') {
1205                  $uDriver = 'postgresql';
1206              }
1207  
1208              if ($uDriver == $dbDriver) {
1209                  $schemapath = $entry;
1210                  break;
1211              }
1212          }
1213  
1214          if ($schemapath === '') {
1215              return $updateCount;
1216          }
1217  
1218          $files = Folder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$');
1219  
1220          if (empty($files)) {
1221              return $updateCount;
1222          }
1223  
1224          Log::add(Text::_('JLIB_INSTALLER_SQL_BEGIN'), Log::INFO, 'Update');
1225  
1226          $files = str_replace('.sql', '', $files);
1227          usort($files, 'version_compare');
1228  
1229          $query = $db->getQuery(true)
1230              ->select('version_id')
1231              ->from('#__schemas')
1232              ->where('extension_id = :extension_id')
1233              ->bind(':extension_id', $eid, ParameterType::INTEGER);
1234          $db->setQuery($query);
1235  
1236          $hasVersion = true;
1237  
1238          try {
1239              $version = $db->loadResult();
1240  
1241              // No version - use initial version.
1242              if (!$version) {
1243                  $version    = '0.0.0';
1244                  $hasVersion = false;
1245              }
1246          } catch (ExecutionFailureException $e) {
1247              $version = '0.0.0';
1248          }
1249  
1250          Log::add(Text::sprintf('JLIB_INSTALLER_SQL_BEGIN_SCHEMA', $version), Log::INFO, 'Update');
1251  
1252          foreach ($files as $file) {
1253              // Skip over files earlier or equal to the latest schema version recorded for this extension.
1254              if (version_compare($file, $version) <= 0) {
1255                  continue;
1256              }
1257  
1258              $buffer = file_get_contents(sprintf("%s/%s/%s.sql", $this->getPath('extension_root'), $schemapath, $file));
1259  
1260              // Graceful exit and rollback if read not successful
1261              if ($buffer === false) {
1262                  Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), Log::WARNING, 'jerror');
1263  
1264                  return false;
1265              }
1266  
1267              // Create an array of queries from the sql file
1268              $queries = self::splitSql($buffer);
1269  
1270              // Process each query in the $queries array (split out of sql file).
1271              foreach ($queries as $query) {
1272                  $canFail = strlen($query) > self::CAN_FAIL_MARKER_LENGTH + 1 &&
1273                      strtoupper(substr($query, -self::CAN_FAIL_MARKER_LENGTH - 1)) === (self::CAN_FAIL_MARKER . ';');
1274                  $query   = $canFail ? (substr($query, 0, -self::CAN_FAIL_MARKER_LENGTH - 1) . ';') : $query;
1275  
1276                  $queryString = (string) $query;
1277                  $queryString = str_replace(["\r", "\n"], ['', ' '], substr($queryString, 0, 80));
1278  
1279                  try {
1280                      $db->setQuery($query)->execute();
1281                  } catch (ExecutionFailureException | PrepareStatementFailureException $e) {
1282                      if (!$canFail) {
1283                          $errorMessage = Text::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage());
1284  
1285                          // Log the error in the update log file
1286                          Log::add(Text::sprintf('JLIB_INSTALLER_UPDATE_LOG_QUERY', $file, $queryString), Log::INFO, 'Update');
1287                          Log::add($errorMessage, Log::INFO, 'Update');
1288                          Log::add(Text::_('JLIB_INSTALLER_SQL_END_NOT_COMPLETE'), Log::INFO, 'Update');
1289  
1290                          // Show the error message to the user
1291                          Log::add($errorMessage, Log::WARNING, 'jerror');
1292  
1293                          return false;
1294                      }
1295                  }
1296  
1297                  Log::add(Text::sprintf('JLIB_INSTALLER_UPDATE_LOG_QUERY', $file, $queryString), Log::INFO, 'Update');
1298  
1299                  $updateCount++;
1300              }
1301  
1302              // Update the schema version for this extension
1303              try {
1304                  $this->updateSchemaTable($eid, $file, $hasVersion);
1305                  $hasVersion = true;
1306              } catch (ExecutionFailureException $e) {
1307                  Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), Log::WARNING, 'jerror');
1308  
1309                  return false;
1310              }
1311          }
1312  
1313          Log::add(Text::_('JLIB_INSTALLER_SQL_END'), Log::INFO, 'Update');
1314  
1315          return $updateCount;
1316      }
1317  
1318      /**
1319       * Update the schema table with the latest version
1320       *
1321       * @param   int     $eid      Extension ID.
1322       * @param   string  $version  Latest schema version ID.
1323       * @param   boolean $update   Should I run an update against an existing record or insert a new one?
1324       *
1325       * @return  void
1326       *
1327       * @since   4.2.0
1328       */
1329      protected function updateSchemaTable(int $eid, string $version, bool $update = false): void
1330      {
1331          /** @var DatabaseDriver $db */
1332          $db    = Factory::getContainer()->get('DatabaseDriver');
1333  
1334          $o = (object) [
1335              'extension_id' => $eid,
1336              'version_id'   => $version,
1337          ];
1338  
1339          try {
1340              if ($update) {
1341                  $db->updateObject('#__schemas', $o, 'extension_id');
1342              } else {
1343                  $db->insertObject('#__schemas', $o);
1344              }
1345          } catch (ExecutionFailureException $e) {
1346              /**
1347               * Safe fallback: delete any existing record and insert afresh.
1348               *
1349               * It is possible that the schema version may be populated after we detected it does not
1350               * exist (or removed after we detected it exists) and before we finish executing the SQL
1351               * update script. This could happen e.g. if the update SQL script messes with it, or if
1352               * another process is also tinkering with the #__schemas table.
1353               *
1354               * The safe fallback below even runs inside a transaction to prevent interference from
1355               * another process.
1356               */
1357              $db->transactionStart();
1358  
1359              $query = $db->getQuery(true)
1360                  ->delete('#__schemas')
1361                  ->where('extension_id = :extension_id')
1362                  ->bind(':extension_id', $eid, ParameterType::INTEGER);
1363  
1364              $db->setQuery($query)->execute();
1365  
1366              $db->insertObject('#__schemas', $o);
1367  
1368              $db->transactionCommit();
1369          }
1370      }
1371  
1372      /**
1373       * Method to parse through a files element of the installation manifest and take appropriate
1374       * action.
1375       *
1376       * @param   \SimpleXMLElement  $element   The XML node to process
1377       * @param   integer            $cid       Application ID of application to install to
1378       * @param   array              $oldFiles  List of old files (SimpleXMLElement's)
1379       * @param   array              $oldMD5    List of old MD5 sums (indexed by filename with value as MD5)
1380       *
1381       * @return  boolean      True on success
1382       *
1383       * @since   3.1
1384       */
1385      public function parseFiles(\SimpleXMLElement $element, $cid = 0, $oldFiles = null, $oldMD5 = null)
1386      {
1387          // Get the array of file nodes to process; we checked whether this had children above.
1388          if (!$element || !\count($element->children())) {
1389              // Either the tag does not exist or has no children (hence no files to process) therefore we return zero files processed.
1390              return 0;
1391          }
1392  
1393          $copyfiles = array();
1394  
1395          // Get the client info
1396          $client = ApplicationHelper::getClientInfo($cid);
1397  
1398          /*
1399           * Here we set the folder we are going to remove the files from.
1400           */
1401          if ($client) {
1402              $pathname = 'extension_' . $client->name;
1403              $destination = $this->getPath($pathname);
1404          } else {
1405              $pathname = 'extension_root';
1406              $destination = $this->getPath($pathname);
1407          }
1408  
1409          /*
1410           * Here we set the folder we are going to copy the files from.
1411           *
1412           * Does the element have a folder attribute?
1413           *
1414           * If so this indicates that the files are in a subdirectory of the source
1415           * folder and we should append the folder attribute to the source path when
1416           * copying files.
1417           */
1418  
1419          $folder = (string) $element->attributes()->folder;
1420  
1421          if ($folder && file_exists($this->getPath('source') . '/' . $folder)) {
1422              $source = $this->getPath('source') . '/' . $folder;
1423          } else {
1424              $source = $this->getPath('source');
1425          }
1426  
1427          // Work out what files have been deleted
1428          if ($oldFiles && ($oldFiles instanceof \SimpleXMLElement)) {
1429              $oldEntries = $oldFiles->children();
1430  
1431              if (\count($oldEntries)) {
1432                  $deletions = $this->findDeletedFiles($oldEntries, $element->children());
1433  
1434                  foreach ($deletions['folders'] as $deleted_folder) {
1435                      Folder::delete($destination . '/' . $deleted_folder);
1436                  }
1437  
1438                  foreach ($deletions['files'] as $deleted_file) {
1439                      File::delete($destination . '/' . $deleted_file);
1440                  }
1441              }
1442          }
1443  
1444          $path = array();
1445  
1446          // Copy the MD5SUMS file if it exists
1447          if (file_exists($source . '/MD5SUMS')) {
1448              $path['src'] = $source . '/MD5SUMS';
1449              $path['dest'] = $destination . '/MD5SUMS';
1450              $path['type'] = 'file';
1451              $copyfiles[] = $path;
1452          }
1453  
1454          // Process each file in the $files array (children of $tagName).
1455          foreach ($element->children() as $file) {
1456              $path['src'] = $source . '/' . $file;
1457              $path['dest'] = $destination . '/' . $file;
1458  
1459              // Is this path a file or folder?
1460              $path['type'] = $file->getName() === 'folder' ? 'folder' : 'file';
1461  
1462              /*
1463               * Before we can add a file to the copyfiles array we need to ensure
1464               * that the folder we are copying our file to exists and if it doesn't,
1465               * we need to create it.
1466               */
1467  
1468              if (basename($path['dest']) !== $path['dest']) {
1469                  $newdir = \dirname($path['dest']);
1470  
1471                  if (!Folder::create($newdir)) {
1472                      Log::add(
1473                          Text::sprintf(
1474                              'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
1475                              Text::_('JLIB_INSTALLER_INSTALL'),
1476                              $newdir
1477                          ),
1478                          Log::WARNING,
1479                          'jerror'
1480                      );
1481  
1482                      return false;
1483                  }
1484              }
1485  
1486              // Add the file to the copyfiles array
1487              $copyfiles[] = $path;
1488          }
1489  
1490          return $this->copyFiles($copyfiles);
1491      }
1492  
1493      /**
1494       * Method to parse through a languages element of the installation manifest and take appropriate
1495       * action.
1496       *
1497       * @param   \SimpleXMLElement  $element  The XML node to process
1498       * @param   integer            $cid      Application ID of application to install to
1499       *
1500       * @return  boolean  True on success
1501       *
1502       * @since   3.1
1503       */
1504      public function parseLanguages(\SimpleXMLElement $element, $cid = 0)
1505      {
1506          // TODO: work out why the below line triggers 'node no longer exists' errors with files
1507          if (!$element || !\count($element->children())) {
1508              // Either the tag does not exist or has no children therefore we return zero files processed.
1509              return 0;
1510          }
1511  
1512          $copyfiles = array();
1513  
1514          // Get the client info
1515          $client = ApplicationHelper::getClientInfo($cid);
1516  
1517          // Here we set the folder we are going to copy the files to.
1518          // 'languages' Files are copied to JPATH_BASE/language/ folder
1519  
1520          $destination = $client->path . '/language';
1521  
1522          /*
1523           * Here we set the folder we are going to copy the files from.
1524           *
1525           * Does the element have a folder attribute?
1526           *
1527           * If so this indicates that the files are in a subdirectory of the source
1528           * folder and we should append the folder attribute to the source path when
1529           * copying files.
1530           */
1531  
1532          $folder = (string) $element->attributes()->folder;
1533  
1534          if ($folder && file_exists($this->getPath('source') . '/' . $folder)) {
1535              $source = $this->getPath('source') . '/' . $folder;
1536          } else {
1537              $source = $this->getPath('source');
1538          }
1539  
1540          // Process each file in the $files array (children of $tagName).
1541          foreach ($element->children() as $file) {
1542              /*
1543               * Language files go in a subfolder based on the language code, ie.
1544               * <language tag="en-US">en-US.mycomponent.ini</language>
1545               * would go in the en-US subdirectory of the language folder.
1546               */
1547  
1548              // We will only install language files where a core language pack
1549              // already exists.
1550  
1551              if ((string) $file->attributes()->tag !== '') {
1552                  $path['src'] = $source . '/' . $file;
1553  
1554                  if ((string) $file->attributes()->client !== '') {
1555                      // Override the client
1556                      $langclient = ApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
1557                      $path['dest'] = $langclient->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
1558                  } else {
1559                      // Use the default client
1560                      $path['dest'] = $destination . '/' . $file->attributes()->tag . '/' . basename((string) $file);
1561                  }
1562  
1563                  // If the language folder is not present, then the core pack hasn't been installed... ignore
1564                  if (!Folder::exists(\dirname($path['dest']))) {
1565                      continue;
1566                  }
1567              } else {
1568                  $path['src'] = $source . '/' . $file;
1569                  $path['dest'] = $destination . '/' . $file;
1570              }
1571  
1572              /*
1573               * Before we can add a file to the copyfiles array we need to ensure
1574               * that the folder we are copying our file to exists and if it doesn't,
1575               * we need to create it.
1576               */
1577  
1578              if (basename($path['dest']) !== $path['dest']) {
1579                  $newdir = \dirname($path['dest']);
1580  
1581                  if (!Folder::create($newdir)) {
1582                      Log::add(
1583                          Text::sprintf(
1584                              'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
1585                              Text::_('JLIB_INSTALLER_INSTALL'),
1586                              $newdir
1587                          ),
1588                          Log::WARNING,
1589                          'jerror'
1590                      );
1591  
1592                      return false;
1593                  }
1594              }
1595  
1596              // Add the file to the copyfiles array
1597              $copyfiles[] = $path;
1598          }
1599  
1600          return $this->copyFiles($copyfiles);
1601      }
1602  
1603      /**
1604       * Method to parse through a media element of the installation manifest and take appropriate
1605       * action.
1606       *
1607       * @param   \SimpleXMLElement  $element  The XML node to process
1608       * @param   integer            $cid      Application ID of application to install to
1609       *
1610       * @return  boolean     True on success
1611       *
1612       * @since   3.1
1613       */
1614      public function parseMedia(\SimpleXMLElement $element, $cid = 0)
1615      {
1616          if (!$element || !\count($element->children())) {
1617              // Either the tag does not exist or has no children therefore we return zero files processed.
1618              return 0;
1619          }
1620  
1621          $copyfiles = array();
1622  
1623          // Here we set the folder we are going to copy the files to.
1624          // Default 'media' Files are copied to the JPATH_BASE/media folder
1625  
1626          $folder = ((string) $element->attributes()->destination) ? '/' . $element->attributes()->destination : null;
1627          $destination = Path::clean(JPATH_ROOT . '/media' . $folder);
1628  
1629          // Here we set the folder we are going to copy the files from.
1630  
1631          /*
1632           * Does the element have a folder attribute?
1633           * If so this indicates that the files are in a subdirectory of the source
1634           * folder and we should append the folder attribute to the source path when
1635           * copying files.
1636           */
1637  
1638          $folder = (string) $element->attributes()->folder;
1639  
1640          if ($folder && file_exists($this->getPath('source') . '/' . $folder)) {
1641              $source = $this->getPath('source') . '/' . $folder;
1642          } else {
1643              $source = $this->getPath('source');
1644          }
1645  
1646          // Process each file in the $files array (children of $tagName).
1647          foreach ($element->children() as $file) {
1648              $path['src'] = $source . '/' . $file;
1649              $path['dest'] = $destination . '/' . $file;
1650  
1651              // Is this path a file or folder?
1652              $path['type'] = $file->getName() === 'folder' ? 'folder' : 'file';
1653  
1654              /*
1655               * Before we can add a file to the copyfiles array we need to ensure
1656               * that the folder we are copying our file to exists and if it doesn't,
1657               * we need to create it.
1658               */
1659  
1660              if (basename($path['dest']) !== $path['dest']) {
1661                  $newdir = \dirname($path['dest']);
1662  
1663                  if (!Folder::create($newdir)) {
1664                      Log::add(
1665                          Text::sprintf(
1666                              'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
1667                              Text::_('JLIB_INSTALLER_INSTALL'),
1668                              $newdir
1669                          ),
1670                          Log::WARNING,
1671                          'jerror'
1672                      );
1673  
1674                      return false;
1675                  }
1676              }
1677  
1678              // Add the file to the copyfiles array
1679              $copyfiles[] = $path;
1680          }
1681  
1682          return $this->copyFiles($copyfiles);
1683      }
1684  
1685      /**
1686       * Method to parse the parameters of an extension, build the JSON string for its default parameters, and return the JSON string.
1687       *
1688       * @return  string  JSON string of parameter values
1689       *
1690       * @since   3.1
1691       * @note    This method must always return a JSON compliant string
1692       */
1693      public function getParams()
1694      {
1695          // Validate that we have a fieldset to use
1696          if (!isset($this->manifest->config->fields->fieldset)) {
1697              return '{}';
1698          }
1699  
1700          // Getting the fieldset tags
1701          $fieldsets = $this->manifest->config->fields->fieldset;
1702  
1703          // Creating the data collection variable:
1704          $ini = array();
1705  
1706          // Iterating through the fieldsets:
1707          foreach ($fieldsets as $fieldset) {
1708              if (!\count($fieldset->children())) {
1709                  // Either the tag does not exist or has no children therefore we return zero files processed.
1710                  return '{}';
1711              }
1712  
1713              // Iterating through the fields and collecting the name/default values:
1714              foreach ($fieldset as $field) {
1715                  // Check against the null value since otherwise default values like "0"
1716                  // cause entire parameters to be skipped.
1717  
1718                  if (($name = $field->attributes()->name) === null) {
1719                      continue;
1720                  }
1721  
1722                  if (($value = $field->attributes()->default) === null) {
1723                      continue;
1724                  }
1725  
1726                  $ini[(string) $name] = (string) $value;
1727              }
1728          }
1729  
1730          return json_encode($ini);
1731      }
1732  
1733      /**
1734       * Copyfiles
1735       *
1736       * Copy files from source directory to the target directory
1737       *
1738       * @param   array    $files      Array with filenames
1739       * @param   boolean  $overwrite  True if existing files can be replaced
1740       *
1741       * @return  boolean  True on success
1742       *
1743       * @since   3.1
1744       */
1745      public function copyFiles($files, $overwrite = null)
1746      {
1747          /*
1748           * To allow for manual override on the overwriting flag, we check to see if
1749           * the $overwrite flag was set and is a boolean value.  If not, use the object
1750           * allowOverwrite flag.
1751           */
1752  
1753          if ($overwrite === null || !\is_bool($overwrite)) {
1754              $overwrite = $this->overwrite;
1755          }
1756  
1757          /*
1758           * $files must be an array of filenames.  Verify that it is an array with
1759           * at least one file to copy.
1760           */
1761          if (\is_array($files) && \count($files) > 0) {
1762              foreach ($files as $file) {
1763                  // Get the source and destination paths
1764                  $filesource = Path::clean($file['src']);
1765                  $filedest = Path::clean($file['dest']);
1766                  $filetype = \array_key_exists('type', $file) ? $file['type'] : 'file';
1767  
1768                  if (!file_exists($filesource)) {
1769                      /*
1770                       * The source file does not exist.  Nothing to copy so set an error
1771                       * and return false.
1772                       */
1773                      Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_NO_FILE', $filesource), Log::WARNING, 'jerror');
1774  
1775                      return false;
1776                  } elseif (($exists = file_exists($filedest)) && !$overwrite) {
1777                      // It's okay if the manifest already exists
1778                      if ($this->getPath('manifest') === $filesource) {
1779                          continue;
1780                      }
1781  
1782                      // The destination file already exists and the overwrite flag is false.
1783                      // Set an error and return false.
1784                      Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS', $filedest), Log::WARNING, 'jerror');
1785  
1786                      return false;
1787                  } else {
1788                      // Copy the folder or file to the new location.
1789                      if ($filetype === 'folder') {
1790                          if (!Folder::copy($filesource, $filedest, null, $overwrite)) {
1791                              Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER', $filesource, $filedest), Log::WARNING, 'jerror');
1792  
1793                              return false;
1794                          }
1795  
1796                          $step = array('type' => 'folder', 'path' => $filedest);
1797                      } else {
1798                          if (!File::copy($filesource, $filedest, null)) {
1799                              Log::add(Text::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE', $filesource, $filedest), Log::WARNING, 'jerror');
1800  
1801                              // In 3.2, TinyMCE language handling changed.  Display a special notice in case an older language pack is installed.
1802                              if (strpos($filedest, 'media/editors/tinymce/jscripts/tiny_mce/langs')) {
1803                                  Log::add(Text::_('JLIB_INSTALLER_NOT_ERROR'), Log::WARNING, 'jerror');
1804                              }
1805  
1806                              return false;
1807                          }
1808  
1809                          $step = array('type' => 'file', 'path' => $filedest);
1810                      }
1811  
1812                      /*
1813                       * Since we copied a file/folder, we want to add it to the installation step stack so that
1814                       * in case we have to roll back the installation we can remove the files copied.
1815                       */
1816                      if (!$exists) {
1817                          $this->stepStack[] = $step;
1818                      }
1819                  }
1820              }
1821          } else {
1822              // The $files variable was either not an array or an empty array
1823              return false;
1824          }
1825  
1826          return \count($files);
1827      }
1828  
1829      /**
1830       * Method to parse through a files element of the installation manifest and remove
1831       * the files that were installed
1832       *
1833       * @param   object   $element  The XML node to process
1834       * @param   integer  $cid      Application ID of application to remove from
1835       *
1836       * @return  boolean  True on success
1837       *
1838       * @since   3.1
1839       */
1840      public function removeFiles($element, $cid = 0)
1841      {
1842          if (!$element || !\count($element->children())) {
1843              // Either the tag does not exist or has no children therefore we return zero files processed.
1844              return true;
1845          }
1846  
1847          $retval = true;
1848  
1849          // Get the client info if we're using a specific client
1850          if ($cid > -1) {
1851              $client = ApplicationHelper::getClientInfo($cid);
1852          } else {
1853              $client = null;
1854          }
1855  
1856          // Get the array of file nodes to process
1857          $files = $element->children();
1858  
1859          if (\count($files) === 0) {
1860              // No files to process
1861              return true;
1862          }
1863  
1864          $folder = '';
1865  
1866          /*
1867           * Here we set the folder we are going to remove the files from.  There are a few
1868           * special cases that need to be considered for certain reserved tags.
1869           */
1870          switch ($element->getName()) {
1871              case 'media':
1872                  if ((string) $element->attributes()->destination) {
1873                      $folder = (string) $element->attributes()->destination;
1874                  } else {
1875                      $folder = '';
1876                  }
1877  
1878                  $source = $client->path . '/media/' . $folder;
1879  
1880                  break;
1881  
1882              case 'languages':
1883                  $lang_client = (string) $element->attributes()->client;
1884  
1885                  if ($lang_client) {
1886                      $client = ApplicationHelper::getClientInfo($lang_client, true);
1887                      $source = $client->path . '/language';
1888                  } else {
1889                      if ($client) {
1890                          $source = $client->path . '/language';
1891                      } else {
1892                          $source = '';
1893                      }
1894                  }
1895  
1896                  break;
1897  
1898              default:
1899                  if ($client) {
1900                      $pathname = 'extension_' . $client->name;
1901                      $source = $this->getPath($pathname);
1902                  } else {
1903                      $pathname = 'extension_root';
1904                      $source = $this->getPath($pathname);
1905                  }
1906  
1907                  break;
1908          }
1909  
1910          // Process each file in the $files array (children of $tagName).
1911          foreach ($files as $file) {
1912              /*
1913               * If the file is a language, we must handle it differently.  Language files
1914               * go in a subdirectory based on the language code, ie.
1915               * <language tag="en_US">en_US.mycomponent.ini</language>
1916               * would go in the en_US subdirectory of the languages directory.
1917               */
1918  
1919              if ($file->getName() === 'language' && (string) $file->attributes()->tag !== '') {
1920                  if ($source) {
1921                      $path = $source . '/' . $file->attributes()->tag . '/' . basename((string) $file);
1922                  } else {
1923                      $target_client = ApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
1924                      $path = $target_client->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
1925                  }
1926  
1927                  // If the language folder is not present, then the core pack hasn't been installed... ignore
1928                  if (!Folder::exists(\dirname($path))) {
1929                      continue;
1930                  }
1931              } else {
1932                  $path = $source . '/' . $file;
1933              }
1934  
1935              // Actually delete the files/folders
1936  
1937              if (is_dir($path)) {
1938                  $val = Folder::delete($path);
1939              } else {
1940                  $val = File::delete($path);
1941              }
1942  
1943              if ($val === false) {
1944                  Log::add('Failed to delete ' . $path, Log::WARNING, 'jerror');
1945                  $retval = false;
1946              }
1947          }
1948  
1949          if (!empty($folder)) {
1950              Folder::delete($source);
1951          }
1952  
1953          return $retval;
1954      }
1955  
1956      /**
1957       * Copies the installation manifest file to the extension folder in the given client
1958       *
1959       * @param   integer  $cid  Where to copy the installfile [optional: defaults to 1 (admin)]
1960       *
1961       * @return  boolean  True on success, False on error
1962       *
1963       * @since   3.1
1964       */
1965      public function copyManifest($cid = 1)
1966      {
1967          // Get the client info
1968          $client = ApplicationHelper::getClientInfo($cid);
1969  
1970          $path['src'] = $this->getPath('manifest');
1971  
1972          if ($client) {
1973              $pathname = 'extension_' . $client->name;
1974              $path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
1975          } else {
1976              $pathname = 'extension_root';
1977              $path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
1978          }
1979  
1980          return $this->copyFiles(array($path), true);
1981      }
1982  
1983      /**
1984       * Tries to find the package manifest file
1985       *
1986       * @return  boolean  True on success, False on error
1987       *
1988       * @since   3.1
1989       */
1990      public function findManifest()
1991      {
1992          // Do nothing if folder does not exist for some reason
1993          if (!Folder::exists($this->getPath('source'))) {
1994              return false;
1995          }
1996  
1997          // Main folder manifests (higher priority)
1998          $parentXmlfiles = Folder::files($this->getPath('source'), '.xml$', false, true);
1999  
2000          // Search for children manifests (lower priority)
2001          $allXmlFiles    = Folder::files($this->getPath('source'), '.xml$', 1, true);
2002  
2003          // Create an unique array of files ordered by priority
2004          $xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles));
2005  
2006          // If at least one XML file exists
2007          if (!empty($xmlfiles)) {
2008              foreach ($xmlfiles as $file) {
2009                  // Is it a valid Joomla installation manifest file?
2010                  $manifest = $this->isManifest($file);
2011  
2012                  if ($manifest !== null) {
2013                      // If the root method attribute is set to upgrade, allow file overwrite
2014                      if ((string) $manifest->attributes()->method === 'upgrade') {
2015                          $this->upgrade = true;
2016                          $this->overwrite = true;
2017                      }
2018  
2019                      // If the overwrite option is set, allow file overwriting
2020                      if ((string) $manifest->attributes()->overwrite === 'true') {
2021                          $this->overwrite = true;
2022                      }
2023  
2024                      // Set the manifest object and path
2025                      $this->manifest = $manifest;
2026                      $this->setPath('manifest', $file);
2027  
2028                      // Set the installation source path to that of the manifest file
2029                      $this->setPath('source', \dirname($file));
2030  
2031                      return true;
2032                  }
2033              }
2034  
2035              // None of the XML files found were valid install files
2036              Log::add(Text::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), Log::WARNING, 'jerror');
2037  
2038              return false;
2039          } else {
2040              // No XML files were found in the install folder
2041              Log::add(Text::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), Log::WARNING, 'jerror');
2042  
2043              return false;
2044          }
2045      }
2046  
2047      /**
2048       * Is the XML file a valid Joomla installation manifest file.
2049       *
2050       * @param   string  $file  An xmlfile path to check
2051       *
2052       * @return  \SimpleXMLElement|null  A \SimpleXMLElement, or null if the file failed to parse
2053       *
2054       * @since   3.1
2055       */
2056      public function isManifest($file)
2057      {
2058          $xml = simplexml_load_file($file);
2059  
2060          // If we cannot load the XML file return null
2061          if (!$xml) {
2062              return;
2063          }
2064  
2065          // Check for a valid XML root tag.
2066          if ($xml->getName() !== 'extension') {
2067              return;
2068          }
2069  
2070          // Valid manifest file return the object
2071          return $xml;
2072      }
2073  
2074      /**
2075       * Generates a manifest cache
2076       *
2077       * @return string serialised manifest data
2078       *
2079       * @since   3.1
2080       */
2081      public function generateManifestCache()
2082      {
2083          return json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
2084      }
2085  
2086      /**
2087       * Cleans up discovered extensions if they're being installed some other way
2088       *
2089       * @param   string   $type     The type of extension (component, etc)
2090       * @param   string   $element  Unique element identifier (e.g. com_content)
2091       * @param   string   $folder   The folder of the extension (plugins; e.g. system)
2092       * @param   integer  $client   The client application (administrator or site)
2093       *
2094       * @return  object    Result of query
2095       *
2096       * @since   3.1
2097       */
2098      public function cleanDiscoveredExtension($type, $element, $folder = '', $client = 0)
2099      {
2100          $db    = $this->getDatabase();
2101          $query = $db->getQuery(true)
2102              ->delete($db->quoteName('#__extensions'))
2103              ->where('type = :type')
2104              ->where('element = :element')
2105              ->where('folder = :folder')
2106              ->where('client_id = :client_id')
2107              ->where('state = -1')
2108              ->bind(':type', $type)
2109              ->bind(':element', $element)
2110              ->bind(':folder', $folder)
2111              ->bind(':client_id', $client, ParameterType::INTEGER);
2112          $db->setQuery($query);
2113  
2114          return $db->execute();
2115      }
2116  
2117      /**
2118       * Compares two "files" entries to find deleted files/folders
2119       *
2120       * @param   array  $oldFiles  An array of \SimpleXMLElement objects that are the old files
2121       * @param   array  $newFiles  An array of \SimpleXMLElement objects that are the new files
2122       *
2123       * @return  array  An array with the delete files and folders in findDeletedFiles[files] and findDeletedFiles[folders] respectively
2124       *
2125       * @since   3.1
2126       */
2127      public function findDeletedFiles($oldFiles, $newFiles)
2128      {
2129          // The magic find deleted files function!
2130          // The files that are new
2131          $files = array();
2132  
2133          // The folders that are new
2134          $folders = array();
2135  
2136          // The folders of the files that are new
2137          $containers = array();
2138  
2139          // A list of files to delete
2140          $files_deleted = array();
2141  
2142          // A list of folders to delete
2143          $folders_deleted = array();
2144  
2145          foreach ($newFiles as $file) {
2146              switch ($file->getName()) {
2147                  case 'folder':
2148                      // Add any folders to the list
2149                      $folders[] = (string) $file;
2150                      break;
2151  
2152                  case 'file':
2153                  default:
2154                      // Add any files to the list
2155                      $files[] = (string) $file;
2156  
2157                      // Now handle the folder part of the file to ensure we get any containers
2158                      // Break up the parts of the directory
2159                      $container_parts = explode('/', \dirname((string) $file));
2160  
2161                      // Make sure this is clean and empty
2162                      $container = '';
2163  
2164                      foreach ($container_parts as $part) {
2165                          // Iterate through each part
2166                          // Add a slash if its not empty
2167                          if (!empty($container)) {
2168                              $container .= '/';
2169                          }
2170  
2171                          // Append the folder part
2172                          $container .= $part;
2173  
2174                          if (!\in_array($container, $containers)) {
2175                              // Add the container if it doesn't already exist
2176                              $containers[] = $container;
2177                          }
2178                      }
2179                      break;
2180              }
2181          }
2182  
2183          foreach ($oldFiles as $file) {
2184              switch ($file->getName()) {
2185                  case 'folder':
2186                      if (!\in_array((string) $file, $folders)) {
2187                          // See whether the folder exists in the new list
2188                          if (!\in_array((string) $file, $containers)) {
2189                              // Check if the folder exists as a container in the new list
2190                              // If it's not in the new list or a container then delete it
2191                              $folders_deleted[] = (string) $file;
2192                          }
2193                      }
2194                      break;
2195  
2196                  case 'file':
2197                  default:
2198                      if (!\in_array((string) $file, $files)) {
2199                          // Look if the file exists in the new list
2200                          if (!\in_array(\dirname((string) $file), $folders)) {
2201                              // Look if the file is now potentially in a folder
2202                              $files_deleted[] = (string) $file;
2203                          }
2204                      }
2205                      break;
2206              }
2207          }
2208  
2209          return array('files' => $files_deleted, 'folders' => $folders_deleted);
2210      }
2211  
2212      /**
2213       * Loads an MD5SUMS file into an associative array
2214       *
2215       * @param   string  $filename  Filename to load
2216       *
2217       * @return  array  Associative array with filenames as the index and the MD5 as the value
2218       *
2219       * @since   3.1
2220       */
2221      public function loadMD5Sum($filename)
2222      {
2223          if (!file_exists($filename)) {
2224              // Bail if the file doesn't exist
2225              return false;
2226          }
2227  
2228          $data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2229          $retval = array();
2230  
2231          foreach ($data as $row) {
2232              // Split up the data
2233              $results = explode('  ', $row);
2234  
2235              // Cull any potential prefix
2236              $results[1] = str_replace('./', '', $results[1]);
2237  
2238              // Throw into the array
2239              $retval[$results[1]] = $results[0];
2240          }
2241  
2242          return $retval;
2243      }
2244  
2245      /**
2246       * Parse a XML install manifest file.
2247       *
2248       * XML Root tag should be 'install' except for languages which use meta file.
2249       *
2250       * @param   string  $path  Full path to XML file.
2251       *
2252       * @return  array  XML metadata.
2253       *
2254       * @since   3.0.0
2255       */
2256      public static function parseXMLInstallFile($path)
2257      {
2258          // Check if xml file exists.
2259          if (!file_exists($path)) {
2260              return false;
2261          }
2262  
2263          // Read the file to see if it's a valid component XML file
2264          $xml = simplexml_load_file($path);
2265  
2266          if (!$xml) {
2267              return false;
2268          }
2269  
2270          // Check for a valid XML root tag.
2271  
2272          // Extensions use 'extension' as the root tag.  Languages use 'metafile' instead
2273  
2274          $name = $xml->getName();
2275  
2276          if ($name !== 'extension' && $name !== 'metafile') {
2277              unset($xml);
2278  
2279              return false;
2280          }
2281  
2282          $data = array();
2283  
2284          $data['name'] = (string) $xml->name;
2285  
2286          // Check if we're a language. If so use metafile.
2287          $data['type'] = $xml->getName() === 'metafile' ? 'language' : (string) $xml->attributes()->type;
2288  
2289          $data['creationDate'] = ((string) $xml->creationDate) ?: Text::_('JLIB_UNKNOWN');
2290          $data['author'] = ((string) $xml->author) ?: Text::_('JLIB_UNKNOWN');
2291  
2292          $data['copyright'] = (string) $xml->copyright;
2293          $data['authorEmail'] = (string) $xml->authorEmail;
2294          $data['authorUrl'] = (string) $xml->authorUrl;
2295          $data['version'] = (string) $xml->version;
2296          $data['description'] = (string) $xml->description;
2297          $data['group'] = (string) $xml->group;
2298  
2299          // Child template specific fields.
2300          if (isset($xml->inheritable)) {
2301              $data['inheritable'] = (string) $xml->inheritable === '0' ? false : true;
2302          }
2303  
2304          if (isset($xml->parent) && (string) $xml->parent !== '') {
2305              $data['parent'] = (string) $xml->parent;
2306          }
2307  
2308          if ($xml->files && \count($xml->files->children())) {
2309              $filename = basename($path);
2310              $data['filename'] = File::stripExt($filename);
2311  
2312              foreach ($xml->files->children() as $oneFile) {
2313                  if ((string) $oneFile->attributes()->plugin) {
2314                      $data['filename'] = (string) $oneFile->attributes()->plugin;
2315                      break;
2316                  }
2317              }
2318          }
2319  
2320          return $data;
2321      }
2322  
2323      /**
2324       * Gets a list of available install adapters.
2325       *
2326       * @param   array  $options  An array of options to inject into the adapter
2327       * @param   array  $custom   Array of custom install adapters
2328       *
2329       * @return  string[]  An array of the class names of available install adapters.
2330       *
2331       * @since   3.4
2332       */
2333      public function getAdapters($options = array(), array $custom = array())
2334      {
2335          $files = new \DirectoryIterator($this->_basepath . '/' . $this->_adapterfolder);
2336  
2337          // Process the core adapters
2338          foreach ($files as $file) {
2339              $fileName = $file->getFilename();
2340  
2341              // Only load for php files.
2342              if (!$file->isFile() || $file->getExtension() !== 'php') {
2343                  continue;
2344              }
2345  
2346              // Derive the class name from the filename.
2347              $name  = str_ireplace('.php', '', trim($fileName));
2348              $name  = str_ireplace('adapter', '', trim($name));
2349              $class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name) . 'Adapter';
2350  
2351              if (!class_exists($class)) {
2352                  // Not namespaced
2353                  $class = $this->_classprefix . ucfirst($name);
2354              }
2355  
2356              // Core adapters should autoload based on classname, keep this fallback just in case
2357              if (!class_exists($class)) {
2358                  // Try to load the adapter object
2359                  \JLoader::register($class, $this->_basepath . '/' . $this->_adapterfolder . '/' . $fileName);
2360  
2361                  if (!class_exists($class)) {
2362                      // Skip to next one
2363                      continue;
2364                  }
2365              }
2366  
2367              $adapters[] = $name;
2368          }
2369  
2370          // Add any custom adapters if specified
2371          if (\count($custom) >= 1) {
2372              foreach ($custom as $adapter) {
2373                  // Setup the class name
2374                  // TODO - Can we abstract this to not depend on the Joomla class namespace without PHP namespaces?
2375                  $class = $this->_classprefix . ucfirst(trim($adapter));
2376  
2377                  // If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
2378                  if (!class_exists($class)) {
2379                      continue;
2380                  }
2381  
2382                  $adapters[] = str_ireplace('.php', '', $fileName);
2383              }
2384          }
2385  
2386          return $adapters;
2387      }
2388  
2389      /**
2390       * Method to load an adapter instance
2391       *
2392       * @param   string  $adapter  Adapter name
2393       * @param   array   $options  Adapter options
2394       *
2395       * @return  InstallerAdapter
2396       *
2397       * @since   3.4
2398       * @throws  \InvalidArgumentException
2399       */
2400      public function loadAdapter($adapter, $options = array())
2401      {
2402          $class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($adapter) . 'Adapter';
2403  
2404          if (!class_exists($class)) {
2405              // Not namespaced
2406              $class = $this->_classprefix . ucfirst($adapter);
2407          }
2408  
2409          if (!class_exists($class)) {
2410              throw new \InvalidArgumentException(sprintf('The %s install adapter does not exist.', $adapter));
2411          }
2412  
2413          // Ensure the adapter type is part of the options array
2414          $options['type'] = $adapter;
2415  
2416          // Check for a possible service from the container otherwise manually instantiate the class
2417          if (Factory::getContainer()->has($class)) {
2418              return Factory::getContainer()->get($class);
2419          }
2420  
2421          $adapter = new $class($this, $this->getDatabase(), $options);
2422  
2423          if ($adapter instanceof ContainerAwareInterface) {
2424              $adapter->setContainer(Factory::getContainer());
2425          }
2426  
2427          return $adapter;
2428      }
2429  }


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