[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Administrator 5 * @subpackage com_joomlaupdate 6 * 7 * @copyright (C) 2012 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\Joomlaupdate\Administrator\Model; 12 13 use Joomla\CMS\Authentication\Authentication; 14 use Joomla\CMS\Component\ComponentHelper; 15 use Joomla\CMS\Extension\ExtensionHelper; 16 use Joomla\CMS\Factory; 17 use Joomla\CMS\Filesystem\File; 18 use Joomla\CMS\Filter\InputFilter; 19 use Joomla\CMS\Http\Http; 20 use Joomla\CMS\Http\HttpFactory; 21 use Joomla\CMS\Installer\Installer; 22 use Joomla\CMS\Language\Text; 23 use Joomla\CMS\Log\Log; 24 use Joomla\CMS\MVC\Model\BaseDatabaseModel; 25 use Joomla\CMS\Plugin\PluginHelper; 26 use Joomla\CMS\Updater\Update; 27 use Joomla\CMS\Updater\Updater; 28 use Joomla\CMS\User\UserHelper; 29 use Joomla\CMS\Version; 30 use Joomla\Database\ParameterType; 31 use Joomla\Registry\Registry; 32 use Joomla\Utilities\ArrayHelper; 33 34 // phpcs:disable PSR1.Files.SideEffects 35 \defined('_JEXEC') or die; 36 // phpcs:enable PSR1.Files.SideEffects 37 38 /** 39 * Joomla! update overview Model 40 * 41 * @since 2.5.4 42 */ 43 class UpdateModel extends BaseDatabaseModel 44 { 45 /** 46 * @var array $updateInformation null 47 * Holds the update information evaluated in getUpdateInformation. 48 * 49 * @since 3.10.0 50 */ 51 private $updateInformation = null; 52 53 /** 54 * Detects if the Joomla! update site currently in use matches the one 55 * configured in this component. If they don't match, it changes it. 56 * 57 * @return void 58 * 59 * @since 2.5.4 60 */ 61 public function applyUpdateSite() 62 { 63 // Determine the intended update URL. 64 $params = ComponentHelper::getParams('com_joomlaupdate'); 65 66 switch ($params->get('updatesource', 'nochange')) { 67 // "Minor & Patch Release for Current version AND Next Major Release". 68 case 'next': 69 $updateURL = 'https://update.joomla.org/core/sts/list_sts.xml'; 70 break; 71 72 // "Testing" 73 case 'testing': 74 $updateURL = 'https://update.joomla.org/core/test/list_test.xml'; 75 break; 76 77 // "Custom" 78 // @todo: check if the customurl is valid and not just "not empty". 79 case 'custom': 80 if (trim($params->get('customurl', '')) != '') { 81 $updateURL = trim($params->get('customurl', '')); 82 } else { 83 Factory::getApplication()->enqueueMessage(Text::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM_ERROR'), 'error'); 84 85 return; 86 } 87 break; 88 89 /** 90 * "Minor & Patch Release for Current version (recommended and default)". 91 * The commented "case" below are for documenting where 'default' and legacy options falls 92 * case 'default': 93 * case 'lts': 94 * case 'sts': (It's shown as "Default" because that option does not exist any more) 95 * case 'nochange': 96 */ 97 default: 98 $updateURL = 'https://update.joomla.org/core/list.xml'; 99 } 100 101 $id = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id; 102 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 103 $query = $db->getQuery(true) 104 ->select($db->quoteName('us') . '.*') 105 ->from($db->quoteName('#__update_sites_extensions', 'map')) 106 ->join( 107 'INNER', 108 $db->quoteName('#__update_sites', 'us'), 109 $db->quoteName('us.update_site_id') . ' = ' . $db->quoteName('map.update_site_id') 110 ) 111 ->where($db->quoteName('map.extension_id') . ' = :id') 112 ->bind(':id', $id, ParameterType::INTEGER); 113 $db->setQuery($query); 114 $update_site = $db->loadObject(); 115 116 if ($update_site->location != $updateURL) { 117 // Modify the database record. 118 $update_site->last_check_timestamp = 0; 119 $update_site->location = $updateURL; 120 $db->updateObject('#__update_sites', $update_site, 'update_site_id'); 121 122 // Remove cached updates. 123 $query->clear() 124 ->delete($db->quoteName('#__updates')) 125 ->where($db->quoteName('extension_id') . ' = :id') 126 ->bind(':id', $id, ParameterType::INTEGER); 127 $db->setQuery($query); 128 $db->execute(); 129 } 130 } 131 132 /** 133 * Makes sure that the Joomla! update cache is up-to-date. 134 * 135 * @param boolean $force Force reload, ignoring the cache timeout. 136 * 137 * @return void 138 * 139 * @since 2.5.4 140 */ 141 public function refreshUpdates($force = false) 142 { 143 if ($force) { 144 $cache_timeout = 0; 145 } else { 146 $update_params = ComponentHelper::getParams('com_installer'); 147 $cache_timeout = (int) $update_params->get('cachetimeout', 6); 148 $cache_timeout = 3600 * $cache_timeout; 149 } 150 151 $updater = Updater::getInstance(); 152 $minimumStability = Updater::STABILITY_STABLE; 153 $comJoomlaupdateParams = ComponentHelper::getParams('com_joomlaupdate'); 154 155 if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) { 156 $minimumStability = $comJoomlaupdateParams->get('minimum_stability', Updater::STABILITY_STABLE); 157 } 158 159 $reflection = new \ReflectionObject($updater); 160 $reflectionMethod = $reflection->getMethod('findUpdates'); 161 $methodParameters = $reflectionMethod->getParameters(); 162 163 if (count($methodParameters) >= 4) { 164 // Reinstall support is available in Updater 165 $updater->findUpdates(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id, $cache_timeout, $minimumStability, true); 166 } else { 167 $updater->findUpdates(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id, $cache_timeout, $minimumStability); 168 } 169 } 170 171 /** 172 * Makes sure that the Joomla! Update Component Update is in the database and check if there is a new version. 173 * 174 * @return boolean True if there is an update else false 175 * 176 * @since 4.0.0 177 */ 178 public function getCheckForSelfUpdate() 179 { 180 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 181 182 $query = $db->getQuery(true) 183 ->select($db->quoteName('extension_id')) 184 ->from($db->quoteName('#__extensions')) 185 ->where($db->quoteName('element') . ' = ' . $db->quote('com_joomlaupdate')); 186 $db->setQuery($query); 187 188 try { 189 // Get the component extension ID 190 $joomlaUpdateComponentId = $db->loadResult(); 191 } catch (\RuntimeException $e) { 192 // Something is wrong here! 193 $joomlaUpdateComponentId = 0; 194 Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); 195 } 196 197 // Try the update only if we have an extension id 198 if ($joomlaUpdateComponentId != 0) { 199 // Always force to check for an update! 200 $cache_timeout = 0; 201 202 $updater = Updater::getInstance(); 203 $updater->findUpdates($joomlaUpdateComponentId, $cache_timeout, Updater::STABILITY_STABLE); 204 205 // Fetch the update information from the database. 206 $query = $db->getQuery(true) 207 ->select('*') 208 ->from($db->quoteName('#__updates')) 209 ->where($db->quoteName('extension_id') . ' = :id') 210 ->bind(':id', $joomlaUpdateComponentId, ParameterType::INTEGER); 211 $db->setQuery($query); 212 213 try { 214 $joomlaUpdateComponentObject = $db->loadObject(); 215 } catch (\RuntimeException $e) { 216 // Something is wrong here! 217 $joomlaUpdateComponentObject = null; 218 Factory::getApplication()->enqueueMessage($e->getMessage(), 'error'); 219 } 220 221 return !empty($joomlaUpdateComponentObject); 222 } 223 224 return false; 225 } 226 227 /** 228 * Returns an array with the Joomla! update information. 229 * 230 * @return array 231 * 232 * @since 2.5.4 233 */ 234 public function getUpdateInformation() 235 { 236 if ($this->updateInformation) { 237 return $this->updateInformation; 238 } 239 240 // Initialise the return array. 241 $this->updateInformation = array( 242 'installed' => \JVERSION, 243 'latest' => null, 244 'object' => null, 245 'hasUpdate' => false, 246 'current' => JVERSION // This is deprecated please use 'installed' or JVERSION directly 247 ); 248 249 // Fetch the update information from the database. 250 $id = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id; 251 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 252 $query = $db->getQuery(true) 253 ->select('*') 254 ->from($db->quoteName('#__updates')) 255 ->where($db->quoteName('extension_id') . ' = :id') 256 ->bind(':id', $id, ParameterType::INTEGER); 257 $db->setQuery($query); 258 $updateObject = $db->loadObject(); 259 260 if (is_null($updateObject)) { 261 // We have not found any update in the database - we seem to be running the latest version. 262 $this->updateInformation['latest'] = \JVERSION; 263 264 return $this->updateInformation; 265 } 266 267 // Check whether this is a valid update or not 268 if (version_compare($updateObject->version, JVERSION, '<')) { 269 // This update points to an outdated version. We should not offer to update to this. 270 $this->updateInformation['latest'] = JVERSION; 271 272 return $this->updateInformation; 273 } 274 275 $minimumStability = Updater::STABILITY_STABLE; 276 $comJoomlaupdateParams = ComponentHelper::getParams('com_joomlaupdate'); 277 278 if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) { 279 $minimumStability = $comJoomlaupdateParams->get('minimum_stability', Updater::STABILITY_STABLE); 280 } 281 282 // Fetch the full update details from the update details URL. 283 $update = new Update(); 284 $update->loadFromXml($updateObject->detailsurl, $minimumStability); 285 286 // Make sure we use the current information we got from the detailsurl 287 $this->updateInformation['object'] = $update; 288 $this->updateInformation['latest'] = $updateObject->version; 289 290 // Check whether this is an update or not. 291 if (version_compare($this->updateInformation['latest'], JVERSION, '>')) { 292 $this->updateInformation['hasUpdate'] = true; 293 } 294 295 return $this->updateInformation; 296 } 297 298 /** 299 * Removes all of the updates from the table and enable all update streams. 300 * 301 * @return boolean Result of operation. 302 * 303 * @since 3.0 304 */ 305 public function purge() 306 { 307 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 308 309 // Modify the database record 310 $update_site = new \stdClass(); 311 $update_site->last_check_timestamp = 0; 312 $update_site->enabled = 1; 313 $update_site->update_site_id = 1; 314 $db->updateObject('#__update_sites', $update_site, 'update_site_id'); 315 316 $query = $db->getQuery(true) 317 ->delete($db->quoteName('#__updates')) 318 ->where($db->quoteName('update_site_id') . ' = 1'); 319 $db->setQuery($query); 320 321 if ($db->execute()) { 322 $this->_message = Text::_('COM_JOOMLAUPDATE_CHECKED_UPDATES'); 323 324 return true; 325 } else { 326 $this->_message = Text::_('COM_JOOMLAUPDATE_FAILED_TO_CHECK_UPDATES'); 327 328 return false; 329 } 330 } 331 332 /** 333 * Downloads the update package to the site. 334 * 335 * @return array 336 * 337 * @since 2.5.4 338 */ 339 public function download() 340 { 341 $updateInfo = $this->getUpdateInformation(); 342 $packageURL = trim($updateInfo['object']->downloadurl->_data); 343 $sources = $updateInfo['object']->get('downloadSources', array()); 344 345 // We have to manually follow the redirects here so we set the option to false. 346 $httpOptions = new Registry(); 347 $httpOptions->set('follow_location', false); 348 349 try { 350 $head = HttpFactory::getHttp($httpOptions)->head($packageURL); 351 } catch (\RuntimeException $e) { 352 // Passing false here -> download failed message 353 $response['basename'] = false; 354 355 return $response; 356 } 357 358 // Follow the Location headers until the actual download URL is known 359 while (isset($head->headers['location'])) { 360 $packageURL = (string) $head->headers['location'][0]; 361 362 try { 363 $head = HttpFactory::getHttp($httpOptions)->head($packageURL); 364 } catch (\RuntimeException $e) { 365 // Passing false here -> download failed message 366 $response['basename'] = false; 367 368 return $response; 369 } 370 } 371 372 // Remove protocol, path and query string from URL 373 $basename = basename($packageURL); 374 375 if (strpos($basename, '?') !== false) { 376 $basename = substr($basename, 0, strpos($basename, '?')); 377 } 378 379 // Find the path to the temp directory and the local package. 380 $tempdir = (string) InputFilter::getInstance( 381 [], 382 [], 383 InputFilter::ONLY_BLOCK_DEFINED_TAGS, 384 InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES 385 ) 386 ->clean(Factory::getApplication()->get('tmp_path'), 'path'); 387 $target = $tempdir . '/' . $basename; 388 $response = []; 389 390 // Do we have a cached file? 391 $exists = File::exists($target); 392 393 if (!$exists) { 394 // Not there, let's fetch it. 395 $mirror = 0; 396 397 while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) { 398 $name = $sources[$mirror]; 399 $packageURL = trim($name->url); 400 $mirror++; 401 } 402 403 $response['basename'] = $download; 404 } else { 405 // Is it a 0-byte file? If so, re-download please. 406 $filesize = @filesize($target); 407 408 if (empty($filesize)) { 409 $mirror = 0; 410 411 while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) { 412 $name = $sources[$mirror]; 413 $packageURL = trim($name->url); 414 $mirror++; 415 } 416 417 $response['basename'] = $download; 418 } 419 420 // Yes, it's there, skip downloading. 421 $response['basename'] = $basename; 422 } 423 424 $response['check'] = $this->isChecksumValid($target, $updateInfo['object']); 425 426 return $response; 427 } 428 429 /** 430 * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest 431 * 432 * @param string $packagefile Location of the package to be installed 433 * @param Update $updateObject The Update Object 434 * 435 * @return boolean False in case the validation did not work; true in any other case. 436 * 437 * @note This method has been forked from (JInstallerHelper::isChecksumValid) so it 438 * does not depend on an up-to-date InstallerHelper at the update time 439 * 440 * @since 3.9.0 441 */ 442 private function isChecksumValid($packagefile, $updateObject) 443 { 444 $hashes = array('sha256', 'sha384', 'sha512'); 445 446 foreach ($hashes as $hash) { 447 if ($updateObject->get($hash, false)) { 448 $hashPackage = hash_file($hash, $packagefile); 449 $hashRemote = $updateObject->$hash->_data; 450 451 if ($hashPackage !== $hashRemote) { 452 // Return false in case the hash did not match 453 return false; 454 } 455 } 456 } 457 458 // Well nothing was provided or all worked 459 return true; 460 } 461 462 /** 463 * Downloads a package file to a specific directory 464 * 465 * @param string $url The URL to download from 466 * @param string $target The directory to store the file 467 * 468 * @return boolean True on success 469 * 470 * @since 2.5.4 471 */ 472 protected function downloadPackage($url, $target) 473 { 474 try { 475 Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), Log::INFO, 'Update'); 476 } catch (\RuntimeException $exception) { 477 // Informational log only 478 } 479 480 // Make sure the target does not exist. 481 File::delete($target); 482 483 // Download the package 484 try { 485 $result = HttpFactory::getHttp([], ['curl', 'stream'])->get($url); 486 } catch (\RuntimeException $e) { 487 return false; 488 } 489 490 if (!$result || ($result->code != 200 && $result->code != 310)) { 491 return false; 492 } 493 494 // Write the file to disk 495 File::write($target, $result->body); 496 497 return basename($target); 498 } 499 500 /** 501 * Backwards compatibility. Use createUpdateFile() instead. 502 * 503 * @param null $basename The basename of the file to create 504 * 505 * @return boolean 506 * @since 2.5.1 507 * @deprecated 5.0 508 */ 509 public function createRestorationFile($basename = null): bool 510 { 511 return $this->createUpdateFile($basename); 512 } 513 514 /** 515 * Create the update.php file and trigger onJoomlaBeforeUpdate event. 516 * 517 * The onJoomlaBeforeUpdate event stores the core files for which overrides have been defined. 518 * This will be compared in the onJoomlaAfterUpdate event with the current filesystem state, 519 * thereby determining how many and which overrides need to be checked and possibly updated 520 * after Joomla installed an update. 521 * 522 * @param string $basename Optional base path to the file. 523 * 524 * @return boolean True if successful; false otherwise. 525 * 526 * @since 2.5.4 527 */ 528 public function createUpdateFile($basename = null): bool 529 { 530 // Load overrides plugin. 531 PluginHelper::importPlugin('installer'); 532 533 // Get a password 534 $password = UserHelper::genRandomPassword(32); 535 $app = Factory::getApplication(); 536 537 // Trigger event before joomla update. 538 $app->triggerEvent('onJoomlaBeforeUpdate'); 539 540 // Get the absolute path to site's root. 541 $siteroot = JPATH_SITE; 542 543 // If the package name is not specified, get it from the update info. 544 if (empty($basename)) { 545 $updateInfo = $this->getUpdateInformation(); 546 $packageURL = $updateInfo['object']->downloadurl->_data; 547 $basename = basename($packageURL); 548 } 549 550 // Get the package name. 551 $config = $app->getConfig(); 552 $tempdir = $config->get('tmp_path'); 553 $file = $tempdir . '/' . $basename; 554 555 $filesize = @filesize($file); 556 $app->setUserState('com_joomlaupdate.password', $password); 557 $app->setUserState('com_joomlaupdate.filesize', $filesize); 558 559 $data = "<?php\ndefined('_JOOMLA_UPDATE') or die('Restricted access');\n"; 560 $data .= '$extractionSetup = [' . "\n"; 561 $data .= <<<ENDDATA 562 'security.password' => '$password', 563 'setup.sourcefile' => '$file', 564 'setup.destdir' => '$siteroot', 565 ENDDATA; 566 567 $data .= '];'; 568 569 // Remove the old file, if it's there... 570 $configpath = JPATH_COMPONENT_ADMINISTRATOR . '/update.php'; 571 572 if (File::exists($configpath)) { 573 if (!File::delete($configpath)) { 574 File::invalidateFileCache($configpath); 575 @unlink($configpath); 576 } 577 } 578 579 // Write new file. First try with File. 580 $result = File::write($configpath, $data); 581 582 // In case File used FTP but direct access could help. 583 if (!$result) { 584 if (function_exists('file_put_contents')) { 585 $result = @file_put_contents($configpath, $data); 586 587 if ($result !== false) { 588 $result = true; 589 } 590 } else { 591 $fp = @fopen($configpath, 'wt'); 592 593 if ($fp !== false) { 594 $result = @fwrite($fp, $data); 595 596 if ($result !== false) { 597 $result = true; 598 } 599 600 @fclose($fp); 601 } 602 } 603 } 604 605 return $result; 606 } 607 608 /** 609 * Finalise the upgrade. 610 * 611 * This method will do the following: 612 * * Run the schema update SQL files. 613 * * Run the Joomla post-update script. 614 * * Update the manifest cache and #__extensions entry for Joomla itself. 615 * 616 * It performs essentially the same function as InstallerFile::install() without the file copy. 617 * 618 * @return boolean True on success. 619 * 620 * @since 2.5.4 621 */ 622 public function finaliseUpgrade() 623 { 624 $installer = Installer::getInstance(); 625 626 $manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml'); 627 628 if ($manifest === false) { 629 $installer->abort(Text::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST')); 630 631 return false; 632 } 633 634 $installer->manifest = $manifest; 635 636 $installer->setUpgrade(true); 637 $installer->setOverwrite(true); 638 639 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 640 $installer->extension = new \Joomla\CMS\Table\Extension($db); 641 $installer->extension->load(ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id); 642 643 $installer->setAdapter($installer->extension->type); 644 645 $installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml'); 646 $installer->setPath('source', JPATH_MANIFESTS . '/files'); 647 $installer->setPath('extension_root', JPATH_ROOT); 648 649 // Run the script file. 650 \JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php'); 651 652 $manifestClass = new \JoomlaInstallerScript(); 653 654 ob_start(); 655 ob_implicit_flush(false); 656 657 if ($manifestClass && method_exists($manifestClass, 'preflight')) { 658 if ($manifestClass->preflight('update', $installer) === false) { 659 $installer->abort( 660 Text::sprintf( 661 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE', 662 Text::_('JLIB_INSTALLER_INSTALL') 663 ) 664 ); 665 666 return false; 667 } 668 } 669 670 // Create msg object; first use here. 671 $msg = ob_get_contents(); 672 ob_end_clean(); 673 674 // Get a database connector object. 675 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 676 677 /* 678 * Check to see if a file extension by the same name is already installed. 679 * If it is, then update the table because if the files aren't there 680 * we can assume that it was (badly) uninstalled. 681 * If it isn't, add an entry to extensions. 682 */ 683 $query = $db->getQuery(true) 684 ->select($db->quoteName('extension_id')) 685 ->from($db->quoteName('#__extensions')) 686 ->where($db->quoteName('type') . ' = ' . $db->quote('file')) 687 ->where($db->quoteName('element') . ' = ' . $db->quote('joomla')); 688 $db->setQuery($query); 689 690 try { 691 $db->execute(); 692 } catch (\RuntimeException $e) { 693 // Install failed, roll back changes. 694 $installer->abort( 695 Text::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', Text::_('JLIB_INSTALLER_UPDATE'), $e->getMessage()) 696 ); 697 698 return false; 699 } 700 701 $id = $db->loadResult(); 702 $row = new \Joomla\CMS\Table\Extension($db); 703 704 if ($id) { 705 // Load the entry and update the manifest_cache. 706 $row->load($id); 707 708 // Update name. 709 $row->set('name', 'files_joomla'); 710 711 // Update manifest. 712 $row->manifest_cache = $installer->generateManifestCache(); 713 714 if (!$row->store()) { 715 // Install failed, roll back changes. 716 $installer->abort( 717 Text::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', Text::_('JLIB_INSTALLER_UPDATE'), $row->getError()) 718 ); 719 720 return false; 721 } 722 } else { 723 // Add an entry to the extension table with a whole heap of defaults. 724 $row->set('name', 'files_joomla'); 725 $row->set('type', 'file'); 726 $row->set('element', 'joomla'); 727 728 // There is no folder for files so leave it blank. 729 $row->set('folder', ''); 730 $row->set('enabled', 1); 731 $row->set('protected', 0); 732 $row->set('access', 0); 733 $row->set('client_id', 0); 734 $row->set('params', ''); 735 $row->set('manifest_cache', $installer->generateManifestCache()); 736 737 if (!$row->store()) { 738 // Install failed, roll back changes. 739 $installer->abort(Text::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError())); 740 741 return false; 742 } 743 744 // Set the insert id. 745 $row->set('extension_id', $db->insertid()); 746 747 // Since we have created a module item, we add it to the installation step stack 748 // so that if we have to rollback the changes we can undo it. 749 $installer->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id)); 750 } 751 752 $result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id); 753 754 if ($result === false) { 755 // Install failed, rollback changes (message already logged by the installer). 756 $installer->abort(); 757 758 return false; 759 } 760 761 // Reinitialise the installer's extensions table's properties. 762 $installer->extension->getFields(true); 763 764 // Start Joomla! 1.6. 765 ob_start(); 766 ob_implicit_flush(false); 767 768 if ($manifestClass && method_exists($manifestClass, 'update')) { 769 if ($manifestClass->update($installer) === false) { 770 // Install failed, rollback changes. 771 $installer->abort( 772 Text::sprintf( 773 'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE', 774 Text::_('JLIB_INSTALLER_INSTALL') 775 ) 776 ); 777 778 return false; 779 } 780 } 781 782 // Append messages. 783 $msg .= ob_get_contents(); 784 ob_end_clean(); 785 786 // Clobber any possible pending updates. 787 $update = new \Joomla\CMS\Table\Update($db); 788 $uid = $update->find( 789 array('element' => 'joomla', 'type' => 'file', 'client_id' => '0', 'folder' => '') 790 ); 791 792 if ($uid) { 793 $update->delete($uid); 794 } 795 796 // And now we run the postflight. 797 ob_start(); 798 ob_implicit_flush(false); 799 800 if ($manifestClass && method_exists($manifestClass, 'postflight')) { 801 $manifestClass->postflight('update', $installer); 802 } 803 804 // Append messages. 805 $msg .= ob_get_contents(); 806 ob_end_clean(); 807 808 if ($msg != '') { 809 $installer->set('extension_message', $msg); 810 } 811 812 // Refresh versionable assets cache. 813 Factory::getApplication()->flushAssets(); 814 815 return true; 816 } 817 818 /** 819 * Removes the extracted package file and trigger onJoomlaAfterUpdate event. 820 * 821 * The onJoomlaAfterUpdate event compares the stored list of files previously overridden with 822 * the updated core files, finding out which files have changed during the update, thereby 823 * determining how many and which override files need to be checked and possibly updated after 824 * the Joomla update. 825 * 826 * @return void 827 * 828 * @since 2.5.4 829 */ 830 public function cleanUp() 831 { 832 // Load overrides plugin. 833 PluginHelper::importPlugin('installer'); 834 835 $app = Factory::getApplication(); 836 837 // Trigger event after joomla update. 838 $app->triggerEvent('onJoomlaAfterUpdate'); 839 840 // Remove the update package. 841 $tempdir = $app->get('tmp_path'); 842 843 $file = $app->getUserState('com_joomlaupdate.file', null); 844 File::delete($tempdir . '/' . $file); 845 846 // Remove the update.php file used in Joomla 4.0.3 and later. 847 if (File::exists(JPATH_COMPONENT_ADMINISTRATOR . '/update.php')) { 848 File::delete(JPATH_COMPONENT_ADMINISTRATOR . '/update.php'); 849 } 850 851 // Remove the legacy restoration.php file (when updating from Joomla 4.0.2 and earlier). 852 if (File::exists(JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php')) { 853 File::delete(JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php'); 854 } 855 856 // Remove the legacy restore_finalisation.php file used in Joomla 4.0.2 and earlier. 857 if (File::exists(JPATH_COMPONENT_ADMINISTRATOR . '/restore_finalisation.php')) { 858 File::delete(JPATH_COMPONENT_ADMINISTRATOR . '/restore_finalisation.php'); 859 } 860 861 // Remove joomla.xml from the site's root. 862 if (File::exists(JPATH_ROOT . '/joomla.xml')) { 863 File::delete(JPATH_ROOT . '/joomla.xml'); 864 } 865 866 // Unset the update filename from the session. 867 $app = Factory::getApplication(); 868 $app->setUserState('com_joomlaupdate.file', null); 869 $oldVersion = $app->getUserState('com_joomlaupdate.oldversion'); 870 871 // Trigger event after joomla update. 872 $app->triggerEvent('onJoomlaAfterUpdate', array($oldVersion)); 873 $app->setUserState('com_joomlaupdate.oldversion', null); 874 } 875 876 /** 877 * Uploads what is presumably an update ZIP file under a mangled name in the temporary directory. 878 * 879 * @return void 880 * 881 * @since 3.6.0 882 */ 883 public function upload() 884 { 885 // Get the uploaded file information. 886 $input = Factory::getApplication()->input; 887 888 // Do not change the filter type 'raw'. We need this to let files containing PHP code to upload. See \JInputFiles::get. 889 $userfile = $input->files->get('install_package', null, 'raw'); 890 891 // Make sure that file uploads are enabled in php. 892 if (!(bool) ini_get('file_uploads')) { 893 throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLFILE'), 500); 894 } 895 896 // Make sure that zlib is loaded so that the package can be unpacked. 897 if (!extension_loaded('zlib')) { 898 throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLZLIB'), 500); 899 } 900 901 // If there is no uploaded file, we have a problem... 902 if (!is_array($userfile)) { 903 throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_NO_FILE_SELECTED'), 500); 904 } 905 906 // Is the PHP tmp directory missing? 907 if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_NO_TMP_DIR)) { 908 throw new \RuntimeException( 909 Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br>' . 910 Text::_('COM_INSTALLER_MSG_WARNINGS_PHPUPLOADNOTSET'), 911 500 912 ); 913 } 914 915 // Is the max upload size too small in php.ini? 916 if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_INI_SIZE)) { 917 throw new \RuntimeException( 918 Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br>' . Text::_('COM_INSTALLER_MSG_WARNINGS_SMALLUPLOADSIZE'), 919 500 920 ); 921 } 922 923 // Check if there was a different problem uploading the file. 924 if ($userfile['error'] || $userfile['size'] < 1) { 925 throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500); 926 } 927 928 // Build the appropriate paths. 929 $tmp_dest = tempnam(Factory::getApplication()->get('tmp_path'), 'ju'); 930 $tmp_src = $userfile['tmp_name']; 931 932 // Move uploaded file. 933 $result = File::upload($tmp_src, $tmp_dest, false, true); 934 935 if (!$result) { 936 throw new \RuntimeException(Text::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500); 937 } 938 939 Factory::getApplication()->setUserState('com_joomlaupdate.temp_file', $tmp_dest); 940 } 941 942 /** 943 * Checks the super admin credentials are valid for the currently logged in users 944 * 945 * @param array $credentials The credentials to authenticate the user with 946 * 947 * @return boolean 948 * 949 * @since 3.6.0 950 */ 951 public function captiveLogin($credentials) 952 { 953 // Make sure the username matches 954 $username = $credentials['username'] ?? null; 955 $user = Factory::getUser(); 956 957 if (strtolower($user->username) != strtolower($username)) { 958 return false; 959 } 960 961 // Make sure the user is authorised 962 if (!$user->authorise('core.admin')) { 963 return false; 964 } 965 966 // Get the global Authentication object. 967 $authenticate = Authentication::getInstance(); 968 $response = $authenticate->authenticate($credentials); 969 970 if ($response->status !== Authentication::STATUS_SUCCESS) { 971 return false; 972 } 973 974 return true; 975 } 976 977 /** 978 * Does the captive (temporary) file we uploaded before still exist? 979 * 980 * @return boolean 981 * 982 * @since 3.6.0 983 */ 984 public function captiveFileExists() 985 { 986 $file = Factory::getApplication()->getUserState('com_joomlaupdate.temp_file', null); 987 988 if (empty($file) || !File::exists($file)) { 989 return false; 990 } 991 992 return true; 993 } 994 995 /** 996 * Remove the captive (temporary) file we uploaded before and the . 997 * 998 * @return void 999 * 1000 * @since 3.6.0 1001 */ 1002 public function removePackageFiles() 1003 { 1004 $files = array( 1005 Factory::getApplication()->getUserState('com_joomlaupdate.temp_file', null), 1006 Factory::getApplication()->getUserState('com_joomlaupdate.file', null), 1007 ); 1008 1009 foreach ($files as $file) { 1010 if ($file !== null && File::exists($file)) { 1011 File::delete($file); 1012 } 1013 } 1014 } 1015 1016 /** 1017 * Gets PHP options. 1018 * @todo: Outsource, build common code base for pre install and pre update check 1019 * 1020 * @return array Array of PHP config options 1021 * 1022 * @since 3.10.0 1023 */ 1024 public function getPhpOptions() 1025 { 1026 $options = array(); 1027 1028 /* 1029 * Check the PHP Version. It is already checked in Update. 1030 * A Joomla! Update which is not supported by current PHP 1031 * version is not shown. So this check is actually unnecessary. 1032 */ 1033 $option = new \stdClass(); 1034 $option->label = Text::sprintf('INSTL_PHP_VERSION_NEWER', $this->getTargetMinimumPHPVersion()); 1035 $option->state = $this->isPhpVersionSupported(); 1036 $option->notice = null; 1037 $options[] = $option; 1038 1039 // Check for zlib support. 1040 $option = new \stdClass(); 1041 $option->label = Text::_('INSTL_ZLIB_COMPRESSION_SUPPORT'); 1042 $option->state = extension_loaded('zlib'); 1043 $option->notice = null; 1044 $options[] = $option; 1045 1046 // Check for XML support. 1047 $option = new \stdClass(); 1048 $option->label = Text::_('INSTL_XML_SUPPORT'); 1049 $option->state = extension_loaded('xml'); 1050 $option->notice = null; 1051 $options[] = $option; 1052 1053 // Check for mbstring options. 1054 if (extension_loaded('mbstring')) { 1055 // Check for default MB language. 1056 $option = new \stdClass(); 1057 $option->label = Text::_('INSTL_MB_LANGUAGE_IS_DEFAULT'); 1058 $option->state = strtolower(ini_get('mbstring.language')) === 'neutral'; 1059 $option->notice = $option->state ? null : Text::_('INSTL_NOTICEMBLANGNOTDEFAULT'); 1060 $options[] = $option; 1061 1062 // Check for MB function overload. 1063 $option = new \stdClass(); 1064 $option->label = Text::_('INSTL_MB_STRING_OVERLOAD_OFF'); 1065 $option->state = ini_get('mbstring.func_overload') == 0; 1066 $option->notice = $option->state ? null : Text::_('INSTL_NOTICEMBSTRINGOVERLOAD'); 1067 $options[] = $option; 1068 } 1069 1070 // Check for a missing native parse_ini_file implementation. 1071 $option = new \stdClass(); 1072 $option->label = Text::_('INSTL_PARSE_INI_FILE_AVAILABLE'); 1073 $option->state = $this->getIniParserAvailability(); 1074 $option->notice = null; 1075 $options[] = $option; 1076 1077 // Check for missing native json_encode / json_decode support. 1078 $option = new \stdClass(); 1079 $option->label = Text::_('INSTL_JSON_SUPPORT_AVAILABLE'); 1080 $option->state = function_exists('json_encode') && function_exists('json_decode'); 1081 $option->notice = null; 1082 $options[] = $option; 1083 $updateInformation = $this->getUpdateInformation(); 1084 1085 // Check if configured database is compatible with the next major version of Joomla 1086 $nextMajorVersion = Version::MAJOR_VERSION + 1; 1087 1088 if (version_compare($updateInformation['latest'], (string) $nextMajorVersion, '>=')) { 1089 $option = new \stdClass(); 1090 $option->label = Text::sprintf('INSTL_DATABASE_SUPPORTED', $this->getConfiguredDatabaseType()); 1091 $option->state = $this->isDatabaseTypeSupported(); 1092 $option->notice = null; 1093 $options[] = $option; 1094 } 1095 1096 // Check if database structure is up to date 1097 $option = new \stdClass(); 1098 $option->label = Text::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_TITLE'); 1099 $option->state = $this->getDatabaseSchemaCheck(); 1100 $option->notice = $option->state ? null : Text::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_NOTICE'); 1101 $options[] = $option; 1102 1103 return $options; 1104 } 1105 1106 /** 1107 * Gets PHP Settings. 1108 * @todo: Outsource, build common code base for pre install and pre update check 1109 * 1110 * @return array 1111 * 1112 * @since 3.10.0 1113 */ 1114 public function getPhpSettings() 1115 { 1116 $settings = array(); 1117 1118 // Check for display errors. 1119 $setting = new \stdClass(); 1120 $setting->label = Text::_('INSTL_DISPLAY_ERRORS'); 1121 $setting->state = (bool) ini_get('display_errors'); 1122 $setting->recommended = false; 1123 $settings[] = $setting; 1124 1125 // Check for file uploads. 1126 $setting = new \stdClass(); 1127 $setting->label = Text::_('INSTL_FILE_UPLOADS'); 1128 $setting->state = (bool) ini_get('file_uploads'); 1129 $setting->recommended = true; 1130 $settings[] = $setting; 1131 1132 // Check for output buffering. 1133 $setting = new \stdClass(); 1134 $setting->label = Text::_('INSTL_OUTPUT_BUFFERING'); 1135 $setting->state = (int) ini_get('output_buffering') !== 0; 1136 $setting->recommended = false; 1137 $settings[] = $setting; 1138 1139 // Check for session auto-start. 1140 $setting = new \stdClass(); 1141 $setting->label = Text::_('INSTL_SESSION_AUTO_START'); 1142 $setting->state = (bool) ini_get('session.auto_start'); 1143 $setting->recommended = false; 1144 $settings[] = $setting; 1145 1146 // Check for native ZIP support. 1147 $setting = new \stdClass(); 1148 $setting->label = Text::_('INSTL_ZIP_SUPPORT_AVAILABLE'); 1149 $setting->state = function_exists('zip_open') && function_exists('zip_read'); 1150 $setting->recommended = true; 1151 $settings[] = $setting; 1152 1153 // Check for GD support 1154 $setting = new \stdClass(); 1155 $setting->label = Text::sprintf('INSTL_EXTENSION_AVAILABLE', 'GD'); 1156 $setting->state = extension_loaded('gd'); 1157 $setting->recommended = true; 1158 $settings[] = $setting; 1159 1160 // Check for iconv support 1161 $setting = new \stdClass(); 1162 $setting->label = Text::sprintf('INSTL_EXTENSION_AVAILABLE', 'iconv'); 1163 $setting->state = function_exists('iconv'); 1164 $setting->recommended = true; 1165 $settings[] = $setting; 1166 1167 // Check for intl support 1168 $setting = new \stdClass(); 1169 $setting->label = Text::sprintf('INSTL_EXTENSION_AVAILABLE', 'intl'); 1170 $setting->state = function_exists('transliterator_transliterate'); 1171 $setting->recommended = true; 1172 $settings[] = $setting; 1173 1174 return $settings; 1175 } 1176 1177 /** 1178 * Returns the configured database type id (mysqli or sqlsrv or ...) 1179 * 1180 * @return string 1181 * 1182 * @since 3.10.0 1183 */ 1184 private function getConfiguredDatabaseType() 1185 { 1186 return Factory::getApplication()->get('dbtype'); 1187 } 1188 1189 /** 1190 * Returns true, if J! version is < 4 or current configured 1191 * database type is compatible with the update. 1192 * 1193 * @return boolean 1194 * 1195 * @since 3.10.0 1196 */ 1197 public function isDatabaseTypeSupported() 1198 { 1199 $updateInformation = $this->getUpdateInformation(); 1200 $nextMajorVersion = Version::MAJOR_VERSION + 1; 1201 1202 // Check if configured database is compatible with Joomla 4 1203 if (version_compare($updateInformation['latest'], (string) $nextMajorVersion, '>=')) { 1204 $unsupportedDatabaseTypes = array('sqlsrv', 'sqlazure'); 1205 $currentDatabaseType = $this->getConfiguredDatabaseType(); 1206 1207 return !in_array($currentDatabaseType, $unsupportedDatabaseTypes); 1208 } 1209 1210 return true; 1211 } 1212 1213 1214 /** 1215 * Returns true, if current installed php version is compatible with the update. 1216 * 1217 * @return boolean 1218 * 1219 * @since 3.10.0 1220 */ 1221 public function isPhpVersionSupported() 1222 { 1223 return version_compare(PHP_VERSION, $this->getTargetMinimumPHPVersion(), '>='); 1224 } 1225 1226 /** 1227 * Returns the PHP minimum version for the update. 1228 * Returns JOOMLA_MINIMUM_PHP, if there is no information given. 1229 * 1230 * @return string 1231 * 1232 * @since 3.10.0 1233 */ 1234 private function getTargetMinimumPHPVersion() 1235 { 1236 $updateInformation = $this->getUpdateInformation(); 1237 1238 return isset($updateInformation['object']->php_minimum) ? 1239 $updateInformation['object']->php_minimum->_data : 1240 JOOMLA_MINIMUM_PHP; 1241 } 1242 1243 /** 1244 * Checks the availability of the parse_ini_file and parse_ini_string functions. 1245 * @todo: Outsource, build common code base for pre install and pre update check 1246 * 1247 * @return boolean True if the method exists. 1248 * 1249 * @since 3.10.0 1250 */ 1251 public function getIniParserAvailability() 1252 { 1253 $disabledFunctions = ini_get('disable_functions'); 1254 1255 if (!empty($disabledFunctions)) { 1256 // Attempt to detect them in the PHP INI disable_functions variable. 1257 $disabledFunctions = explode(',', trim($disabledFunctions)); 1258 $numberOfDisabledFunctions = count($disabledFunctions); 1259 1260 for ($i = 0; $i < $numberOfDisabledFunctions; $i++) { 1261 $disabledFunctions[$i] = trim($disabledFunctions[$i]); 1262 } 1263 1264 $result = !in_array('parse_ini_string', $disabledFunctions); 1265 } else { 1266 // Attempt to detect their existence; even pure PHP implementations of them will trigger a positive response, though. 1267 $result = function_exists('parse_ini_string'); 1268 } 1269 1270 return $result; 1271 } 1272 1273 1274 /** 1275 * Check if database structure is up to date 1276 * 1277 * @return boolean True if ok, false if not. 1278 * 1279 * @since 3.10.0 1280 */ 1281 private function getDatabaseSchemaCheck(): bool 1282 { 1283 $mvcFactory = $this->bootComponent('com_installer')->getMVCFactory(); 1284 1285 /** @var \Joomla\Component\Installer\Administrator\Model\DatabaseModel $model */ 1286 $model = $mvcFactory->createModel('Database', 'Administrator'); 1287 1288 // Check if no default text filters found 1289 if (!$model->getDefaultTextFilters()) { 1290 return false; 1291 } 1292 1293 $coreExtensionInfo = \Joomla\CMS\Extension\ExtensionHelper::getExtensionRecord('joomla', 'file'); 1294 $cache = new \Joomla\Registry\Registry($coreExtensionInfo->manifest_cache); 1295 1296 $updateVersion = $cache->get('version'); 1297 1298 // Check if database update version does not match CMS version 1299 if (version_compare($updateVersion, JVERSION) != 0) { 1300 return false; 1301 } 1302 1303 // Ensure we only get information for core 1304 $model->setState('filter.extension_id', $coreExtensionInfo->extension_id); 1305 1306 // We're filtering by a single extension which must always exist - so can safely access this through 1307 // element 0 of the array 1308 $changeInformation = $model->getItems()[0]; 1309 1310 // Check if schema errors found 1311 if ($changeInformation['errorsCount'] !== 0) { 1312 return false; 1313 } 1314 1315 // Check if database schema version does not match CMS version 1316 if ($model->getSchemaVersion($coreExtensionInfo->extension_id) != $changeInformation['schema']) { 1317 return false; 1318 } 1319 1320 // No database problems found 1321 return true; 1322 } 1323 1324 /** 1325 * Gets an array containing all installed extensions, that are not core extensions. 1326 * 1327 * @return array name,version,updateserver 1328 * 1329 * @since 3.10.0 1330 */ 1331 public function getNonCoreExtensions() 1332 { 1333 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 1334 $query = $db->getQuery(true); 1335 1336 $query->select( 1337 [ 1338 $db->quoteName('ex.name'), 1339 $db->quoteName('ex.extension_id'), 1340 $db->quoteName('ex.manifest_cache'), 1341 $db->quoteName('ex.type'), 1342 $db->quoteName('ex.folder'), 1343 $db->quoteName('ex.element'), 1344 $db->quoteName('ex.client_id'), 1345 ] 1346 ) 1347 ->from($db->quoteName('#__extensions', 'ex')) 1348 ->where($db->quoteName('ex.package_id') . ' = 0') 1349 ->whereNotIn($db->quoteName('ex.extension_id'), ExtensionHelper::getCoreExtensionIds()); 1350 1351 $db->setQuery($query); 1352 $rows = $db->loadObjectList(); 1353 1354 foreach ($rows as $extension) { 1355 $decode = json_decode($extension->manifest_cache); 1356 1357 // Remove unused fields so they do not cause javascript errors during pre-update check 1358 unset($decode->description); 1359 unset($decode->copyright); 1360 unset($decode->creationDate); 1361 1362 $this->translateExtensionName($extension); 1363 $extension->version 1364 = isset($decode->version) ? $decode->version : Text::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION'); 1365 unset($extension->manifest_cache); 1366 $extension->manifest_cache = $decode; 1367 } 1368 1369 return $rows; 1370 } 1371 1372 /** 1373 * Gets an array containing all installed and enabled plugins, that are not core plugins. 1374 * 1375 * @param array $folderFilter Limit the list of plugins to a specific set of folder values 1376 * 1377 * @return array name,version,updateserver 1378 * 1379 * @since 3.10.0 1380 */ 1381 public function getNonCorePlugins($folderFilter = ['system','user','authentication','actionlog','multifactorauth']) 1382 { 1383 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 1384 $query = $db->getQuery(true); 1385 1386 $query->select( 1387 $db->qn('ex.name') . ', ' . 1388 $db->qn('ex.extension_id') . ', ' . 1389 $db->qn('ex.manifest_cache') . ', ' . 1390 $db->qn('ex.type') . ', ' . 1391 $db->qn('ex.folder') . ', ' . 1392 $db->qn('ex.element') . ', ' . 1393 $db->qn('ex.client_id') . ', ' . 1394 $db->qn('ex.package_id') 1395 )->from( 1396 $db->qn('#__extensions', 'ex') 1397 )->where( 1398 $db->qn('ex.type') . ' = ' . $db->quote('plugin') 1399 )->where( 1400 $db->qn('ex.enabled') . ' = 1' 1401 )->whereNotIn( 1402 $db->quoteName('ex.extension_id'), 1403 ExtensionHelper::getCoreExtensionIds() 1404 ); 1405 1406 if (count($folderFilter) > 0) { 1407 $folderFilter = array_map(array($db, 'quote'), $folderFilter); 1408 1409 $query->where($db->qn('folder') . ' IN (' . implode(',', $folderFilter) . ')'); 1410 } 1411 1412 $db->setQuery($query); 1413 $rows = $db->loadObjectList(); 1414 1415 foreach ($rows as $plugin) { 1416 $decode = json_decode($plugin->manifest_cache); 1417 1418 // Remove unused fields so they do not cause javascript errors during pre-update check 1419 unset($decode->description); 1420 unset($decode->copyright); 1421 unset($decode->creationDate); 1422 1423 $this->translateExtensionName($plugin); 1424 $plugin->version = $decode->version ?? Text::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION'); 1425 unset($plugin->manifest_cache); 1426 $plugin->manifest_cache = $decode; 1427 } 1428 1429 return $rows; 1430 } 1431 1432 /** 1433 * Called by controller's fetchExtensionCompatibility, which is called via AJAX. 1434 * 1435 * @param string $extensionID The ID of the checked extension 1436 * @param string $joomlaTargetVersion Target version of Joomla 1437 * 1438 * @return object 1439 * 1440 * @since 3.10.0 1441 */ 1442 public function fetchCompatibility($extensionID, $joomlaTargetVersion) 1443 { 1444 $updateSites = $this->getUpdateSitesInfo($extensionID); 1445 1446 if (empty($updateSites)) { 1447 return (object) array('state' => 2); 1448 } 1449 1450 foreach ($updateSites as $updateSite) { 1451 if ($updateSite['type'] === 'collection') { 1452 $updateFileUrls = $this->getCollectionDetailsUrls($updateSite, $joomlaTargetVersion); 1453 1454 foreach ($updateFileUrls as $updateFileUrl) { 1455 $compatibleVersions = $this->checkCompatibility($updateFileUrl, $joomlaTargetVersion); 1456 1457 // Return the compatible versions 1458 return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions); 1459 } 1460 } else { 1461 $compatibleVersions = $this->checkCompatibility($updateSite['location'], $joomlaTargetVersion); 1462 1463 // Return the compatible versions 1464 return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions); 1465 } 1466 } 1467 1468 // In any other case we mark this extension as not compatible 1469 return (object) array('state' => 0); 1470 } 1471 1472 /** 1473 * Returns records with update sites and extension information for a given extension ID. 1474 * 1475 * @param int $extensionID The extension ID 1476 * 1477 * @return array 1478 * 1479 * @since 3.10.0 1480 */ 1481 private function getUpdateSitesInfo($extensionID) 1482 { 1483 $id = (int) $extensionID; 1484 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 1485 $query = $db->getQuery(true); 1486 1487 $query->select( 1488 $db->qn('us.type') . ', ' . 1489 $db->qn('us.location') . ', ' . 1490 $db->qn('e.element') . ' AS ' . $db->qn('ext_element') . ', ' . 1491 $db->qn('e.type') . ' AS ' . $db->qn('ext_type') . ', ' . 1492 $db->qn('e.folder') . ' AS ' . $db->qn('ext_folder') 1493 ) 1494 ->from($db->quoteName('#__update_sites', 'us')) 1495 ->join( 1496 'LEFT', 1497 $db->quoteName('#__update_sites_extensions', 'ue'), 1498 $db->quoteName('ue.update_site_id') . ' = ' . $db->quoteName('us.update_site_id') 1499 ) 1500 ->join( 1501 'LEFT', 1502 $db->quoteName('#__extensions', 'e'), 1503 $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('ue.extension_id') 1504 ) 1505 ->where($db->quoteName('e.extension_id') . ' = :id') 1506 ->bind(':id', $id, ParameterType::INTEGER); 1507 1508 $db->setQuery($query); 1509 1510 $result = $db->loadAssocList(); 1511 1512 if (!is_array($result)) { 1513 return array(); 1514 } 1515 1516 return $result; 1517 } 1518 1519 /** 1520 * Method to get details URLs from a collection update site for given extension and Joomla target version. 1521 * 1522 * @param array $updateSiteInfo The update site and extension information record to process 1523 * @param string $joomlaTargetVersion The Joomla! version to test against, 1524 * 1525 * @return array An array of URLs. 1526 * 1527 * @since 3.10.0 1528 */ 1529 private function getCollectionDetailsUrls($updateSiteInfo, $joomlaTargetVersion) 1530 { 1531 $return = array(); 1532 1533 $http = new Http(); 1534 1535 try { 1536 $response = $http->get($updateSiteInfo['location']); 1537 } catch (\RuntimeException $e) { 1538 $response = null; 1539 } 1540 1541 if ($response === null || $response->code !== 200) { 1542 return $return; 1543 } 1544 1545 $updateSiteXML = simplexml_load_string($response->body); 1546 1547 foreach ($updateSiteXML->extension as $extension) { 1548 $attribs = new \stdClass(); 1549 1550 $attribs->element = ''; 1551 $attribs->type = ''; 1552 $attribs->folder = ''; 1553 $attribs->targetplatformversion = ''; 1554 1555 foreach ($extension->attributes() as $key => $value) { 1556 $attribs->$key = (string) $value; 1557 } 1558 1559 if ( 1560 $attribs->element === $updateSiteInfo['ext_element'] 1561 && $attribs->type === $updateSiteInfo['ext_type'] 1562 && $attribs->folder === $updateSiteInfo['ext_folder'] 1563 && preg_match('/^' . $attribs->targetplatformversion . '/', $joomlaTargetVersion) 1564 ) { 1565 $return[] = (string) $extension['detailsurl']; 1566 } 1567 } 1568 1569 return $return; 1570 } 1571 1572 /** 1573 * Method to check non core extensions for compatibility. 1574 * 1575 * @param string $updateFileUrl The items update XML url. 1576 * @param string $joomlaTargetVersion The Joomla! version to test against 1577 * 1578 * @return array An array of strings with compatible version numbers 1579 * 1580 * @since 3.10.0 1581 */ 1582 private function checkCompatibility($updateFileUrl, $joomlaTargetVersion) 1583 { 1584 $minimumStability = ComponentHelper::getParams('com_installer')->get('minimum_stability', Updater::STABILITY_STABLE); 1585 1586 $update = new Update(); 1587 $update->set('jversion.full', $joomlaTargetVersion); 1588 $update->loadFromXml($updateFileUrl, $minimumStability); 1589 1590 $compatibleVersions = $update->get('compatibleVersions'); 1591 1592 // Check if old version of the updater library 1593 if (!isset($compatibleVersions)) { 1594 $downloadUrl = $update->get('downloadurl'); 1595 $updateVersion = $update->get('version'); 1596 1597 return empty($downloadUrl) || empty($downloadUrl->_data) || empty($updateVersion) ? array() : array($updateVersion->_data); 1598 } 1599 1600 usort($compatibleVersions, 'version_compare'); 1601 1602 return $compatibleVersions; 1603 } 1604 1605 /** 1606 * Translates an extension name 1607 * 1608 * @param object &$item The extension of which the name needs to be translated 1609 * 1610 * @return void 1611 * 1612 * @since 3.10.0 1613 */ 1614 protected function translateExtensionName(&$item) 1615 { 1616 // @todo: Cleanup duplicated code. from com_installer/models/extension.php 1617 $lang = Factory::getLanguage(); 1618 $path = $item->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE; 1619 1620 $extension = $item->element; 1621 $source = JPATH_SITE; 1622 1623 switch ($item->type) { 1624 case 'component': 1625 $extension = $item->element; 1626 $source = $path . '/components/' . $extension; 1627 break; 1628 case 'module': 1629 $extension = $item->element; 1630 $source = $path . '/modules/' . $extension; 1631 break; 1632 case 'file': 1633 $extension = 'files_' . $item->element; 1634 break; 1635 case 'library': 1636 $extension = 'lib_' . $item->element; 1637 break; 1638 case 'plugin': 1639 $extension = 'plg_' . $item->folder . '_' . $item->element; 1640 $source = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element; 1641 break; 1642 case 'template': 1643 $extension = 'tpl_' . $item->element; 1644 $source = $path . '/templates/' . $item->element; 1645 } 1646 1647 $lang->load("$extension.sys", JPATH_ADMINISTRATOR) 1648 || $lang->load("$extension.sys", $source); 1649 $lang->load($extension, JPATH_ADMINISTRATOR) 1650 || $lang->load($extension, $source); 1651 1652 // Translate the extension name if possible 1653 $item->name = strip_tags(Text::_($item->name)); 1654 } 1655 1656 /** 1657 * Checks whether a given template is active 1658 * 1659 * @param string $template The template name to be checked 1660 * 1661 * @return boolean 1662 * 1663 * @since 3.10.4 1664 */ 1665 public function isTemplateActive($template) 1666 { 1667 $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase(); 1668 $query = $db->getQuery(true); 1669 1670 $query->select( 1671 $db->qn( 1672 array( 1673 'id', 1674 'home' 1675 ) 1676 ) 1677 )->from( 1678 $db->qn('#__template_styles') 1679 )->where( 1680 $db->qn('template') . ' = :template' 1681 )->bind(':template', $template, ParameterType::STRING); 1682 1683 $templates = $db->setQuery($query)->loadObjectList(); 1684 1685 $home = array_filter( 1686 $templates, 1687 function ($value) { 1688 return $value->home > 0; 1689 } 1690 ); 1691 1692 $ids = ArrayHelper::getColumn($templates, 'id'); 1693 1694 $menu = false; 1695 1696 if (count($ids)) { 1697 $query = $db->getQuery(true); 1698 1699 $query->select( 1700 'COUNT(*)' 1701 )->from( 1702 $db->qn('#__menu') 1703 )->whereIn( 1704 $db->qn('template_style_id'), 1705 $ids 1706 ); 1707 1708 $menu = $db->setQuery($query)->loadResult() > 0; 1709 } 1710 1711 return $home || $menu; 1712 } 1713 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |