diff --git a/administrator/components/com_joomlaupdate/controller.php b/administrator/components/com_joomlaupdate/controller.php index 0f2434b12ee2f..1040f51a948f9 100644 --- a/administrator/components/com_joomlaupdate/controller.php +++ b/administrator/components/com_joomlaupdate/controller.php @@ -43,8 +43,22 @@ public function display($cachable = false, $urlparams = false) $view->ftp = &$ftp; // Get the model for the view. + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('default'); + // Push the Installer Warnings model into the view, if we can load it + if (!class_exists('InstallerModelWarnings')) + { + @include_once JPATH_ADMINISTRATOR . '/components/com_installer/models/warnings.php'; + } + + $warningsModel = $this->getModel('warnings', 'InstallerModel'); + + if (is_object($warningsModel)) + { + $view->setModel($warningsModel, false); + } + // Perform update source preference check and refresh update information. $model->applyUpdateSite(); $model->refreshUpdates(); diff --git a/administrator/components/com_joomlaupdate/controllers/update.php b/administrator/components/com_joomlaupdate/controllers/update.php index 7d4f8792461ae..8886883c9d538 100644 --- a/administrator/components/com_joomlaupdate/controllers/update.php +++ b/administrator/components/com_joomlaupdate/controllers/update.php @@ -33,6 +33,7 @@ public function download() $this->_applyCredentials(); + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $file = $model->download(); @@ -71,6 +72,7 @@ public function install() $this->_applyCredentials(); + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null); @@ -94,6 +96,7 @@ public function finalise() JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE'), JLog::INFO, 'Update'); $this->_applyCredentials(); + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $model->finaliseUpgrade(); @@ -117,6 +120,7 @@ public function cleanup() JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_CLEANUP'), JLog::INFO, 'Update'); $this->_applyCredentials(); + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $model->cleanUp(); @@ -131,13 +135,15 @@ public function cleanup() * * @return void * - * @since 3.0 + * @since 3.0 */ public function purge() { - // Purge updates // Check for request forgeries JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN')); + + // Purge updates + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $model->purge(); @@ -145,6 +151,132 @@ public function purge() $this->setRedirect($url, $model->_message); } + /** + * Uploads an update package to the temporary directory, under a random name + * + * @return void + * + * @since 3.5.2 + */ + public function upload() + { + // Check for request forgeries + JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN')); + + // Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)? + JFactory::getUser()->authorise('core.admin') or jexit(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')); + + $this->_applyCredentials(); + + /** @var JoomlaupdateModelDefault $model */ + $model = $this->getModel('Default'); + + try + { + $model->upload(); + } + catch (RuntimeException $e) + { + $url = 'index.php?option=com_joomlaupdate'; + $this->setRedirect($url, $e->getMessage(), 'error'); + } + + $token = JSession::getFormToken(); + $url = 'index.php?option=com_joomlaupdate&task=update.captive&' . $token . '=1'; + $this->setRedirect($url); + } + + /** + * Checks there is a valid update package and redirects to the captive view for super admin authentication. + * + * @return array + * + * @since 3.5.2 + */ + public function captive() + { + // Check for request forgeries + JSession::checkToken('get') or jexit(JText::_('JINVALID_TOKEN')); + + // Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)? + if (!JFactory::getUser()->authorise('core.admin')) + { + throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); + } + + // Do I really have an update package? + $tempFile = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null); + + JLoader::import('joomla.filesystem.file'); + + if (empty($tempFile) || !JFile::exists($tempFile)) + { + throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); + } + + $this->input->set('view', 'upload'); + $this->input->set('layout', 'captive'); + + $this->display(); + } + + /** + * Checks the admin has super administrator privileges and then proceeds with the update. + * + * @return array + * + * @since 3.5.2 + */ + public function confirm() + { + // Check for request forgeries + JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN')); + + // Did a non Super User tried to upload something (a.k.a. pathetic hacking attempt)? + if (!JFactory::getUser()->authorise('core.admin')) + { + throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); + } + + // Get the model + /** @var JoomlaupdateModelDefault $model */ + $model = $this->getModel('default'); + + // Get the captive file before the session resets + $tempFile = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null); + + // Do I really have an update package? + if (!$model->captiveFileExists()) + { + throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); + } + + // Try to log in + $credentials = array( + 'username' => $this->input->post->get('username', '', 'username'), + 'password' => $this->input->post->get('passwd', '', 'raw'), + 'secretkey' => $this->input->post->get('secretkey', '', 'raw'), + ); + + $result = $model->captiveLogin($credentials); + + if (!$result) + { + $model->removePackageFiles(); + + throw new RuntimeException(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403); + } + + // Set the update source in the session + JFactory::getApplication()->setUserState('com_joomlaupdate.file', basename($tempFile)); + + JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $tempFile), JLog::INFO, 'Update'); + + // Redirect to the actual update page + $url = 'index.php?option=com_joomlaupdate&task=update.install'; + $this->setRedirect($url); + } + /** * Method to display a view. * @@ -153,7 +285,7 @@ public function purge() * * @return JoomlaupdateControllerUpdate This object to support chaining. * - * @since 2.5.4 + * @since 2.5.4 */ public function display($cachable = false, $urlparams = array()) { @@ -169,6 +301,7 @@ public function display($cachable = false, $urlparams = array()) if ($view = $this->getView($vName, $vFormat)) { // Get the model for the view. + /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); // Push the model into the view (as default). @@ -188,10 +321,12 @@ public function display($cachable = false, $urlparams = array()) * * @return void * - * @since 2.5.4 + * @since 2.5.4 */ protected function _applyCredentials() { + JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd'); + if (!JClientHelper::hasCredentials('ftp')) { $user = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.ftp_user', 'ftp_user', null, 'raw'); diff --git a/administrator/components/com_joomlaupdate/helpers/select.php b/administrator/components/com_joomlaupdate/helpers/select.php index 384b294ccfef7..5f83842b0c72e 100644 --- a/administrator/components/com_joomlaupdate/helpers/select.php +++ b/administrator/components/com_joomlaupdate/helpers/select.php @@ -20,17 +20,20 @@ class JoomlaupdateHelperSelect * Returns an HTML select element with the different extraction modes * * @param string $default The default value of the select element + * @param string $name The name of the form field + * @param string $id The id of the select field * * @return string * * @since 2.5.4 */ - public static function getMethods($default = 'direct') + public static function getMethods($default = 'hybrid', $name = 'method', $id = 'extraction_method') { $options = array(); $options[] = JHtml::_('select.option', 'direct', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD_DIRECT')); + $options[] = JHtml::_('select.option', 'hybrid', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD_HYBRID')); $options[] = JHtml::_('select.option', 'ftp', JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD_FTP')); - return JHtml::_('select.genericlist', $options, 'method', '', 'value', 'text', $default, 'extraction_method'); + return JHtml::_('select.genericlist', $options, $name, '', 'value', 'text', $default, $id); } } diff --git a/administrator/components/com_joomlaupdate/joomlaupdate.xml b/administrator/components/com_joomlaupdate/joomlaupdate.xml index 75fe660fd17f6..b730be4b42145 100644 --- a/administrator/components/com_joomlaupdate/joomlaupdate.xml +++ b/administrator/components/com_joomlaupdate/joomlaupdate.xml @@ -7,10 +7,15 @@ GNU General Public License version 2 or later; see LICENSE.txt admin@joomla.org www.joomla.org - 3.0.0 + 3.5.2 COM_JOOMLAUPDATE_XML_DESCRIPTION + + js + + com_joomlaupdate + access.xml config.xml controller.php joomlaupdate.php @@ -29,4 +34,3 @@ http://update.joomla.org/core/extensions/com_joomlaupdate.xml - diff --git a/administrator/components/com_joomlaupdate/models/default.php b/administrator/components/com_joomlaupdate/models/default.php index 13caaaf00f9ec..f64830e182c3d 100644 --- a/administrator/components/com_joomlaupdate/models/default.php +++ b/administrator/components/com_joomlaupdate/models/default.php @@ -120,7 +120,20 @@ public function refreshUpdates($force = false) } $updater = JUpdater::getInstance(); - $updater->findUpdates(700, $cache_timeout); + + $reflection = new ReflectionObject($updater); + $reflectionMethod = $reflection->getMethod('findUpdates'); + $methodParameters = $reflectionMethod->getParameters(); + + if (count($methodParameters) >= 4) + { + // Reinstall support is available in JUpdater + $updater->findUpdates(700, $cache_timeout, JUpdater::STABILITY_STABLE, true); + } + else + { + $updater->findUpdates(700, $cache_timeout, JUpdater::STABILITY_STABLE); + } } /** @@ -135,8 +148,9 @@ public function getUpdateInformation() // Initialise the return array. $ret = array( 'installed' => JVERSION, - 'latest' => null, - 'object' => null + 'latest' => null, + 'object' => null, + 'hasUpdate' => false ); // Fetch the update information from the database. @@ -154,25 +168,16 @@ public function getUpdateInformation() return $ret; } - else - { - $ret['latest'] = $updateObject->version; - } + + $ret['latest'] = $updateObject->version; + $ret['hasUpdate'] = $updateObject->version != JVERSION; // Fetch the full update details from the update details URL. jimport('joomla.updater.update'); $update = new JUpdate; $update->loadFromXML($updateObject->detailsurl); - // Pass the update object. - if ($ret['latest'] == JVERSION) - { - $ret['object'] = null; - } - else - { - $ret['object'] = $update; - } + $ret['object'] = $update; return $ret; } @@ -189,12 +194,12 @@ public function getFTPOptions() $config = JFactory::getConfig(); return array( - 'host' => $config->get('ftp_host'), - 'port' => $config->get('ftp_port'), - 'username' => $config->get('ftp_user'), - 'password' => $config->get('ftp_pass'), + 'host' => $config->get('ftp_host'), + 'port' => $config->get('ftp_port'), + 'username' => $config->get('ftp_user'), + 'password' => $config->get('ftp_pass'), 'directory' => $config->get('ftp_root'), - 'enabled' => $config->get('ftp_enable'), + 'enabled' => $config->get('ftp_enable'), ); } @@ -246,12 +251,12 @@ public function download() { $updateInfo = $this->getUpdateInformation(); $packageURL = $updateInfo['object']->downloadurl->_data; - $basename = basename($packageURL); + $basename = basename($packageURL); // Find the path to the temp directory and the local package. - $config = JFactory::getConfig(); + $config = JFactory::getConfig(); $tempdir = $config->get('tmp_path'); - $target = $tempdir . '/' . $basename; + $target = $tempdir . '/' . $basename; // Do we have a cached file? $exists = JFile::exists($target); @@ -319,7 +324,7 @@ public function createRestorationFile($basename = null) $app->setUserState('com_joomlaupdate.password', $password); // Do we have to use FTP? - $method = $app->input->get('method', 'direct'); + $method = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd'); // Get the absolute path to site's root. $siteroot = JPATH_SITE; @@ -333,9 +338,9 @@ public function createRestorationFile($basename = null) } // Get the package name. - $config = JFactory::getConfig(); + $config = JFactory::getConfig(); $tempdir = $config->get('tmp_path'); - $file = $tempdir . '/' . $basename; + $file = $tempdir . '/' . $basename; $filesize = @filesize($file); $app->setUserState('com_joomlaupdate.password', $password); @@ -356,7 +361,7 @@ public function createRestorationFile($basename = null) 'kickstart.setup.dryrun' => '0' ENDDATA; - if ($method == 'ftp') + if ($method != 'direct') { /* * Fetch the FTP parameters from the request. Note: The password should be @@ -366,7 +371,7 @@ public function createRestorationFile($basename = null) $ftp_host = $app->input->get('ftp_host', ''); $ftp_port = $app->input->get('ftp_port', '21'); $ftp_user = $app->input->get('ftp_user', ''); - $ftp_pass = $app->input->get('ftp_pass', '', 'default', 'none', 2); + $ftp_pass = $app->input->get('ftp_pass', '', 'raw'); $ftp_root = $app->input->get('ftp_root', ''); // Is the tempdir really writable? @@ -419,7 +424,8 @@ public function createRestorationFile($basename = null) if (!is_dir($tempdir)) { JFolder::create($tempdir, 511); - JFile::write($tempdir . '/.htaccess', "order deny,allow\ndeny from all\nallow from none\n"); + $htaccessContents = "order deny,allow\ndeny from all\nallow from none\n"; + JFile::write($tempdir . '/.htaccess', $htaccessContents); } // If it exists and it is unwritable, try creating a writable admintools subdirectory. @@ -564,8 +570,6 @@ public function finaliseUpgrade() $installer->setPath('source', JPATH_MANIFESTS . '/files'); $installer->setPath('extension_root', JPATH_ROOT); - $manifestPath = JPath::clean($installer->getPath('manifest')); - // Run the script file. JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php'); @@ -779,4 +783,177 @@ public function cleanUp() // Unset the update filename from the session. JFactory::getApplication()->setUserState('com_joomlaupdate.file', null); } + + /** + * Uploads what is presumably an update ZIP file under a mangled name in the temporary directory. + * + * @return void + * + * @since 3.5.2 + */ + public function upload() + { + // Get the uploaded file information. + $input = JFactory::getApplication()->input; + + // Do not change the filter type 'raw'. We need this to let files containing PHP code to upload. See JInputFiles::get. + $userfile = $input->files->get('install_package', null, 'raw'); + + // Make sure that file uploads are enabled in php. + if (!(bool) ini_get('file_uploads')) + { + throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLFILE'), 500); + } + + // Make sure that zlib is loaded so that the package can be unpacked. + if (!extension_loaded('zlib')) + { + throw new RuntimeException(('COM_INSTALLER_MSG_INSTALL_WARNINSTALLZLIB'), 500); + } + + // If there is no uploaded file, we have a problem... + if (!is_array($userfile)) + { + throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_NO_FILE_SELECTED'), 500); + } + + // Is the PHP tmp directory missing? + if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_NO_TMP_DIR)) + { + throw new RuntimeException( + JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '
' . + JText::_('COM_INSTALLER_MSG_WARNINGS_PHPUPLOADNOTSET'), + 500 + ); + } + + // Is the max upload size too small in php.ini? + if ($userfile['error'] && ($userfile['error'] == UPLOAD_ERR_INI_SIZE)) + { + throw new RuntimeException( + JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR') . '
' . JText::_('COM_INSTALLER_MSG_WARNINGS_SMALLUPLOADSIZE'), + 500 + ); + } + + // Check if there was a different problem uploading the file. + if ($userfile['error'] || $userfile['size'] < 1) + { + throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500); + } + + // Build the appropriate paths. + $config = JFactory::getConfig(); + $tmp_dest = tempnam($config->get('tmp_path'), 'ju'); + $tmp_src = $userfile['tmp_name']; + + // Move uploaded file. + jimport('joomla.filesystem.file'); + + if (version_compare(JVERSION, '3.4.0', 'ge')) + { + $result = JFile::upload($tmp_src, $tmp_dest, false, true); + } + else + { + // Old Joomla! versions didn't have UploadShield and don't need the fourth parameter to accept uploads + $result = JFile::upload($tmp_src, $tmp_dest); + } + + if (!$result) + { + throw new RuntimeException(JText::_('COM_INSTALLER_MSG_INSTALL_WARNINSTALLUPLOADERROR'), 500); + } + + JFactory::getApplication()->setUserState('com_joomlaupdate.temp_file', $tmp_dest); + } + + /** + * Checks the super admin credentials are valid for the currently logged in users + * + * @param array $credentials The credentials to authenticate the user with + * + * @return bool + * + * @since 3.5.2 + */ + public function captiveLogin($credentials) + { + // Make sure the username matches + $username = isset($credentials['username']) ? $credentials['username'] : null; + $user = JFactory::getUser(); + + if ($user->username != $username) + { + return false; + } + + // Make sure the user we're authorising is a Super User + if (!$user->authorise('core.admin')) + { + return false; + } + + // Get the global JAuthentication object. + jimport('joomla.user.authentication'); + + $authenticate = JAuthentication::getInstance(); + $response = $authenticate->authenticate($credentials); + + if ($response->status !== JAuthentication::STATUS_SUCCESS) + { + return false; + } + + return true; + } + + /** + * Does the captive (temporary) file we uploaded before still exist? + * + * @return bool + * + * @since 3.5.2 + */ + public function captiveFileExists() + { + $file = JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null); + + JLoader::import('joomla.filesystem.file'); + + if (empty($file) || !JFile::exists($file)) + { + return false; + } + + return true; + } + + /** + * Remove the captive (temporary) file we uploaded before and the . + * + * @return void + * + * @since 3.5.2 + */ + public function removePackageFiles() + { + $files = array( + JFactory::getApplication()->getUserState('com_joomlaupdate.temp_file', null), + JFactory::getApplication()->getUserState('com_joomlaupdate.file', null), + ); + + JLoader::import('joomla.filesystem.file'); + + foreach ($files as $file) + { + if (JFile::exists($file)) + { + if (!@unlink($file)) + { + JFile::delete($file); + } + } + } + } } diff --git a/administrator/components/com_joomlaupdate/views/default/tmpl/default.php b/administrator/components/com_joomlaupdate/views/default/tmpl/default.php index d44995df41ece..eae06982fc9e6 100644 --- a/administrator/components/com_joomlaupdate/views/default/tmpl/default.php +++ b/administrator/components/com_joomlaupdate/views/default/tmpl/default.php @@ -9,187 +9,68 @@ defined('_JEXEC') or die; -$ftpFieldsDisplay = $this->ftp['enabled'] ? '' : 'style = "display: none"'; -$params = JComponentHelper::getParams('com_joomlaupdate'); - -switch ($params->get('updatesource', 'default')) -{ - // "Minor & Patch Release for Current version AND Next Major Release". - case 'sts': - case 'next': - $langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_NEXT'; - $updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_NEXT'); - break; - - // "Testing" - case 'testing': - $langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_TESTING'; - $updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_TESTING'); - break; - - // "Custom" - case 'custom': - $langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_CUSTOM'; - $updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM'); - break; - - /** - * "Minor & Patch Release for Current version (recommended and default)". - * The commented "case" below are for documenting where 'default' and legacy options falls - * case 'default': - * case 'lts': - * case 'nochange': - */ - default: - $langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_DEFAULT'; - $updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DEFAULT'); -} +/** @var JoomlaupdateViewDefault $this */ JHtml::_('jquery.framework'); +JHtml::_('bootstrap.tooltip'); JHtml::_('formbehavior.chosen', 'select'); JHtml::script('com_joomlaupdate/default.js', false, true, false); -?> - -
- -updateInfo['object'])) : ?> -
- - - -

- -

-

- -

- -
- - - -
- - - -

- -

- - - - - - - - - - - - - - - - - - - - - - - - > - - - - > - - - - > - - - - > - - - - > - - - - - - - - - - -
- - - updateInfo['installed']; ?> -
- - - updateInfo['latest']; ?> -
- - - - updateInfo['object']->downloadurl->_data; ?> - -
- - - - updateInfo['object']->get('infourl')->title; ?> - -
- - - methodSelect; ?> -
- - - -
- - - -
- - - -
- - - -
- - - -
-   - - -
-
- - - - - - -
+JFactory::getDocument()->addScriptDeclaration(" +jQuery(document).ready(function($) { + $('#extraction_method').change(function(e){ + extractionMethodHandler('#extraction_method', 'row_ftp'); + }); + $('#upload_method').change(function(e){ + extractionMethodHandler('#upload_method', 'upload_ftp'); + }); + + $('button.submit').on('click', function() { + $('div.download_message').show(); + }); +});"); +?> -