[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/administrator/components/com_installer/src/Model/ -> UpdateModel.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Administrator
   5   * @subpackage  com_installer
   6   *
   7   * @copyright   (C) 2008 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9   */
  10  
  11  namespace Joomla\Component\Installer\Administrator\Model;
  12  
  13  use Joomla\CMS\Extension\ExtensionHelper;
  14  use Joomla\CMS\Factory;
  15  use Joomla\CMS\Filesystem\File;
  16  use Joomla\CMS\Form\Form;
  17  use Joomla\CMS\Installer\Installer;
  18  use Joomla\CMS\Installer\InstallerHelper;
  19  use Joomla\CMS\Language\Text;
  20  use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
  21  use Joomla\CMS\MVC\Model\ListModel;
  22  use Joomla\CMS\Plugin\PluginHelper;
  23  use Joomla\CMS\Updater\Update;
  24  use Joomla\CMS\Updater\Updater;
  25  use Joomla\Database\DatabaseQuery;
  26  use Joomla\Database\Exception\ExecutionFailureException;
  27  use Joomla\Database\ParameterType;
  28  use Joomla\Utilities\ArrayHelper;
  29  
  30  // phpcs:disable PSR1.Files.SideEffects
  31  \defined('_JEXEC') or die;
  32  // phpcs:enable PSR1.Files.SideEffects
  33  
  34  /**
  35   * Installer Update Model
  36   *
  37   * @since  1.6
  38   */
  39  class UpdateModel extends ListModel
  40  {
  41      /**
  42       * Constructor.
  43       *
  44       * @param   array                $config   An optional associative array of configuration settings.
  45       * @param   MVCFactoryInterface  $factory  The factory.
  46       *
  47       * @see     \Joomla\CMS\MVC\Model\ListModel
  48       * @since   1.6
  49       */
  50      public function __construct($config = array(), MVCFactoryInterface $factory = null)
  51      {
  52          if (empty($config['filter_fields'])) {
  53              $config['filter_fields'] = array(
  54                  'name', 'u.name',
  55                  'client_id', 'u.client_id', 'client_translated',
  56                  'type', 'u.type', 'type_translated',
  57                  'folder', 'u.folder', 'folder_translated',
  58                  'extension_id', 'u.extension_id',
  59              );
  60          }
  61  
  62          parent::__construct($config, $factory);
  63      }
  64  
  65      /**
  66       * Method to auto-populate the model state.
  67       *
  68       * Note. Calling getState in this method will result in recursion.
  69       *
  70       * @param   string  $ordering   An optional ordering field.
  71       * @param   string  $direction  An optional direction (asc|desc).
  72       *
  73       * @return  void
  74       *
  75       * @since   1.6
  76       */
  77      protected function populateState($ordering = 'u.name', $direction = 'asc')
  78      {
  79          $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
  80          $this->setState('filter.client_id', $this->getUserStateFromRequest($this->context . '.filter.client_id', 'filter_client_id', null, 'int'));
  81          $this->setState('filter.type', $this->getUserStateFromRequest($this->context . '.filter.type', 'filter_type', '', 'string'));
  82          $this->setState('filter.folder', $this->getUserStateFromRequest($this->context . '.filter.folder', 'filter_folder', '', 'string'));
  83  
  84          $app = Factory::getApplication();
  85          $this->setState('message', $app->getUserState('com_installer.message'));
  86          $this->setState('extension_message', $app->getUserState('com_installer.extension_message'));
  87          $app->setUserState('com_installer.message', '');
  88          $app->setUserState('com_installer.extension_message', '');
  89  
  90          parent::populateState($ordering, $direction);
  91      }
  92  
  93      /**
  94       * Method to get the database query
  95       *
  96       * @return  \Joomla\Database\DatabaseQuery  The database query
  97       *
  98       * @since   1.6
  99       */
 100      protected function getListQuery()
 101      {
 102          $db = $this->getDatabase();
 103  
 104          // Grab updates ignoring new installs
 105          $query = $db->getQuery(true)
 106              ->select('u.*')
 107              ->select($db->quoteName('e.manifest_cache'))
 108              ->from($db->quoteName('#__updates', 'u'))
 109              ->join(
 110                  'LEFT',
 111                  $db->quoteName('#__extensions', 'e'),
 112                  $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('u.extension_id')
 113              )
 114              ->where($db->quoteName('u.extension_id') . ' != 0');
 115  
 116          // Process select filters.
 117          $clientId    = $this->getState('filter.client_id');
 118          $type        = $this->getState('filter.type');
 119          $folder      = $this->getState('filter.folder');
 120          $extensionId = $this->getState('filter.extension_id');
 121  
 122          if ($type) {
 123              $query->where($db->quoteName('u.type') . ' = :type')
 124                  ->bind(':type', $type);
 125          }
 126  
 127          if ($clientId != '') {
 128              $clientId = (int) $clientId;
 129              $query->where($db->quoteName('u.client_id') . ' = :clientid')
 130                  ->bind(':clientid', $clientId, ParameterType::INTEGER);
 131          }
 132  
 133          if ($folder != '' && in_array($type, array('plugin', 'library', ''))) {
 134              $folder = $folder === '*' ? '' : $folder;
 135              $query->where($db->quoteName('u.folder') . ' = :folder')
 136                  ->bind(':folder', $folder);
 137          }
 138  
 139          if ($extensionId) {
 140              $extensionId = (int) $extensionId;
 141              $query->where($db->quoteName('u.extension_id') . ' = :extensionid')
 142                  ->bind(':extensionid', $extensionId, ParameterType::INTEGER);
 143          } else {
 144              $eid = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
 145              $query->where($db->quoteName('u.extension_id') . ' != 0')
 146                  ->where($db->quoteName('u.extension_id') . ' != :eid')
 147                  ->bind(':eid', $eid, ParameterType::INTEGER);
 148          }
 149  
 150          // Process search filter.
 151          $search = $this->getState('filter.search');
 152  
 153          if (!empty($search)) {
 154              if (stripos($search, 'eid:') !== false) {
 155                  $sid = (int) substr($search, 4);
 156                  $query->where($db->quoteName('u.extension_id') . ' = :sid')
 157                      ->bind(':sid', $sid, ParameterType::INTEGER);
 158              } else {
 159                  if (stripos($search, 'uid:') !== false) {
 160                      $suid = (int) substr($search, 4);
 161                      $query->where($db->quoteName('u.update_site_id') . ' = :suid')
 162                          ->bind(':suid', $suid, ParameterType::INTEGER);
 163                  } elseif (stripos($search, 'id:') !== false) {
 164                      $uid = (int) substr($search, 3);
 165                      $query->where($db->quoteName('u.update_id') . ' = :uid')
 166                          ->bind(':uid', $uid, ParameterType::INTEGER);
 167                  } else {
 168                      $search = '%' . str_replace(' ', '%', trim($search)) . '%';
 169                      $query->where($db->quoteName('u.name') . ' LIKE :search')
 170                          ->bind(':search', $search);
 171                  }
 172              }
 173          }
 174  
 175          return $query;
 176      }
 177  
 178      /**
 179       * Translate a list of objects
 180       *
 181       * @param   array  $items  The array of objects
 182       *
 183       * @return  array The array of translated objects
 184       *
 185       * @since   3.5
 186       */
 187      protected function translate(&$items)
 188      {
 189          foreach ($items as &$item) {
 190              $item->client_translated  = Text::_([0 => 'JSITE', 1 => 'JADMINISTRATOR', 3 => 'JAPI'][$item->client_id] ?? 'JSITE');
 191              $manifest                 = json_decode($item->manifest_cache);
 192              $item->current_version    = $manifest->version ?? Text::_('JLIB_UNKNOWN');
 193              $item->description        = $item->description !== '' ? $item->description : Text::_('COM_INSTALLER_MSG_UPDATE_NODESC');
 194              $item->type_translated    = Text::_('COM_INSTALLER_TYPE_' . strtoupper($item->type));
 195              $item->folder_translated  = $item->folder ?: Text::_('COM_INSTALLER_TYPE_NONAPPLICABLE');
 196              $item->install_type       = $item->extension_id ? Text::_('COM_INSTALLER_MSG_UPDATE_UPDATE') : Text::_('COM_INSTALLER_NEW_INSTALL');
 197          }
 198  
 199          return $items;
 200      }
 201  
 202      /**
 203       * Returns an object list
 204       *
 205       * @param   DatabaseQuery  $query       The query
 206       * @param   int            $limitstart  Offset
 207       * @param   int            $limit       The number of records
 208       *
 209       * @return  array
 210       *
 211       * @since   3.5
 212       */
 213      protected function _getList($query, $limitstart = 0, $limit = 0)
 214      {
 215          $db = $this->getDatabase();
 216          $listOrder = $this->getState('list.ordering', 'u.name');
 217          $listDirn  = $this->getState('list.direction', 'asc');
 218  
 219          // Process ordering.
 220          if (in_array($listOrder, array('client_translated', 'folder_translated', 'type_translated'))) {
 221              $db->setQuery($query);
 222              $result = $db->loadObjectList();
 223              $this->translate($result);
 224              $result = ArrayHelper::sortObjects($result, $listOrder, strtolower($listDirn) === 'desc' ? -1 : 1, true, true);
 225              $total = count($result);
 226  
 227              if ($total < $limitstart) {
 228                  $limitstart = 0;
 229                  $this->setState('list.start', 0);
 230              }
 231  
 232              return array_slice($result, $limitstart, $limit ?: null);
 233          } else {
 234              $query->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn));
 235  
 236              $result = parent::_getList($query, $limitstart, $limit);
 237              $this->translate($result);
 238  
 239              return $result;
 240          }
 241      }
 242  
 243      /**
 244       * Get the count of disabled update sites
 245       *
 246       * @return  integer
 247       *
 248       * @since   3.4
 249       */
 250      public function getDisabledUpdateSites()
 251      {
 252          $db = $this->getDatabase();
 253  
 254          $query = $db->getQuery(true)
 255              ->select('COUNT(*)')
 256              ->from($db->quoteName('#__update_sites'))
 257              ->where($db->quoteName('enabled') . ' = 0');
 258  
 259          $db->setQuery($query);
 260  
 261          return $db->loadResult();
 262      }
 263  
 264      /**
 265       * Finds updates for an extension.
 266       *
 267       * @param   int  $eid               Extension identifier to look for
 268       * @param   int  $cacheTimeout      Cache timeout
 269       * @param   int  $minimumStability  Minimum stability for updates {@see Updater} (0=dev, 1=alpha, 2=beta, 3=rc, 4=stable)
 270       *
 271       * @return  boolean Result
 272       *
 273       * @since   1.6
 274       */
 275      public function findUpdates($eid = 0, $cacheTimeout = 0, $minimumStability = Updater::STABILITY_STABLE)
 276      {
 277          Updater::getInstance()->findUpdates($eid, $cacheTimeout, $minimumStability);
 278  
 279          return true;
 280      }
 281  
 282      /**
 283       * Removes all of the updates from the table.
 284       *
 285       * @return  boolean result of operation
 286       *
 287       * @since   1.6
 288       */
 289      public function purge()
 290      {
 291          $db = $this->getDatabase();
 292  
 293          try {
 294              $db->truncateTable('#__updates');
 295          } catch (ExecutionFailureException $e) {
 296              $this->_message = Text::_('JLIB_INSTALLER_FAILED_TO_PURGE_UPDATES');
 297  
 298              return false;
 299          }
 300  
 301          // Reset the last update check timestamp
 302          $query = $db->getQuery(true)
 303              ->update($db->quoteName('#__update_sites'))
 304              ->set($db->quoteName('last_check_timestamp') . ' = ' . $db->quote(0));
 305          $db->setQuery($query);
 306          $db->execute();
 307  
 308          // Clear the administrator cache
 309          $this->cleanCache('_system');
 310  
 311          $this->_message = Text::_('JLIB_INSTALLER_PURGED_UPDATES');
 312  
 313          return true;
 314      }
 315  
 316      /**
 317       * Update function.
 318       *
 319       * Sets the "result" state with the result of the operation.
 320       *
 321       * @param   int[]  $uids              List of updates to apply
 322       * @param   int    $minimumStability  The minimum allowed stability for installed updates {@see Updater}
 323       *
 324       * @return  void
 325       *
 326       * @since   1.6
 327       */
 328      public function update($uids, $minimumStability = Updater::STABILITY_STABLE)
 329      {
 330          $result = true;
 331  
 332          foreach ($uids as $uid) {
 333              $update = new Update();
 334              $instance = new \Joomla\CMS\Table\Update($this->getDatabase());
 335  
 336              if (!$instance->load($uid)) {
 337                  // Update no longer available, maybe already updated by a package.
 338                  continue;
 339              }
 340  
 341              $update->loadFromXml($instance->detailsurl, $minimumStability);
 342  
 343              // Find and use extra_query from update_site if available
 344              $updateSiteInstance = new \Joomla\CMS\Table\UpdateSite($this->getDatabase());
 345              $updateSiteInstance->load($instance->update_site_id);
 346  
 347              if ($updateSiteInstance->extra_query) {
 348                  $update->set('extra_query', $updateSiteInstance->extra_query);
 349              }
 350  
 351              $this->preparePreUpdate($update, $instance);
 352  
 353              // Install sets state and enqueues messages
 354              $res = $this->install($update);
 355  
 356              if ($res) {
 357                  $instance->delete($uid);
 358              }
 359  
 360              $result = $res & $result;
 361          }
 362  
 363          // Clear the cached extension data and menu cache
 364          $this->cleanCache('_system');
 365          $this->cleanCache('com_modules');
 366          $this->cleanCache('com_plugins');
 367          $this->cleanCache('mod_menu');
 368  
 369          // Set the final state
 370          $this->setState('result', $result);
 371      }
 372  
 373      /**
 374       * Handles the actual update installation.
 375       *
 376       * @param   Update  $update  An update definition
 377       *
 378       * @return  boolean   Result of install
 379       *
 380       * @since   1.6
 381       */
 382      private function install($update)
 383      {
 384          // Load overrides plugin.
 385          PluginHelper::importPlugin('installer');
 386  
 387          $app = Factory::getApplication();
 388  
 389          if (!isset($update->get('downloadurl')->_data)) {
 390              Factory::getApplication()->enqueueMessage(Text::_('COM_INSTALLER_INVALID_EXTENSION_UPDATE'), 'error');
 391  
 392              return false;
 393          }
 394  
 395          $url     = trim($update->downloadurl->_data);
 396          $sources = $update->get('downloadSources', array());
 397  
 398          if ($extra_query = $update->get('extra_query')) {
 399              $url .= (strpos($url, '?') === false) ? '?' : '&amp;';
 400              $url .= $extra_query;
 401          }
 402  
 403          $mirror = 0;
 404  
 405          while (!($p_file = InstallerHelper::downloadPackage($url)) && isset($sources[$mirror])) {
 406              $name = $sources[$mirror];
 407              $url  = trim($name->url);
 408  
 409              if ($extra_query) {
 410                  $url .= (strpos($url, '?') === false) ? '?' : '&amp;';
 411                  $url .= $extra_query;
 412              }
 413  
 414              $mirror++;
 415          }
 416  
 417          // Was the package downloaded?
 418          if (!$p_file) {
 419              Factory::getApplication()->enqueueMessage(Text::sprintf('COM_INSTALLER_PACKAGE_DOWNLOAD_FAILED', $url), 'error');
 420  
 421              return false;
 422          }
 423  
 424          $config   = $app->getConfig();
 425          $tmp_dest = $config->get('tmp_path');
 426  
 427          // Unpack the downloaded package file
 428          $package = InstallerHelper::unpack($tmp_dest . '/' . $p_file);
 429  
 430          if (empty($package)) {
 431              $app->enqueueMessage(Text::sprintf('COM_INSTALLER_UNPACK_ERROR', $p_file), 'error');
 432  
 433              return false;
 434          }
 435  
 436          // Get an installer instance
 437          $installer = Installer::getInstance();
 438          $update->set('type', $package['type']);
 439  
 440          // Check the package
 441          $check = InstallerHelper::isChecksumValid($package['packagefile'], $update);
 442  
 443          if ($check === InstallerHelper::HASH_NOT_VALIDATED) {
 444              $app->enqueueMessage(Text::_('COM_INSTALLER_INSTALL_CHECKSUM_WRONG'), 'error');
 445  
 446              return false;
 447          }
 448  
 449          if ($check === InstallerHelper::HASH_NOT_PROVIDED) {
 450              $app->enqueueMessage(Text::_('COM_INSTALLER_INSTALL_CHECKSUM_WARNING'), 'warning');
 451          }
 452  
 453          // Install the package
 454          if (!$installer->update($package['dir'])) {
 455              // There was an error updating the package
 456              $app->enqueueMessage(
 457                  Text::sprintf(
 458                      'COM_INSTALLER_MSG_UPDATE_ERROR',
 459                      Text::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($package['type']))
 460                  ),
 461                  'error'
 462              );
 463              $result = false;
 464          } else {
 465              // Package updated successfully
 466              $app->enqueueMessage(
 467                  Text::sprintf(
 468                      'COM_INSTALLER_MSG_UPDATE_SUCCESS',
 469                      Text::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($package['type']))
 470                  ),
 471                  'success'
 472              );
 473              $result = true;
 474          }
 475  
 476          // Quick change
 477          $this->type = $package['type'];
 478  
 479          // @todo: Reconfigure this code when you have more battery life left
 480          $this->setState('name', $installer->get('name'));
 481          $this->setState('result', $result);
 482          $app->setUserState('com_installer.message', $installer->message);
 483          $app->setUserState('com_installer.extension_message', $installer->get('extension_message'));
 484  
 485          // Cleanup the install files
 486          if (!is_file($package['packagefile'])) {
 487              $package['packagefile'] = $config->get('tmp_path') . '/' . $package['packagefile'];
 488          }
 489  
 490          InstallerHelper::cleanupInstall($package['packagefile'], $package['extractdir']);
 491  
 492          return $result;
 493      }
 494  
 495      /**
 496       * Method to get the row form.
 497       *
 498       * @param   array    $data      Data for the form.
 499       * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
 500       *
 501       * @return  Form|bool  A Form object on success, false on failure
 502       *
 503       * @since   2.5.2
 504       */
 505      public function getForm($data = array(), $loadData = true)
 506      {
 507          // Get the form.
 508          Form::addFormPath(JPATH_COMPONENT . '/models/forms');
 509          Form::addFieldPath(JPATH_COMPONENT . '/models/fields');
 510          $form = Form::getInstance('com_installer.update', 'update', array('load_data' => $loadData));
 511  
 512          // Check for an error.
 513          if ($form == false) {
 514              $this->setError($form->getMessage());
 515  
 516              return false;
 517          }
 518  
 519          // Check the session for previously entered form data.
 520          $data = $this->loadFormData();
 521  
 522          // Bind the form data if present.
 523          if (!empty($data)) {
 524              $form->bind($data);
 525          }
 526  
 527          return $form;
 528      }
 529  
 530      /**
 531       * Method to get the data that should be injected in the form.
 532       *
 533       * @return  mixed  The data for the form.
 534       *
 535       * @since   2.5.2
 536       */
 537      protected function loadFormData()
 538      {
 539          // Check the session for previously entered form data.
 540          $data = Factory::getApplication()->getUserState($this->context, array());
 541  
 542          return $data;
 543      }
 544  
 545      /**
 546       * Method to add parameters to the update
 547       *
 548       * @param   Update                    $update  An update definition
 549       * @param   \Joomla\CMS\Table\Update  $table   The update instance from the database
 550       *
 551       * @return  void
 552       *
 553       * @since   3.7.0
 554       */
 555      protected function preparePreUpdate($update, $table)
 556      {
 557          switch ($table->type) {
 558              // Components could have a helper which adds additional data
 559              case 'component':
 560                  $ename = str_replace('com_', '', $table->element);
 561                  $fname = $ename . '.php';
 562                  $cname = ucfirst($ename) . 'Helper';
 563  
 564                  $path = JPATH_ADMINISTRATOR . '/components/' . $table->element . '/helpers/' . $fname;
 565  
 566                  if (File::exists($path)) {
 567                      require_once $path;
 568  
 569                      if (class_exists($cname) && is_callable(array($cname, 'prepareUpdate'))) {
 570                          call_user_func_array(array($cname, 'prepareUpdate'), array(&$update, &$table));
 571                      }
 572                  }
 573  
 574                  break;
 575  
 576              // Modules could have a helper which adds additional data
 577              case 'module':
 578                  $cname = str_replace('_', '', $table->element) . 'Helper';
 579                  $path = ($table->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $table->element . '/helper.php';
 580  
 581                  if (File::exists($path)) {
 582                      require_once $path;
 583  
 584                      if (class_exists($cname) && is_callable(array($cname, 'prepareUpdate'))) {
 585                          call_user_func_array(array($cname, 'prepareUpdate'), array(&$update, &$table));
 586                      }
 587                  }
 588  
 589                  break;
 590  
 591              // If we have a plugin, we can use the plugin trigger "onInstallerBeforePackageDownload"
 592              // But we should make sure, that our plugin is loaded, so we don't need a second "installer" plugin
 593              case 'plugin':
 594                  $cname = str_replace('plg_', '', $table->element);
 595                  PluginHelper::importPlugin($table->folder, $cname);
 596                  break;
 597          }
 598      }
 599  
 600      /**
 601       * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
 602       *
 603       * @return DatabaseQuery
 604       *
 605       * @since 4.0.0
 606       */
 607      protected function getEmptyStateQuery()
 608      {
 609          $query = parent::getEmptyStateQuery();
 610  
 611          $query->where($this->getDatabase()->quoteName('extension_id') . ' != 0');
 612  
 613          return $query;
 614      }
 615  }


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