Файловый менеджер - Редактировать - /home/wuectly/www/03cbe/com_joomlaupdate.zip
Назад
PK �|!]c��� � helpers/joomlaupdate.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * Joomla! update helper. * * @since 2.5.4 */ class JoomlaupdateHelper { /** * Gets a list of the actions that can be performed. * * @return JObject * * @since 2.5.4 * @deprecated 3.2 Use JHelperContent::getActions() instead */ public static function getActions() { // Log usage of deprecated function try { JLog::add( sprintf('%s() is deprecated. Use JHelperContent::getActions() with new arguments order instead.', __METHOD__), JLog::WARNING, 'deprecated' ); } catch (RuntimeException $exception) { // Informational log only } // Get list of actions return JHelperContent::getActions('com_joomlaupdate'); } } PK �|!]��CK� � helpers/select.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * Joomla! update selection list helper. * * @since 2.5.4 */ 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 = '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, $name, '', 'value', 'text', $default, $id); } } PK �|!]PڞB �B controllers/update.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * The Joomla! update controller for the Update view * * @since 2.5.4 */ class JoomlaupdateControllerUpdate extends JControllerLegacy { /** * Performs the download of the update package * * @return void * * @since 2.5.4 */ public function download() { $this->checkToken(); $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; $options['text_file'] = 'joomla_update.php'; JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror')); $user = JFactory::getUser(); try { JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_START', $user->id, $user->name, JVERSION), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } $this->_applyCredentials(); /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $result = $model->download(); $file = $result['basename']; $message = null; $messageType = null; // The validation was not successful for now just a warning. // TODO: In Joomla 4 this will abort the installation if ($result['check'] === false) { $message = JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_CHECKSUM_WRONG'); $messageType = 'warning'; try { JLog::add($message, JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } } if ($file) { JFactory::getApplication()->setUserState('com_joomlaupdate.file', $file); $url = 'index.php?option=com_joomlaupdate&task=update.install&' . JFactory::getSession()->getFormToken() . '=1'; try { JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $file), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } } else { JFactory::getApplication()->setUserState('com_joomlaupdate.file', null); $url = 'index.php?option=com_joomlaupdate'; $message = JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_DOWNLOADFAILED'); $messageType = 'error'; } $this->setRedirect($url, $message, $messageType); } /** * Start the installation of the new Joomla! version * * @return void * * @since 2.5.4 */ public function install() { $this->checkToken('get'); JFactory::getApplication()->setUserState('com_joomlaupdate.oldversion', JVERSION); $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; $options['text_file'] = 'joomla_update.php'; JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror')); try { JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL'), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } $this->_applyCredentials(); /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null); $model->createRestorationFile($file); $this->display(); } /** * Finalise the upgrade by running the necessary scripts * * @return void * * @since 2.5.4 */ public function finalise() { /* * Finalize with login page. Used for pre-token check versions * to allow updates without problems but with a maximum of security. */ if (!JSession::checkToken('get')) { $this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm'); return false; } $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; $options['text_file'] = 'joomla_update.php'; JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror')); try { JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE'), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } $this->_applyCredentials(); /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $model->finaliseUpgrade(); $url = 'index.php?option=com_joomlaupdate&task=update.cleanup&' . JFactory::getSession()->getFormToken() . '=1'; $this->setRedirect($url); } /** * Clean up after ourselves * * @return void * * @since 2.5.4 */ public function cleanup() { /* * Cleanup with login page. Used for pre-token check versions to be able to update * from =< 3.2.7 to allow updates without problems but with a maximum of security. */ if (!JSession::checkToken('get')) { $this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm'); return false; } $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}'; $options['text_file'] = 'joomla_update.php'; JLog::addLogger($options, JLog::INFO, array('Update', 'databasequery', 'jerror')); try { JLog::add(JText::_('COM_JOOMLAUPDATE_UPDATE_LOG_CLEANUP'), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } $this->_applyCredentials(); /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $model->cleanUp(); $url = 'index.php?option=com_joomlaupdate&view=default&layout=complete'; $this->setRedirect($url); try { JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_COMPLETE', JVERSION), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } } /** * Purges updates. * * @return void * * @since 3.0 */ public function purge() { // Check for request forgeries $this->checkToken(); // Purge updates /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('Default'); $model->purge(); $url = 'index.php?option=com_joomlaupdate'; $this->setRedirect($url, $model->_message); } /** * Uploads an update package to the temporary directory, under a random name * * @return void * * @since 3.6.0 */ public function upload() { // Check for request forgeries $this->checkToken(); // 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'); return; } $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.6.0 */ public function captive() { // Check for request forgeries $this->checkToken('get'); // 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.6.0 */ public function confirm() { // Check for request forgeries $this->checkToken(); // 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)); try { JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $tempFile), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } // Redirect to the actual update page $url = 'index.php?option=com_joomlaupdate&task=update.install&' . JFactory::getSession()->getFormToken() . '=1'; $this->setRedirect($url); } /** * Method to display a view. * * @param boolean $cachable If true, the view output will be cached * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}. * * @return JoomlaupdateControllerUpdate This object to support chaining. * * @since 2.5.4 */ public function display($cachable = false, $urlparams = array()) { // Get the document object. $document = JFactory::getDocument(); // Set the default view name and format from the Request. $vName = $this->input->get('view', 'update'); $vFormat = $document->getType(); $lName = $this->input->get('layout', 'default', 'string'); // Get and render the view. 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). $view->setModel($model, true); $view->setLayout($lName); // Push document object into the view. $view->document = $document; $view->display(); } return $this; } /** * Applies FTP credentials to Joomla! itself, when required * * @return void * * @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'); $pass = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.ftp_pass', 'ftp_pass', null, 'raw'); if ($user != '' && $pass != '') { // Add credentials to the session if (!JClientHelper::setCredentials('ftp', $user, $pass)) { JError::raiseWarning(500, JText::_('JLIB_CLIENT_ERROR_HELPER_SETCREDENTIALSFROMREQUEST_FAILED')); } } } } /** * Checks the admin has super administrator privileges and then proceeds with the final & cleanup steps. * * @return array * * @since 3.6.3 */ public function finaliseconfirm() { // Check for request forgeries $this->checkToken(); // Did a non Super User try do this? 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'); // 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); // The login fails? if (!$result) { JFactory::getApplication()->enqueueMessage(JText::_('JGLOBAL_AUTH_INVALID_PASS'), 'warning'); $this->setRedirect('index.php?option=com_joomlaupdate&view=update&layout=finaliseconfirm'); return false; } // Redirect back to the actual finalise page $this->setRedirect('index.php?option=com_joomlaupdate&task=update.finalise&' . JFactory::getSession()->getFormToken() . '=1'); } /** * Fetch Extension update XML proxy. Used to prevent Access-Control-Allow-Origin errors. * Prints a JSON string. * Called from JS. * * @since 3.10.0 * * @return void */ public function fetchExtensionCompatibility() { $extensionID = $this->input->get('extension-id', '', 'DEFAULT'); $joomlaTargetVersion = $this->input->get('joomla-target-version', '', 'DEFAULT'); $joomlaCurrentVersion = $this->input->get('joomla-current-version', '', JVERSION); $extensionVersion = $this->input->get('extension-version', '', 'DEFAULT'); /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel('default'); $upgradeCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaTargetVersion); $currentCompatibilityStatus = $model->fetchCompatibility($extensionID, $joomlaCurrentVersion); $upgradeUpdateVersion = false; $currentUpdateVersion = false; $upgradeWarning = 0; if ($upgradeCompatibilityStatus->state == 1 && !empty($upgradeCompatibilityStatus->compatibleVersions)) { $upgradeUpdateVersion = end($upgradeCompatibilityStatus->compatibleVersions); } if ($currentCompatibilityStatus->state == 1 && !empty($currentCompatibilityStatus->compatibleVersions)) { $currentUpdateVersion = end($currentCompatibilityStatus->compatibleVersions); } if ($upgradeUpdateVersion !== false) { $upgradeOldestVersion = $upgradeCompatibilityStatus->compatibleVersions[0]; if ($currentUpdateVersion !== false) { // If there are updates compatible with both CMS versions use these $bothCompatibleVersions = array_values( array_intersect($upgradeCompatibilityStatus->compatibleVersions, $currentCompatibilityStatus->compatibleVersions) ); if (!empty($bothCompatibleVersions)) { $upgradeOldestVersion = $bothCompatibleVersions[0]; $upgradeUpdateVersion = end($bothCompatibleVersions); } } if (version_compare($upgradeOldestVersion, $extensionVersion, '>')) { // Installed version is empty or older than the oldest compatible update: Update required $resultGroup = 2; } else { // Current version is compatible $resultGroup = 3; } if ($currentUpdateVersion !== false && version_compare($upgradeUpdateVersion, $currentUpdateVersion, '<')) { // Special case warning when version compatible with target is lower than current $upgradeWarning = 2; } } elseif ($currentUpdateVersion !== false) { // No compatible version for target version but there is a compatible version for current version $resultGroup = 1; } else { // No update server available $resultGroup = 1; } // Do we need to capture $combinedCompatibilityStatus = array( 'upgradeCompatibilityStatus' => (object) array( 'state' => $upgradeCompatibilityStatus->state, 'compatibleVersion' => $upgradeUpdateVersion ), 'currentCompatibilityStatus' => (object) array( 'state' => $currentCompatibilityStatus->state, 'compatibleVersion' => $currentUpdateVersion ), 'resultGroup' => $resultGroup, 'upgradeWarning' => $upgradeWarning, ); $this->app = JFactory::getApplication(); $this->app->mimeType = 'application/json'; $this->app->charSet = 'utf-8'; $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet); $this->app->sendHeaders(); try { echo new JResponseJson($combinedCompatibilityStatus); } catch (Exception $e) { echo $e; } $this->app->close(); } /** * Fetch and report updates in JSON format, for AJAX requests * * @return void * * @since 3.10.10 */ public function ajax() { $app = JFactory::getApplication(); if (!JSession::checkToken('get')) { $app->setHeader('status', 403, true); $app->sendHeaders(); echo JText::_('JINVALID_TOKEN_NOTICE'); $app->close(); } $model = $this->getModel('default'); $updateInfo = $model->getUpdateInformation(); $update = array(); $update[] = array('version' => $updateInfo['latest']); echo json_encode($update); $app->close(); } } PK �|!]\\�ij j joomlaupdate.xmlnu &1i� <?xml version="1.0" encoding="utf-8"?> <extension type="component" version="3.1" method="upgrade"> <name>com_joomlaupdate</name> <author>Joomla! Project</author> <creationDate>February 2012</creationDate> <copyright>(C) 2012 Open Source Matters, Inc.</copyright> <license>GNU General Public License version 2 or later; see LICENSE.txt</license> <authorEmail>admin@joomla.org</authorEmail> <authorUrl>www.joomla.org</authorUrl> <version>3.10.1</version> <description>COM_JOOMLAUPDATE_XML_DESCRIPTION</description> <media destination="com_joomlaupdate" folder="media"> <folder>js</folder> </media> <administration> <menu img="class:joomlaupdate">com_joomlaupdate</menu> <files folder="admin"> <filename>config.xml</filename> <filename>controller.php</filename> <filename>joomlaupdate.php</filename> <filename>restore.php</filename> <folder>controllers</folder> <folder>helpers</folder> <folder>models</folder> <folder>views</folder> </files> <languages folder="admin"> <language tag="en-GB">language/en-GB.com_joomlaupdate.ini</language> <language tag="en-GB">language/en-GB.com_joomlaupdate.sys.ini</language> </languages> </administration> <updateservers> <server type="extension" name="Joomla! Update Component Update Site">https://update.joomla.org/core/extensions/com_joomlaupdate.xml</server> </updateservers> </extension> PK �|!]srq� controller.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * Joomla! Update Controller * * @since 2.5.4 */ class JoomlaupdateController extends JControllerLegacy { /** * Method to display a view. * * @param boolean $cachable If true, the view output will be cached. * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}. * * @return JController This object to support chaining. * * @since 2.5.4 */ public function display($cachable = false, $urlparams = false) { // Get the document object. $document = JFactory::getDocument(); // Set the default view name and format from the Request. $vName = $this->input->get('view', 'default'); $vFormat = $document->getType(); $lName = $this->input->get('layout', 'default', 'string'); // Get and render the view. if ($view = $this->getView($vName, $vFormat)) { $ftp = JClientHelper::setCredentialsFromRequest('ftp'); $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 static::addModelPath(JPATH_ADMINISTRATOR . '/components/com_installer/models', 'InstallerModel'); $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(); // Push the model into the view (as default). $view->setModel($model, true); $view->setLayout($lName); // Push document object into the view. $view->document = $document; $view->display(); } return $this; } } PK �|!]��W1 1 joomlaupdate.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; if (!JFactory::getUser()->authorise('core.admin')) { throw new JAccessExceptionNotallowed(JText::_('JERROR_ALERTNOAUTHOR'), 403); } $controller = JControllerLegacy::getInstance('Joomlaupdate'); $controller->execute(JFactory::getApplication()->input->get('task')); $controller->redirect(); PK �|!]d�7@y y restore_finalisation.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ // Require the restoration environment or fail cold. Prevents direct web access. defined('_AKEEBA_RESTORATION') or die(); // Fake a miniature Joomla environment if (!defined('_JEXEC')) { define('_JEXEC', 1); } if (!function_exists('jimport')) { /** * We don't use it but the post-update script is using it anyway, so LET'S FAKE IT! * * @param string $path A dot syntax path. * @param string $base Search this directory for the class. * * @return boolean True on success. * * @since 3.5.1 */ function jimport($path, $base = null) { // Do nothing } } // Fake the JFile class, mapping it to Restore's post-processing class if (!class_exists('JFile')) { /** * JFile mock class proxing behaviour in the post-upgrade script to that of either native PHP or restore.php * * @since 3.5.1 */ abstract class JFile { /** * Proxies checking a folder exists to the native php version * * @param string $fileName The path to the file to be checked * * @return boolean * * @since 3.5.1 */ public static function exists($fileName) { return @file_exists($fileName); } /** * Proxies deleting a file to the restore.php version * * @param string $fileName The path to the file to be deleted * * @return boolean * * @since 3.5.1 */ public static function delete($fileName) { $postproc = AKFactory::getPostProc(); $postproc->unlink($fileName); } } } // Fake the JFolder class, mapping it to Restore's post-processing class if (!class_exists('JFolder')) { /** * JFolder mock class proxing behaviour in the post-upgrade script to that of either native PHP or restore.php * * @since 3.5.1 */ abstract class JFolder { /** * Proxies checking a folder exists to the native php version * * @param string $folderName The path to the folder to be checked * * @return boolean * * @since 3.5.1 */ public static function exists($folderName) { return @is_dir($folderName); } /** * Proxies deleting a folder to the restore.php version * * @param string $folderName The path to the folder to be deleted * * @return void * * @since 3.5.1 */ public static function delete($folderName) { recursive_remove_directory($folderName); } } } // Fake the JText class - we aren't going to show errors to people anyhow if (!class_exists('JText')) { /** * JText mock class proxing behaviour in the post-upgrade script to that of either native PHP or restore.php * * @since 3.5.1 */ abstract class JText { /** * No need for translations in a non-interactive script, so always return an empty string here * * @param string $text A language constant * * @return string * * @since 3.5.1 */ public static function sprintf($text) { return ''; } } } if (!function_exists('finalizeRestore')) { /** * Run part of the Joomla! finalisation script, namely the part that cleans up unused files/folders * * @param string $siteRoot The root to the Joomla! site * @param string $restorePath The base path to restore.php * * @return void * * @since 3.5.1 */ function finalizeRestore($siteRoot, $restorePath) { if (!defined('JPATH_ROOT')) { define('JPATH_ROOT', $siteRoot); } $filePath = JPATH_ROOT . '/administrator/components/com_admin/script.php'; if (file_exists($filePath)) { require_once $filePath; } // Make sure Joomla!'s code can figure out which files exist and need be removed clearstatcache(); // Remove obsolete files - prevents errors occurring in some system plugins if (class_exists('JoomlaInstallerScript')) { $script = new JoomlaInstallerScript; $script->deleteUnexistingFiles(); } // Clear OPcache if (function_exists('opcache_reset')) { opcache_reset(); } elseif (function_exists('apc_clear_cache')) { @apc_clear_cache(); } } } PK �|!]{��/�� �� models/default.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; use Joomla\CMS\Extension\ExtensionHelper; use Joomla\CMS\Filter\InputFilter; use Joomla\CMS\Http\HttpFactory; use Joomla\Registry\Registry; jimport('joomla.filesystem.folder'); jimport('joomla.filesystem.file'); /** * Joomla! update overview Model * * @since 2.5.4 */ class JoomlaupdateModelDefault extends JModelLegacy { /** * @var array $updateInformation null * Holds the update information evaluated in getUpdateInformation. * * @since 3.10.0 */ private $updateInformation = null; /** * Detects if the Joomla! update site currently in use matches the one * configured in this component. If they don't match, it changes it. * * @return void * * @since 2.5.4 */ public function applyUpdateSite() { // Determine the intended update URL. $params = JComponentHelper::getParams('com_joomlaupdate'); switch ($params->get('updatesource', 'nochange')) { // "Minor & Patch Release for Current version AND Next Major Release". case 'next': $updateURL = 'https://update.joomla.org/core/sts/list_sts.xml'; break; // "Testing" case 'testing': $updateURL = 'https://update.joomla.org/core/test/list_test.xml'; break; // "Custom" // TODO: check if the customurl is valid and not just "not empty". case 'custom': if (trim($params->get('customurl', '')) != '') { $updateURL = trim($params->get('customurl', '')); } else { return JError::raiseWarning(403, JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM_ERROR')); } 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 'sts': (It's shown as "Default" because that option does not exist any more) * case 'nochange': */ default: $updateURL = 'https://update.joomla.org/core/list.xml'; } $db = $this->getDbo(); $query = $db->getQuery(true) ->select($db->quoteName('us') . '.*') ->from($db->quoteName('#__update_sites_extensions') . ' AS ' . $db->quoteName('map')) ->join( 'INNER', $db->quoteName('#__update_sites') . ' AS ' . $db->quoteName('us') . ' ON (' . 'us.update_site_id = map.update_site_id)' ) ->where('map.extension_id = ' . $db->quote(700)); $db->setQuery($query); $update_site = $db->loadObject(); if ($update_site->location != $updateURL) { // Modify the database record. $update_site->last_check_timestamp = 0; $update_site->location = $updateURL; $db->updateObject('#__update_sites', $update_site, 'update_site_id'); // Remove cached updates. $query->clear() ->delete($db->quoteName('#__updates')) ->where($db->quoteName('extension_id') . ' = ' . $db->quote('700')); $db->setQuery($query); $db->execute(); } } /** * Makes sure that the Joomla! update cache is up-to-date. * * @param boolean $force Force reload, ignoring the cache timeout. * * @return void * * @since 2.5.4 */ public function refreshUpdates($force = false) { if ($force) { $cache_timeout = 0; } else { $cache_timeout = 3600 * JComponentHelper::getParams('com_installer')->get('cachetimeout', 6, 'int'); } $updater = JUpdater::getInstance(); $minimumStability = JUpdater::STABILITY_STABLE; $comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate'); if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) { $minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE); } $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, $minimumStability, true); } else { $updater->findUpdates(700, $cache_timeout, $minimumStability); } } /** * Returns an array with the Joomla! update information. * * @return array * * @since 2.5.4 */ public function getUpdateInformation() { if ($this->updateInformation) { return $this->updateInformation; } // Initialise the return array. $this->updateInformation = array( 'installed' => JVERSION, 'latest' => null, 'object' => null, 'hasUpdate' => false, 'current' => JVERSION, // This is deprecated please use 'installed' or JVERSION directly ); // Fetch the update information from the database. $db = $this->getDbo(); $query = $db->getQuery(true) ->select('*') ->from($db->quoteName('#__updates')) ->where($db->quoteName('extension_id') . ' = ' . $db->quote(700)); $db->setQuery($query); $updateObject = $db->loadObject(); if (is_null($updateObject)) { $this->updateInformation['latest'] = JVERSION; return $this->updateInformation; } // Check whether this is a valid update or not if (version_compare($updateObject->version, JVERSION, '<')) { // This update points to an outdated version we should not offer to update to this $this->updateInformation['latest'] = JVERSION; return $this->updateInformation; } $minimumStability = JUpdater::STABILITY_STABLE; $comJoomlaupdateParams = JComponentHelper::getParams('com_joomlaupdate'); if (in_array($comJoomlaupdateParams->get('updatesource', 'nochange'), array('testing', 'custom'))) { $minimumStability = $comJoomlaupdateParams->get('minimum_stability', JUpdater::STABILITY_STABLE); } // Fetch the full update details from the update details URL. jimport('joomla.updater.update'); $update = new JUpdate; $update->loadFromXML($updateObject->detailsurl, $minimumStability); // Make sure we use the current information we got from the detailsurl $this->updateInformation['object'] = $update; $this->updateInformation['latest'] = $updateObject->version; // Check whether we have got an update from the detailsurl or not. if (version_compare($this->updateInformation['latest'], JVERSION, '>')) { $this->updateInformation['hasUpdate'] = true; } return $this->updateInformation; } /** * Returns an array with the configured FTP options. * * @return array * * @since 2.5.4 */ 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'), 'directory' => $config->get('ftp_root'), 'enabled' => $config->get('ftp_enable'), ); } /** * Removes all of the updates from the table and enable all update streams. * * @return boolean Result of operation. * * @since 3.0 */ public function purge() { $db = $this->getDbo(); // Reset the last update check timestamp $query = $db->getQuery(true) ->update($db->quoteName('#__update_sites')) ->set($db->quoteName('last_check_timestamp') . ' = 0'); $db->setQuery($query); $db->execute(); // We should delete all core updates here $query = $db->getQuery(true) ->delete($db->quoteName('#__updates')) ->where($db->quoteName('element') . ' = ' . $db->quote('joomla')) ->where($db->quoteName('type') . ' = ' . $db->quote('file')); $db->setQuery($query); if ($db->execute()) { $this->_message = JText::_('COM_JOOMLAUPDATE_CHECKED_UPDATES'); return true; } else { $this->_message = JText::_('COM_JOOMLAUPDATE_FAILED_TO_CHECK_UPDATES'); return false; } } /** * Downloads the update package to the site. * * @return boolean|string False on failure, basename of the file in any other case. * * @since 2.5.4 */ public function download() { $updateInfo = $this->getUpdateInformation(); $packageURL = trim($updateInfo['object']->downloadurl->_data); $sources = $updateInfo['object']->get('downloadSources', array()); // We have to manually follow the redirects here so we set the option to false. $httpOptions = new Registry; $httpOptions->set('follow_location', false); try { $head = HttpFactory::getHttp($httpOptions)->head($packageURL); } catch (RuntimeException $e) { // Passing false here -> download failed message $response['basename'] = false; return $response; } // Follow the Location headers until the actual download URL is known while (isset($head->headers['location'])) { $packageURL = $head->headers['location']; try { $head = HttpFactory::getHttp($httpOptions)->head($packageURL); } catch (RuntimeException $e) { // Passing false here -> download failed message $response['basename'] = false; return $response; } } // Remove protocol, path and query string from URL $basename = basename($packageURL); if (strpos($basename, '?') !== false) { $basename = substr($basename, 0, strpos($basename, '?')); } // Find the path to the temp directory and the local package. $config = JFactory::getConfig(); $tempdir = (string) InputFilter::getInstance(array(), array(), 1, 1)->clean($config->get('tmp_path'), 'path'); $target = $tempdir . '/' . $basename; $response = array(); // Do we have a cached file? $exists = JFile::exists($target); if (!$exists) { // Not there, let's fetch it. $mirror = 0; while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) { $name = $sources[$mirror]; $packageURL = trim($name->url); $mirror++; } $response['basename'] = $download; } else { // Is it a 0-byte file? If so, re-download please. $filesize = @filesize($target); if (empty($filesize)) { $mirror = 0; while (!($download = $this->downloadPackage($packageURL, $target)) && isset($sources[$mirror])) { $name = $sources[$mirror]; $packageURL = trim($name->url); $mirror++; } $response['basename'] = $download; } // Yes, it's there, skip downloading. $response['basename'] = $basename; } $response['check'] = $this->isChecksumValid($target, $updateInfo['object']); return $response; } /** * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest * * @param string $packagefile Location of the package to be installed * @param JUpdate $updateObject The Update Object * * @return boolean False in case the validation did not work; true in any other case. * * @note This method has been forked from (JInstallerHelper::isChecksumValid) so it * does not depend on an up-to-date InstallerHelper at the update time * * @since 3.9.0 */ private function isChecksumValid($packagefile, $updateObject) { $hashes = array('sha256', 'sha384', 'sha512'); foreach ($hashes as $hash) { if ($updateObject->get($hash, false)) { $hashPackage = hash_file($hash, $packagefile); $hashRemote = $updateObject->$hash->_data; if ($hashPackage !== $hashRemote) { // Return false in case the hash did not match return false; } } } // Well nothing was provided or all worked return true; } /** * Downloads a package file to a specific directory * * @param string $url The URL to download from * @param string $target The directory to store the file * * @return boolean True on success * * @since 2.5.4 */ protected function downloadPackage($url, $target) { JLoader::import('helpers.download', JPATH_COMPONENT_ADMINISTRATOR); try { JLog::add(JText::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), JLog::INFO, 'Update'); } catch (RuntimeException $exception) { // Informational log only } // Get the handler to download the package try { $http = JHttpFactory::getHttp(null, array('curl', 'stream')); } catch (RuntimeException $e) { return false; } jimport('joomla.filesystem.file'); // Make sure the target does not exist. JFile::delete($target); // Download the package try { $result = $http->get($url); } catch (RuntimeException $e) { return false; } if (!$result || ($result->code != 200 && $result->code != 310)) { return false; } // Write the file to disk JFile::write($target, $result->body); return basename($target); } /** * Create restoration file. * * @param string $basename Optional base path to the file. * * @return boolean True if successful; false otherwise. * * @since 2.5.4 */ public function createRestorationFile($basename = null) { // Get a password $password = JUserHelper::genRandomPassword(32); $app = JFactory::getApplication(); $app->setUserState('com_joomlaupdate.password', $password); // Do we have to use FTP? $method = JFactory::getApplication()->getUserStateFromRequest('com_joomlaupdate.method', 'method', 'direct', 'cmd'); // Get the absolute path to site's root. $siteroot = JPATH_SITE; // If the package name is not specified, get it from the update info. if (empty($basename)) { $updateInfo = $this->getUpdateInformation(); $packageURL = $updateInfo['object']->downloadurl->_data; $basename = basename($packageURL); } // Get the package name. $config = JFactory::getConfig(); $tempdir = $config->get('tmp_path'); $file = $tempdir . '/' . $basename; $filesize = @filesize($file); $app->setUserState('com_joomlaupdate.password', $password); $app->setUserState('com_joomlaupdate.filesize', $filesize); $data = "<?php\ndefined('_AKEEBA_RESTORATION') or die('Restricted access');\n"; $data .= '$restoration_setup = array(' . "\n"; $data .= <<<ENDDATA 'kickstart.security.password' => '$password', 'kickstart.tuning.max_exec_time' => '5', 'kickstart.tuning.run_time_bias' => '75', 'kickstart.tuning.min_exec_time' => '0', 'kickstart.procengine' => '$method', 'kickstart.setup.sourcefile' => '$file', 'kickstart.setup.destdir' => '$siteroot', 'kickstart.setup.restoreperms' => '0', 'kickstart.setup.filetype' => 'zip', 'kickstart.setup.dryrun' => '0', 'kickstart.setup.renamefiles' => array(), 'kickstart.setup.postrenamefiles' => false ENDDATA; if ($method != 'direct') { /* * Fetch the FTP parameters from the request. Note: The password should be * allowed as raw mode, otherwise something like !@<sdf34>43H% would be * sanitised to !@43H% which is just plain wrong. */ $ftp_host = $app->input->get('ftp_host', ''); $ftp_port = $app->input->get('ftp_port', '21'); $ftp_user = $app->input->get('ftp_user', ''); $ftp_pass = addcslashes($app->input->get('ftp_pass', '', 'raw'), "'\\"); $ftp_root = $app->input->get('ftp_root', ''); // Is the tempdir really writable? $writable = @is_writeable($tempdir); if ($writable) { // Let's be REALLY sure. $fp = @fopen($tempdir . '/test.txt', 'w'); if ($fp === false) { $writable = false; } else { fclose($fp); unlink($tempdir . '/test.txt'); } } // If the tempdir is not writable, create a new writable subdirectory. if (!$writable) { $FTPOptions = JClientHelper::getCredentials('ftp'); $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); $dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/'); if (!@mkdir($tempdir . '/admintools')) { $ftp->mkdir($dest); } if (!@chmod($tempdir . '/admintools', 511)) { $ftp->chmod($dest, 511); } $tempdir .= '/admintools'; } // Just in case the temp-directory was off-root, try using the default tmp directory. $writable = @is_writeable($tempdir); if (!$writable) { $tempdir = JPATH_ROOT . '/tmp'; // Does the JPATH_ROOT/tmp directory exist? if (!is_dir($tempdir)) { JFolder::create($tempdir, 511); $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. if (!is_writable($tempdir)) { $FTPOptions = JClientHelper::getCredentials('ftp'); $ftp = JClientFtp::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']); $dest = JPath::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $tempdir . '/admintools'), '/'); if (!@mkdir($tempdir . '/admintools')) { $ftp->mkdir($dest); } if (!@chmod($tempdir . '/admintools', 511)) { $ftp->chmod($dest, 511); } $tempdir .= '/admintools'; } } // If we still have no writable directory, we'll try /tmp and the system's temp-directory. $writable = @is_writeable($tempdir); if (!$writable) { if (@is_dir('/tmp') && @is_writable('/tmp')) { $tempdir = '/tmp'; } else { // Try to find the system temp path. $tmpfile = @tempnam('dummy', ''); $systemp = @dirname($tmpfile); @unlink($tmpfile); if (!empty($systemp)) { if (@is_dir($systemp) && @is_writable($systemp)) { $tempdir = $systemp; } } } } $data .= <<<ENDDATA , 'kickstart.ftp.ssl' => '0', 'kickstart.ftp.passive' => '1', 'kickstart.ftp.host' => '$ftp_host', 'kickstart.ftp.port' => '$ftp_port', 'kickstart.ftp.user' => '$ftp_user', 'kickstart.ftp.pass' => '$ftp_pass', 'kickstart.ftp.dir' => '$ftp_root', 'kickstart.ftp.tempdir' => '$tempdir' ENDDATA; } $data .= ');'; // Remove the old file, if it's there... $configpath = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php'; if (JFile::exists($configpath)) { JFile::delete($configpath); } // Write new file. First try with JFile. $result = JFile::write($configpath, $data); // In case JFile used FTP but direct access could help. if (!$result) { if (function_exists('file_put_contents')) { $result = @file_put_contents($configpath, $data); if ($result !== false) { $result = true; } } else { $fp = @fopen($configpath, 'wt'); if ($fp !== false) { $result = @fwrite($fp, $data); if ($result !== false) { $result = true; } @fclose($fp); } } } return $result; } /** * Runs the schema update SQL files, the PHP update script and updates the * manifest cache and #__extensions entry. Essentially, it is identical to * JInstallerFile::install() without the file copy. * * @return boolean True on success. * * @since 2.5.4 */ public function finaliseUpgrade() { $installer = JInstaller::getInstance(); $manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml'); if ($manifest === false) { $installer->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST')); return false; } $installer->manifest = $manifest; $installer->setUpgrade(true); $installer->setOverwrite(true); $installer->extension = JTable::getInstance('extension'); $installer->extension->load(700); $installer->setAdapter($installer->extension->type); $installer->setPath('manifest', JPATH_MANIFESTS . '/files/joomla.xml'); $installer->setPath('source', JPATH_MANIFESTS . '/files'); $installer->setPath('extension_root', JPATH_ROOT); // Run the script file. JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php'); $manifestClass = new JoomlaInstallerScript; ob_start(); ob_implicit_flush(false); if ($manifestClass && method_exists($manifestClass, 'preflight')) { if ($manifestClass->preflight('update', $installer) === false) { $installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE')); return false; } } // Create msg object; first use here. $msg = ob_get_contents(); ob_end_clean(); // Get a database connector object. $db = $this->getDbo(); /* * Check to see if a file extension by the same name is already installed. * If it is, then update the table because if the files aren't there * we can assume that it was (badly) uninstalled. * If it isn't, add an entry to extensions. */ $query = $db->getQuery(true) ->select($db->quoteName('extension_id')) ->from($db->quoteName('#__extensions')) ->where($db->quoteName('type') . ' = ' . $db->quote('file')) ->where($db->quoteName('element') . ' = ' . $db->quote('joomla')); $db->setQuery($query); try { $db->execute(); } catch (RuntimeException $e) { // Install failed, roll back changes. $installer->abort( JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $e->getMessage()) ); return false; } $id = $db->loadResult(); $row = JTable::getInstance('extension'); if ($id) { // Load the entry and update the manifest_cache. $row->load($id); // Update name. $row->set('name', 'files_joomla'); // Update manifest. $row->manifest_cache = $installer->generateManifestCache(); if (!$row->store()) { // Install failed, roll back changes. $installer->abort( JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_UPDATE'), $row->getError()) ); return false; } } else { // Add an entry to the extension table with a whole heap of defaults. $row->set('name', 'files_joomla'); $row->set('type', 'file'); $row->set('element', 'joomla'); // There is no folder for files so leave it blank. $row->set('folder', ''); $row->set('enabled', 1); $row->set('protected', 0); $row->set('access', 0); $row->set('client_id', 0); $row->set('params', ''); $row->set('system_data', ''); $row->set('manifest_cache', $installer->generateManifestCache()); if (!$row->store()) { // Install failed, roll back changes. $installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError())); return false; } // Set the insert id. $row->set('extension_id', $db->insertid()); // Since we have created a module item, we add it to the installation step stack // so that if we have to rollback the changes we can undo it. $installer->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id)); } $result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id); if ($result === false) { // Install failed, rollback changes. $installer->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_UPDATE_SQL_ERROR', $db->stderr(true))); return false; } // Start Joomla! 1.6. ob_start(); ob_implicit_flush(false); if ($manifestClass && method_exists($manifestClass, 'update')) { if ($manifestClass->update($installer) === false) { // Install failed, rollback changes. $installer->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE')); return false; } } // Append messages. $msg .= ob_get_contents(); ob_end_clean(); // Clobber any possible pending updates. $update = JTable::getInstance('update'); $uid = $update->find( array('element' => 'joomla', 'type' => 'file', 'client_id' => '0', 'folder' => '') ); if ($uid) { $update->delete($uid); } // And now we run the postflight. ob_start(); ob_implicit_flush(false); if ($manifestClass && method_exists($manifestClass, 'postflight')) { $manifestClass->postflight('update', $installer); } // Append messages. $msg .= ob_get_contents(); ob_end_clean(); if ($msg != '') { $installer->set('extension_message', $msg); } // Refresh versionable assets cache. JFactory::getApplication()->flushAssets(); return true; } /** * Removes the extracted package file. * * @return void * * @since 2.5.4 */ public function cleanUp() { // Remove the update package. $config = JFactory::getConfig(); $tempdir = $config->get('tmp_path'); $file = JFactory::getApplication()->getUserState('com_joomlaupdate.file', null); $target = $tempdir . '/' . $file; if (!@unlink($target)) { JFile::delete($target); } // Remove the restoration.php file. $target = JPATH_COMPONENT_ADMINISTRATOR . '/restoration.php'; if (!@unlink($target)) { JFile::delete($target); } // Remove joomla.xml from the site's root. $target = JPATH_ROOT . '/joomla.xml'; if (!@unlink($target)) { JFile::delete($target); } // Unset the update filename from the session. JFactory::getApplication()->setUserState('com_joomlaupdate.file', null); $oldVersion = JFactory::getApplication()->getUserState('com_joomlaupdate.oldversion'); // Trigger event after joomla update. JFactory::getApplication()->triggerEvent('onJoomlaAfterUpdate', array($oldVersion)); JFactory::getApplication()->setUserState('com_joomlaupdate.oldversion', null); } /** * Uploads what is presumably an update ZIP file under a mangled name in the temporary directory. * * @return void * * @since 3.6.0 */ 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') . '<br />' . 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') . '<br />' . 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 boolean * * @since 3.6.0 */ public function captiveLogin($credentials) { // Make sure the username matches $username = isset($credentials['username']) ? $credentials['username'] : null; $user = JFactory::getUser(); if (strtolower($user->username) != strtolower($username)) { return false; } // Make sure the user is authorised if (!$user->authorise('core.admin')) { return false; } // Get the global JAuthentication object. $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 boolean * * @since 3.6.0 */ 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.6.0 */ 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); } } } } /** * Gets PHP options. * TODO: Outsource, build common code base for pre install and pre update check * * @return array Array of PHP config options * * @since 3.10.0 */ public function getPhpOptions() { $options = array(); /* * Check the PHP Version. It is already checked in JUpdate. * A Joomla! Update which is not supported by current PHP * version is not shown. So this check is actually unnecessary. */ $option = new stdClass; $option->label = JText::sprintf('INSTL_PHP_VERSION_NEWER', $this->getTargetMinimumPHPVersion()); $option->state = $this->isPhpVersionSupported(); $option->notice = null; $options[] = $option; // Only check if required PHP version is less than 7. if (version_compare($this->getTargetMinimumPHPVersion(), '7', '<')) { // Check for magic quotes gpc. $option = new stdClass; $option->label = JText::_('INSTL_MAGIC_QUOTES_GPC'); $option->state = (ini_get('magic_quotes_gpc') == false); $option->notice = null; $options[] = $option; // Check for register globals. $option = new stdClass; $option->label = JText::_('INSTL_REGISTER_GLOBALS'); $option->state = (ini_get('register_globals') == false); $option->notice = null; $options[] = $option; } // Check for zlib support. $option = new stdClass; $option->label = JText::_('INSTL_ZLIB_COMPRESSION_SUPPORT'); $option->state = extension_loaded('zlib'); $option->notice = null; $options[] = $option; // Check for XML support. $option = new stdClass; $option->label = JText::_('INSTL_XML_SUPPORT'); $option->state = extension_loaded('xml'); $option->notice = null; $options[] = $option; // Check for mbstring options. if (extension_loaded('mbstring')) { // Check for default MB language. $option = new stdClass; $option->label = JText::_('INSTL_MB_LANGUAGE_IS_DEFAULT'); $option->state = strtolower(ini_get('mbstring.language')) === 'neutral'; $option->notice = $option->state ? null : JText::_('INSTL_NOTICEMBLANGNOTDEFAULT'); $options[] = $option; // Check for MB function overload. $option = new stdClass; $option->label = JText::_('INSTL_MB_STRING_OVERLOAD_OFF'); $option->state = ini_get('mbstring.func_overload') == 0; $option->notice = $option->state ? null : JText::_('INSTL_NOTICEMBSTRINGOVERLOAD'); $options[] = $option; } // Check for a missing native parse_ini_file implementation. $option = new stdClass; $option->label = JText::_('INSTL_PARSE_INI_FILE_AVAILABLE'); $option->state = $this->getIniParserAvailability(); $option->notice = null; $options[] = $option; // Check for missing native json_encode / json_decode support. $option = new stdClass; $option->label = JText::_('INSTL_JSON_SUPPORT_AVAILABLE'); $option->state = function_exists('json_encode') && function_exists('json_decode'); $option->notice = null; $options[] = $option; $updateInformation = $this->getUpdateInformation(); // Check if configured database is compatible with Joomla 4 if (version_compare($updateInformation['latest'], '4', '>=')) { $option = new stdClass; $option->label = JText::sprintf('INSTL_DATABASE_SUPPORTED', $this->getConfiguredDatabaseType()); $option->state = $this->isDatabaseTypeSupported(); $option->notice = null; $options[] = $option; } // Check if database structure is up to date $option = new stdClass; $option->label = JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_TITLE'); $option->state = $this->getDatabaseSchemaCheck(); $option->notice = $option->state ? null : JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_DATABASE_STRUCTURE_NOTICE'); $options[] = $option; return $options; } /** * Gets PHP Settings. * TODO: Outsource, build common code base for pre install and pre update check * * @return array * * @since 3.10.0 */ public function getPhpSettings() { $settings = array(); // Check for display errors. $setting = new stdClass; $setting->label = JText::_('INSTL_DISPLAY_ERRORS'); $setting->state = (bool) ini_get('display_errors'); $setting->recommended = false; $settings[] = $setting; // Check for file uploads. $setting = new stdClass; $setting->label = JText::_('INSTL_FILE_UPLOADS'); $setting->state = (bool) ini_get('file_uploads'); $setting->recommended = true; $settings[] = $setting; // Only check if required PHP version is less than 7. if (version_compare($this->getTargetMinimumPHPVersion(), '7', '<')) { // Check for magic quotes runtimes. $setting = new stdClass; $setting->label = JText::_('INSTL_MAGIC_QUOTES_RUNTIME'); $setting->state = (bool) ini_get('magic_quotes_runtime'); $setting->recommended = false; $settings[] = $setting; // Check for safe mode. $setting = new stdClass; $setting->label = JText::_('INSTL_SAFE_MODE'); $setting->state = (bool) ini_get('safe_mode'); $setting->recommended = false; $settings[] = $setting; } // Check for output buffering. $setting = new stdClass; $setting->label = JText::_('INSTL_OUTPUT_BUFFERING'); $setting->state = (int) ini_get('output_buffering') !== 0; $setting->recommended = false; $settings[] = $setting; // Check for session auto-start. $setting = new stdClass; $setting->label = JText::_('INSTL_SESSION_AUTO_START'); $setting->state = (bool) ini_get('session.auto_start'); $setting->recommended = false; $settings[] = $setting; // Check for native ZIP support. $setting = new stdClass; $setting->label = JText::_('INSTL_ZIP_SUPPORT_AVAILABLE'); $setting->state = function_exists('zip_open') && function_exists('zip_read'); $setting->recommended = true; $settings[] = $setting; return $settings; } /** * Returns the configured database type id (mysqli or sqlsrv or ...) * * @return string * * @since 3.10.0 */ private function getConfiguredDatabaseType() { return JFactory::getApplication()->get('dbtype'); } /** * Returns true, if J! version is < 4 or current configured * database type is compatible with the update. * * @return boolean * * @since 3.10.0 */ public function isDatabaseTypeSupported() { $updateInformation = $this->getUpdateInformation(); // Check if configured database is compatible with Joomla 4 if (version_compare($updateInformation['latest'], '4', '>=')) { $unsupportedDatabaseTypes = array('sqlsrv', 'sqlazure'); $currentDatabaseType = $this->getConfiguredDatabaseType(); return !in_array($currentDatabaseType, $unsupportedDatabaseTypes); } return true; } /** * Returns true, if current installed php version is compatible with the update. * * @return boolean * * @since 3.10.0 */ public function isPhpVersionSupported() { return version_compare(PHP_VERSION, $this->getTargetMinimumPHPVersion(), '>='); } /** * Returns the PHP minimum version for the update. * Returns JOOMLA_MINIMUM_PHP, if there is no information given. * * @return string * * @since 3.10.0 */ private function getTargetMinimumPHPVersion() { $updateInformation = $this->getUpdateInformation(); return isset($updateInformation['object']->php_minimum) ? $updateInformation['object']->php_minimum->_data : JOOMLA_MINIMUM_PHP; } /** * Checks the availability of the parse_ini_file and parse_ini_string functions. * TODO: Outsource, build common code base for pre install and pre update check * * @return boolean True if the method exists. * * @since 3.10.0 */ public function getIniParserAvailability() { $disabledFunctions = ini_get('disable_functions'); if (!empty($disabledFunctions)) { // Attempt to detect them in the disable_functions blacklist. $disabledFunctions = explode(',', trim($disabledFunctions)); $numberOfDisabledFunctions = count($disabledFunctions); for ($i = 0; $i < $numberOfDisabledFunctions; $i++) { $disabledFunctions[$i] = trim($disabledFunctions[$i]); } $result = !in_array('parse_ini_string', $disabledFunctions); } else { // Attempt to detect their existence; even pure PHP implementations of them will trigger a positive response, though. $result = function_exists('parse_ini_string'); } return $result; } /** * Check if database structure is up to date * * @return boolean True if ok, false if not. * * @since 3.10.0 */ private function getDatabaseSchemaCheck() { JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_installer/models', 'InstallerModel'); // Get the database model $model = JModelLegacy::getInstance('Database', 'InstallerModel'); // Check if no default text filters found if (!$model->getDefaultTextFilters()) { return false; } // Check if database update version does not match CMS version if (version_compare($model->getUpdateVersion(), JVERSION) != 0) { return false; } // Get the schema change set $changeSet = $model->getItems(); $changeSetCheck = $changeSet->check(); // Check if schema errors found if (!empty($changeSetCheck)) { return false; } // Check if database schema version does not match CMS version if ($model->getSchemaVersion() != $changeSet->getSchema()) { return false; } // No database problems found return true; } /** * Gets an array containing all installed extensions, that are not core extensions. * * @return array name,version,updateserver * * @since 3.10.0 */ public function getNonCoreExtensions() { $db = $this->getDbo(); $query = $db->getQuery(true); $query->select( $db->qn('ex.name') . ', ' . $db->qn('ex.extension_id') . ', ' . $db->qn('ex.manifest_cache') . ', ' . $db->qn('ex.type') . ', ' . $db->qn('ex.folder') . ', ' . $db->qn('ex.element') . ', ' . $db->qn('ex.client_id') )->from( $db->qn('#__extensions', 'ex') )->where( $db->qn('ex.package_id') . ' = 0' ); $db->setQuery($query); $rows = $db->loadObjectList(); $rows = array_filter($rows, 'JoomlaupdateModelDefault::isNonCoreExtension'); foreach ($rows as $extension) { $decode = json_decode($extension->manifest_cache); // Remove unused fields so they do not cause javascript errors during pre-update check unset($decode->description); unset($decode->copyright); unset($decode->creationDate); $this->translateExtensionName($extension); $extension->version = isset($decode->version) ? $decode->version : JText::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION'); unset($extension->manifest_cache); $extension->manifest_cache = $decode; } return $rows; } /** * Checks if extension is non core extension. * * @param object $extension The extension to be checked * * @return bool true if extension is not a core extension * * @since 3.10.0 */ private static function isNonCoreExtension($extension) { return !\JExtensionHelper::checkIfCoreExtension($extension->type, $extension->element, $extension->client_id, $extension->folder); } /** * Gets an array containing all installed and enabled plugins, that are not core plugins. * * @param array $folderFilter Limit the list of plugins to a specific set of folder values * * @return array name,version,updateserver * * @since 3.10.0 */ public function getNonCorePlugins($folderFilter = array()) { $db = $this->getDbo(); $query = $db->getQuery(true); $query->select( $db->qn('ex.name') . ', ' . $db->qn('ex.extension_id') . ', ' . $db->qn('ex.manifest_cache') . ', ' . $db->qn('ex.type') . ', ' . $db->qn('ex.folder') . ', ' . $db->qn('ex.element') . ', ' . $db->qn('ex.client_id') . ', ' . $db->qn('ex.package_id') )->from( $db->qn('#__extensions', 'ex') )->where( $db->qn('ex.type') . ' = ' . $db->quote('plugin') )->where( $db->qn('ex.enabled') . ' = 1' ); if (count($folderFilter) > 0) { $folderFilter = array_map(array($db, 'quote'), $folderFilter); $query->where($db->qn('folder') . ' IN (' . implode(',', $folderFilter) . ')'); } $db->setQuery($query); $rows = $db->loadObjectList(); $rows = array_filter($rows, 'JoomlaupdateModelDefault::isNonCoreExtension'); foreach ($rows as $plugin) { $decode = json_decode($plugin->manifest_cache); // Remove unused fields so they do not cause javascript errors during pre-update check unset($decode->description); unset($decode->copyright); unset($decode->creationDate); $this->translateExtensionName($plugin); $plugin->version = isset($decode->version) ? $decode->version : JText::_('COM_JOOMLAUPDATE_PREUPDATE_UNKNOWN_EXTENSION_MANIFESTCACHE_VERSION'); unset($plugin->manifest_cache); $plugin->manifest_cache = $decode; } return $rows; } /** * Called by controller's fetchExtensionCompatibility, which is called via AJAX. * * @param string $extensionID The ID of the checked extension * @param string $joomlaTargetVersion Target version of Joomla * * @return object * * @since 3.10.0 */ public function fetchCompatibility($extensionID, $joomlaTargetVersion) { $updateSites = $this->getUpdateSitesInfo($extensionID); if (empty($updateSites)) { return (object) array('state' => 2); } foreach ($updateSites as $updateSite) { if ($updateSite['type'] === 'collection') { $updateFileUrls = $this->getCollectionDetailsUrls($updateSite, $joomlaTargetVersion); foreach ($updateFileUrls as $updateFileUrl) { $compatibleVersions = $this->checkCompatibility($updateFileUrl, $joomlaTargetVersion); // Return the compatible versions return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions); } } else { $compatibleVersions = $this->checkCompatibility($updateSite['location'], $joomlaTargetVersion); // Return the compatible versions return (object) array('state' => 1, 'compatibleVersions' => $compatibleVersions); } } // In any other case we mark this extension as not compatible return (object) array('state' => 0); } /** * Returns records with update sites and extension information for a given extension ID. * * @param int $extensionID The extension ID * * @return array * * @since 3.10.0 */ private function getUpdateSitesInfo($extensionID) { $db = $this->getDbo(); $query = $db->getQuery(true); $query->select( $db->qn('us.type') . ', ' . $db->qn('us.location') . ', ' . $db->qn('e.element') . ' AS ' . $db->qn('ext_element') . ', ' . $db->qn('e.type') . ' AS ' . $db->qn('ext_type') . ', ' . $db->qn('e.folder') . ' AS ' . $db->qn('ext_folder') )->from( $db->qn('#__update_sites', 'us') )->leftJoin( $db->qn('#__update_sites_extensions', 'ue') . ' ON ' . $db->qn('ue.update_site_id') . ' = ' . $db->qn('us.update_site_id') )->leftJoin( $db->qn('#__extensions', 'e') . ' ON ' . $db->qn('e.extension_id') . ' = ' . $db->qn('ue.extension_id') )->where($db->qn('e.extension_id') . ' = ' . (int) $extensionID); $db->setQuery($query); $result = $db->loadAssocList(); if (!is_array($result)) { return array(); } return $result; } /** * Method to get details URLs from a colletion update site for given extension and Joomla target version. * * @param array $updateSiteInfo The update site and extension information record to process * @param string $joomlaTargetVersion The Joomla! version to test against, * * @return array An array of URLs. * * @since 3.10.0 */ private function getCollectionDetailsUrls($updateSiteInfo, $joomlaTargetVersion) { $return = array(); $http = new JHttp; try { $response = $http->get($updateSiteInfo['location']); } catch (RuntimeException $e) { $response = null; } if ($response === null || $response->code !== 200) { return $return; } $updateSiteXML = simplexml_load_string($response->body); foreach ($updateSiteXML->extension as $extension) { $attribs = new stdClass; $attribs->element = ''; $attribs->type = ''; $attribs->folder = ''; $attribs->targetplatformversion = ''; foreach ($extension->attributes() as $key => $value) { $attribs->$key = (string) $value; } if ($attribs->element === $updateSiteInfo['ext_element'] && $attribs->type === $updateSiteInfo['ext_type'] && $attribs->folder === $updateSiteInfo['ext_folder'] && preg_match('/^' . $attribs->targetplatformversion . '/', $joomlaTargetVersion)) { $return[] = (string) $extension['detailsurl']; } } return $return; } /** * Method to check non core extensions for compatibility. * * @param string $updateFileUrl The items update XML url. * @param string $joomlaTargetVersion The Joomla! version to test against * * @return array An array of strings with compatible version numbers * * @since 3.10.0 */ private function checkCompatibility($updateFileUrl, $joomlaTargetVersion) { // Get the minimum stability information from com_installer $minimumStability = JComponentHelper::getParams('com_installer')->get('minimum_stability', JUpdater::STABILITY_STABLE); $update = new JUpdate; $update->set('jversion.full', $joomlaTargetVersion); $update->loadFromXML($updateFileUrl, $minimumStability); $compatibleVersions = $update->get('compatibleVersions'); // Check if old version of the updater library if (!isset($compatibleVersions)) { $downloadUrl = $update->get('downloadurl'); $updateVersion = $update->get('version'); return empty($downloadUrl) || empty($downloadUrl->_data) || empty($updateVersion) ? array() : array($updateVersion->_data); } usort($compatibleVersions, 'version_compare'); return $compatibleVersions; } /** * Translates an extension name * * @param object &$item The extension of which the name needs to be translated * * @return void * * @since 3.10.0 */ protected function translateExtensionName(&$item) { // ToDo: Cleanup duplicated code. from com_installer/models/extension.php $lang = JFactory::getLanguage(); $path = $item->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE; $extension = $item->element; $source = JPATH_SITE; switch ($item->type) { case 'component': $extension = $item->element; $source = $path . '/components/' . $extension; break; case 'module': $extension = $item->element; $source = $path . '/modules/' . $extension; break; case 'file': $extension = 'files_' . $item->element; break; case 'library': $extension = 'lib_' . $item->element; break; case 'plugin': $extension = 'plg_' . $item->folder . '_' . $item->element; $source = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element; break; case 'template': $extension = 'tpl_' . $item->element; $source = $path . '/templates/' . $item->element; } $lang->load("$extension.sys", JPATH_ADMINISTRATOR, null, false, true) || $lang->load("$extension.sys", $source, null, false, true); $lang->load($extension, JPATH_ADMINISTRATOR, null, false, true) || $lang->load($extension, $source, null, false, true); // Translate the extension name if possible $item->name = strip_tags(JText::_($item->name)); } /** * Checks whether a given template is active * * @param string $template The template name to be checked * * @return boolean * * @since 3.10.4 */ public function isTemplateActive($template) { $db = $this->getDbo(); $query = $db->getQuery(true); $query->select( $db->qn( array( 'id', 'home' ) ) )->from( $db->qn('#__template_styles') )->where( $db->qn('template') . ' = ' . $db->q($template) ); $templates = $db->setQuery($query)->loadObjectList(); $home = array_filter( $templates, function($value) { return $value->home > 0; } ); $ids = JArrayHelper::getColumn($templates, 'id'); $menu = false; if (count($ids)) { $query = $db->getQuery(true); $query->select( 'COUNT(*)' )->from( $db->qn('#__menu') )->where( $db->qn('template_style_id') . ' IN(' . implode(',', $ids) . ')' ); $menu = $db->setQuery($query)->loadResult() > 0; } return $home || $menu; } } PK �|!]�:J�� � config.xmlnu &1i� <?xml version="1.0" encoding="utf-8"?> <config> <fieldset name="sources" label="COM_JOOMLAUPDATE_CONFIG_SOURCES_LABEL" description="COM_JOOMLAUPDATE_CONFIG_SOURCES_DESC" > <field name="updatesource" type="list" label="COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_LABEL" description="COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DESC" default="default" > <!-- Note: Changed the values lts to default and sts to next with 3.4.0 --> <!-- Eliminated the 'nochange' option with 3.4.0 --> <!-- All invalid/unsupported/obsolete options equated to default in code with 3.4.0 --> <option value="default">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DEFAULT</option> <option value="next">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_NEXT</option> <option value="testing">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_TESTING</option> <option value="custom">COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_CUSTOM</option> </field> <field name="minimum_stability" type="list" label="COM_JOOMLAUPDATE_MINIMUM_STABILITY_LABEL" description="COM_JOOMLAUPDATE_MINIMUM_STABILITY_DESC" default="4" showon="updatesource:testing[OR]updatesource:custom" > <option value="0">COM_JOOMLAUPDATE_MINIMUM_STABILITY_DEV</option> <option value="1">COM_JOOMLAUPDATE_MINIMUM_STABILITY_ALPHA</option> <option value="2">COM_JOOMLAUPDATE_MINIMUM_STABILITY_BETA</option> <option value="3">COM_JOOMLAUPDATE_MINIMUM_STABILITY_RC</option> <option value="4">COM_JOOMLAUPDATE_MINIMUM_STABILITY_STABLE</option> </field> <field name="customurl" type="text" label="COM_JOOMLAUPDATE_CONFIG_CUSTOMURL_LABEL" description="COM_JOOMLAUPDATE_CONFIG_CUSTOMURL_DESC" default="" length="50" showon="updatesource:custom" /> </fieldset> </config> PK �|!]��Z� � restore.phpnu &1i� <?php /** * Akeeba Restore * * An archive extraction engine for ZIP, JPA and JPS archives. * * @copyright 2008-2017 Nicholas K. Dionysopoulos / Akeeba Ltd. * @license GNU GPL v2 or - at your option - any later version * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. */ define('_AKEEBA_RESTORATION', 1); defined('DS') or define('DS', DIRECTORY_SEPARATOR); // Unarchiver run states define('AK_STATE_NOFILE', 0); // File header not read yet define('AK_STATE_HEADER', 1); // File header read; ready to process data define('AK_STATE_DATA', 2); // Processing file data define('AK_STATE_DATAREAD', 3); // Finished processing file data; ready to post-process define('AK_STATE_POSTPROC', 4); // Post-processing define('AK_STATE_DONE', 5); // Done with post-processing /* Windows system detection */ if (!defined('_AKEEBA_IS_WINDOWS')) { if (function_exists('php_uname')) { define('_AKEEBA_IS_WINDOWS', stristr(php_uname(), 'windows')); } else { define('_AKEEBA_IS_WINDOWS', DIRECTORY_SEPARATOR == '\\'); } } // Get the file's root if (!defined('KSROOTDIR')) { define('KSROOTDIR', dirname(__FILE__)); } if (!defined('KSLANGDIR')) { define('KSLANGDIR', KSROOTDIR); } // Make sure the locale is correct for basename() to work if (function_exists('setlocale')) { @setlocale(LC_ALL, 'en_US.UTF8'); } // fnmatch not available on non-POSIX systems // Thanks to soywiz@php.net for this useful alternative function [http://gr2.php.net/fnmatch] if (!function_exists('fnmatch')) { function fnmatch($pattern, $string) { return @preg_match( '/^' . strtr(addcslashes($pattern, '/\\.+^$(){}=!<>|'), array('*' => '.*', '?' => '.?')) . '$/i', $string ); } } // Unicode-safe binary data length function if (!function_exists('akstringlen')) { if (function_exists('mb_strlen')) { function akstringlen($string) { return mb_strlen($string, '8bit'); } } else { function akstringlen($string) { return strlen($string); } } } if (!function_exists('aksubstr')) { if (function_exists('mb_strlen')) { function aksubstr($string, $start, $length = null) { return mb_substr($string, $start, $length, '8bit'); } } else { function aksubstr($string, $start, $length = null) { return substr($string, $start, $length); } } } /** * Gets a query parameter from GET or POST data * * @param $key * @param $default */ function getQueryParam($key, $default = null) { $value = $default; if (array_key_exists($key, $_REQUEST)) { $value = $_REQUEST[$key]; if (PHP_VERSION_ID < 50400 && get_magic_quotes_gpc() && !is_null($value)) { $value = stripslashes($value); } } return $value; } // Debugging function function debugMsg($msg) { if (!defined('KSDEBUG')) { return; } $fp = fopen('debug.txt', 'at'); fwrite($fp, $msg . "\n"); fclose($fp); // Echo to stdout if KSDEBUGCLI is defined if (defined('KSDEBUGCLI')) { echo $msg . "\n"; } } /** * The base class of Akeeba Engine objects. Allows for error and warnings logging * and propagation. Largely based on the Joomla! 1.5 JObject class. */ abstract class AKAbstractObject { /** @var array The queue size of the $_errors array. Set to 0 for infinite size. */ protected $_errors_queue_size = 0; /** @var array The queue size of the $_warnings array. Set to 0 for infinite size. */ protected $_warnings_queue_size = 0; /** @var array An array of errors */ private $_errors = array(); /** @var array An array of warnings */ private $_warnings = array(); /** * Get the most recent error message * * @param integer $i Optional error index * * @return string Error message */ public function getError($i = null) { return $this->getItemFromArray($this->_errors, $i); } /** * Returns the last item of a LIFO string message queue, or a specific item * if so specified. * * @param array $array An array of strings, holding messages * @param int $i Optional message index * * @return mixed The message string, or false if the key doesn't exist */ private function getItemFromArray($array, $i = null) { // Find the item if ($i === null) { // Default, return the last item $item = end($array); } else if (!array_key_exists($i, $array)) { // If $i has been specified but does not exist, return false return false; } else { $item = $array[$i]; } return $item; } /** * Return all errors, if any * * @return array Array of error messages */ public function getErrors() { return $this->_errors; } /** * Resets all error messages */ public function resetErrors() { $this->_errors = array(); } /** * Get the most recent warning message * * @param integer $i Optional warning index * * @return string Error message */ public function getWarning($i = null) { return $this->getItemFromArray($this->_warnings, $i); } /** * Return all warnings, if any * * @return array Array of error messages */ public function getWarnings() { return $this->_warnings; } /** * Resets all warning messages */ public function resetWarnings() { $this->_warnings = array(); } /** * Propagates errors and warnings to a foreign object. The foreign object SHOULD * implement the setError() and/or setWarning() methods but DOESN'T HAVE TO be of * AKAbstractObject type. For example, this can even be used to propagate to a * JObject instance in Joomla!. Propagated items will be removed from ourselves. * * @param object $object The object to propagate errors and warnings to. */ public function propagateToObject(&$object) { // Skip non-objects if (!is_object($object)) { return; } if (method_exists($object, 'setError')) { if (!empty($this->_errors)) { foreach ($this->_errors as $error) { $object->setError($error); } $this->_errors = array(); } } if (method_exists($object, 'setWarning')) { if (!empty($this->_warnings)) { foreach ($this->_warnings as $warning) { $object->setWarning($warning); } $this->_warnings = array(); } } } /** * Propagates errors and warnings from a foreign object. Each propagated list is * then cleared on the foreign object, as long as it implements resetErrors() and/or * resetWarnings() methods. * * @param object $object The object to propagate errors and warnings from */ public function propagateFromObject(&$object) { if (method_exists($object, 'getErrors')) { $errors = $object->getErrors(); if (!empty($errors)) { foreach ($errors as $error) { $this->setError($error); } } if (method_exists($object, 'resetErrors')) { $object->resetErrors(); } } if (method_exists($object, 'getWarnings')) { $warnings = $object->getWarnings(); if (!empty($warnings)) { foreach ($warnings as $warning) { $this->setWarning($warning); } } if (method_exists($object, 'resetWarnings')) { $object->resetWarnings(); } } } /** * Add an error message * * @param string $error Error message */ public function setError($error) { if ($this->_errors_queue_size > 0) { if (count($this->_errors) >= $this->_errors_queue_size) { array_shift($this->_errors); } } $this->_errors[] = $error; } /** * Add an error message * * @param string $error Error message */ public function setWarning($warning) { if ($this->_warnings_queue_size > 0) { if (count($this->_warnings) >= $this->_warnings_queue_size) { array_shift($this->_warnings); } } $this->_warnings[] = $warning; } /** * Sets the size of the error queue (acts like a LIFO buffer) * * @param int $newSize The new queue size. Set to 0 for infinite length. */ protected function setErrorsQueueSize($newSize = 0) { $this->_errors_queue_size = (int) $newSize; } /** * Sets the size of the warnings queue (acts like a LIFO buffer) * * @param int $newSize The new queue size. Set to 0 for infinite length. */ protected function setWarningsQueueSize($newSize = 0) { $this->_warnings_queue_size = (int) $newSize; } } /** * The superclass of all Akeeba Kickstart parts. The "parts" are intelligent stateful * classes which perform a single procedure and have preparation, running and * finalization phases. The transition between phases is handled automatically by * this superclass' tick() final public method, which should be the ONLY public API * exposed to the rest of the Akeeba Engine. */ abstract class AKAbstractPart extends AKAbstractObject { /** * Indicates whether this part has finished its initialisation cycle * * @var boolean */ protected $isPrepared = false; /** * Indicates whether this part has more work to do (it's in running state) * * @var boolean */ protected $isRunning = false; /** * Indicates whether this part has finished its finalization cycle * * @var boolean */ protected $isFinished = false; /** * Indicates whether this part has finished its run cycle * * @var boolean */ protected $hasRan = false; /** * The name of the engine part (a.k.a. Domain), used in return table * generation. * * @var string */ protected $active_domain = ""; /** * The step this engine part is in. Used verbatim in return table and * should be set by the code in the _run() method. * * @var string */ protected $active_step = ""; /** * A more detailed description of the step this engine part is in. Used * verbatim in return table and should be set by the code in the _run() * method. * * @var string */ protected $active_substep = ""; /** * Any configuration variables, in the form of an array. * * @var array */ protected $_parametersArray = array(); /** @var string The database root key */ protected $databaseRoot = array(); /** @var array An array of observers */ protected $observers = array(); /** @var int Last reported warnings's position in array */ private $warnings_pointer = -1; /** * The public interface to an engine part. This method takes care for * calling the correct method in order to perform the initialisation - * run - finalisation cycle of operation and return a proper response array. * * @return array A Response Array */ final public function tick() { // Call the right action method, depending on engine part state switch ($this->getState()) { case "init": $this->_prepare(); break; case "prepared": case "running": $this->_run(); break; case "postrun": $this->_finalize(); break; } // Send a Return Table back to the caller $out = $this->_makeReturnTable(); return $out; } /** * Returns the state of this engine part. * * @return string The state of this engine part. It can be one of * error, init, prepared, running, postrun, finished. */ final public function getState() { if ($this->getError()) { return "error"; } if (!($this->isPrepared)) { return "init"; } if (!($this->isFinished) && !($this->isRunning) && !($this->hasRun) && ($this->isPrepared)) { return "prepared"; } if (!($this->isFinished) && $this->isRunning && !($this->hasRun)) { return "running"; } if (!($this->isFinished) && !($this->isRunning) && $this->hasRun) { return "postrun"; } if ($this->isFinished) { return "finished"; } // Unknown internal state. This should never happen. return "error"; } /** * Runs the preparation for this part. Should set _isPrepared * to true */ abstract protected function _prepare(); /** * Runs the main functionality loop for this part. Upon calling, * should set the _isRunning to true. When it finished, should set * the _hasRan to true. If an error is encountered, setError should * be used. */ abstract protected function _run(); /** * Runs the finalisation process for this part. Should set * _isFinished to true. */ abstract protected function _finalize(); /** * Constructs a Response Array based on the engine part's state. * * @return array The Response Array for the current state */ final protected function _makeReturnTable() { // Get a list of warnings $warnings = $this->getWarnings(); // Report only new warnings if there is no warnings queue size if ($this->_warnings_queue_size == 0) { if (($this->warnings_pointer > 0) && ($this->warnings_pointer < (count($warnings)))) { $warnings = array_slice($warnings, $this->warnings_pointer + 1); $this->warnings_pointer += count($warnings); } else { $this->warnings_pointer = count($warnings); } } $out = array( 'HasRun' => (!($this->isFinished)), 'Domain' => $this->active_domain, 'Step' => $this->active_step, 'Substep' => $this->active_substep, 'Error' => $this->getError(), 'Warnings' => $warnings ); return $out; } /** * Returns a copy of the class's status array * * @return array */ public function getStatusArray() { return $this->_makeReturnTable(); } /** * Sends any kind of setup information to the engine part. Using this, * we avoid passing parameters to the constructor of the class. These * parameters should be passed as an indexed array and should be taken * into account during the preparation process only. This function will * set the error flag if it's called after the engine part is prepared. * * @param array $parametersArray The parameters to be passed to the * engine part. */ final public function setup($parametersArray) { if ($this->isPrepared) { $this->setState('error', "Can't modify configuration after the preparation of " . $this->active_domain); } else { $this->_parametersArray = $parametersArray; if (array_key_exists('root', $parametersArray)) { $this->databaseRoot = $parametersArray['root']; } } } /** * Sets the engine part's internal state, in an easy to use manner * * @param string $state One of init, prepared, running, postrun, finished, error * @param string $errorMessage The reported error message, should the state be set to error */ protected function setState($state = 'init', $errorMessage = 'Invalid setState argument') { switch ($state) { case 'init': $this->isPrepared = false; $this->isRunning = false; $this->isFinished = false; $this->hasRun = false; break; case 'prepared': $this->isPrepared = true; $this->isRunning = false; $this->isFinished = false; $this->hasRun = false; break; case 'running': $this->isPrepared = true; $this->isRunning = true; $this->isFinished = false; $this->hasRun = false; break; case 'postrun': $this->isPrepared = true; $this->isRunning = false; $this->isFinished = false; $this->hasRun = true; break; case 'finished': $this->isPrepared = true; $this->isRunning = false; $this->isFinished = true; $this->hasRun = false; break; case 'error': default: $this->setError($errorMessage); break; } } final public function getDomain() { return $this->active_domain; } final public function getStep() { return $this->active_step; } final public function getSubstep() { return $this->active_substep; } /** * Attaches an observer object * * @param AKAbstractPartObserver $obs */ function attach(AKAbstractPartObserver $obs) { $this->observers["$obs"] = $obs; } /** * Detaches an observer object * * @param AKAbstractPartObserver $obs */ function detach(AKAbstractPartObserver $obs) { unset($this->observers["$obs"]); } /** * Sets the BREAKFLAG, which instructs this engine part that the current step must break immediately, * in fear of timing out. */ protected function setBreakFlag() { AKFactory::set('volatile.breakflag', true); } final protected function setDomain($new_domain) { $this->active_domain = $new_domain; } final protected function setStep($new_step) { $this->active_step = $new_step; } final protected function setSubstep($new_substep) { $this->active_substep = $new_substep; } /** * Notifies observers each time something interesting happened to the part * * @param mixed $message The event object */ protected function notify($message) { foreach ($this->observers as $obs) { $obs->update($this, $message); } } } /** * The base class of unarchiver classes */ abstract class AKAbstractUnarchiver extends AKAbstractPart { /** @var array List of the names of all archive parts */ public $archiveList = array(); /** @var int The total size of all archive parts */ public $totalSize = array(); /** @var array Which files to rename */ public $renameFiles = array(); /** @var array Which directories to rename */ public $renameDirs = array(); /** @var array Which files to skip */ public $skipFiles = array(); /** @var string Archive filename */ protected $filename = null; /** @var integer Current archive part number */ protected $currentPartNumber = -1; /** @var integer The offset inside the current part */ protected $currentPartOffset = 0; /** @var bool Should I restore permissions? */ protected $flagRestorePermissions = false; /** @var AKAbstractPostproc Post processing class */ protected $postProcEngine = null; /** @var string Absolute path to prepend to extracted files */ protected $addPath = ''; /** @var string Absolute path to remove from extracted files */ protected $removePath = ''; /** @var integer Chunk size for processing */ protected $chunkSize = 524288; /** @var resource File pointer to the current archive part file */ protected $fp = null; /** @var int Run state when processing the current archive file */ protected $runState = null; /** @var stdClass File header data, as read by the readFileHeader() method */ protected $fileHeader = null; /** @var int How much of the uncompressed data we've read so far */ protected $dataReadLength = 0; /** @var array Unwriteable files in these directories are always ignored and do not cause errors when not extracted */ protected $ignoreDirectories = array(); /** * Wakeup function, called whenever the class is unserialized */ public function __wakeup() { if ($this->currentPartNumber >= 0 && !empty($this->archiveList[$this->currentPartNumber])) { $this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb'); if ((is_resource($this->fp)) && ($this->currentPartOffset > 0)) { @fseek($this->fp, $this->currentPartOffset); } } } /** * Sleep function, called whenever the class is serialized */ public function shutdown() { if (is_resource($this->fp)) { $this->currentPartOffset = @ftell($this->fp); @fclose($this->fp); } } /** * Is this file or directory contained in a directory we've decided to ignore * write errors for? This is useful to let the extraction work despite write * errors in the log, logs and tmp directories which MIGHT be used by the system * on some low quality hosts and Plesk-powered hosts. * * @param string $shortFilename The relative path of the file/directory in the package * * @return boolean True if it belongs in an ignored directory */ public function isIgnoredDirectory($shortFilename) { // return false; if (substr($shortFilename, -1) == '/') { $check = rtrim($shortFilename, '/'); } else { $check = dirname($shortFilename); } return in_array($check, $this->ignoreDirectories); } /** * Implements the abstract _prepare() method */ final protected function _prepare() { if (count($this->_parametersArray) > 0) { foreach ($this->_parametersArray as $key => $value) { switch ($key) { // Archive's absolute filename case 'filename': $this->filename = $value; // Sanity check if (!empty($value)) { $value = strtolower($value); if (strlen($value) > 6) { if ( (substr($value, 0, 7) == 'http://') || (substr($value, 0, 8) == 'https://') || (substr($value, 0, 6) == 'ftp://') || (substr($value, 0, 7) == 'ssh2://') || (substr($value, 0, 6) == 'ssl://') ) { $this->setState('error', 'Invalid archive location'); } } } break; // Should I restore permissions? case 'restore_permissions': $this->flagRestorePermissions = $value; break; // Should I use FTP? case 'post_proc': $this->postProcEngine = AKFactory::getPostProc($value); break; // Path to add in the beginning case 'add_path': $this->addPath = $value; $this->addPath = str_replace('\\', '/', $this->addPath); $this->addPath = rtrim($this->addPath, '/'); if (!empty($this->addPath)) { $this->addPath .= '/'; } break; // Path to remove from the beginning case 'remove_path': $this->removePath = $value; $this->removePath = str_replace('\\', '/', $this->removePath); $this->removePath = rtrim($this->removePath, '/'); if (!empty($this->removePath)) { $this->removePath .= '/'; } break; // Which files to rename (hash array) case 'rename_files': $this->renameFiles = $value; break; // Which files to rename (hash array) case 'rename_dirs': $this->renameDirs = $value; break; // Which files to skip (indexed array) case 'skip_files': $this->skipFiles = $value; break; // Which directories to ignore when we can't write files in them (indexed array) case 'ignoredirectories': $this->ignoreDirectories = $value; break; } } } $this->scanArchives(); $this->readArchiveHeader(); $errMessage = $this->getError(); if (!empty($errMessage)) { $this->setState('error', $errMessage); } else { $this->runState = AK_STATE_NOFILE; $this->setState('prepared'); } } /** * Scans for archive parts */ private function scanArchives() { if (defined('KSDEBUG')) { @unlink('debug.txt'); } debugMsg('Preparing to scan archives'); $privateArchiveList = array(); // Get the components of the archive filename $dirname = dirname($this->filename); $base_extension = $this->getBaseExtension(); $basename = basename($this->filename, $base_extension); $this->totalSize = 0; // Scan for multiple parts until we don't find any more of them $count = 0; $found = true; $this->archiveList = array(); while ($found) { ++$count; $extension = substr($base_extension, 0, 2) . sprintf('%02d', $count); $filename = $dirname . DIRECTORY_SEPARATOR . $basename . $extension; $found = file_exists($filename); if ($found) { debugMsg('- Found archive ' . $filename); // Add yet another part, with a numeric-appended filename $this->archiveList[] = $filename; $filesize = @filesize($filename); $this->totalSize += $filesize; $privateArchiveList[] = array($filename, $filesize); } else { debugMsg('- Found archive ' . $this->filename); // Add the last part, with the regular extension $this->archiveList[] = $this->filename; $filename = $this->filename; $filesize = @filesize($filename); $this->totalSize += $filesize; $privateArchiveList[] = array($filename, $filesize); } } debugMsg('Total archive parts: ' . $count); $this->currentPartNumber = -1; $this->currentPartOffset = 0; $this->runState = AK_STATE_NOFILE; // Send start of file notification $message = new stdClass; $message->type = 'totalsize'; $message->content = new stdClass; $message->content->totalsize = $this->totalSize; $message->content->filelist = $privateArchiveList; $this->notify($message); } /** * Returns the base extension of the file, e.g. '.jpa' * * @return string */ private function getBaseExtension() { static $baseextension; if (empty($baseextension)) { $basename = basename($this->filename); $lastdot = strrpos($basename, '.'); $baseextension = substr($basename, $lastdot); } return $baseextension; } /** * Concrete classes are supposed to use this method in order to read the archive's header and * prepare themselves to the point of being ready to extract the first file. */ protected abstract function readArchiveHeader(); protected function _run() { if ($this->getState() == 'postrun') { return; } $this->setState('running'); $timer = AKFactory::getTimer(); $status = true; while ($status && ($timer->getTimeLeft() > 0)) { switch ($this->runState) { case AK_STATE_NOFILE: debugMsg(__CLASS__ . '::_run() - Reading file header'); $status = $this->readFileHeader(); if ($status) { // Send start of file notification $message = new stdClass; $message->type = 'startfile'; $message->content = new stdClass; $message->content->realfile = $this->fileHeader->file; $message->content->file = $this->fileHeader->file; $message->content->uncompressed = $this->fileHeader->uncompressed; if (array_key_exists('realfile', get_object_vars($this->fileHeader))) { $message->content->realfile = $this->fileHeader->realFile; } if (array_key_exists('compressed', get_object_vars($this->fileHeader))) { $message->content->compressed = $this->fileHeader->compressed; } else { $message->content->compressed = 0; } debugMsg(__CLASS__ . '::_run() - Preparing to extract ' . $message->content->realfile); $this->notify($message); } else { debugMsg(__CLASS__ . '::_run() - Could not read file header'); } break; case AK_STATE_HEADER: case AK_STATE_DATA: debugMsg(__CLASS__ . '::_run() - Processing file data'); $status = $this->processFileData(); break; case AK_STATE_DATAREAD: case AK_STATE_POSTPROC: debugMsg(__CLASS__ . '::_run() - Calling post-processing class'); $this->postProcEngine->timestamp = $this->fileHeader->timestamp; $status = $this->postProcEngine->process(); $this->propagateFromObject($this->postProcEngine); $this->runState = AK_STATE_DONE; break; case AK_STATE_DONE: default: if ($status) { debugMsg(__CLASS__ . '::_run() - Finished extracting file'); // Send end of file notification $message = new stdClass; $message->type = 'endfile'; $message->content = new stdClass; if (array_key_exists('realfile', get_object_vars($this->fileHeader))) { $message->content->realfile = $this->fileHeader->realFile; } else { $message->content->realfile = $this->fileHeader->file; } $message->content->file = $this->fileHeader->file; if (array_key_exists('compressed', get_object_vars($this->fileHeader))) { $message->content->compressed = $this->fileHeader->compressed; } else { $message->content->compressed = 0; } $message->content->uncompressed = $this->fileHeader->uncompressed; $this->notify($message); } $this->runState = AK_STATE_NOFILE; break; } } $error = $this->getError(); if (!$status && ($this->runState == AK_STATE_NOFILE) && empty($error)) { debugMsg(__CLASS__ . '::_run() - Just finished'); // We just finished $this->setState('postrun'); } elseif (!empty($error)) { debugMsg(__CLASS__ . '::_run() - Halted with an error:'); debugMsg($error); $this->setState('error', $error); } } /** * Concrete classes must use this method to read the file header * * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive */ protected abstract function readFileHeader(); /** * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when * it's finished processing the file data. * * @return bool True if processing the file data was successful, false if an error occurred */ protected abstract function processFileData(); protected function _finalize() { // Nothing to do $this->setState('finished'); } /** * Opens the next part file for reading */ protected function nextFile() { debugMsg('Current part is ' . $this->currentPartNumber . '; opening the next part'); ++$this->currentPartNumber; if ($this->currentPartNumber > (count($this->archiveList) - 1)) { $this->setState('postrun'); return false; } else { if (is_resource($this->fp)) { @fclose($this->fp); } debugMsg('Opening file ' . $this->archiveList[$this->currentPartNumber]); $this->fp = @fopen($this->archiveList[$this->currentPartNumber], 'rb'); if ($this->fp === false) { debugMsg('Could not open file - crash imminent'); $this->setError(AKText::sprintf('ERR_COULD_NOT_OPEN_ARCHIVE_PART', $this->archiveList[$this->currentPartNumber])); } fseek($this->fp, 0); $this->currentPartOffset = 0; return true; } } /** * Returns true if we have reached the end of file * * @param $local bool True to return EOF of the local file, false (default) to return if we have reached the end of * the archive set * * @return bool True if we have reached End Of File */ protected function isEOF($local = false) { $eof = @feof($this->fp); if (!$eof) { // Border case: right at the part's end (eeeek!!!). For the life of me, I don't understand why // feof() doesn't report true. It expects the fp to be positioned *beyond* the EOF to report // true. Incredible! :( $position = @ftell($this->fp); $filesize = @filesize($this->archiveList[$this->currentPartNumber]); if ($filesize <= 0) { // 2Gb or more files on a 32 bit version of PHP tend to get screwed up. Meh. $eof = false; } elseif ($position >= $filesize) { $eof = true; } } if ($local) { return $eof; } else { return $eof && ($this->currentPartNumber >= (count($this->archiveList) - 1)); } } /** * Tries to make a directory user-writable so that we can write a file to it * * @param $path string A path to a file */ protected function setCorrectPermissions($path) { static $rootDir = null; if (is_null($rootDir)) { $rootDir = rtrim(AKFactory::get('kickstart.setup.destdir', ''), '/\\'); } $directory = rtrim(dirname($path), '/\\'); if ($directory != $rootDir) { // Is this an unwritable directory? if (!is_writable($directory)) { $this->postProcEngine->chmod($directory, 0755); } } $this->postProcEngine->chmod($path, 0644); } /** * Reads data from the archive and notifies the observer with the 'reading' message * * @param $fp * @param $length */ protected function fread($fp, $length = null) { if (is_numeric($length)) { if ($length > 0) { $data = fread($fp, $length); } else { $data = fread($fp, PHP_INT_MAX); } } else { $data = fread($fp, PHP_INT_MAX); } if ($data === false) { $data = ''; } // Send start of file notification $message = new stdClass; $message->type = 'reading'; $message->content = new stdClass; $message->content->length = strlen($data); $this->notify($message); return $data; } /** * Removes the configured $removePath from the path $path * * @param string $path The path to reduce * * @return string The reduced path */ protected function removePath($path) { if (empty($this->removePath)) { return $path; } if (strpos($path, $this->removePath) === 0) { $path = substr($path, strlen($this->removePath)); $path = ltrim($path, '/\\'); } return $path; } } /** * File post processor engines base class */ abstract class AKAbstractPostproc extends AKAbstractObject { /** @var int The UNIX timestamp of the file's desired modification date */ public $timestamp = 0; /** @var string The current (real) file path we'll have to process */ protected $filename = null; /** @var int The requested permissions */ protected $perms = 0755; /** @var string The temporary file path we gave to the unarchiver engine */ protected $tempFilename = null; /** * Processes the current file, e.g. moves it from temp to final location by FTP */ abstract public function process(); /** * The unarchiver tells us the path to the filename it wants to extract and we give it * a different path instead. * * @param string $filename The path to the real file * @param int $perms The permissions we need the file to have * * @return string The path to the temporary file */ abstract public function processFilename($filename, $perms = 0755); /** * Recursively creates a directory if it doesn't exist * * @param string $dirName The directory to create * @param int $perms The permissions to give to that directory */ abstract public function createDirRecursive($dirName, $perms); abstract public function chmod($file, $perms); abstract public function unlink($file); abstract public function rmdir($directory); abstract public function rename($from, $to); } /** * Descendants of this class can be used in the unarchiver's observer methods (attach, detach and notify) * * @author Nicholas * */ abstract class AKAbstractPartObserver { abstract public function update($object, $message); } /** * Direct file writer */ class AKPostprocDirect extends AKAbstractPostproc { public function process() { $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false); if ($restorePerms) { @chmod($this->filename, $this->perms); } else { if (@is_file($this->filename)) { @chmod($this->filename, 0644); } else { @chmod($this->filename, 0755); } } if ($this->timestamp > 0) { @touch($this->filename, $this->timestamp); } if (substr($this->filename, -4) === '.php') { $this->clearFileInOPCache($this->filename); } return true; } public function processFilename($filename, $perms = 0755) { $this->perms = $perms; $this->filename = $filename; return $filename; } public function createDirRecursive($dirName, $perms) { if (AKFactory::get('kickstart.setup.dryrun', '0')) { return true; } if (@mkdir($dirName, 0755, true)) { @chmod($dirName, 0755); return true; } $root = AKFactory::get('kickstart.setup.destdir'); $root = rtrim(str_replace('\\', '/', $root), '/'); $dir = rtrim(str_replace('\\', '/', $dirName), '/'); if (strpos($dir, $root) === 0) { $dir = ltrim(substr($dir, strlen($root)), '/'); $root .= '/'; } else { $root = ''; } if (empty($dir)) { return true; } $dirArray = explode('/', $dir); $path = ''; foreach ($dirArray as $dir) { $path .= $dir . '/'; $ret = is_dir($root . $path) ? true : @mkdir($root . $path); if (!$ret) { // Is this a file instead of a directory? if (is_file($root . $path)) { $this->clearFileInOPCache($root . $path); @unlink($root . $path); $ret = @mkdir($root . $path); } if (!$ret) { $this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $path)); return false; } } // Try to set new directory permissions to 0755 @chmod($root . $path, $perms); } return true; } public function chmod($file, $perms) { if (AKFactory::get('kickstart.setup.dryrun', '0')) { return true; } return @chmod($file, $perms); } public function unlink($file) { $this->clearFileInOPCache($file); return @unlink($file); } public function rmdir($directory) { return @rmdir($directory); } public function rename($from, $to) { $this->clearFileInOPCache($from); $ret = @rename($from, $to); $this->clearFileInOPCache($to); return $ret; } public function clearFileInOPCache($file){ if (ini_get('opcache.enable') && function_exists('opcache_invalidate') && (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0)) { \opcache_invalidate($file, true); } } } /** * JPA archive extraction class */ class AKUnarchiverJPA extends AKAbstractUnarchiver { protected $archiveHeaderData = array(); protected function readArchiveHeader() { debugMsg('Preparing to read archive header'); // Initialize header data array $this->archiveHeaderData = new stdClass(); // Open the first part debugMsg('Opening the first part'); $this->nextFile(); // Fail for unreadable files if ($this->fp === false) { debugMsg('Could not open the first part'); return false; } // Read the signature $sig = fread($this->fp, 3); if ($sig != 'JPA') { // Not a JPA file debugMsg('Invalid archive signature'); $this->setError(AKText::_('ERR_NOT_A_JPA_FILE')); return false; } // Read and parse header length $header_length_array = unpack('v', fread($this->fp, 2)); $header_length = $header_length_array[1]; // Read and parse the known portion of header data (14 bytes) $bin_data = fread($this->fp, 14); $header_data = unpack('Cmajor/Cminor/Vcount/Vuncsize/Vcsize', $bin_data); // Load any remaining header data (forward compatibility) $rest_length = $header_length - 19; if ($rest_length > 0) { $junk = fread($this->fp, $rest_length); } else { $junk = ''; } // Temporary array with all the data we read $temp = array( 'signature' => $sig, 'length' => $header_length, 'major' => $header_data['major'], 'minor' => $header_data['minor'], 'filecount' => $header_data['count'], 'uncompressedsize' => $header_data['uncsize'], 'compressedsize' => $header_data['csize'], 'unknowndata' => $junk ); // Array-to-object conversion foreach ($temp as $key => $value) { $this->archiveHeaderData->{$key} = $value; } debugMsg('Header data:'); debugMsg('Length : ' . $header_length); debugMsg('Major : ' . $header_data['major']); debugMsg('Minor : ' . $header_data['minor']); debugMsg('File count : ' . $header_data['count']); debugMsg('Uncompressed size : ' . $header_data['uncsize']); debugMsg('Compressed size : ' . $header_data['csize']); $this->currentPartOffset = @ftell($this->fp); $this->dataReadLength = 0; return true; } /** * Concrete classes must use this method to read the file header * * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive */ protected function readFileHeader() { // If the current part is over, proceed to the next part please if ($this->isEOF(true)) { debugMsg('Archive part EOF; moving to next file'); $this->nextFile(); } $this->currentPartOffset = ftell($this->fp); debugMsg("Reading file signature; part $this->currentPartNumber, offset $this->currentPartOffset"); // Get and decode Entity Description Block $signature = fread($this->fp, 3); $this->fileHeader = new stdClass(); $this->fileHeader->timestamp = 0; // Check signature if ($signature != 'JPF') { if ($this->isEOF(true)) { // This file is finished; make sure it's the last one $this->nextFile(); if (!$this->isEOF(false)) { debugMsg('Invalid file signature before end of archive encountered'); $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset)); return false; } // We're just finished return false; } else { $screwed = true; if (AKFactory::get('kickstart.setup.ignoreerrors', false)) { debugMsg('Invalid file block signature; launching heuristic file block signature scanner'); $screwed = !$this->heuristicFileHeaderLocator(); if (!$screwed) { $signature = 'JPF'; } else { debugMsg('Heuristics failed. Brace yourself for the imminent crash.'); } } if ($screwed) { debugMsg('Invalid file block signature'); // This is not a file block! The archive is corrupt. $this->setError(AKText::sprintf('INVALID_FILE_HEADER', $this->currentPartNumber, $this->currentPartOffset)); return false; } } } // This a JPA Entity Block. Process the header. $isBannedFile = false; // Read length of EDB and of the Entity Path Data $length_array = unpack('vblocksize/vpathsize', fread($this->fp, 4)); // Read the path data if ($length_array['pathsize'] > 0) { $file = fread($this->fp, $length_array['pathsize']); } else { $file = ''; } // Handle file renaming $isRenamed = false; if (is_array($this->renameFiles) && (count($this->renameFiles) > 0)) { if (array_key_exists($file, $this->renameFiles)) { $file = $this->renameFiles[$file]; $isRenamed = true; } } // Handle directory renaming $isDirRenamed = false; if (is_array($this->renameDirs) && (count($this->renameDirs) > 0)) { if (array_key_exists(dirname($file), $this->renameDirs)) { $file = rtrim($this->renameDirs[dirname($file)], '/') . '/' . basename($file); $isRenamed = true; $isDirRenamed = true; } } // Read and parse the known data portion $bin_data = fread($this->fp, 14); $header_data = unpack('Ctype/Ccompression/Vcompsize/Vuncompsize/Vperms', $bin_data); // Read any unknown data $restBytes = $length_array['blocksize'] - (21 + $length_array['pathsize']); if ($restBytes > 0) { // Start reading the extra fields while ($restBytes >= 4) { $extra_header_data = fread($this->fp, 4); $extra_header = unpack('vsignature/vlength', $extra_header_data); $restBytes -= 4; $extra_header['length'] -= 4; switch ($extra_header['signature']) { case 256: // File modified timestamp if ($extra_header['length'] > 0) { $bindata = fread($this->fp, $extra_header['length']); $restBytes -= $extra_header['length']; $timestamps = unpack('Vmodified', substr($bindata, 0, 4)); $filectime = $timestamps['modified']; $this->fileHeader->timestamp = $filectime; } break; default: // Unknown field if ($extra_header['length'] > 0) { $junk = fread($this->fp, $extra_header['length']); $restBytes -= $extra_header['length']; } break; } } if ($restBytes > 0) { $junk = fread($this->fp, $restBytes); } } $compressionType = $header_data['compression']; // Populate the return array $this->fileHeader->file = $file; $this->fileHeader->compressed = $header_data['compsize']; $this->fileHeader->uncompressed = $header_data['uncompsize']; switch ($header_data['type']) { case 0: $this->fileHeader->type = 'dir'; break; case 1: $this->fileHeader->type = 'file'; break; case 2: $this->fileHeader->type = 'link'; break; } switch ($compressionType) { case 0: $this->fileHeader->compression = 'none'; break; case 1: $this->fileHeader->compression = 'gzip'; break; case 2: $this->fileHeader->compression = 'bzip2'; break; } $this->fileHeader->permissions = $header_data['perms']; // Find hard-coded banned files if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..")) { $isBannedFile = true; } // Also try to find banned files passed in class configuration if ((count($this->skipFiles) > 0) && (!$isRenamed)) { if (in_array($this->fileHeader->file, $this->skipFiles)) { $isBannedFile = true; } } // If we have a banned file, let's skip it if ($isBannedFile) { debugMsg('Skipping file ' . $this->fileHeader->file); // Advance the file pointer, skipping exactly the size of the compressed data $seekleft = $this->fileHeader->compressed; while ($seekleft > 0) { // Ensure that we can seek past archive part boundaries $curSize = @filesize($this->archiveList[$this->currentPartNumber]); $curPos = @ftell($this->fp); $canSeek = $curSize - $curPos; if ($canSeek > $seekleft) { $canSeek = $seekleft; } @fseek($this->fp, $canSeek, SEEK_CUR); $seekleft -= $canSeek; if ($seekleft) { $this->nextFile(); } } $this->currentPartOffset = @ftell($this->fp); $this->runState = AK_STATE_DONE; return true; } // Remove the removePath, if any $this->fileHeader->file = $this->removePath($this->fileHeader->file); // Last chance to prepend a path to the filename if (!empty($this->addPath) && !$isDirRenamed) { $this->fileHeader->file = $this->addPath . $this->fileHeader->file; } // Get the translated path name $restorePerms = AKFactory::get('kickstart.setup.restoreperms', false); if ($this->fileHeader->type == 'file') { // Regular file; ask the postproc engine to process its filename if ($restorePerms) { $this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file, $this->fileHeader->permissions); } else { $this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file); } } elseif ($this->fileHeader->type == 'dir') { $dir = $this->fileHeader->file; // Directory; just create it if ($restorePerms) { $this->postProcEngine->createDirRecursive($this->fileHeader->file, $this->fileHeader->permissions); } else { $this->postProcEngine->createDirRecursive($this->fileHeader->file, 0755); } $this->postProcEngine->processFilename(null); } else { // Symlink; do not post-process $this->postProcEngine->processFilename(null); } $this->createDirectory(); // Header is read $this->runState = AK_STATE_HEADER; $this->dataReadLength = 0; return true; } protected function heuristicFileHeaderLocator() { $ret = false; $fullEOF = false; while (!$ret && !$fullEOF) { $this->currentPartOffset = @ftell($this->fp); if ($this->isEOF(true)) { $this->nextFile(); } if ($this->isEOF(false)) { $fullEOF = true; continue; } // Read 512Kb $chunk = fread($this->fp, 524288); $size_read = mb_strlen($chunk, '8bit'); //$pos = strpos($chunk, 'JPF'); $pos = mb_strpos($chunk, 'JPF', 0, '8bit'); if ($pos !== false) { // We found it! $this->currentPartOffset += $pos + 3; @fseek($this->fp, $this->currentPartOffset, SEEK_SET); $ret = true; } else { // Not yet found :( $this->currentPartOffset = @ftell($this->fp); } } return $ret; } /** * Creates the directory this file points to */ protected function createDirectory() { if (AKFactory::get('kickstart.setup.dryrun', '0')) { return true; } // Do we need to create a directory? if (empty($this->fileHeader->realFile)) { $this->fileHeader->realFile = $this->fileHeader->file; } $lastSlash = strrpos($this->fileHeader->realFile, '/'); $dirName = substr($this->fileHeader->realFile, 0, $lastSlash); $perms = $this->flagRestorePermissions ? $this->fileHeader->permissions : 0755; $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($dirName); if (($this->postProcEngine->createDirRecursive($dirName, $perms) == false) && (!$ignore)) { $this->setError(AKText::sprintf('COULDNT_CREATE_DIR', $dirName)); return false; } else { return true; } } /** * Concrete classes must use this method to process file data. It must set $runState to AK_STATE_DATAREAD when * it's finished processing the file data. * * @return bool True if processing the file data was successful, false if an error occurred */ protected function processFileData() { switch ($this->fileHeader->type) { case 'dir': return $this->processTypeDir(); break; case 'link': return $this->processTypeLink(); break; case 'file': switch ($this->fileHeader->compression) { case 'none': return $this->processTypeFileUncompressed(); break; case 'gzip': case 'bzip2': return $this->processTypeFileCompressedSimple(); break; } break; default: debugMsg('Unknown file type ' . $this->fileHeader->type); break; } // Unknown file type. Play dumb. return true; } /** * Process the file data of a directory entry * * @return bool */ private function processTypeDir() { // Directory entries in the JPA do not have file data, therefore we're done processing the entry $this->runState = AK_STATE_DATAREAD; return true; } /** * Process the file data of a link entry * * @return bool */ private function processTypeLink() { $readBytes = 0; $toReadBytes = 0; $leftBytes = $this->fileHeader->compressed; $data = ''; while ($leftBytes > 0) { $toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes; $mydata = $this->fread($this->fp, $toReadBytes); $reallyReadBytes = akstringlen($mydata); $data .= $mydata; $leftBytes -= $reallyReadBytes; if ($reallyReadBytes < $toReadBytes) { // We read less than requested! Why? Did we hit local EOF? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Let's go to the next file $this->nextFile(); } else { debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.'); // Nope. The archive is corrupt $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } } $filename = isset($this->fileHeader->realFile) ? $this->fileHeader->realFile : $this->fileHeader->file; if (!AKFactory::get('kickstart.setup.dryrun', '0')) { // Try to remove an existing file or directory by the same name if (file_exists($filename)) { @unlink($filename); @rmdir($filename); } // Remove any trailing slash if (substr($filename, -1) == '/') { $filename = substr($filename, 0, -1); } // Create the symlink - only possible within PHP context. There's no support built in the FTP protocol, so no postproc use is possible here :( @symlink($data, $filename); } $this->runState = AK_STATE_DATAREAD; return true; // No matter if the link was created! } private function processTypeFileUncompressed() { // Uncompressed files are being processed in small chunks, to avoid timeouts if (($this->dataReadLength == 0) && !AKFactory::get('kickstart.setup.dryrun', '0')) { // Before processing file data, ensure permissions are adequate $this->setCorrectPermissions($this->fileHeader->file); } // Open the output file if (!AKFactory::get('kickstart.setup.dryrun', '0')) { $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file); if ($this->dataReadLength == 0) { $outfp = @fopen($this->fileHeader->realFile, 'wb'); } else { $outfp = @fopen($this->fileHeader->realFile, 'ab'); } // Can we write to the file? if (($outfp === false) && (!$ignore)) { // An error occurred debugMsg('Could not write to output file'); $this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile)); return false; } } // Does the file have any data, at all? if ($this->fileHeader->compressed == 0) { // No file data! if (!AKFactory::get('kickstart.setup.dryrun', '0') && is_resource($outfp)) { @fclose($outfp); } $this->runState = AK_STATE_DATAREAD; return true; } // Reference to the global timer $timer = AKFactory::getTimer(); $toReadBytes = 0; $leftBytes = $this->fileHeader->compressed - $this->dataReadLength; // Loop while there's data to read and enough time to do it while (($leftBytes > 0) && ($timer->getTimeLeft() > 0)) { $toReadBytes = ($leftBytes > $this->chunkSize) ? $this->chunkSize : $leftBytes; $data = $this->fread($this->fp, $toReadBytes); $reallyReadBytes = akstringlen($data); $leftBytes -= $reallyReadBytes; $this->dataReadLength += $reallyReadBytes; if ($reallyReadBytes < $toReadBytes) { // We read less than requested! Why? Did we hit local EOF? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Let's go to the next file $this->nextFile(); } else { // Nope. The archive is corrupt debugMsg('Not enough data in file. The archive is truncated or corrupt.'); $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } if (!AKFactory::get('kickstart.setup.dryrun', '0')) { if (is_resource($outfp)) { @fwrite($outfp, $data); } } } // Close the file pointer if (!AKFactory::get('kickstart.setup.dryrun', '0')) { if (is_resource($outfp)) { @fclose($outfp); } } // Was this a pre-timeout bail out? if ($leftBytes > 0) { $this->runState = AK_STATE_DATA; } else { // Oh! We just finished! $this->runState = AK_STATE_DATAREAD; $this->dataReadLength = 0; } return true; } private function processTypeFileCompressedSimple() { if (!AKFactory::get('kickstart.setup.dryrun', '0')) { // Before processing file data, ensure permissions are adequate $this->setCorrectPermissions($this->fileHeader->file); // Open the output file $outfp = @fopen($this->fileHeader->realFile, 'wb'); // Can we write to the file? $ignore = AKFactory::get('kickstart.setup.ignoreerrors', false) || $this->isIgnoredDirectory($this->fileHeader->file); if (($outfp === false) && (!$ignore)) { // An error occurred debugMsg('Could not write to output file'); $this->setError(AKText::sprintf('COULDNT_WRITE_FILE', $this->fileHeader->realFile)); return false; } } // Does the file have any data, at all? if ($this->fileHeader->compressed == 0) { // No file data! if (!AKFactory::get('kickstart.setup.dryrun', '0')) { if (is_resource($outfp)) { @fclose($outfp); } } $this->runState = AK_STATE_DATAREAD; return true; } // Simple compressed files are processed as a whole; we can't do chunk processing $zipData = $this->fread($this->fp, $this->fileHeader->compressed); while (akstringlen($zipData) < $this->fileHeader->compressed) { // End of local file before reading all data, but have more archive parts? if ($this->isEOF(true) && !$this->isEOF(false)) { // Yeap. Read from the next file $this->nextFile(); $bytes_left = $this->fileHeader->compressed - akstringlen($zipData); $zipData .= $this->fread($this->fp, $bytes_left); } else { debugMsg('End of local file before reading all data with no more parts left. The archive is corrupt or truncated.'); $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } if ($this->fileHeader->compression == 'gzip') { $unzipData = gzinflate($zipData); } elseif ($this->fileHeader->compression == 'bzip2') { $unzipData = bzdecompress($zipData); } unset($zipData); // Write to the file. if (!AKFactory::get('kickstart.setup.dryrun', '0') && is_resource($outfp)) { @fwrite($outfp, $unzipData, $this->fileHeader->uncompressed); @fclose($outfp); } unset($unzipData); $this->runState = AK_STATE_DATAREAD; return true; } } /** * ZIP archive extraction class * * Since the file data portion of ZIP and JPA are similarly structured (it's empty for dirs, * linked node name for symlinks, dumped binary data for no compressions and dumped gzipped * binary data for gzip compression) we just have to subclass AKUnarchiverJPA and change the * header reading bits. Reusable code ;) */ class AKUnarchiverZIP extends AKUnarchiverJPA { var $expectDataDescriptor = false; protected function readArchiveHeader() { debugMsg('Preparing to read archive header'); // Initialize header data array $this->archiveHeaderData = new stdClass(); // Open the first part debugMsg('Opening the first part'); $this->nextFile(); // Fail for unreadable files if ($this->fp === false) { debugMsg('The first part is not readable'); return false; } // Read a possible multipart signature $sigBinary = fread($this->fp, 4); $headerData = unpack('Vsig', $sigBinary); // Roll back if it's not a multipart archive if ($headerData['sig'] == 0x04034b50) { debugMsg('The archive is not multipart'); fseek($this->fp, -4, SEEK_CUR); } else { debugMsg('The archive is multipart'); } $multiPartSigs = array( 0x08074b50, // Multi-part ZIP 0x30304b50, // Multi-part ZIP (alternate) 0x04034b50 // Single file ); if (!in_array($headerData['sig'], $multiPartSigs)) { debugMsg('Invalid header signature ' . dechex($headerData['sig'])); $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } $this->currentPartOffset = @ftell($this->fp); debugMsg('Current part offset after reading header: ' . $this->currentPartOffset); $this->dataReadLength = 0; return true; } /** * Concrete classes must use this method to read the file header * * @return bool True if reading the file was successful, false if an error occurred or we reached end of archive */ protected function readFileHeader() { // If the current part is over, proceed to the next part please if ($this->isEOF(true)) { debugMsg('Opening next archive part'); $this->nextFile(); } $this->currentPartOffset = ftell($this->fp); if ($this->expectDataDescriptor) { // The last file had bit 3 of the general purpose bit flag set. This means that we have a // 12 byte data descriptor we need to skip. To make things worse, there might also be a 4 // byte optional data descriptor header (0x08074b50). $junk = @fread($this->fp, 4); $junk = unpack('Vsig', $junk); if ($junk['sig'] == 0x08074b50) { // Yes, there was a signature $junk = @fread($this->fp, 12); debugMsg('Data descriptor (w/ header) skipped at ' . (ftell($this->fp) - 12)); } else { // No, there was no signature, just read another 8 bytes $junk = @fread($this->fp, 8); debugMsg('Data descriptor (w/out header) skipped at ' . (ftell($this->fp) - 8)); } // And check for EOF, too if ($this->isEOF(true)) { debugMsg('EOF before reading header'); $this->nextFile(); } } // Get and decode Local File Header $headerBinary = fread($this->fp, 30); $headerData = unpack('Vsig/C2ver/vbitflag/vcompmethod/vlastmodtime/vlastmoddate/Vcrc/Vcompsize/Vuncomp/vfnamelen/veflen', $headerBinary); // Check signature if (!($headerData['sig'] == 0x04034b50)) { debugMsg('Not a file signature at ' . (ftell($this->fp) - 4)); // The signature is not the one used for files. Is this a central directory record (i.e. we're done)? if ($headerData['sig'] == 0x02014b50) { debugMsg('EOCD signature at ' . (ftell($this->fp) - 4)); // End of ZIP file detected. We'll just skip to the end of file... while ($this->nextFile()) { } @fseek($this->fp, 0, SEEK_END); // Go to EOF return false; } else { debugMsg('Invalid signature ' . dechex($headerData['sig']) . ' at ' . ftell($this->fp)); $this->setError(AKText::_('ERR_CORRUPT_ARCHIVE')); return false; } } // If bit 3 of the bitflag is set, expectDataDescriptor is true $this->expectDataDescriptor = ($headerData['bitflag'] & 4) == 4; $this->fileHeader = new stdClass(); $this->fileHeader->timestamp = 0; // Read the last modified data and time $lastmodtime = $headerData['lastmodtime']; $lastmoddate = $headerData['lastmoddate']; if ($lastmoddate && $lastmodtime) { // ----- Extract time $v_hour = ($lastmodtime & 0xF800) >> 11; $v_minute = ($lastmodtime & 0x07E0) >> 5; $v_seconde = ($lastmodtime & 0x001F) * 2; // ----- Extract date $v_year = (($lastmoddate & 0xFE00) >> 9) + 1980; $v_month = ($lastmoddate & 0x01E0) >> 5; $v_day = $lastmoddate & 0x001F; // ----- Get UNIX date format $this->fileHeader->timestamp = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); } $isBannedFile = false; $this->fileHeader->compressed = $headerData['compsize']; $this->fileHeader->uncompressed = $headerData['uncomp']; $nameFieldLength = $headerData['fnamelen']; $extraFieldLength = $headerData['eflen']; // Read filename field $this->fileHeader->file = fread($this->fp, $nameFieldLength); // Handle file renaming $isRenamed = false; if (is_array($this->renameFiles) && (count($this->renameFiles) > 0)) { if (array_key_exists($this->fileHeader->file, $this->renameFiles)) { $this->fileHeader->file = $this->renameFiles[$this->fileHeader->file]; $isRenamed = true; } } // Handle directory renaming $isDirRenamed = false; if (is_array($this->renameDirs) && (count($this->renameDirs) > 0)) { if (array_key_exists(dirname($this->fileHeader->file), $this->renameDirs)) { $file = rtrim($this->renameDirs[dirname($this->fileHeader->file)], '/') . '/' . basename($this->fileHeader->file); $isRenamed = true; $isDirRenamed = true; } } // Read extra field if present if ($extraFieldLength > 0) { $extrafield = fread($this->fp, $extraFieldLength); } debugMsg('*' . ftell($this->fp) . ' IS START OF ' . $this->fileHeader->file . ' (' . $this->fileHeader->compressed . ' bytes)'); // Decide filetype -- Check for directories $this->fileHeader->type = 'file'; if (strrpos($this->fileHeader->file, '/') == strlen($this->fileHeader->file) - 1) { $this->fileHeader->type = 'dir'; } // Decide filetype -- Check for symbolic links if (($headerData['ver1'] == 10) && ($headerData['ver2'] == 3)) { $this->fileHeader->type = 'link'; } switch ($headerData['compmethod']) { case 0: $this->fileHeader->compression = 'none'; break; case 8: $this->fileHeader->compression = 'gzip'; break; } // Find hard-coded banned files if ((basename($this->fileHeader->file) == ".") || (basename($this->fileHeader->file) == "..")) { $isBannedFile = true; } // Also try to find banned files passed in class configuration if ((count($this->skipFiles) > 0) && (!$isRenamed)) { if (in_array($this->fileHeader->file, $this->skipFiles)) { $isBannedFile = true; } } // If we have a banned file, let's skip it if ($isBannedFile) { // Advance the file pointer, skipping exactly the size of the compressed data $seekleft = $this->fileHeader->compressed; while ($seekleft > 0) { // Ensure that we can seek past archive part boundaries $curSize = @filesize($this->archiveList[$this->currentPartNumber]); $curPos = @ftell($this->fp); $canSeek = $curSize - $curPos; if ($canSeek > $seekleft) { $canSeek = $seekleft; } @fseek($this->fp, $canSeek, SEEK_CUR); $seekleft -= $canSeek; if ($seekleft) { $this->nextFile(); } } $this->currentPartOffset = @ftell($this->fp); $this->runState = AK_STATE_DONE; return true; } // Remove the removePath, if any $this->fileHeader->file = $this->removePath($this->fileHeader->file); // Last chance to prepend a path to the filename if (!empty($this->addPath) && !$isDirRenamed) { $this->fileHeader->file = $this->addPath . $this->fileHeader->file; } // Get the translated path name if ($this->fileHeader->type == 'file') { $this->fileHeader->realFile = $this->postProcEngine->processFilename($this->fileHeader->file); } elseif ($this->fileHeader->type == 'dir') { $this->fileHeader->timestamp = 0; $dir = $this->fileHeader->file; $this->postProcEngine->createDirRecursive($this->fileHeader->file, 0755); $this->postProcEngine->processFilename(null); } else { // Symlink; do not post-process $this->fileHeader->timestamp = 0; $this->postProcEngine->processFilename(null); } $this->createDirectory(); // Header is read $this->runState = AK_STATE_HEADER; return true; } } /** * Timer class */ class AKCoreTimer extends AKAbstractObject { /** @var int Maximum execution time allowance per step */ private $max_exec_time = null; /** @var int Timestamp of execution start */ private $start_time = null; /** * Public constructor, creates the timer object and calculates the execution time limits */ public function __construct() { // Initialize start time $this->start_time = $this->microtime_float(); // Get configured max time per step and bias $config_max_exec_time = AKFactory::get('kickstart.tuning.max_exec_time', 14); $bias = AKFactory::get('kickstart.tuning.run_time_bias', 75) / 100; // Get PHP's maximum execution time (our upper limit) if (@function_exists('ini_get')) { $php_max_exec_time = @ini_get("maximum_execution_time"); if ((!is_numeric($php_max_exec_time)) || ($php_max_exec_time == 0)) { // If we have no time limit, set a hard limit of about 10 seconds // (safe for Apache and IIS timeouts, verbose enough for users) $php_max_exec_time = 14; } } else { // If ini_get is not available, use a rough default $php_max_exec_time = 14; } // Apply an arbitrary correction to counter CMS load time $php_max_exec_time--; // Apply bias $php_max_exec_time = $php_max_exec_time * $bias; $config_max_exec_time = $config_max_exec_time * $bias; // Use the most appropriate time limit value if ($config_max_exec_time > $php_max_exec_time) { $this->max_exec_time = $php_max_exec_time; } else { $this->max_exec_time = $config_max_exec_time; } } /** * Returns the current timestampt in decimal seconds */ private function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float) $usec + (float) $sec); } /** * Wake-up function to reset internal timer when we get unserialized */ public function __wakeup() { // Re-initialize start time on wake-up $this->start_time = $this->microtime_float(); } /** * Gets the number of seconds left, before we hit the "must break" threshold * * @return float */ public function getTimeLeft() { return $this->max_exec_time - $this->getRunningTime(); } /** * Gets the time elapsed since object creation/unserialization, effectively how * long Akeeba Engine has been processing data * * @return float */ public function getRunningTime() { return $this->microtime_float() - $this->start_time; } /** * Enforce the minimum execution time */ public function enforce_min_exec_time() { // Try to get a sane value for PHP's maximum_execution_time INI parameter if (@function_exists('ini_get')) { $php_max_exec = @ini_get("maximum_execution_time"); } else { $php_max_exec = 10; } if (($php_max_exec == "") || ($php_max_exec == 0)) { $php_max_exec = 10; } // Decrease $php_max_exec time by 500 msec we need (approx.) to tear down // the application, as well as another 500msec added for rounding // error purposes. Also make sure this is never gonna be less than 0. $php_max_exec = max($php_max_exec * 1000 - 1000, 0); // Get the "minimum execution time per step" Akeeba Backup configuration variable $minexectime = AKFactory::get('kickstart.tuning.min_exec_time', 0); if (!is_numeric($minexectime)) { $minexectime = 0; } // Make sure we are not over PHP's time limit! if ($minexectime > $php_max_exec) { $minexectime = $php_max_exec; } // Get current running time $elapsed_time = $this->getRunningTime() * 1000; // Only run a sleep delay if we haven't reached the minexectime execution time if (($minexectime > $elapsed_time) && ($elapsed_time > 0)) { $sleep_msec = $minexectime - $elapsed_time; if (function_exists('usleep')) { usleep(1000 * $sleep_msec); } elseif (function_exists('time_nanosleep')) { $sleep_sec = floor($sleep_msec / 1000); $sleep_nsec = 1000000 * ($sleep_msec - ($sleep_sec * 1000)); time_nanosleep($sleep_sec, $sleep_nsec); } elseif (function_exists('time_sleep_until')) { $until_timestamp = time() + $sleep_msec / 1000; time_sleep_until($until_timestamp); } elseif (function_exists('sleep')) { $sleep_sec = ceil($sleep_msec / 1000); sleep($sleep_sec); } } elseif ($elapsed_time > 0) { // No sleep required, even if user configured us to be able to do so. } } /** * Reset the timer. It should only be used in CLI mode! */ public function resetTime() { $this->start_time = $this->microtime_float(); } /** * @param int $max_exec_time */ public function setMaxExecTime($max_exec_time) { $this->max_exec_time = $max_exec_time; } } /** * A filesystem scanner which uses opendir() */ class AKUtilsLister extends AKAbstractObject { public function &getFiles($folder, $pattern = '*') { // Initialize variables $arr = array(); $false = false; if (!is_dir($folder)) { return $false; } $handle = @opendir($folder); // If directory is not accessible, just return FALSE if ($handle === false) { $this->setWarning('Unreadable directory ' . $folder); return $false; } while (($file = @readdir($handle)) !== false) { if (!fnmatch($pattern, $file)) { continue; } if (($file != '.') && ($file != '..')) { $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR; $dir = $folder . $ds . $file; $isDir = is_dir($dir); if (!$isDir) { $arr[] = $dir; } } } @closedir($handle); return $arr; } public function &getFolders($folder, $pattern = '*') { // Initialize variables $arr = array(); $false = false; if (!is_dir($folder)) { return $false; } $handle = @opendir($folder); // If directory is not accessible, just return FALSE if ($handle === false) { $this->setWarning('Unreadable directory ' . $folder); return $false; } while (($file = @readdir($handle)) !== false) { if (!fnmatch($pattern, $file)) { continue; } if (($file != '.') && ($file != '..')) { $ds = ($folder == '') || ($folder == '/') || (@substr($folder, -1) == '/') || (@substr($folder, -1) == DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR; $dir = $folder . $ds . $file; $isDir = is_dir($dir); if ($isDir) { $arr[] = $dir; } } } @closedir($handle); return $arr; } } /** * A simple INI-based i18n engine */ class AKText extends AKAbstractObject { /** * The default (en_GB) translation used when no other translation is available * * @var array */ private $default_translation = array( 'ERR_NOT_A_JPA_FILE' => 'The file is not a JPA archive', 'ERR_CORRUPT_ARCHIVE' => 'The archive file is corrupt, truncated or archive parts are missing', 'ERR_INVALID_LOGIN' => 'Invalid login', 'COULDNT_CREATE_DIR' => 'Could not create %s folder', 'COULDNT_WRITE_FILE' => 'Could not open %s for writing.', 'INVALID_FILE_HEADER' => 'Invalid header in archive file, part %s, offset %s', 'ERR_COULD_NOT_OPEN_ARCHIVE_PART' => 'Could not open archive part file %s for reading. Check that the file exists, is readable by the web server and is not in a directory made out of reach by chroot, open_basedir restrictions or any other restriction put in place by your host.', ); /** * The array holding the translation keys * * @var array */ private $strings; /** * The currently detected language (ISO code) * * @var string */ private $language; /* * Initializes the translation engine * @return AKText */ public function __construct() { // Start with the default translation $this->strings = $this->default_translation; // Try loading the translation file in English, if it exists $this->loadTranslation('en-GB'); // Try loading the translation file in the browser's preferred language, if it exists $this->getBrowserLanguage(); if (!is_null($this->language)) { $this->loadTranslation(); } } private function loadTranslation($lang = null) { if (defined('KSLANGDIR')) { $dirname = KSLANGDIR; } else { $dirname = KSROOTDIR; } $basename = basename(__FILE__, '.php') . '.ini'; if (empty($lang)) { $lang = $this->language; } $translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename; if (!@file_exists($translationFilename) && ($basename != 'kickstart.ini')) { $basename = 'kickstart.ini'; $translationFilename = $dirname . DIRECTORY_SEPARATOR . $lang . '.' . $basename; } if (!@file_exists($translationFilename)) { return; } $temp = self::parse_ini_file($translationFilename, false); if (!is_array($this->strings)) { $this->strings = array(); } if (empty($temp)) { $this->strings = array_merge($this->default_translation, $this->strings); } else { $this->strings = array_merge($this->strings, $temp); } } /** * A PHP based INI file parser. * * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on * the parse_ini_file page on http://gr.php.net/parse_ini_file * * @param string $file Filename to process * @param bool $process_sections True to also process INI sections * * @return array An associative array of sections, keys and values * @access private */ public static function parse_ini_file($file, $process_sections = false, $raw_data = false) { $process_sections = ($process_sections !== true) ? false : true; if (!$raw_data) { $ini = @file($file); } else { $ini = $file; } if (count($ini) == 0) { return array(); } $sections = array(); $values = array(); $result = array(); $globals = array(); $i = 0; if (!empty($ini)) { foreach ($ini as $line) { $line = trim($line); $line = str_replace("\t", " ", $line); // Comments if (!preg_match('/^[a-zA-Z0-9[]/', $line)) { continue; } // Sections if ($line[0] == '[') { $tmp = explode(']', $line); $sections[] = trim(substr($tmp[0], 1)); $i++; continue; } // Key-value pair list($key, $value) = explode('=', $line, 2); $key = trim($key); $value = trim($value); if (strstr($value, ";")) { $tmp = explode(';', $value); if (count($tmp) == 2) { if ((($value[0] != '"') && ($value[0] != "'")) || preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) || preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value) ) { $value = $tmp[0]; } } else { if ($value[0] == '"') { $value = preg_replace('/^"(.*)".*/', '$1', $value); } elseif ($value[0] == "'") { $value = preg_replace("/^'(.*)'.*/", '$1', $value); } else { $value = $tmp[0]; } } } $value = trim($value); $value = trim($value, "'\""); if ($i == 0) { if (substr($line, -1, 2) == '[]') { $globals[$key][] = $value; } else { $globals[$key] = $value; } } else { if (substr($line, -1, 2) == '[]') { $values[$i - 1][$key][] = $value; } else { $values[$i - 1][$key] = $value; } } } } for ($j = 0; $j < $i; $j++) { if ($process_sections === true) { $result[$sections[$j]] = $values[$j]; } else { $result[] = $values[$j]; } } return $result + $globals; } public function getBrowserLanguage() { // Detection code from Full Operating system language detection, by Harald Hope // Retrieved from http://techpatterns.com/downloads/php_language_detection.php $user_languages = array(); //check to see if language is set if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) { $languages = strtolower($_SERVER["HTTP_ACCEPT_LANGUAGE"]); // $languages = ' fr-ch;q=0.3, da, en-us;q=0.8, en;q=0.5, fr;q=0.3'; // need to remove spaces from strings to avoid error $languages = str_replace(' ', '', $languages); $languages = explode(",", $languages); foreach ($languages as $language_list) { // pull out the language, place languages into array of full and primary // string structure: $temp_array = array(); // slice out the part before ; on first step, the part before - on second, place into array $temp_array[0] = substr($language_list, 0, strcspn($language_list, ';'));//full language $temp_array[1] = substr($language_list, 0, 2);// cut out primary language if ((strlen($temp_array[0]) == 5) && ((substr($temp_array[0], 2, 1) == '-') || (substr($temp_array[0], 2, 1) == '_'))) { $langLocation = strtoupper(substr($temp_array[0], 3, 2)); $temp_array[0] = $temp_array[1] . '-' . $langLocation; } //place this array into main $user_languages language array $user_languages[] = $temp_array; } } else// if no languages found { $user_languages[0] = array('', ''); //return blank array. } $this->language = null; $basename = basename(__FILE__, '.php') . '.ini'; // Try to match main language part of the filename, irrespective of the location, e.g. de_DE will do if de_CH doesn't exist. if (class_exists('AKUtilsLister')) { $fs = new AKUtilsLister(); $iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename); if (empty($iniFiles) && ($basename != 'kickstart.ini')) { $basename = 'kickstart.ini'; $iniFiles = $fs->getFiles(KSROOTDIR, '*.' . $basename); } } else { $iniFiles = null; } if (is_array($iniFiles)) { foreach ($user_languages as $languageStruct) { if (is_null($this->language)) { // Get files matching the main lang part $iniFiles = $fs->getFiles(KSROOTDIR, $languageStruct[1] . '-??.' . $basename); if (count($iniFiles) > 0) { $filename = $iniFiles[0]; $filename = substr($filename, strlen(KSROOTDIR) + 1); $this->language = substr($filename, 0, 5); } else { $this->language = null; } } } } if (is_null($this->language)) { // Try to find a full language match foreach ($user_languages as $languageStruct) { if (@file_exists($languageStruct[0] . '.' . $basename) && is_null($this->language)) { $this->language = $languageStruct[0]; } } } else { // Do we have an exact match? foreach ($user_languages as $languageStruct) { if (substr($this->language, 0, strlen($languageStruct[1])) == $languageStruct[1]) { if (file_exists($languageStruct[0] . '.' . $basename)) { $this->language = $languageStruct[0]; } } } } // Now, scan for full language based on the partial match } public static function sprintf($key) { $text = self::getInstance(); $args = func_get_args(); if (count($args) > 0) { $args[0] = $text->_($args[0]); return @call_user_func_array('sprintf', $args); } return ''; } /** * Singleton pattern for Language * * @return AKText The global AKText instance */ public static function &getInstance() { static $instance; if (!is_object($instance)) { $instance = new AKText(); } return $instance; } public static function _($string) { $text = self::getInstance(); $key = strtoupper($string); $key = substr($key, 0, 1) == '_' ? substr($key, 1) : $key; if (isset ($text->strings[$key])) { $string = $text->strings[$key]; } else { if (defined($string)) { $string = constant($string); } } return $string; } public function dumpLanguage() { $out = ''; foreach ($this->strings as $key => $value) { $out .= "$key=$value\n"; } return $out; } public function asJavascript() { $out = ''; foreach ($this->strings as $key => $value) { $key = addcslashes($key, '\\\'"'); $value = addcslashes($value, '\\\'"'); if (!empty($out)) { $out .= ",\n"; } $out .= "'$key':\t'$value'"; } return $out; } public function resetTranslation() { $this->strings = $this->default_translation; } public function addDefaultLanguageStrings($stringList = array()) { if (!is_array($stringList)) { return; } if (empty($stringList)) { return; } $this->strings = array_merge($stringList, $this->strings); } } /** * The Akeeba Kickstart Factory class * This class is reponssible for instanciating all Akeeba Kicsktart classes */ class AKFactory { /** @var array A list of instantiated objects */ private $objectlist = array(); /** @var array Simple hash data storage */ private $varlist = array(); /** @var self Static instance */ private static $instance = null; /** Private constructor makes sure we can't directly instantiate the class */ private function __construct() { } /** * Gets a serialized snapshot of the Factory for safekeeping (hibernate) * * @return string The serialized snapshot of the Factory */ public static function serialize() { $engine = self::getUnarchiver(); $engine->shutdown(); $serialized = serialize(self::getInstance()); if (function_exists('base64_encode') && function_exists('base64_decode')) { $serialized = base64_encode($serialized); } return $serialized; } /** * Gets the unarchiver engine */ public static function &getUnarchiver($configOverride = null) { static $class_name; if (!empty($configOverride)) { if ($configOverride['reset']) { $class_name = null; } } if (empty($class_name)) { $filetype = self::get('kickstart.setup.filetype', null); if (empty($filetype)) { $filename = self::get('kickstart.setup.sourcefile', null); $basename = basename($filename); $baseextension = strtoupper(substr($basename, -3)); switch ($baseextension) { case 'JPA': $filetype = 'JPA'; break; case 'JPS': $filetype = 'JPS'; break; case 'ZIP': $filetype = 'ZIP'; break; default: die('Invalid archive type or extension in file ' . $filename); break; } } $class_name = 'AKUnarchiver' . ucfirst($filetype); } $destdir = self::get('kickstart.setup.destdir', null); if (empty($destdir)) { $destdir = KSROOTDIR; } $object = self::getClassInstance($class_name); if ($object->getState() == 'init') { $sourcePath = self::get('kickstart.setup.sourcepath', ''); $sourceFile = self::get('kickstart.setup.sourcefile', ''); if (!empty($sourcePath)) { $sourceFile = rtrim($sourcePath, '/\\') . '/' . $sourceFile; } // Initialize the object –– Any change here MUST be reflected to echoHeadJavascript (default values) $config = array( 'filename' => $sourceFile, 'restore_permissions' => self::get('kickstart.setup.restoreperms', 0), 'post_proc' => self::get('kickstart.procengine', 'direct'), 'add_path' => self::get('kickstart.setup.targetpath', $destdir), 'remove_path' => self::get('kickstart.setup.removepath', ''), 'rename_files' => self::get('kickstart.setup.renamefiles', array( '.htaccess' => 'htaccess.bak', 'php.ini' => 'php.ini.bak', 'web.config' => 'web.config.bak', '.user.ini' => '.user.ini.bak' )), 'skip_files' => self::get('kickstart.setup.skipfiles', array( basename(__FILE__), 'kickstart.php', 'abiautomation.ini', 'htaccess.bak', 'php.ini.bak', 'cacert.pem' )), 'ignoredirectories' => self::get('kickstart.setup.ignoredirectories', array( 'tmp', 'log', 'logs' )), ); if (!defined('KICKSTART')) { // In restore.php mode we have to exclude the restoration.php files $moreSkippedFiles = array( // Akeeba Backup for Joomla! 'administrator/components/com_akeeba/restoration.php', // Joomla! Update 'administrator/components/com_joomlaupdate/restoration.php', // Akeeba Backup for WordPress 'wp-content/plugins/akeebabackupwp/app/restoration.php', 'wp-content/plugins/akeebabackupcorewp/app/restoration.php', 'wp-content/plugins/akeebabackup/app/restoration.php', 'wp-content/plugins/akeebabackupwpcore/app/restoration.php', // Akeeba Solo 'app/restoration.php', ); $config['skip_files'] = array_merge($config['skip_files'], $moreSkippedFiles); } if (!empty($configOverride)) { $config = array_merge($config, $configOverride); } $object->setup($config); } return $object; } // ======================================================================== // Public factory interface // ======================================================================== public static function get($key, $default = null) { $self = self::getInstance(); if (array_key_exists($key, $self->varlist)) { return $self->varlist[$key]; } else { return $default; } } /** * Gets a single, internally used instance of the Factory * * @param string $serialized_data [optional] Serialized data to spawn the instance from * * @return AKFactory A reference to the unique Factory object instance */ protected static function &getInstance($serialized_data = null) { if (!is_object(self::$instance) || !is_null($serialized_data)) { if (!is_null($serialized_data)) { self::$instance = unserialize($serialized_data); } else { self::$instance = new self(); } } return self::$instance; } /** * Internal function which instanciates a class named $class_name. * The autoloader * * @param string $class_name * * @return object */ protected static function &getClassInstance($class_name) { $self = self::getInstance(); if (!isset($self->objectlist[$class_name])) { $self->objectlist[$class_name] = new $class_name; } return $self->objectlist[$class_name]; } // ======================================================================== // Public hash data storage interface // ======================================================================== /** * Regenerates the full Factory state from a serialized snapshot (resume) * * @param string $serialized_data The serialized snapshot to resume from */ public static function unserialize($serialized_data) { if (function_exists('base64_encode') && function_exists('base64_decode')) { $serialized_data = base64_decode($serialized_data); } self::getInstance($serialized_data); } /** * Reset the internal factory state, freeing all previously created objects */ public static function nuke() { self::$instance = null; } // ======================================================================== // Akeeba Kickstart classes // ======================================================================== public static function set($key, $value) { $self = self::getInstance(); $self->varlist[$key] = $value; } /** * Gets the post processing engine * * @param string $proc_engine */ public static function &getPostProc($proc_engine = null) { static $class_name; if (empty($class_name)) { if (empty($proc_engine)) { $proc_engine = self::get('kickstart.procengine', 'direct'); } $class_name = 'AKPostproc' . ucfirst($proc_engine); } return self::getClassInstance($class_name); } /** * Get the a reference to the Akeeba Engine's timer * * @return AKCoreTimer */ public static function &getTimer() { /** @noinspection PhpIncompatibleReturnTypeInspection */ return self::getClassInstance('AKCoreTimer'); } } /** * Interface for AES encryption adapters */ interface AKEncryptionAESAdapterInterface { /** * Decrypts a string. Returns the raw binary ciphertext, zero-padded. * * @param string $plainText The plaintext to encrypt * @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size) * * @return string The raw encrypted binary string. */ public function decrypt($plainText, $key); /** * Returns the encryption block size in bytes * * @return int */ public function getBlockSize(); /** * Is this adapter supported? * * @return bool */ public function isSupported(); } /** * Abstract AES encryption class */ abstract class AKEncryptionAESAdapterAbstract { /** * Trims or zero-pads a key / IV * * @param string $key The key or IV to treat * @param int $size The block size of the currently used algorithm * * @return null|string Null if $key is null, treated string of $size byte length otherwise */ public function resizeKey($key, $size) { if (empty($key)) { return null; } $keyLength = strlen($key); if (function_exists('mb_strlen')) { $keyLength = mb_strlen($key, 'ASCII'); } if ($keyLength == $size) { return $key; } if ($keyLength > $size) { if (function_exists('mb_substr')) { return mb_substr($key, 0, $size, 'ASCII'); } return substr($key, 0, $size); } return $key . str_repeat("\0", ($size - $keyLength)); } /** * Returns null bytes to append to the string so that it's zero padded to the specified block size * * @param string $string The binary string which will be zero padded * @param int $blockSize The block size * * @return string The zero bytes to append to the string to zero pad it to $blockSize */ protected function getZeroPadding($string, $blockSize) { $stringSize = strlen($string); if (function_exists('mb_strlen')) { $stringSize = mb_strlen($string, 'ASCII'); } if ($stringSize == $blockSize) { return ''; } if ($stringSize < $blockSize) { return str_repeat("\0", $blockSize - $stringSize); } $paddingBytes = $stringSize % $blockSize; return str_repeat("\0", $blockSize - $paddingBytes); } } class Mcrypt extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface { protected $cipherType = MCRYPT_RIJNDAEL_128; protected $cipherMode = MCRYPT_MODE_CBC; public function decrypt($cipherText, $key) { $iv_size = $this->getBlockSize(); $key = $this->resizeKey($key, $iv_size); $iv = substr($cipherText, 0, $iv_size); $cipherText = substr($cipherText, $iv_size); $plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv); return $plainText; } public function isSupported() { if (!function_exists('mcrypt_get_key_size')) { return false; } if (!function_exists('mcrypt_get_iv_size')) { return false; } if (!function_exists('mcrypt_create_iv')) { return false; } if (!function_exists('mcrypt_encrypt')) { return false; } if (!function_exists('mcrypt_decrypt')) { return false; } if (!function_exists('mcrypt_list_algorithms')) { return false; } if (!function_exists('hash')) { return false; } if (!function_exists('hash_algos')) { return false; } $algorightms = mcrypt_list_algorithms(); if (!in_array('rijndael-128', $algorightms)) { return false; } if (!in_array('rijndael-192', $algorightms)) { return false; } if (!in_array('rijndael-256', $algorightms)) { return false; } $algorightms = hash_algos(); if (!in_array('sha256', $algorightms)) { return false; } return true; } public function getBlockSize() { return mcrypt_get_iv_size($this->cipherType, $this->cipherMode); } } class OpenSSL extends AKEncryptionAESAdapterAbstract implements AKEncryptionAESAdapterInterface { /** * The OpenSSL options for encryption / decryption * * @var int */ protected $openSSLOptions = 0; /** * The encryption method to use * * @var string */ protected $method = 'aes-128-cbc'; public function __construct() { $this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING; } public function decrypt($cipherText, $key) { $iv_size = $this->getBlockSize(); $key = $this->resizeKey($key, $iv_size); $iv = substr($cipherText, 0, $iv_size); $cipherText = substr($cipherText, $iv_size); $plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv); return $plainText; } public function isSupported() { if (!function_exists('openssl_get_cipher_methods')) { return false; } if (!function_exists('openssl_random_pseudo_bytes')) { return false; } if (!function_exists('openssl_cipher_iv_length')) { return false; } if (!function_exists('openssl_encrypt')) { return false; } if (!function_exists('openssl_decrypt')) { return false; } if (!function_exists('hash')) { return false; } if (!function_exists('hash_algos')) { return false; } $algorightms = openssl_get_cipher_methods(); if (!in_array('aes-128-cbc', $algorightms)) { return false; } $algorightms = hash_algos(); if (!in_array('sha256', $algorightms)) { return false; } return true; } /** * @return int */ public function getBlockSize() { return openssl_cipher_iv_length($this->method); } } /** * AES implementation in PHP (c) Chris Veness 2005-2016. * Right to use and adapt is granted for under a simple creative commons attribution * licence. No warranty of any form is offered. * * Heavily modified for Akeeba Backup by Nicholas K. Dionysopoulos * Also added AES-128 CBC mode (with mcrypt and OpenSSL) on top of AES CTR */ class AKEncryptionAES { // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [�5.1.1] protected static $Sbox = array(0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16); // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2] protected static $Rcon = array( array(0x00, 0x00, 0x00, 0x00), array(0x01, 0x00, 0x00, 0x00), array(0x02, 0x00, 0x00, 0x00), array(0x04, 0x00, 0x00, 0x00), array(0x08, 0x00, 0x00, 0x00), array(0x10, 0x00, 0x00, 0x00), array(0x20, 0x00, 0x00, 0x00), array(0x40, 0x00, 0x00, 0x00), array(0x80, 0x00, 0x00, 0x00), array(0x1b, 0x00, 0x00, 0x00), array(0x36, 0x00, 0x00, 0x00)); protected static $passwords = array(); /** * The algorithm to use for PBKDF2. Must be a supported hash_hmac algorithm. Default: sha1 * * @var string */ private static $pbkdf2Algorithm = 'sha1'; /** * Number of iterations to use for PBKDF2 * * @var int */ private static $pbkdf2Iterations = 1000; /** * Should we use a static salt for PBKDF2? * * @var int */ private static $pbkdf2UseStaticSalt = 0; /** * The static salt to use for PBKDF2 * * @var string */ private static $pbkdf2StaticSalt = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; /** * Encrypt a text using AES encryption in Counter mode of operation * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf * * Unicode multi-byte character safe * * @param string $plaintext Source text to be encrypted * @param string $password The password to use to generate a key * @param int $nBits Number of bits to be used in the key (128, 192, or 256) * * @return string Encrypted text */ public static function AESEncryptCtr($plaintext, $password, $nBits) { $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) { return ''; } // standard allows 128/192/256 bit keys // note PHP (5) gives us plaintext and password in UTF8 encoding! // use AES itself to encrypt password to get cipher key (using plain password as source for // key expansion) - gives us well encrypted key $nBytes = $nBits / 8; // no bytes in key $pwBytes = array(); for ($i = 0; $i < $nBytes; $i++) { $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; } $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes)); $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long // initialise counter block (NIST SP800-38A �B.2): millisecond time-stamp for nonce in // 1st 8 bytes, block counter in 2nd 8 bytes $counterBlock = array(); $nonce = floor(microtime(true) * 1000); // timestamp: milliseconds since 1-Jan-1970 $nonceSec = floor($nonce / 1000); $nonceMs = $nonce % 1000; // encode nonce with seconds in 1st 4 bytes, and (repeated) ms part filling 2nd 4 bytes for ($i = 0; $i < 4; $i++) { $counterBlock[$i] = self::urs($nonceSec, $i * 8) & 0xff; } for ($i = 0; $i < 4; $i++) { $counterBlock[$i + 4] = $nonceMs & 0xff; } // and convert it to a string to go on the front of the ciphertext $ctrTxt = ''; for ($i = 0; $i < 8; $i++) { $ctrTxt .= chr($counterBlock[$i]); } // generate key schedule - an expansion of the key into distinct Key Rounds for each round $keySchedule = self::KeyExpansion($key); $blockCount = ceil(strlen($plaintext) / $blockSize); $ciphertxt = array(); // ciphertext as array of strings for ($b = 0; $b < $blockCount; $b++) { // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB) for ($c = 0; $c < 4; $c++) { $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff; } for ($c = 0; $c < 4; $c++) { $counterBlock[15 - $c - 4] = self::urs($b / 0x100000000, $c * 8); } $cipherCntr = self::Cipher($counterBlock, $keySchedule); // -- encrypt counter block -- // block size is reduced on final block $blockLength = $b < $blockCount - 1 ? $blockSize : (strlen($plaintext) - 1) % $blockSize + 1; $cipherByte = array(); for ($i = 0; $i < $blockLength; $i++) { // -- xor plaintext with ciphered counter byte-by-byte -- $cipherByte[$i] = $cipherCntr[$i] ^ ord(substr($plaintext, $b * $blockSize + $i, 1)); $cipherByte[$i] = chr($cipherByte[$i]); } $ciphertxt[$b] = implode('', $cipherByte); // escape troublesome characters in ciphertext } // implode is more efficient than repeated string concatenation $ciphertext = $ctrTxt . implode('', $ciphertxt); $ciphertext = base64_encode($ciphertext); return $ciphertext; } /** * AES Cipher function: encrypt 'input' with Rijndael algorithm * * @param array $input Message as byte-array (16 bytes) * @param array $w key schedule as 2D byte-array (Nr+1 x Nb bytes) - * generated from the cipher key by KeyExpansion() * * @return string Ciphertext as byte-array (16 bytes) */ protected static function Cipher($input, $w) { // main Cipher function [�5.1] $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) $Nr = count($w) / $Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4] for ($i = 0; $i < 4 * $Nb; $i++) { $state[$i % 4][floor($i / 4)] = $input[$i]; } $state = self::AddRoundKey($state, $w, 0, $Nb); for ($round = 1; $round < $Nr; $round++) { // apply Nr rounds $state = self::SubBytes($state, $Nb); $state = self::ShiftRows($state, $Nb); $state = self::MixColumns($state); $state = self::AddRoundKey($state, $w, $round, $Nb); } $state = self::SubBytes($state, $Nb); $state = self::ShiftRows($state, $Nb); $state = self::AddRoundKey($state, $w, $Nr, $Nb); $output = array(4 * $Nb); // convert state to 1-d array before returning [�3.4] for ($i = 0; $i < 4 * $Nb; $i++) { $output[$i] = $state[$i % 4][floor($i / 4)]; } return $output; } protected static function AddRoundKey($state, $w, $rnd, $Nb) { // xor Round Key into state S [�5.1.4] for ($r = 0; $r < 4; $r++) { for ($c = 0; $c < $Nb; $c++) { $state[$r][$c] ^= $w[$rnd * 4 + $c][$r]; } } return $state; } protected static function SubBytes($s, $Nb) { // apply SBox to state S [�5.1.1] for ($r = 0; $r < 4; $r++) { for ($c = 0; $c < $Nb; $c++) { $s[$r][$c] = self::$Sbox[$s[$r][$c]]; } } return $s; } protected static function ShiftRows($s, $Nb) { // shift row r of state S left by r bytes [�5.1.2] $t = array(4); for ($r = 1; $r < 4; $r++) { for ($c = 0; $c < 4; $c++) { $t[$c] = $s[$r][($c + $r) % $Nb]; } // shift into temp copy for ($c = 0; $c < 4; $c++) { $s[$r][$c] = $t[$c]; } // and copy back } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES): return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf } protected static function MixColumns($s) { // combine bytes of each col of state S [�5.1.3] for ($c = 0; $c < 4; $c++) { $a = array(4); // 'a' is a copy of the current column from 's' $b = array(4); // 'b' is a�{02} in GF(2^8) for ($i = 0; $i < 4; $i++) { $a[$i] = $s[$i][$c]; $b[$i] = $s[$i][$c] & 0x80 ? $s[$i][$c] << 1 ^ 0x011b : $s[$i][$c] << 1; } // a[n] ^ b[n] is a�{03} in GF(2^8) $s[0][$c] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3 $s[1][$c] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3 $s[2][$c] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3 $s[3][$c] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3 } return $s; } /** * Key expansion for Rijndael Cipher(): performs key expansion on cipher key * to generate a key schedule * * @param array $key Cipher key byte-array (16 bytes) * * @return array Key schedule as 2D byte-array (Nr+1 x Nb bytes) */ protected static function KeyExpansion($key) { // generate Key Schedule from Cipher Key [�5.2] // block size (in words): no of columns in state (fixed at 4 for AES) $Nb = 4; // key length (in words): 4/6/8 for 128/192/256-bit keys $Nk = (int) (count($key) / 4); // no of rounds: 10/12/14 for 128/192/256-bit keys $Nr = $Nk + 6; $w = array(); $temp = array(); for ($i = 0; $i < $Nk; $i++) { $r = array($key[4 * $i], $key[4 * $i + 1], $key[4 * $i + 2], $key[4 * $i + 3]); $w[$i] = $r; } for ($i = $Nk; $i < ($Nb * ($Nr + 1)); $i++) { $w[$i] = array(); for ($t = 0; $t < 4; $t++) { $temp[$t] = $w[$i - 1][$t]; } if ($i % $Nk == 0) { $temp = self::SubWord(self::RotWord($temp)); for ($t = 0; $t < 4; $t++) { $rConIndex = (int) ($i / $Nk); $temp[$t] ^= self::$Rcon[$rConIndex][$t]; } } else if ($Nk > 6 && $i % $Nk == 4) { $temp = self::SubWord($temp); } for ($t = 0; $t < 4; $t++) { $w[$i][$t] = $w[$i - $Nk][$t] ^ $temp[$t]; } } return $w; } protected static function SubWord($w) { // apply SBox to 4-byte word w for ($i = 0; $i < 4; $i++) { $w[$i] = self::$Sbox[$w[$i]]; } return $w; } /* * Unsigned right shift function, since PHP has neither >>> operator nor unsigned ints * * @param a number to be shifted (32-bit integer) * @param b number of bits to shift a to the right (0..31) * @return a right-shifted and zero-filled by b bits */ protected static function RotWord($w) { // rotate 4-byte word w left by one byte $tmp = $w[0]; for ($i = 0; $i < 3; $i++) { $w[$i] = $w[$i + 1]; } $w[3] = $tmp; return $w; } protected static function urs($a, $b) { $a &= 0xffffffff; $b &= 0x1f; // (bounds check) if ($a & 0x80000000 && $b > 0) { // if left-most bit set $a = ($a >> 1) & 0x7fffffff; // right-shift one bit & clear left-most bit $a = $a >> ($b - 1); // remaining right-shifts } else { // otherwise $a = ($a >> $b); // use normal right-shift } return $a; } /** * Decrypt a text encrypted by AES in counter mode of operation * * @param string $ciphertext Source text to be decrypted * @param string $password The password to use to generate a key * @param int $nBits Number of bits to be used in the key (128, 192, or 256) * * @return string Decrypted text */ public static function AESDecryptCtr($ciphertext, $password, $nBits) { $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES if (!($nBits == 128 || $nBits == 192 || $nBits == 256)) { return ''; } // standard allows 128/192/256 bit keys $ciphertext = base64_decode($ciphertext); // use AES to encrypt password (mirroring encrypt routine) $nBytes = $nBits / 8; // no bytes in key $pwBytes = array(); for ($i = 0; $i < $nBytes; $i++) { $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; } $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes)); $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long // recover nonce from 1st element of ciphertext $counterBlock = array(); $ctrTxt = substr($ciphertext, 0, 8); for ($i = 0; $i < 8; $i++) { $counterBlock[$i] = ord(substr($ctrTxt, $i, 1)); } // generate key schedule $keySchedule = self::KeyExpansion($key); // separate ciphertext into blocks (skipping past initial 8 bytes) $nBlocks = ceil((strlen($ciphertext) - 8) / $blockSize); $ct = array(); for ($b = 0; $b < $nBlocks; $b++) { $ct[$b] = substr($ciphertext, 8 + $b * $blockSize, 16); } $ciphertext = $ct; // ciphertext is now array of block-length strings // plaintext will get generated block-by-block into array of block-length strings $plaintxt = array(); for ($b = 0; $b < $nBlocks; $b++) { // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) for ($c = 0; $c < 4; $c++) { $counterBlock[15 - $c] = self::urs($b, $c * 8) & 0xff; } for ($c = 0; $c < 4; $c++) { $counterBlock[15 - $c - 4] = self::urs(($b + 1) / 0x100000000 - 1, $c * 8) & 0xff; } $cipherCntr = self::Cipher($counterBlock, $keySchedule); // encrypt counter block $plaintxtByte = array(); for ($i = 0; $i < strlen($ciphertext[$b]); $i++) { // -- xor plaintext with ciphered counter byte-by-byte -- $plaintxtByte[$i] = $cipherCntr[$i] ^ ord(substr($ciphertext[$b], $i, 1)); $plaintxtByte[$i] = chr($plaintxtByte[$i]); } $plaintxt[$b] = implode('', $plaintxtByte); } // join array of blocks into single plaintext string $plaintext = implode('', $plaintxt); return $plaintext; } /** * AES decryption in CBC mode. This is the standard mode (the CTR methods * actually use Rijndael-128 in CTR mode, which - technically - isn't AES). * * It supports AES-128 only. It assumes that the last 4 bytes * contain a little-endian unsigned long integer representing the unpadded * data length. * * @since 3.0.1 * @author Nicholas K. Dionysopoulos * * @param string $ciphertext The data to encrypt * @param string $password Encryption password * * @return string The plaintext */ public static function AESDecryptCBC($ciphertext, $password) { $adapter = self::getAdapter(); if (!$adapter->isSupported()) { return false; } // Read the data size $data_size = unpack('V', substr($ciphertext, -4)); // Do I have a PBKDF2 salt? $salt = substr($ciphertext, -92, 68); $rightStringLimit = -4; $params = self::getKeyDerivationParameters(); $keySizeBytes = $params['keySize']; $algorithm = $params['algorithm']; $iterations = $params['iterations']; $useStaticSalt = $params['useStaticSalt']; if (substr($salt, 0, 4) == 'JPST') { // We have a stored salt. Retrieve it and tell decrypt to process the string minus the last 44 bytes // (4 bytes for JPST, 16 bytes for the salt, 4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the // uncompressed string length - note that using PBKDF2 means we're also using a randomized IV per the // format specification). $salt = substr($salt, 4); $rightStringLimit -= 68; $key = self::pbkdf2($password, $salt, $algorithm, $iterations, $keySizeBytes); } elseif ($useStaticSalt) { // We have a static salt. Use it for PBKDF2. $key = self::getStaticSaltExpandedKey($password); } else { // Get the expanded key from the password. THIS USES THE OLD, INSECURE METHOD. $key = self::expandKey($password); } // Try to get the IV from the data $iv = substr($ciphertext, -24, 20); if (substr($iv, 0, 4) == 'JPIV') { // We have a stored IV. Retrieve it and tell mdecrypt to process the string minus the last 24 bytes // (4 bytes for JPIV, 16 bytes for the IV, 4 bytes for the uncompressed string length) $iv = substr($iv, 4); $rightStringLimit -= 20; } else { // No stored IV. Do it the dumb way. $iv = self::createTheWrongIV($password); } // Decrypt $plaintext = $adapter->decrypt($iv . substr($ciphertext, 0, $rightStringLimit), $key); // Trim padding, if necessary if (strlen($plaintext) > $data_size) { $plaintext = substr($plaintext, 0, $data_size); } return $plaintext; } /** * That's the old way of creating an IV that's definitely not cryptographically sound. * * DO NOT USE, EVER, UNLESS YOU WANT TO DECRYPT LEGACY DATA * * @param string $password The raw password from which we create an IV in a super bozo way * * @return string A 16-byte IV string */ public static function createTheWrongIV($password) { static $ivs = array(); $key = md5($password); if (!isset($ivs[$key])) { $nBytes = 16; // AES uses a 128 -bit (16 byte) block size, hence the IV size is always 16 bytes $pwBytes = array(); for ($i = 0; $i < $nBytes; $i++) { $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; } $iv = self::Cipher($pwBytes, self::KeyExpansion($pwBytes)); $newIV = ''; foreach ($iv as $int) { $newIV .= chr($int); } $ivs[$key] = $newIV; } return $ivs[$key]; } /** * Expand the password to an appropriate 128-bit encryption key * * @param string $password * * @return string * * @since 5.2.0 * @author Nicholas K. Dionysopoulos */ public static function expandKey($password) { // Try to fetch cached key or create it if it doesn't exist $nBits = 128; $lookupKey = md5($password . '-' . $nBits); if (array_key_exists($lookupKey, self::$passwords)) { $key = self::$passwords[$lookupKey]; return $key; } // use AES itself to encrypt password to get cipher key (using plain password as source for // key expansion) - gives us well encrypted key. $nBytes = $nBits / 8; // Number of bytes in key $pwBytes = array(); for ($i = 0; $i < $nBytes; $i++) { $pwBytes[$i] = ord(substr($password, $i, 1)) & 0xff; } $key = self::Cipher($pwBytes, self::KeyExpansion($pwBytes)); $key = array_merge($key, array_slice($key, 0, $nBytes - 16)); // expand key to 16/24/32 bytes long $newKey = ''; foreach ($key as $int) { $newKey .= chr($int); } $key = $newKey; self::$passwords[$lookupKey] = $key; return $key; } /** * Returns the correct AES-128 CBC encryption adapter * * @return AKEncryptionAESAdapterInterface * * @since 5.2.0 * @author Nicholas K. Dionysopoulos */ public static function getAdapter() { static $adapter = null; if (is_object($adapter) && ($adapter instanceof AKEncryptionAESAdapterInterface)) { return $adapter; } $adapter = new OpenSSL(); if (!$adapter->isSupported()) { $adapter = new Mcrypt(); } return $adapter; } /** * @return string */ public static function getPbkdf2Algorithm() { return self::$pbkdf2Algorithm; } /** * @param string $pbkdf2Algorithm * @return void */ public static function setPbkdf2Algorithm($pbkdf2Algorithm) { self::$pbkdf2Algorithm = $pbkdf2Algorithm; } /** * @return int */ public static function getPbkdf2Iterations() { return self::$pbkdf2Iterations; } /** * @param int $pbkdf2Iterations * @return void */ public static function setPbkdf2Iterations($pbkdf2Iterations) { self::$pbkdf2Iterations = $pbkdf2Iterations; } /** * @return int */ public static function getPbkdf2UseStaticSalt() { return self::$pbkdf2UseStaticSalt; } /** * @param int $pbkdf2UseStaticSalt * @return void */ public static function setPbkdf2UseStaticSalt($pbkdf2UseStaticSalt) { self::$pbkdf2UseStaticSalt = $pbkdf2UseStaticSalt; } /** * @return string */ public static function getPbkdf2StaticSalt() { return self::$pbkdf2StaticSalt; } /** * @param string $pbkdf2StaticSalt * @return void */ public static function setPbkdf2StaticSalt($pbkdf2StaticSalt) { self::$pbkdf2StaticSalt = $pbkdf2StaticSalt; } /** * Get the parameters fed into PBKDF2 to expand the user password into an encryption key. These are the static * parameters (key size, hashing algorithm and number of iterations). A new salt is used for each encryption block * to minimize the risk of attacks against the password. * * @return array */ public static function getKeyDerivationParameters() { return array( 'keySize' => 16, 'algorithm' => self::$pbkdf2Algorithm, 'iterations' => self::$pbkdf2Iterations, 'useStaticSalt' => self::$pbkdf2UseStaticSalt, 'staticSalt' => self::$pbkdf2StaticSalt, ); } /** * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt * * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt * * This implementation of PBKDF2 was originally created by https://defuse.ca * With improvements by http://www.variations-of-shadow.com * Modified for Akeeba Engine by Akeeba Ltd (removed unnecessary checks to make it faster) * * @param string $password The password. * @param string $salt A salt that is unique to the password. * @param string $algorithm The hash algorithm to use. Default is sha1. * @param int $count Iteration count. Higher is better, but slower. Default: 1000. * @param int $key_length The length of the derived key in bytes. * * @return string A string of $key_length bytes */ public static function pbkdf2($password, $salt, $algorithm = 'sha1', $count = 1000, $key_length = 16) { if (function_exists("hash_pbkdf2")) { return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, true); } $hash_length = akstringlen(hash($algorithm, "", true)); $block_count = ceil($key_length / $hash_length); $output = ""; for ($i = 1; $i <= $block_count; $i++) { // $i encoded as 4 bytes, big endian. $last = $salt . pack("N", $i); // First iteration $xorResult = hash_hmac($algorithm, $last, $password, true); $last = $xorResult; // Perform the other $count - 1 iterations for ($j = 1; $j < $count; $j++) { $last = hash_hmac($algorithm, $last, $password, true); $xorResult ^= $last; } $output .= $xorResult; } return aksubstr($output, 0, $key_length); } /** * Get the expanded key from the user supplied password using a static salt. The results are cached for performance * reasons. * * @param string $password The user-supplied password, UTF-8 encoded. * * @return string The expanded key */ private static function getStaticSaltExpandedKey($password) { $params = self::getKeyDerivationParameters(); $keySizeBytes = $params['keySize']; $algorithm = $params['algorithm']; $iterations = $params['iterations']; $staticSalt = $params['staticSalt']; $lookupKey = "PBKDF2-$algorithm-$iterations-" . md5($password . $staticSalt); if (!array_key_exists($lookupKey, self::$passwords)) { self::$passwords[$lookupKey] = self::pbkdf2($password, $staticSalt, $algorithm, $iterations, $keySizeBytes); } return self::$passwords[$lookupKey]; } } /** * The Master Setup will read the configuration parameters from restoration.php or * the JSON-encoded "configuration" input variable and return the status. * * @return bool True if the master configuration was applied to the Factory object */ function masterSetup() { // ------------------------------------------------------------ // 1. Import basic setup parameters // ------------------------------------------------------------ $ini_data = null; // Require restoration.php or fail $setupFile = 'restoration.php'; if (!file_exists($setupFile)) { AKFactory::set('kickstart.enabled', false); return false; } // Load restoration.php. It creates a global variable named $restoration_setup require_once $setupFile; /** @var array $restoration_setup This is defined in the restoration.php file */ $ini_data = $restoration_setup; if (empty($ini_data)) { // No parameters fetched. Darn, how am I supposed to work like that?! AKFactory::set('kickstart.enabled', false); return false; } AKFactory::set('kickstart.enabled', true); // Import any data from the $restoration_setup array read from restoration.php if (!empty($ini_data)) { foreach ($ini_data as $key => $value) { AKFactory::set($key, $value); } AKFactory::set('kickstart.enabled', true); } // Reinitialize $ini_data $ini_data = null; // ------------------------------------------------------------ // 2. Explode JSON parameters into $_REQUEST scope // ------------------------------------------------------------ // Detect a JSON string in the request variable and store it. $json = getQueryParam('json', null); // Remove everything from the request, post and get arrays if (!empty($_REQUEST)) { foreach ($_REQUEST as $key => $value) { unset($_REQUEST[$key]); } } if (!empty($_POST)) { foreach ($_POST as $key => $value) { unset($_POST[$key]); } } if (!empty($_GET)) { foreach ($_GET as $key => $value) { unset($_GET[$key]); } } // Decrypt a possibly encrypted JSON string $password = AKFactory::get('kickstart.security.password', null); if (!empty($json)) { if (!empty($password)) { $json = AKEncryptionAES::AESDecryptCtr($json, $password, 128); if (empty($json)) { die('###{"status":false,"message":"Invalid login"}###'); } } // Get the raw data $raw = json_decode($json, true); if (!empty($password) && (empty($raw))) { die('###{"status":false,"message":"Invalid login"}###'); } // Pass all JSON data to the request array if (!empty($raw)) { foreach ($raw as $key => $value) { $_REQUEST[$key] = $value; } } } elseif (!empty($password)) { die('###{"status":false,"message":"Invalid login"}###'); } // ------------------------------------------------------------ // 3. Try the "factory" variable // ------------------------------------------------------------ // A "factory" variable will override all other settings. $serialized = getQueryParam('factory', null); if (!is_null($serialized)) { // Get the serialized factory AKFactory::unserialize($serialized); AKFactory::set('kickstart.enabled', true); return true; } return AKFactory::get('kickstart.enabled', false); } // Mini-controller for restore.php if (!defined('KICKSTART')) { // The observer class, used to report number of files and bytes processed class RestorationObserver extends AKAbstractPartObserver { public $compressedTotal = 0; public $uncompressedTotal = 0; public $filesProcessed = 0; public function update($object, $message) { if (!is_object($message)) { return; } if (!array_key_exists('type', get_object_vars($message))) { return; } if ($message->type == 'startfile') { $this->filesProcessed++; $this->compressedTotal += $message->content->compressed; $this->uncompressedTotal += $message->content->uncompressed; } } public function __toString() { return __CLASS__; } } // Import configuration masterSetup(); $retArray = array( 'status' => true, 'message' => null ); $enabled = AKFactory::get('kickstart.enabled', false); if ($enabled) { $task = getQueryParam('task'); switch ($task) { case 'ping': // ping task - really does nothing! $timer = AKFactory::getTimer(); $timer->enforce_min_exec_time(); break; /** * There are two separate steps here since we were using an inefficient restoration intialization method in * the past. Now both startRestore and stepRestore are identical. The difference in behavior depends * exclusively on the calling Javascript. If no serialized factory was passed in the request then we start a * new restoration. If a serialized factory was passed in the request then the restoration is resumed. For * this reason we should NEVER call AKFactory::nuke() in startRestore anymore: that would simply reset the * extraction engine configuration which was done in masterSetup() leading to an error about the file being * invalid (since no file is found). */ case 'startRestore': case 'stepRestore': $engine = AKFactory::getUnarchiver(); // Get the engine $observer = new RestorationObserver(); // Create a new observer $engine->attach($observer); // Attach the observer $engine->tick(); $ret = $engine->getStatusArray(); if ($ret['Error'] != '') { $retArray['status'] = false; $retArray['done'] = true; $retArray['message'] = $ret['Error']; } elseif (!$ret['HasRun']) { $retArray['files'] = $observer->filesProcessed; $retArray['bytesIn'] = $observer->compressedTotal; $retArray['bytesOut'] = $observer->uncompressedTotal; $retArray['status'] = true; $retArray['done'] = true; } else { $retArray['files'] = $observer->filesProcessed; $retArray['bytesIn'] = $observer->compressedTotal; $retArray['bytesOut'] = $observer->uncompressedTotal; $retArray['status'] = true; $retArray['done'] = false; $retArray['factory'] = AKFactory::serialize(); } break; case 'finalizeRestore': $root = AKFactory::get('kickstart.setup.destdir'); // Remove the installation directory recursive_remove_directory($root . '/installation'); $postproc = AKFactory::getPostProc(); /** * Should I rename the htaccess.bak and web.config.bak files back to their live filenames...? */ $renameFiles = AKFactory::get('kickstart.setup.postrenamefiles', true); if ($renameFiles) { // Rename htaccess.bak to .htaccess if (file_exists($root . '/htaccess.bak')) { if (file_exists($root . '/.htaccess')) { $postproc->unlink($root . '/.htaccess'); } $postproc->rename($root . '/htaccess.bak', $root . '/.htaccess'); } // Rename htaccess.bak to .htaccess if (file_exists($root . '/web.config.bak')) { if (file_exists($root . '/web.config')) { $postproc->unlink($root . '/web.config'); } $postproc->rename($root . '/web.config.bak', $root . '/web.config'); } } // Remove restoration.php $basepath = KSROOTDIR; $basepath = rtrim(str_replace('\\', '/', $basepath), '/'); if (!empty($basepath)) { $basepath .= '/'; } $postproc->unlink($basepath . 'restoration.php'); // Import a custom finalisation file $filename = dirname(__FILE__) . '/restore_finalisation.php'; if (file_exists($filename)) { // We cannot use the Filesystem API here. if (ini_get('opcache.enable') && function_exists('opcache_invalidate') && (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0) ) { \opcache_invalidate($filename, true); } if (function_exists('apc_compile_file')) { \apc_compile_file($filename); } if (function_exists('wincache_refresh_if_changed')) { \wincache_refresh_if_changed(array($filename)); } if (function_exists('xcache_asm')) { xcache_asm($filename); } include_once $filename; } // Run a custom finalisation script if (function_exists('finalizeRestore')) { finalizeRestore($root, $basepath); } break; default: // Invalid task! $enabled = false; break; } } // Maybe we weren't authorized or the task was invalid? if (!$enabled) { // Maybe the user failed to enter any information $retArray['status'] = false; $retArray['message'] = AKText::_('ERR_INVALID_LOGIN'); } // JSON encode the message $json = json_encode($retArray); // Do I have to encrypt? $password = AKFactory::get('kickstart.security.password', null); if (!empty($password)) { $json = AKEncryptionAES::AESEncryptCtr($json, $password, 128); } // Return the message echo "###$json###"; } // ------------ lixlpixel recursive PHP functions ------------- // recursive_remove_directory( directory to delete, empty ) // expects path to directory and optional TRUE / FALSE to empty // of course PHP has to have the rights to delete the directory // you specify and all files and folders inside the directory // ------------------------------------------------------------ function recursive_remove_directory($directory) { // if the path has a slash at the end we remove it here if (substr($directory, -1) == '/') { $directory = substr($directory, 0, -1); } // if the path is not valid or is not a directory ... if (!file_exists($directory) || !is_dir($directory)) { // ... we return false and exit the function return false; // ... if the path is not readable } elseif (!is_readable($directory)) { // ... we return false and exit the function return false; // ... else if the path is readable } else { // we open the directory $handle = opendir($directory); $postproc = AKFactory::getPostProc(); // and scan through the items inside while (false !== ($item = readdir($handle))) { // if the filepointer is not the current directory // or the parent directory if ($item != '.' && $item != '..') { // we build the new path to delete $path = $directory . '/' . $item; // if the new path is a directory if (is_dir($path)) { // we call this function with the new path recursive_remove_directory($path); // if the new path is a file } else { // we remove the file $postproc->unlink($path); } } } // close the directory closedir($handle); // try to delete the now empty directory if (!$postproc->rmdir($directory)) { // return false if not possible return false; } // return success return true; } } PK �|!]1]�u u views/update/view.html.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * Joomla! Update's Update View * * @since 2.5.4 */ class JoomlaupdateViewUpdate extends JViewLegacy { /** * Renders the view. * * @param string $tpl Template name. * * @return void */ public function display($tpl = null) { JFactory::getApplication()->input->set('hidemainmenu', true); // Set the toolbar information. JToolbarHelper::title(JText::_('COM_JOOMLAUPDATE_OVERVIEW'), 'loop install'); // Import com_login's model JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_login/models', 'LoginModel'); // Render the view. parent::display($tpl); } } PK �|!]�;� % views/update/tmpl/finaliseconfirm.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; JHtml::_('behavior.keepalive'); JHtml::_('bootstrap.tooltip'); $twofactormethods = JAuthenticationHelper::getTwoFactorMethods(); ?> <div class="alert alert-warning"> <h4 class="alert-heading"> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_HEAD'); ?> </h4> <p> <?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_HEAD_DESC', JFactory::getConfig()->get('sitename')); ?> </p> </div> <hr/> <form action="<?php echo JRoute::_('index.php', true); ?>" method="post" id="form-login" class="form-inline center"> <fieldset class="loginform"> <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <span class="icon-user hasTooltip" title="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" aria-hidden="true"></span> <label for="mod-login-username" class="element-invisible"> <?php echo JText::_('JGLOBAL_USERNAME'); ?> </label> </span> <input name="username" tabindex="1" id="mod-login-username" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" size="15" autofocus="true" /> </div> </div> </div> <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <span class="icon-lock hasTooltip" title="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" aria-hidden="true"></span> <label for="mod-login-password" class="element-invisible"> <?php echo JText::_('JGLOBAL_PASSWORD'); ?> </label> </span> <input name="passwd" tabindex="2" id="mod-login-password" type="password" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" size="15"/> </div> </div> </div> <?php if (count($twofactormethods) > 1) : ?> <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <span class="icon-star hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" aria-hidden="true"></span> <label for="mod-login-secretkey" class="element-invisible"> <?php echo JText::_('JGLOBAL_SECRETKEY'); ?> </label> </span> <input name="secretkey" autocomplete="one-time-code" tabindex="3" id="mod-login-secretkey" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" size="15"/> <span class="btn width-auto hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY_HELP'); ?>"> <span class="icon-help" aria-hidden="true"></span> </span> </div> </div> </div> <?php endif; ?> <div class="control-group"> <div class="controls"> <div class="btn-group"> <a tabindex="4" class="btn btn-danger btn-small" href="index.php?option=com_joomlaupdate"> <span class="icon-cancel icon-white" aria-hidden="true"></span> <?php echo JText::_('JCANCEL'); ?> </a> </div> <div class="btn-group"> <button tabindex="5" class="btn btn-primary btn-large"> <span class="icon-play icon-white" aria-hidden="true"></span> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_FINALISE_CONFIRM_AND_CONTINUE'); ?> </button> </div> </div> </div> <input type="hidden" name="option" value="com_joomlaupdate"/> <input type="hidden" name="task" value="update.finaliseconfirm" /> <?php echo JHtml::_('form.token'); ?> </fieldset> </form> PK �|!]��[ [ views/update/tmpl/default.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; // Include jQuery. JHtml::_('jquery.framework'); // Load the scripts JHtml::_('script', 'com_joomlaupdate/json2.js', array('version' => 'auto', 'relative' => true)); JHtml::_('script', 'com_joomlaupdate/encryption.js', array('version' => 'auto', 'relative' => true)); JHtml::_('script', 'com_joomlaupdate/update.js', array('version' => 'auto', 'relative' => true)); $password = JFactory::getApplication()->getUserState('com_joomlaupdate.password', null); $filesize = JFactory::getApplication()->getUserState('com_joomlaupdate.filesize', null); $ajaxUrl = JUri::base() . 'components/com_joomlaupdate/restore.php'; $returnUrl = 'index.php?option=com_joomlaupdate&task=update.finalise&' . JFactory::getSession()->getFormToken() . '=1'; JFactory::getDocument()->addScriptDeclaration( " var joomlaupdate_password = '$password'; var joomlaupdate_totalsize = '$filesize'; var joomlaupdate_ajax_url = '$ajaxUrl'; var joomlaupdate_return_url = '$returnUrl'; jQuery(document).ready(function(){ window.pingExtract(); }); " ); ?> <p class="nowarning"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_INPROGRESS'); ?></p> <div id="update-progress"> <div id="extprogress"> <div id="progress" class="progress progress-striped active"> <div id="progress-bar" class="bar bar-success" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div> </div> <div class="extprogrow"> <span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_PERCENT'); ?></span> <span class="extvalue" id="extpercent"></span> </div> <div class="extprogrow"> <span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_BYTESREAD'); ?></span> <span class="extvalue" id="extbytesin"></span> </div> <div class="extprogrow"> <span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_BYTESEXTRACTED'); ?></span> <span class="extvalue" id="extbytesout"></span> </div> <div class="extprogrow"> <span class="extlabel"><?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPDATE_FILESEXTRACTED'); ?></span> <span class="extvalue" id="extfiles"></span> </div> </div> </div> PK �|!]ĩ*H H views/upload/tmpl/captive.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; JHtml::_('behavior.keepalive'); JHtml::_('bootstrap.tooltip'); $twofactormethods = JAuthenticationHelper::getTwoFactorMethods(); ?> <div class="alert alert-warning"> <h4 class="alert-heading"> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_UPLOAD_CAPTIVE_INTRO_HEAD'); ?> </h4> <p> <?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_UPLOAD_CAPTIVE_INTRO_BODY', JFactory::getConfig()->get('sitename')); ?> </p> </div> <hr/> <form action="<?php echo JRoute::_('index.php', true); ?>" method="post" id="form-login" class="form-inline center"> <fieldset class="loginform"> <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <span class="icon-user hasTooltip" title="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" aria-hidden="true"></span> <label for="mod-login-username" class="element-invisible"> <?php echo JText::_('JGLOBAL_USERNAME'); ?> </label> </span> <input name="username" tabindex="1" id="mod-login-username" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_USERNAME'); ?>" size="15" autofocus="true" /> </div> </div> </div> <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <span class="icon-lock hasTooltip" title="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" aria-hidden="true"></span> <label for="mod-login-password" class="element-invisible"> <?php echo JText::_('JGLOBAL_PASSWORD'); ?> </label> </span> <input name="passwd" tabindex="2" id="mod-login-password" type="password" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_PASSWORD'); ?>" size="15"/> </div> </div> </div> <?php if (count($twofactormethods) > 1) : ?> <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <span class="icon-star hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" aria-hidden="true"></span> <label for="mod-login-secretkey" class="element-invisible"> <?php echo JText::_('JGLOBAL_SECRETKEY'); ?> </label> </span> <input name="secretkey" autocomplete="one-time-code" tabindex="3" id="mod-login-secretkey" type="text" class="input-medium" placeholder="<?php echo JText::_('JGLOBAL_SECRETKEY'); ?>" size="15"/> <span class="btn width-auto hasTooltip" title="<?php echo JText::_('JGLOBAL_SECRETKEY_HELP'); ?>"> <span class="icon-help" aria-hidden="true"></span> </span> </div> </div> </div> <?php endif; ?> <div class="control-group"> <div class="controls"> <div class="btn-group"> <a tabindex="4" class="btn btn-danger" href="index.php?option=com_joomlaupdate"> <span class="icon-cancel icon-white" aria-hidden="true"></span> <?php echo JText::_('JCANCEL'); ?> </a> </div> <div class="btn-group"> <button tabindex="5" class="btn btn-primary"> <span class="icon-play icon-white" aria-hidden="true"></span> <?php echo JText::_('COM_INSTALLER_INSTALL_BUTTON'); ?> </button> </div> </div> </div> <input type="hidden" name="option" value="com_joomlaupdate"/> <input type="hidden" name="task" value="update.confirm"/> <?php echo JHtml::_('form.token'); ?> </fieldset> </form> PK �|!]�}� � views/upload/view.html.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * Joomla! Update's Update View * * @since 3.6.0 */ class JoomlaupdateViewUpload extends JViewLegacy { /** * Renders the view. * * @param string $tpl Template name. * * @return void * * @since 3.6.0 */ public function display($tpl = null) { // Set the toolbar information. JToolbarHelper::title(JText::_('COM_JOOMLAUPDATE_OVERVIEW'), 'loop install'); JToolbarHelper::divider(); JToolbarHelper::help('JHELP_COMPONENTS_JOOMLA_UPDATE'); // Load com_installer's language $language = JFactory::getLanguage(); $language->load('com_installer', JPATH_ADMINISTRATOR, 'en-GB', false, true); $language->load('com_installer', JPATH_ADMINISTRATOR, null, true); // Import com_login's model JModelLegacy::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_login/models', 'LoginModel'); // Render the view. parent::display($tpl); } } PK �|!]�}� � views/default/view.html.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** * Joomla! Update's Default View * * @since 2.5.4 */ class JoomlaupdateViewDefault extends JViewLegacy { /** * An array with the Joomla! update information. * * @var array * * @since 3.6.0 */ protected $updateInfo = null; /** * The form field for the extraction select * * @var string * * @since 3.6.0 */ protected $methodSelect = null; /** * The form field for the upload select * * @var string * * @since 3.6.0 */ protected $methodSelectUpload = null; /** * PHP options. * * @var array Array of PHP config options * * @since 3.10.0 */ protected $phpOptions = null; /** * PHP settings. * * @var array Array of PHP settings * * @since 3.10.0 */ protected $phpSettings = null; /** * Non Core Extensions. * * @var array Array of Non-Core-Extensions * * @since 3.10.0 */ protected $nonCoreExtensions = null; /** * Renders the view * * @param string $tpl Template name * * @return void * * @since 2.5.4 */ public function display($tpl = null) { // Get data from the model. $this->state = $this->get('State'); // Load useful classes. /** @var JoomlaupdateModelDefault $model */ $model = $this->getModel(); $this->loadHelper('select'); // Assign view variables. $this->ftp = $model->getFTPOptions(); $defaultMethod = $this->ftp['enabled'] ? 'hybrid' : 'direct'; $this->updateInfo = $model->getUpdateInformation(); $this->methodSelect = JoomlaupdateHelperSelect::getMethods($defaultMethod); $this->methodSelectUpload = JoomlaupdateHelperSelect::getMethods($defaultMethod, 'method', 'upload_method'); // Get results of pre update check evaluations $this->phpOptions = $model->getPhpOptions(); $this->phpSettings = $model->getPhpSettings(); $this->nonCoreExtensions = $model->getNonCoreExtensions(); $this->isBackendTemplateIsis = (bool) $model->isTemplateActive('isis'); // Disable the critical plugins check for non-major updates. $this->nonCoreCriticalPlugins = array(); if (version_compare($this->updateInfo['latest'], '4', '>=')) { $this->nonCoreCriticalPlugins = $model->getNonCorePlugins(array('system','user','authentication','actionlog','twofactorauth')); } // Set the toolbar information. JToolbarHelper::title(JText::_('COM_JOOMLAUPDATE_OVERVIEW'), 'loop install'); JToolbarHelper::custom('update.purge', 'loop', 'loop', 'COM_JOOMLAUPDATE_TOOLBAR_CHECK', false); // Add toolbar buttons. if (JFactory::getUser()->authorise('core.admin')) { JToolbarHelper::preferences('com_joomlaupdate'); } JToolbarHelper::divider(); JToolbarHelper::help('JHELP_COMPONENTS_JOOMLA_UPDATE'); if (!is_null($this->updateInfo['object'])) { // Show the message if an update is found. JFactory::getApplication()->enqueueMessage(JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATE_NOTICE'), 'warning'); } $this->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 'next': $this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_NEXT'; $this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_NEXT'); break; // "Testing" case 'testing': $this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_TESTING'; $this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_TESTING'); break; // "Custom" case 'custom': $this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_CUSTOM'; $this->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 'sts': * case 'nochange': */ default: $this->langKey = 'COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATES_INFO_DEFAULT'; $this->updateSourceKey = JText::_('COM_JOOMLAUPDATE_CONFIG_UPDATESOURCE_DEFAULT'); } $this->warnings = array(); /** @var InstallerModelWarnings $warningsModel */ $warningsModel = $this->getModel('warnings'); if (is_object($warningsModel) && $warningsModel instanceof JModelLegacy) { $language = JFactory::getLanguage(); $language->load('com_installer', JPATH_ADMINISTRATOR, 'en-GB', false, true); $language->load('com_installer', JPATH_ADMINISTRATOR, null, true); $this->warnings = $warningsModel->getItems(); } $this->selfUpdate = $this->checkForSelfUpdate(); // Only Super Users have access to the Update & Install for obvious security reasons $this->showUploadAndUpdate = JFactory::getUser()->authorise('core.admin'); // Remove temporary files $model->removePackageFiles(); // Render the view. parent::display($tpl); } /** * Makes sure that the Joomla! Update Component Update is in the database and check if there is a new version. * * @return boolean True if there is an update else false * * @since 3.6.3 */ private function checkForSelfUpdate() { $db = JFactory::getDbo(); $query = $db->getQuery(true) ->select($db->quoteName('extension_id')) ->from($db->quoteName('#__extensions')) ->where($db->quoteName('element') . ' = ' . $db->quote('com_joomlaupdate')); $db->setQuery($query); try { // Get the component extension ID $joomlaUpdateComponentId = $db->loadResult(); } catch (RuntimeException $e) { // Something is wrong here! $joomlaUpdateComponentId = 0; JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); } // Try the update only if we have an extension id if ($joomlaUpdateComponentId != 0) { // Always force to check for an update! $cache_timeout = 0; $updater = JUpdater::getInstance(); $updater->findUpdates($joomlaUpdateComponentId, $cache_timeout, JUpdater::STABILITY_STABLE); // Fetch the update information from the database. $query = $db->getQuery(true) ->select('*') ->from($db->quoteName('#__updates')) ->where($db->quoteName('extension_id') . ' = ' . $db->quote($joomlaUpdateComponentId)); $db->setQuery($query); try { $joomlaUpdateComponentObject = $db->loadObject(); } catch (RuntimeException $e) { // Something is wrong here! $joomlaUpdateComponentObject = null; JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error'); } if (is_null($joomlaUpdateComponentObject)) { // No Update great! return false; } return true; } } /** * Returns true, if the pre update check should be displayed. * This logic is not hardcoded in tmpl files, because it is * used by the Hathor tmpl too. * * @return boolean * * @since 3.10.0 */ public function shouldDisplayPreUpdateCheck() { // When the download URL is not found there is no core upgrade path if (!isset($this->updateInfo['object']->downloadurl->_data)) { return false; } $nextMinor = JVersion::MAJOR_VERSION . '.' . (JVersion::MINOR_VERSION + 1); // Show only when we found a download URL, we have an update and when we update to the next minor or greater. return $this->updateInfo['hasUpdate'] && version_compare($this->updateInfo['latest'], $nextMinor, '>='); } } PK �|!]��= = % views/default/tmpl/default_update.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** @var JoomlaupdateViewDefault $this */ ?> <fieldset> <legend> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_UPDATEFOUND'); ?> </legend> <p> <?php echo JText::sprintf($this->langKey, $this->updateSourceKey); ?> </p> <table class="table table-striped"> <tbody> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALLED'); ?> </td> <td> <?php echo '‎' . $this->updateInfo['installed']; ?> </td> </tr> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_LATEST'); ?> </td> <td> <?php echo '‎' . $this->updateInfo['latest']; ?> </td> </tr> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_PACKAGE'); ?> </td> <td> <a href="<?php echo $this->updateInfo['object']->downloadurl->_data; ?>" target="_blank" rel="noopener noreferrer"> <?php echo $this->updateInfo['object']->downloadurl->_data; ?> <span class="icon-out-2" aria-hidden="true"></span> <span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span> </a> </td> </tr> <?php if (isset($this->updateInfo['object']->get('infourl')->_data) && isset($this->updateInfo['object']->get('infourl')->title)) : ?> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INFOURL'); ?> </td> <td> <a href="<?php echo $this->updateInfo['object']->get('infourl')->_data; ?>" target="_blank" rel="noopener noreferrer"> <?php echo $this->updateInfo['object']->get('infourl')->title; ?> <span class="icon-out-2" aria-hidden="true"></span> <span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span> </a> </td> </tr> <?php endif; ?> <?php // Hide FTP settings when updating to Joomla 4 given that the supporting code has been dropped there ?> <?php if (version_compare($this->updateInfo['latest'], '4', '<')) : ?> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD'); ?> </td> <td> <?php echo $this->methodSelect; ?> </td> </tr> <tr id="row_ftp_hostname" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_HOSTNAME'); ?> </td> <td> <input type="text" name="ftp_host" value="<?php echo $this->ftp['host']; ?>" /> </td> </tr> <tr id="row_ftp_port" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PORT'); ?> </td> <td> <input type="text" name="ftp_port" value="<?php echo $this->ftp['port']; ?>" /> </td> </tr> <tr id="row_ftp_username" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_USERNAME'); ?> </td> <td> <input type="text" name="ftp_user" value="<?php echo $this->ftp['username']; ?>" /> </td> </tr> <tr id="row_ftp_password" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PASSWORD'); ?> </td> <td> <input type="password" name="ftp_pass" value="<?php echo $this->ftp['password']; ?>" /> </td> </tr> <tr id="row_ftp_directory" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_DIRECTORY'); ?> </td> <td> <input type="text" name="ftp_root" value="<?php echo $this->ftp['directory']; ?>" /> </td> </tr> <?php endif; ?> </tbody> <tfoot> <tr id="preupdateCheckWarning"> <td colspan="2"> <div class="alert"> <h4 class="alert-heading"> <?php echo JText::_('WARNING'); ?> </h4> <div class="alert-message"> <div class="preupdateCheckIncomplete"> <?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_CHECK_NOT_COMPLETE'); ?> </div> </div> </div> </td> </tr> <tr id="preupdateCheckCompleteProblems" class="hidden"> <td colspan="2"> <div class="alert"> <h4 class="alert-heading"> <?php echo JText::_('WARNING'); ?> </h4> <div class="alert-message"> <div class="preupdateCheckComplete"> <?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_CHECK_COMPLETED_YOU_HAVE_DANGEROUS_PLUGINS'); ?> </div> </div> </div> </td> </tr> <tr id="preupdateconfirmation" > <td colspan="2"> <label class="preupdateconfirmation_label label label-warning span12"> <h3> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NON_CORE_PLUGIN_BEING_CHECKED'); ?> </h3> </label> </td> </tr> <tr id="preupdatecheckheadings"> <td colspan="2"> <table class="table table-striped"> <thead> <th> <?php echo JText::_('COM_INSTALLER_TYPE_PLUGIN'); ?> </th> <th> <?php echo JText::_('COM_INSTALLER_TYPE_PACKAGE'); ?> </th> <th> <?php echo JText::_('COM_INSTALLER_AUTHOR_INFORMATION'); ?> </th> <th> <?php echo JText::_('COM_JOOMLAUPDATE_PREUPDATE_CHECK_EXTENSION_AUTHOR_URL'); ?> </th> </thead> <tbody> <?php foreach ($this->nonCoreCriticalPlugins as $nonCoreCriticalPlugin) : ?> <tr id='plg_<?php echo $nonCoreCriticalPlugin->extension_id ?>'> <td> <?php echo JText::_($nonCoreCriticalPlugin->name); ?> </td> <?php if ($nonCoreCriticalPlugin->package_id > 0) : ?> <?php foreach ($this->nonCoreExtensions as $nonCoreExtension) : ?> <?php if ($nonCoreCriticalPlugin->package_id == $nonCoreExtension->extension_id) : ?> <td> <?php echo $nonCoreExtension->name; ?> </td> <?php endif; ?> <?php endforeach; ?> <?php else : ?> <td/> <?php endif; ?> <td> <?php if (isset($nonCoreCriticalPlugin->manifest_cache->author)) : ?> <?php echo JText::_($nonCoreCriticalPlugin->manifest_cache->author); ?> <?php elseif ($nonCoreCriticalPlugin->package_id > 0) : ?> <?php foreach ($this->nonCoreExtensions as $nonCoreExtension) : ?> <?php if ($nonCoreCriticalPlugin->package_id == $nonCoreExtension->extension_id) : ?> <td> <?php echo $nonCoreExtension->name; ?> </td> <?php endif; ?> <?php endforeach; ?> <?php endif; ?> </td> <td> <?php $authorURL = ''; ?> <?php if (isset($nonCoreCriticalPlugin->manifest_cache->authorUrl)) : ?> <?php $authorURL = $nonCoreCriticalPlugin->manifest_cache->authorUrl; ?> <?php elseif ($nonCoreCriticalPlugin->package_id > 0) : ?> <?php foreach ($this->nonCoreExtensions as $nonCoreExtension) : ?> <?php if ($nonCoreCriticalPlugin->package_id == $nonCoreExtension->extension_id) : ?> <?php $authorURL = $nonCoreExtension->manifest_cache->authorUrl; ?> <?php endif; ?> <?php endforeach; ?> <?php endif; ?> <?php if (!empty($authorURL)) : ?> <a href="<?php echo $authorURL; ?>" target="_blank"> <?php echo $authorURL; ?> <span class="icon-out-2" aria-hidden="true"></span> <span class="element-invisible"> <?php echo JText::_('JBROWSERTARGET_NEW'); ?> </span> </a> <?php endif;?> </td> </tr> <?php endforeach; ?> </tbody> </table> </td> </tr> <tr id="preupdatecheckbox"> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NON_CORE_PLUGIN_CONFIRMATION'); ?> </td> <td> <input type="checkbox" id="noncoreplugins" name="noncoreplugins" value="1" required aria-required="true" /> </td> </tr> <tr> <td> </td> <td> <button class="btn btn-primary disabled submitupdate" type="submit" disabled> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALLUPDATE'); ?> </button> </td> </tr> </tfoot> </table> </fieldset> PK �|!]�x� � ( views/default/tmpl/default_reinstall.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** @var JoomlaupdateViewDefault $this */ ?> <fieldset> <legend> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NOUPDATES'); ?> </legend> <p> <?php echo JText::sprintf($this->langKey, $this->updateSourceKey); ?> </p> <div class="alert alert-success"> <?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_NOUPDATESNOTICE', JVERSION); ?> </div> <?php if (is_object($this->updateInfo['object']) && ($this->updateInfo['object'] instanceof JUpdate)) : ?> <table class="table table-striped"> <tbody> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_PACKAGE_REINSTALL'); ?> </td> <td> <a href="<?php echo $this->updateInfo['object']->downloadurl->_data; ?>" target="_blank" rel="noopener noreferrer"> <?php echo $this->updateInfo['object']->downloadurl->_data; ?> <span class="icon-out-2" aria-hidden="true"></span> <span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span> </a> </td> </tr> <?php if (isset($this->updateInfo['object']->get('infourl')->_data) && isset($this->updateInfo['object']->get('infourl')->title)) : ?> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INFOURL'); ?> </td> <td> <a href="<?php echo $this->updateInfo['object']->get('infourl')->_data; ?>" target="_blank" rel="noopener noreferrer"> <?php echo $this->updateInfo['object']->get('infourl')->title; ?> <span class="icon-out-2" aria-hidden="true"></span> <span class="element-invisible"><?php echo JText::_('JBROWSERTARGET_NEW'); ?></span> </a> </td> </tr> <?php endif; ?> <tr> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_METHOD'); ?> </td> <td> <?php echo $this->methodSelect; ?> </td> </tr> <tr id="row_ftp_hostname" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_HOSTNAME'); ?> </td> <td> <input type="text" name="ftp_host" value="<?php echo $this->ftp['host']; ?>" /> </td> </tr> <tr id="row_ftp_port" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PORT'); ?> </td> <td> <input type="text" name="ftp_port" value="<?php echo $this->ftp['port']; ?>" /> </td> </tr> <tr id="row_ftp_username" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_USERNAME'); ?> </td> <td> <input type="text" name="ftp_user" value="<?php echo $this->ftp['username']; ?>" /> </td> </tr> <tr id="row_ftp_password" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_PASSWORD'); ?> </td> <td> <input type="password" name="ftp_pass" value="<?php echo $this->ftp['password']; ?>" /> </td> </tr> <tr id="row_ftp_directory" <?php echo $this->ftpFieldsDisplay; ?>> <td> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_FTP_DIRECTORY'); ?> </td> <td> <input type="text" name="ftp_root" value="<?php echo $this->ftp['directory']; ?>" /> </td> </tr> </tbody> <tfoot> <tr> <td> </td> <td> <button class="btn btn-warning" type="submit"> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_INSTALLAGAIN'); ?> </button> </td> </tr> </tfoot> </table> <?php endif; ?> </fieldset> PK �|!]�d%� , views/default/tmpl/default_updatemefirst.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; /** @var JoomlaupdateViewDefault $this */ ?> <fieldset> <legend> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_DEFAULT_NO_LIVE_UPDATE'); ?> </legend> <p> <?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_DEFAULT_NO_LIVE_UPDATE_DESC'); ?> </p> </fieldset> PK �|!]��� � views/default/tmpl/complete.phpnu &1i� <?php /** * @package Joomla.Administrator * @subpackage com_joomlaupdate * * @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; ?> <fieldset> <legend> <?php echo JText::_('COM_JOOMLAUPDATE_VIEW_COMPLETE_HEADING'); ?> </legend> <p class="alert alert-success"> <?php echo JText::sprintf('COM_JOOMLAUPDATE_VIEW_COMPLETE_MESSAGE', JVERSION); ?> </p> </fieldset> <form action="<?php echo JRoute::_('index.php?option=com_joomlaupdate'); ?>" method="post" id="adminForm"> <input type="hidden" name="task" value="" /> <?php echo JHtml::_('form.token'); ?> </form> PK �|!] ��� � '