1<?php 2/** 3 * @package Joomla.Administrator 4 * @subpackage com_joomlaupdate 5 * 6 * @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved. 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10defined('_JEXEC') or die; 11 12jimport('joomla.filesystem.folder'); 13jimport('joomla.filesystem.file'); 14 15/** 16 * Joomla! update overview Model 17 * 18 * @since 2.5.4 19 */ 20class JoomlaupdateModelDefault extends JModelLegacy 21{ 22 /** 23 * Detects if the Joomla! update site currently in use matches the one 24 * configured in this component. If they don't match, it changes it. 25 * 26 * @return void 27 * 28 * @since 2.5.4 29 */ 30 public function applyUpdateSite() 31 { 32 // Determine the intended update URL. 33 $params = JComponentHelper::getParams('com_joomlaupdate'); 34 35 switch ($params->get('updatesource', 'nochange')) 36 { 37 // "Minor & Patch Release for Current version AND Next Major Release". 38 case 'sts': 39 case 'next': 40 $updateURL = 'https://update.joomla.org/core/sts/list_sts.xml'; 41 break; 42 43 // "Testing" 44 case 'testing': 45 $updateURL = 'https://update.joomla.org/core/test/list_test.xml'; 46 break; 47 48 // "Custom" 49 // TODO: check if the customurl is valid and not just "not empty". 50 case 'custom': 51 if (trim($params->get('customurl', '')) != '') 52 { 53 $updateURL = trim($params->get('customurl', '')); 54 } 55 else 56 { 57 return JError::raiseWarning(403, JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM_ERROR')); 58 } 59 break; 60 61 /** 62 * "Minor & Patch Release for Current version (recommended and default)". 63 * The commented "case" below are for documenting where 'default' and legacy options falls 64 * case 'default': 65 * case 'lts': 66 * case 'nochange': 67 */ 68 default: 69 $updateURL = 'https://update.joomla.org/core/list.xml'; 70 } 71 72 $db = $this->getDbo(); 73 $query = $db->getQuery(true) 74 ->select($db->quoteName('us') . '.*') 75 ->from($db->quoteName('#__update_sites_extensions') . ' AS ' . $db->quoteName('map')) 76 ->join( 77 'INNER', $db->quoteName('#__update_sites') . ' AS ' . $db->quoteName('us') 78 . ' ON (' . 'us.update_site_id = map.update_site_id)' 79 ) 80 ->where('map.extension_id = ' . $db->quote(700)); 81 $db->setQuery($query); 82 $update_site = $db->loadObject(); 83 84 if ($update_site->location != $updateURL) 85 { 86 // Modify the database record. 87 $update_site->last_check_timestamp = 0; 88 $update_site->location = $updateURL; 89 $db->updateObject('#__update_sites', $update_site, 'update_site_id'); 90 91 // Remove cached updates. 92 $query->clear() 93 ->delete($db->quoteName('#__updates')) 94 ->where($db->quoteName('extension_id') . ' = ' . $db->quote('700')); 95 $db->setQuery($query); 96 $db->execute(); 97 } 98 } 99 100 /** 101 * Makes sure that the Joomla! update cache is up-to-date. 102 * 103 * @param boolean $force Force reload, ignoring the cache timeout. 104 * 105 * @return void 106 * 107 * @since 2.5.4 108 */ 109 public function refreshUpdates($force = false) 110 { 111 if ($force) 112 { 113 $cache_timeout = 0; 114 } 115 else 116 { 117 $cache_timeout = 3600 * JComponentHelper::getParams('com_installer')->get('cachetimeout', 6, 'int'); 118 } 119 120 $updater = JUpdater::getInstance(); 121 $minimumStability = JUpdater::STABILITY_STABLE; 122 $comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate'); 123 124 if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) 125 { 126 $minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE); 127 } 128 129 $reflection = new ReflectionObject($updater); 130 $reflectionMethod = $reflection->getMethod('findUpdates'); 131 $methodParameters = $reflectionMethod->getParameters(); 132 133 if (count($methodParameters) >= 4) 134 { 135 // Reinstall support is available in JUpdater 136 $updater->findUpdates(700, $cache_timeout, $minimumStability, true); 137 } 138 else 139 { 140 $updater->findUpdates(700, $cache_timeout, $minimumStability); 141 } 142 } 143 144 /** 145 * Returns an array with the Joomla! update information. 146 * 147 * @return array 148 * 149 * @since 2.5.4 150 */ 151 public function getUpdateInformation() 152 { 153 // Initialise the return array. 154 $ret = array( 155 'installed' => JVERSION, 156 'latest' => null, 157 'object' => null, 158 'hasUpdate' => false, 159 ); 160 161 // Fetch the update information from the database. 162 $db = $this->getDbo(); 163 $query = $db->getQuery(true) 164 ->select('*') 165 ->from($db->quoteName('#__updates')) 166 ->where($db->quoteName('extension_id') . ' = ' . $db->quote(700)); 167 $db->setQuery($query); 168 $updateObject = $db->loadObject(); 169 170 if (is_null($updateObject)) 171 { 172 // We have not found any update in the database we seem to run the latest version 173 $ret['latest'] = JVERSION; 174 175 return $ret; 176 } 177 178 // Check whether this is a valid update or not 179 if (version_compare($updateObject->version, JVERSION, '<')) 180 { 181 // This update points to an outdated version we should not offer to update to this 182 $ret['latest'] = JVERSION; 183 184 return $ret; 185 } 186 187 $ret['latest'] = $updateObject->version; 188 189 // Check whether this is an update or not. 190 if (version_compare($updateObject->version, JVERSION, '>')) 191 { 192 $ret['hasUpdate'] = true; 193 } 194 195 $minimumStability = JUpdater::STABILITY_STABLE; 196 $comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate'); 197 198 if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) 199 { 200 $minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE); 201 } 202 203 // Fetch the full update details from the update details URL. 204 jimport('joomla.updater.update'); 205 $update = new JUpdate; 206 $update->loadFromXML($updateObject->detailsurl, $minimumStability); 207 208 $ret['object'] = $update; 209 210 return $ret; 211 } 212 213 /** 214 * Returns an array with the configured FTP options. 215 * 216 * @return array 217 * 218 * @since 2.5.4 219 */ 220 public function getFTPOptions() 221 { 222 $config = JFactory::getConfig(); 223 224 return array( 225 'host' => $config->get('ftp_host'), 226 'port' => $config->get('ftp_port'), 227 'username' => $config->get('ftp_user'), 228 'password' => $config->get('ftp_pass'), 229 'directory' => $config->get('ftp_root'), 230 'enabled' => $config->get('ftp_enable'), 231 ); 232 } 233 234 /** 235 * Removes all of the updates from the table and enable all update streams. 236 * 237 * @return boolean Result of operation. 238 * 239 * @since 3.0 240 */ 241 public function purge() 242 { 243 $db = $this->getDbo(); 244 245 // Reset the last update check timestamp 246 $query = $db->getQuery(true) 247 ->update($db->quoteName('#__update_sites')) 248 ->set($db->quoteName('last_check_timestamp') . ' = 0'); 249 $db->setQuery($query); 250 $db->execute(); 251 252 // We should delete all core updates here 253 $query = $db->getQuery(true) 254 ->delete($db->quoteName('#__updates')) 255 ->where($db->quoteName('element') . ' = ' . $db->quote('joomla')) 256 ->where($db->quoteName('type') . ' = ' . $db->quote('file')); 257 $db->setQuery($query); 258 259 if ($db->execute()) 260 { 261 $this->_message = JText::_('COM_JOOMLAUPDATE_CHECKED_UPDATES'); 262 263 return true; 264 } 265 else 266 { 267 $this->_message = JText::_('COM_JOOMLAUPDATE_FAILED_TO_CHECK_UPDATES'); 268 269 return false; 270 } 271 } 272 273 /** 274 * Downloads the update package to the site. 275 * 276 * @return boolean|string False on failure, basename of the file in any other case. 277 * 278 * @since 2.5.4 279 */ 280 public function download() 281 { 282 $updateInfo = $this->getUpdateInformation(); 283 $packageURL = trim($updateInfo['object']->downloadurl->_data); 284 $sources = $updateInfo['object']->get('downloadSources', array()); 285 $headers = get_headers($packageURL, 1); 286 287 // Follow the Location headers until the actual download URL is known 288 while (isset($headers['Location'])) 289 { 290 $packageURL = $headers['Location']; 291 $headers = get_headers($packageURL, 1); 292 } 293 294 // Remove protocol, path and query string from URL 295 $basename = basename($packageURL); 296 297 if (strpos($basename, '?') !== false) 298 { 299 $basename = substr($basename, 0, strpos($basename, '?')); 300 } 301 302 // Find the path to the temp directory and the local package. 303 $config = JFactory::getConfig(); 304 $tempdir = $config->get('tmp_path'); 305 $target = $tempdir . '/' . $basename; 306 $response = array(); 307 308 // Do we have a cached file? 309 $exists = JFile::exists($target); 310 311 if (!$exists) 312 { 313 // Not there, let's fetch it. 314 $mirror = 0; 315 316 while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) 317 { 318 $name = $sources[$mirror]; 319 $packageURL = trim($name->url); 320 $mirror++; 321 } 322 323 $response['basename'] = $download; 324 } 325 else 326 { 327 // Is it a 0-byte file? If so, re-download please. 328 $filesize = @filesize($target); 329 330 if (empty($filesize)) 331 { 332 $mirror = 0; 333 334 while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) 335 { 336 $name = $sources[$mirror]; 337 $packageURL = trim($name->url); 338 $mirror++; 339 } 340 341 $response['basename'] = $download; 342 } 343 344 // Yes, it's there, skip downloading. 345 $response['basename'] = $basename; 346 } 347 348 $response['check'] = $this->isChecksumValid($target, $updateInfo['object']); 349 350 return $response; 351 } 352 353 /** 354 * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest 355 * 356 * @param string $packagefile Location of the package to be installed 357 * @param JUpdate $updateObject The Update Object 358 * 359 * @return boolean False in case the validation did not work; true in any other case. 360 * 361 * @note This method has been forked from (JInstallerHelper::isChecksumValid) so it 362 * does not depend on an up-to-date InstallerHelper at the update time 363 * 364 * @since 3.9.0 365 */ 366 private function isChecksumValid($packagefile, $updateObject) 367 { 368 $hashes = array('sha256', 'sha384', 'sha512'); 369 370 foreach ($hashes as $hash) 371 { 372 if ($updateObject->get($hash, false)) 373 { 374 $hashPackage = hash_file($hash, $packagefile); 375 $hashRemote = $updateObject->$hash->_data; 376 377 if ($hashPackage !== $hashRemote) 378 { 379 // Return false in case the hash did not match 380 return false; 381 } 382 } 383 } 384 385 // Well nothing was provided or all worked 386 return true; 387 } 388 389 /** 390 * Downloads a package file to a specific directory 391 * 392 * @param string $url The URL to download from 393 * @param string $target The directory to store the file 394 * 395 * @return boolean True on success 396 * 397 * @since 2.5.4 398 */ 399 protected function downloadPackage($url, $target) 400 { 401 JLoader::import('helpers.download', JPATH_COMPONENT_ADMINISTRATOR); 402 403 try 404 { 405 JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), JLog::INFO, 'Update'); 406 } 407 catch (RuntimeException $exception) 408 { 409 // Informational log only 410 } 411 412 // Get the handler to download the package 413 try 414 { 415 $http = JHttpFactory::getHttp(null, array('curl', 'stream')); 416 } 417 catch (RuntimeException $e) 418 { 419 return false; 420 } 421 422 jimport('joomla.filesystem.file'); 423 424 // Make sure the target does not exist. 425 JFile::delete($target); 426 427 // Download the package 428 try 429 { 430 $result = $http->get($url); 431 } 432 catch (RuntimeException $e) 433 { 434 return false; 435 } 436 437 if (!$result || ($result->code != 200 && $result->code != 310)) 438 { 439 return false; 440 } 441 442 // Write the file to disk 443 JFile::write($target, $result->body); 444 445 return basename($target); 446 } 447 448 /** 449 * Create restoration file. 450 * 451 * @param string $basename Optional base path to the file. 452 * 453 * @return boolean True if successful; false otherwise. 454 * 455 * @since 2.5.4 456 */ 457 public function createRestorationFile($basename = null) 458 { 459 // Get a password 460 $password = JUserHelper::genRandomPassword(32); 461 $app = JFactory::getApplication(); 462 $app->setUserState('com_joomlaupdate.password', $password); 463 464 // Do we have to use FTP? 465 $method = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd'); 466 467 // Get the absolute path to site's root. 468 $siteroot = JPATH_SITE; 469 470 // If the package name is not specified, get it from the update info. 471 if (empty($basename)) 472 { 473 $updateInfo = $this->getUpdateInformation(); 474 $packageURL = $updateInfo['object']->downloadurl->_data; 475 $basename = basename($packageURL); 476 } 477 478 // Get the package name. 479 $config = JFactory::getConfig(); 480 $tempdir = $config->get('tmp_path'); 481 $file = $tempdir . '/' . $basename; 482 483 $filesize = @filesize($file); 484 $app->setUserState('com_joomlaupdate.password', $password); 485 $app->setUserState('com_joomlaupdate.filesize', $filesize); 486 487 $data = "<?php\ndefined('_AKEEBA_RESTORATION') or die('Restricted access');\n"; 488 $data .= '$restoration_setup = array(' . "\n"; 489 $data .= <<<ENDDATA 490 'kickstart.security.password' => '$password', 491 'kickstart.tuning.max_exec_time' => '5', 492 'kickstart.tuning.run_time_bias' => '75', 493 'kickstart.tuning.min_exec_time' => '0', 494 'kickstart.procengine' => '$method', 495 'kickstart.setup.sourcefile' => '$file', 496 'kickstart.setup.destdir' => '$siteroot', 497 'kickstart.setup.restoreperms' => '0', 498 'kickstart.setup.filetype' => 'zip', 499 'kickstart.setup.dryrun' => '0', 500 'kickstart.setup.renamefiles' => array(), 501 'kickstart.setup.postrenamefiles' => false 502ENDDATA; 503 504 if ($method != 'direct') 505 { 506 /* 507 * Fetch the FTP parameters from the request. Note: The password should be 508 * allowed as raw mode, otherwise something like !@<sdf34>43H% would be 509 * sanitised to !@43H% which is just plain wrong. 510 */ 511 $ftp_host = $app->input->get('ftp_host', ''); 512 $ftp_port = $app->input->get('ftp_port', '21'); 513 $ftp_user = $app->input->get('ftp_user', ''); 514 $ftp_pass = addcslashes($app->input->get('ftp_pass', '', 'raw'), "'\\"); 515 $ftp_root = $app->input->get('ftp_root', ''); 516 517 // Is the tempdir really writable? 518 $writable = @is_writeable($tempdir); 519 520 if ($writable) 521 { 522 // Let's be REALLY sure. 523 $fp = @fopen($tempdir . '/test.txt', 'w'); 524 525 if ($fp === false) 526 { 527 $writable = false; 528 } 529 else 530 { 531 fclose($fp); 532 unlink($tempdir . '/test.txt'); 533 } 534 } 535 536 // If the tempdir is not writable, create a new writable subdirectory. 537 if (!$writable) 538 { 539 $FTPOptions = JClientHelper::getCredentials('ftp'); 540 $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); 541 $dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/'); 542 543 if (!@mkdir($tempdir . '/admintools')) 544 { 545 $ftp->mkdir($dest); 546 } 547 548 if (!@chmod($tempdir . '/admintools', 511)) 549 { 550 $ftp->chmod($dest, 511); 551 } 552 553 $tempdir .= '/admintools'; 554 } 555 556 // Just in case the temp-directory was off-root, try using the default tmp directory. 557 $writable = @is_writeable($tempdir); 558 559 if (!$writable) 560 { 561 $tempdir = JPATH_ROOT . '/tmp'; 562 563 // Does the JPATH_ROOT/tmp directory exist? 564 if (!is_dir($tempdir)) 565 { 566 JFolder::create($tempdir, 511); 567 $htaccessContents = "order deny,allow\ndeny from all\nallow from none\n"; 568 JFile::write($tempdir . '/.htaccess', $htaccessContents); 569 } 570 571 // If it exists and it is unwritable, try creating a writable admintools subdirectory. 572 if (!is_writable($tempdir)) 573 { 574 $FTPOptions = JClientHelper::getCredentials('ftp'); 575 $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); 576 $dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/'); 577 578 if (!@mkdir($tempdir . '/admintools')) 579 { 580 $ftp->mkdir($dest); 581 } 582 583 if (!@chmod($tempdir . '/admintools', 511)) 584 { 585 $ftp->chmod($dest, 511); 586 } 587 588 $tempdir .= '/admintools'; 589 } 590 } 591 592 // If we still have no writable directory, we'll try /tmp and the system's temp-directory. 593 $writable = @is_writeable($tempdir); 594 595 if (!$writable) 596 { 597 if (@is_dir('/tmp') && @is_writable('/tmp')) 598 { 599 $tempdir = '/tmp'; 600 } 601 else 602 { 603 // Try to find the system temp path. 604 $tmpfile = @tempnam('dummy', ''); 605 $systemp = @dirname($tmpfile); 606 @unlink($tmpfile); 607 608 if (!empty($systemp)) 609 { 610 if (@is_dir($systemp) && @is_writable($systemp)) 611 { 612 $tempdir = $systemp; 613 } 614 } 615 } 616 } 617 618 $data .= <<<ENDDATA 619 , 620 'kickstart.ftp.ssl' => '0', 621 'kickstart.ftp.passive' => '1', 622 'kickstart.ftp.host' => '$ftp_host', 623 'kickstart.ftp.port' => '$ftp_port', 624 'kickstart.ftp.user' => '$ftp_user', 625 'kickstart.ftp.pass' => '$ftp_pass', 626 'kickstart.ftp.dir' => '$ftp_root', 627 'kickstart.ftp.tempdir' => '$tempdir' 628ENDDATA; 629 } 630 631 $data .= ');'; 632 633 // Remove the old file, if it's there... 634 $configpath = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php'; 635 636 if (JFile::exists($configpath)) 637 { 638 JFile::delete($configpath); 639 } 640 641 // Write new file. First try with JFile. 642 $result = JFile::write($configpath, $data); 643 644 // In case JFile used FTP but direct access could help. 645 if (!$result) 646 { 647 if (function_exists('file_put_contents')) 648 { 649 $result = @file_put_contents($configpath, $data); 650 651 if ($result !== false) 652 { 653 $result = true; 654 } 655 } 656 else 657 { 658 $fp = @fopen($configpath, 'wt'); 659 660 if ($fp !== false) 661 { 662 $result = @fwrite($fp, $data); 663 664 if ($result !== false) 665 { 666 $result = true; 667 } 668 669 @fclose($fp); 670 } 671 } 672 } 673 674 return $result; 675 } 676 677 /** 678 * Runs the schema update SQL files, the PHP update script and updates the 679 * manifest cache and #__extensions entry. Essentially, it is identical to 680 * JInstallerFile::install() without the file copy. 681 * 682 * @return boolean True on success. 683 * 684 * @since 2.5.4 685 */ 686 public function finaliseUpgrade() 687 { 688 $installer = JInstaller::getInstance(); 689 690 $manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml'); 691 692 if ($manifest === false) 693 { 694 $installer->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST')); 695 696 return false; 697 } 698 699 $installer->manifest = $manifest; 700 701 $installer->setUpgrade(true); 702 $installer->setOverwrite(true); 703 704 $installer->extension = JTable::getInstance('extension'); 705 $installer->extension->load(700); 706 707 $installer->setAdapter($installer->extension->type); 708 709 $installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml'); 710 $installer->setPath('source', JPATH_MANIFESTS . '/files'); 711 $installer->setPath('extension_root', JPATH_ROOT); 712 713 // Run the script file. 714 JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php'); 715 716 $manifestClass = new JoomlaInstallerScript; 717 718 ob_start(); 719 ob_implicit_flush(false); 720 721 if ($manifestClass && method_exists($manifestClass, 'preflight')) 722 { 723 if ($manifestClass->preflight('update', $installer) === false) 724 { 725 $installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE')); 726 727 return false; 728 } 729 } 730 731 // Create msg object; first use here. 732 $msg = ob_get_contents(); 733 ob_end_clean(); 734 735 // Get a database connector object. 736 $db = $this->getDbo(); 737 738 /* 739 * Check to see if a file extension by the same name is already installed. 740 * If it is, then update the table because if the files aren't there 741 * we can assume that it was (badly) uninstalled. 742 * If it isn't, add an entry to extensions. 743 */ 744 $query = $db->getQuery(true) 745 ->select($db->quoteName('extension_id')) 746 ->from($db->quoteName('#__extensions')) 747 ->where($db->quoteName('type') . ' = ' . $db->quote('file')) 748 ->where($db->quoteName('element') . ' = ' . $db->quote('joomla')); 749 $db->setQuery($query); 750 751 try 752 { 753 $db->execute(); 754 } 755 catch (RuntimeException $e) 756 { 757 // Install failed, roll back changes. 758 $installer->abort( 759 JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $e->getMessage()) 760 ); 761 762 return false; 763 } 764 765 $id = $db->loadResult(); 766 $row = JTable::getInstance('extension'); 767 768 if ($id) 769 { 770 // Load the entry and update the manifest_cache. 771 $row->load($id); 772 773 // Update name. 774 $row->set('name', 'files_joomla'); 775 776 // Update manifest. 777 $row->manifest_cache = $installer->generateManifestCache(); 778 779 if (!$row->store()) 780 { 781 // Install failed, roll back changes. 782 $installer->abort( 783 JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $row->getError()) 784 ); 785 786 return false; 787 } 788 } 789 else 790 { 791 // Add an entry to the extension table with a whole heap of defaults. 792 $row->set('name', 'files_joomla'); 793 $row->set('type', 'file'); 794 $row->set('element', 'joomla'); 795 796 // There is no folder for files so leave it blank. 797 $row->set('folder', ''); 798 $row->set('enabled', 1); 799 $row->set('protected', 0); 800 $row->set('access', 0); 801 $row->set('client_id', 0); 802 $row->set('params', ''); 803 $row->set('system_data', ''); 804 $row->set('manifest_cache', $installer->generateManifestCache()); 805 806 if (!$row->store()) 807 { 808 // Install failed, roll back changes. 809 $installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError())); 810 811 return false; 812 } 813 814 // Set the insert id. 815 $row->set('extension_id', $db->insertid()); 816 817 // Since we have created a module item, we add it to the installation step stack 818 // so that if we have to rollback the changes we can undo it. 819 $installer->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id)); 820 } 821 822 $result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id); 823 824 if ($result === false) 825 { 826 // Install failed, rollback changes. 827 $installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_UPDATE_SQL_ERROR', $db->stderr(true))); 828 829 return false; 830 } 831 832 // Start Joomla! 1.6. 833 ob_start(); 834 ob_implicit_flush(false); 835 836 if ($manifestClass && method_exists($manifestClass, 'update')) 837 { 838 if ($manifestClass->update($installer) === false) 839 { 840 // Install failed, rollback changes. 841 $installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE')); 842 843 return false; 844 } 845 } 846 847 // Append messages. 848 $msg .= ob_get_contents(); 849 ob_end_clean(); 850 851 // Clobber any possible pending updates. 852 $update = JTable::getInstance('update'); 853 $uid = $update->find( 854 array('element' => 'joomla', 'type' => 'file', 'client_id' => '0', 'folder' => '') 855 ); 856 857 if ($uid) 858 { 859 $update->delete($uid); 860 } 861 862 // And now we run the postflight. 863 ob_start(); 864 ob_implicit_flush(false); 865 866 if ($manifestClass && method_exists($manifestClass, 'postflight')) 867 { 868 $manifestClass->postflight('update', $installer); 869 } 870 871 // Append messages. 872 $msg .= ob_get_contents(); 873 ob_end_clean(); 874 875 if ($msg != '') 876 { 877 $installer->set('extension_message', $msg); 878 } 879 880 // Refresh versionable assets cache. 881 JFactory::getApplication()->flushAssets(); 882 883 return true; 884 } 885 886 /** 887 * Removes the extracted package file. 888 * 889 * @return void 890 * 891 * @since 2.5.4 892 */ 893 public function cleanUp() 894 { 895 // Remove the update package. 896 $config = JFactory::getConfig(); 897 $tempdir = $config->get('tmp_path'); 898 899 $file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null); 900 $target = $tempdir . '/' . $file; 901 902 if (!@unlink($target)) 903 { 904 JFile::delete($target); 905 } 906 907 // Remove the restoration.php file. 908 $target = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php'; 909 910 if (!@unlink($target)) 911 { 912 JFile::delete($target); 913 } 914 915 // Remove joomla.xml from the site's root. 916 $target = JPATH_ROOT . '/joomla.xml'; 917 918 if (!@unlink($target)) 919 { 920 JFile::delete($target); 921 } 922 923 // Unset the update filename from the session. 924 JFactory::getApplication()->setUserState('com_joomlaupdate.file', null); 925 $oldVersion = JFactory::getApplication()->getUserState('com_joomlaupdate.oldversion'); 926 927 // Trigger event after joomla update. 928 JFactory::getApplication()->triggerEvent('onJoomlaAfterUpdate', array($oldVersion)); 929 JFactory::getApplication()->setUserState('com_joomlaupdate.oldversion', null); 930 } 931 932 /** 933 * Uploads what is presumably an update ZIP file under a mangled name in the temporary directory. 934 * 935 * @return void 936 * 937 * @since 3.6.0 938 */ 939 public function upload() 940 { 941 // Get the uploaded file information. 942 $input = JFactory::getApplication()->input; 943 944 // Do not change the filter type 'raw'. We need this to let files containing PHP code to upload. See JInputFiles::get. 945 $userfile = $input->files->get('install_package', null, 'raw'); 946 947 // Make sure that file uploads are enabled in php. 948 if (!(bool) ini_get('file_uploads')) 949 { 950 throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLFILE'), 500); 951 } 952 953 // Make sure that zlib is loaded so that the package can be unpacked. 954 if (!extension_loaded('zlib')) 955 { 956 throw new RuntimeException('COM_INSTALLER_MSG_INSTALL_WARNINSTALLZLIB', 500); 957 } 958 959 // If there is no uploaded file, we have a problem... 960 if (!is_array($userfile)) 961 { 962 throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_NO_FILE_SELECTED'), 500); 963 } 964 965 // Is the PHP tmp directory missing? 966 if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_NO_TMP_DIR)) 967 { 968 throw new RuntimeException( 969 JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br />' . 970 JText::_('COM_INSTALLER_MSG_WARNINGS_PHPUPLOADNOTSET'), 971 500 972 ); 973 } 974 975 // Is the max upload size too small in php.ini? 976 if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_INI_SIZE)) 977 { 978 throw new RuntimeException( 979 JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '<br />' . JText::_('COM_INSTALLER_MSG_WARNINGS_SMALLUPLOADSIZE'), 980 500 981 ); 982 } 983 984 // Check if there was a different problem uploading the file. 985 if ($userfile['error'] || $userfile['size'] < 1) 986 { 987 throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500); 988 } 989 990 // Build the appropriate paths. 991 $config = JFactory::getConfig(); 992 $tmp_dest = tempnam($config->get('tmp_path'), 'ju'); 993 $tmp_src = $userfile['tmp_name']; 994 995 // Move uploaded file. 996 jimport('joomla.filesystem.file'); 997 998 if (version_compare(JVERSION, '3.4.0', 'ge')) 999 { 1000 $result = JFile::upload($tmp_src, $tmp_dest, false, true); 1001 } 1002 else 1003 { 1004 // Old Joomla! versions didn't have UploadShield and don't need the fourth parameter to accept uploads 1005 $result = JFile::upload($tmp_src, $tmp_dest); 1006 } 1007 1008 if (!$result) 1009 { 1010 throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500); 1011 } 1012 1013 JFactory::getApplication()->setUserState('com_joomlaupdate.temp_file', $tmp_dest); 1014 } 1015 1016 /** 1017 * Checks the super admin credentials are valid for the currently logged in users 1018 * 1019 * @param array $credentials The credentials to authenticate the user with 1020 * 1021 * @return boolean 1022 * 1023 * @since 3.6.0 1024 */ 1025 public function captiveLogin($credentials) 1026 { 1027 // Make sure the username matches 1028 $username = isset($credentials['username']) ? $credentials['username'] : null; 1029 $user = JFactory::getUser(); 1030 1031 if (strtolower($user->username) != strtolower($username)) 1032 { 1033 return false; 1034 } 1035 1036 // Make sure the user is authorised 1037 if (!$user->authorise('core.admin')) 1038 { 1039 return false; 1040 } 1041 1042 // Get the global JAuthentication object. 1043 $authenticate = JAuthentication::getInstance(); 1044 $response = $authenticate->authenticate($credentials); 1045 1046 if ($response->status !== JAuthentication::STATUS_SUCCESS) 1047 { 1048 return false; 1049 } 1050 1051 return true; 1052 } 1053 1054 /** 1055 * Does the captive (temporary) file we uploaded before still exist? 1056 * 1057 * @return boolean 1058 * 1059 * @since 3.6.0 1060 */ 1061 public function captiveFileExists() 1062 { 1063 $file = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null); 1064 1065 JLoader::import('joomla.filesystem.file'); 1066 1067 if (empty($file) || !JFile::exists($file)) 1068 { 1069 return false; 1070 } 1071 1072 return true; 1073 } 1074 1075 /** 1076 * Remove the captive (temporary) file we uploaded before and the . 1077 * 1078 * @return void 1079 * 1080 * @since 3.6.0 1081 */ 1082 public function removePackageFiles() 1083 { 1084 $files = array( 1085 JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null), 1086 JFactory::getApplication()->getUserState('com_joomlaupdate.file', null), 1087 ); 1088 1089 JLoader::import('joomla.filesystem.file'); 1090 1091 foreach ($files as $file) 1092 { 1093 if (JFile::exists($file)) 1094 { 1095 if (!@unlink($file)) 1096 { 1097 JFile::delete($file); 1098 } 1099 } 1100 } 1101 } 1102} 1103