| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Plugins\CoreUpdater; |
|
|
| use Exception; |
| use Piwik\AssetManager; |
| use Piwik\Common; |
| use Piwik\Config; |
| use Piwik\DataTable\Renderer\Json; |
| use Piwik\DbHelper; |
| use Piwik\Development; |
| use Piwik\Filechecks; |
| use Piwik\FileIntegrity; |
| use Piwik\Filesystem; |
| use Piwik\Nonce; |
| use Piwik\Option; |
| use Piwik\Piwik; |
| use Piwik\Plugin\Manager as PluginManager; |
| use Piwik\Plugins\CoreVue\CoreVue; |
| use Piwik\Plugins\Marketplace\Plugins; |
| use Piwik\Request; |
| use Piwik\SettingsPiwik; |
| use Piwik\SettingsServer; |
| use Piwik\Updater as DbUpdater; |
| use Piwik\Updater\Migration\Db as DbMigration; |
| use Piwik\Url; |
| use Piwik\Version; |
| use Piwik\View; |
| use Piwik\View\OneClickDone; |
|
|
| class Controller extends \Piwik\Plugin\Controller |
| { |
| private $coreError = false; |
| private $warningMessages = array(); |
| private $errorMessages = array(); |
| private $deactivatedPlugins = array(); |
|
|
| |
| |
| |
| private $updater; |
|
|
| |
| |
| |
| private $marketplacePlugins; |
|
|
| public function __construct(Updater $updater, ?Plugins $marketplacePlugins = null) |
| { |
| $this->updater = $updater; |
| $this->marketplacePlugins = $marketplacePlugins; |
|
|
| parent::__construct(); |
| } |
|
|
| |
| |
| |
| |
| |
| public function getUpdaterCss() |
| { |
| Common::sendHeader('Content-Type: text/css'); |
| Common::sendHeader('Cache-Control: max-age=' . (60 * 60)); |
|
|
| $files = array( |
| 'plugins/Morpheus/stylesheets/base/bootstrap.css', |
| 'plugins/Morpheus/stylesheets/base/icons.css', |
| "node_modules/jquery-ui-dist/jquery-ui.theme.min.css", |
| "node_modules/jquery-ui-dist/jquery-ui.structure.min.css", |
| 'node_modules/@materializecss/materialize/dist/css/materialize.min.css', |
| 'plugins/Morpheus/stylesheets/base.less', |
| 'plugins/Morpheus/stylesheets/general/_forms.less', |
| 'plugins/Morpheus/stylesheets/simple_structure.css', |
| 'plugins/CoreHome/stylesheets/jquery.ui.autocomplete.css', |
| 'plugins/Dashboard/stylesheets/dashboard.less', |
| 'plugins/CoreUpdater/stylesheets/updateLayout.css', |
| ); |
|
|
| return AssetManager::compileCustomStylesheets($files); |
| } |
|
|
| |
| |
| |
| |
| |
| public function getUpdaterJs() |
| { |
| Common::sendHeader('Content-Type: application/javascript; charset=UTF-8'); |
| Common::sendHeader('Cache-Control: max-age=' . (60 * 60)); |
|
|
| $files = array( |
| "node_modules/jquery/dist/jquery.min.js", |
| "node_modules/jquery-ui-dist/jquery-ui.min.js", |
| 'node_modules/@materializecss/materialize/dist/js/materialize.min.js', |
| "plugins/CoreHome/javascripts/materialize-bc.js", |
| 'plugins/Morpheus/javascripts/piwikHelper.js', |
| "plugins/CoreHome/javascripts/broadcast.js", |
| 'plugins/CoreUpdater/javascripts/updateLayout.js', |
| 'plugins/Installation/javascripts/installation.js', |
| ); |
|
|
| CoreVue::addJsFilesTo($files); |
|
|
| $coreHomeUmd = Development::isEnabled() ? 'CoreHome.umd.js' : 'CoreHome.umd.min.js'; |
| $files[] = "plugins/CoreHome/vue/dist/$coreHomeUmd"; |
|
|
| return AssetManager::compileCustomJs($files); |
| } |
|
|
| public function newVersionAvailable() |
| { |
| Piwik::checkUserHasSuperUserAccess(); |
|
|
| if (!SettingsPiwik::isAutoUpdateEnabled()) { |
| throw new Exception('Auto updater is disabled'); |
| } |
|
|
| $this->checkNewVersionIsAvailableOrDie(); |
|
|
| $newVersion = $this->updater->getLatestVersion(); |
|
|
| $view = new View('@CoreUpdater/newVersionAvailable'); |
| $this->addCustomLogoInfo($view); |
| $this->setBasicVariablesView($view); |
|
|
| $view->piwik_version = Version::VERSION; |
| $view->piwik_new_version = $newVersion; |
|
|
| $incompatiblePlugins = $this->getIncompatiblePlugins($newVersion); |
|
|
| $marketplacePlugins = array(); |
| try { |
| if (!empty($incompatiblePlugins) && $this->marketplacePlugins) { |
| $marketplacePlugins = $this->marketplacePlugins->getAllAvailablePluginNames(); |
| } |
| } catch (\Exception $e) { |
| } |
|
|
| $view->marketplacePlugins = $marketplacePlugins; |
| $view->incompatiblePlugins = $incompatiblePlugins; |
| $view->piwik_latest_version_url = $this->updater->getArchiveUrl($newVersion); |
| $view->can_auto_update = Filechecks::canAutoUpdate(); |
| $view->makeWritableCommands = Filechecks::getAutoUpdateMakeWritableMessage(); |
| $view->nonce = Nonce::getNonce('oneClickUpdate'); |
|
|
| return $view->render(); |
| } |
|
|
| public function oneClickUpdate() |
| { |
| Piwik::checkUserHasSuperUserAccess(); |
|
|
| if (!SettingsPiwik::isAutoUpdateEnabled()) { |
| throw new Exception('Auto updater is disabled'); |
| } |
|
|
| Nonce::checkNonce('oneClickUpdate'); |
|
|
| $view = new OneClickDone(Piwik::getCurrentUserTokenAuth()); |
|
|
| $useHttps = Common::getRequestVar('https', 1, 'int'); |
|
|
| try { |
| $messages = $this->updater->updatePiwik($useHttps); |
| $this->refreshUpdateDetailsToken(); |
| } catch (ArchiveDownloadException $e) { |
| $view->httpsFail = $useHttps; |
| $view->error = $e->getMessage(); |
| $messages = $e->getUpdateLogMessages(); |
| } catch (UpdaterException $e) { |
| $view->error = $e->getMessage(); |
| $messages = $e->getUpdateLogMessages(); |
| } |
|
|
| $view->feedbackMessages = $messages; |
| $result = $view->render(); |
|
|
| return $result; |
| } |
|
|
| public function oneClickUpdatePartTwo() |
| { |
| if (!SettingsPiwik::isAutoUpdateEnabled()) { |
| throw new Exception('Auto updater is disabled'); |
| } |
|
|
| Json::sendHeaderJSON(); |
|
|
| $task = "Couldn't update Marketplace plugins."; |
|
|
| $nonce = Common::getRequestVar('nonce', '', 'string'); |
| if (empty($nonce)) { |
| return json_encode(['No token. ' . $task]); |
| } |
| $value = Option::get('NonceOneClickUpdatePartTwo'); |
| if (empty($value)) { |
| return json_encode(['Invalid token. ' . $task]); |
| } |
| $value = json_decode($value, true); |
|
|
| if ( |
| empty($value['nonce']) |
| || empty($value['ttl']) |
| || time() > (int) $value['ttl'] |
| || $nonce !== $value['nonce'] |
| ) { |
| return json_encode(['Invalid nonce or nonce expired. ' . $task]); |
| } |
|
|
| try { |
| $messages = $this->updater->oneClickUpdatePartTwo(); |
| } catch (UpdaterException $e) { |
| $messages = $e->getUpdateLogMessages(); |
| $messages[] = $e->getMessage(); |
| } catch (Exception $e) { |
| $messages = [$e->getMessage()]; |
| } |
|
|
| return json_encode($messages); |
| } |
|
|
| public function oneClickResults() |
| { |
| if (!SettingsPiwik::isAutoUpdateEnabled()) { |
| throw new Exception('Auto updater is disabled'); |
| } |
|
|
| $httpsFail = (bool) Common::getRequestVar('httpsFail', 0, 'int', $_POST); |
| $error = Common::getRequestVar('error', '', 'string', $_POST); |
|
|
| if ($httpsFail) { |
| $view = new View('@CoreUpdater/updateHttpsError'); |
| $view->nonce = Nonce::getNonce('oneClickUpdate'); |
| $view->error = $error; |
| } elseif ($error) { |
| $view = new View('@CoreUpdater/updateHttpError'); |
| $view->error = $error; |
| } else { |
| $updateDetailsToken = null; |
| if (Piwik::hasUserSuperUserAccess()) { |
| $updateDetailsToken = $this->getUpdateDetailsToken(); |
| if ($updateDetailsToken === null) { |
| $updateDetailsToken = $this->refreshUpdateDetailsToken(); |
| } |
| } |
| $runUpdaterUrl = Url::getCurrentUrlWithoutQueryString(); |
|
|
| if ($updateDetailsToken !== null) { |
| $runUpdaterUrl .= '?module=CoreUpdater&updateDetailsToken=' . urlencode($updateDetailsToken); |
| } |
|
|
| $view = new View('@CoreUpdater/updateSuccess'); |
| $view->runUpdaterUrl = $runUpdaterUrl; |
| } |
|
|
| $messages = safe_unserialize(Request::fromPost()->getStringParameter('messages', '')); |
| if (!is_array($messages)) { |
| $messages = array(); |
| } |
| $view->feedbackMessages = $messages; |
|
|
| $this->addCustomLogoInfo($view); |
| $this->setBasicVariablesView($view); |
| return $view->render(); |
| } |
|
|
| protected function redirectToDashboardWhenNoError(DbUpdater $updater) |
| { |
| if ( |
| count($updater->getSqlQueriesToExecute()) == 1 |
| && !$this->coreError |
| && empty($this->warningMessages) |
| && empty($this->errorMessages) |
| && empty($this->deactivatedPlugins) |
| ) { |
| Piwik::redirectToModule('CoreHome'); |
| } |
| } |
|
|
| private function checkNewVersionIsAvailableOrDie() |
| { |
| if (!$this->updater->isNewVersionAvailable()) { |
| throw new Exception(Piwik::translate('CoreUpdater_ExceptionAlreadyLatestVersion', Version::VERSION)); |
| } |
| } |
|
|
| public function index() |
| { |
| try { |
| return $this->runUpdaterAndExit(); |
| } catch (NoUpdatesFoundException $e) { |
| Piwik::redirectToModule('CoreHome'); |
| } |
| } |
|
|
| public function runUpdaterAndExit($doDryRun = null) |
| { |
| if (!SettingsPiwik::isAutoUpdateEnabled()) { |
| throw new Exception('Auto updater is disabled'); |
| } |
|
|
| $updater = new DbUpdater(); |
| $componentsWithUpdateFile = $updater->getComponentUpdates(); |
|
|
| if (empty($componentsWithUpdateFile)) { |
| |
| $this->removeUpdateDetailsToken(); |
|
|
| throw new NoUpdatesFoundException("Everything is already up to date."); |
| } |
|
|
| SettingsServer::setMaxExecutionTime(0); |
|
|
| $welcomeTemplate = '@CoreUpdater/runUpdaterAndExit_welcome'; |
| $doneTemplate = '@CoreUpdater/runUpdaterAndExit_done'; |
|
|
| $viewWelcome = new View($welcomeTemplate); |
| $this->addCustomLogoInfo($viewWelcome); |
| $this->setBasicVariablesView($viewWelcome); |
|
|
| $viewDone = new View($doneTemplate); |
| $this->addCustomLogoInfo($viewDone); |
| $this->setBasicVariablesView($viewDone); |
|
|
| $doExecuteUpdates = Request::fromRequest()->getBoolParameter('updateCorePlugins', false); |
|
|
| if (is_null($doDryRun)) { |
| $doDryRun = !$doExecuteUpdates; |
| } |
|
|
| if ($doDryRun) { |
| $migrations = $updater->getSqlQueriesToExecute(); |
| $queryCount = count($migrations); |
| $migrations = $this->groupMigrations($migrations); |
|
|
| $viewWelcome->migrations = $migrations; |
| $viewWelcome->queryCount = $queryCount; |
| $viewWelcome->isMajor = $updater->hasMajorDbUpdate(); |
| $viewWelcome->showUpdateDetails = $this->checkUpdateDetailsToken(); |
|
|
| $this->doWelcomeUpdates($viewWelcome, $componentsWithUpdateFile); |
|
|
| return $viewWelcome->render(); |
| } |
|
|
| |
| if ($doExecuteUpdates) { |
| $this->warningMessages = []; |
|
|
| $this->doExecuteUpdates($viewDone, $updater, $componentsWithUpdateFile); |
| $this->removeUpdateDetailsToken(); |
| $this->redirectToDashboardWhenNoError($updater); |
|
|
| return $viewDone->render(); |
| } |
|
|
| exit; |
| } |
|
|
| private function groupMigrations($migrations) |
| { |
| $result = []; |
|
|
| $group = null; |
| foreach ($migrations as $migration) { |
| $type = $migration instanceof DbMigration ? 'sql' : 'command'; |
| if ( |
| $group === null |
| || $type != $group['type'] |
| ) { |
| $group = [ |
| 'type' => $type, |
| 'migrations' => [], |
| ]; |
| $result[] = $group; |
| } |
|
|
| $result[count($result) - 1]['migrations'][] = $migration; |
| } |
|
|
| return $result; |
| } |
|
|
| private function doWelcomeUpdates($view, $componentsWithUpdateFile) |
| { |
| $view->new_piwik_version = Version::VERSION; |
| $view->commandUpgradePiwik = "php " . Filesystem::getPathToPiwikRoot() . "/console core:update"; |
|
|
| $instanceId = SettingsPiwik::getPiwikInstanceId(); |
|
|
| if ($instanceId) { |
| $view->commandUpgradePiwik .= ' --matomo-domain="' . $instanceId . '"'; |
| } |
|
|
| $pluginNamesToUpdate = array(); |
| $dimensionsToUpdate = array(); |
| $coreToUpdate = false; |
|
|
| |
| if (!DbHelper::isInstalled()) { |
| $this->errorMessages[] = Piwik::translate('CoreUpdater_EmptyDatabaseError', Config::getInstance()->database['dbname']); |
| $this->coreError = true; |
| $currentVersion = 'N/A'; |
| } else { |
| $this->errorMessages = array(); |
| try { |
| $currentVersion = Option::get('version_core'); |
| } catch (Exception $e) { |
| $currentVersion = '<= 0.2.9'; |
| } |
|
|
| foreach ($componentsWithUpdateFile as $name => $filenames) { |
| if ($name == 'core') { |
| $coreToUpdate = true; |
| } elseif (0 === strpos($name, 'log_')) { |
| $dimensionsToUpdate[] = $name; |
| } else { |
| $pluginNamesToUpdate[] = $name; |
| } |
| } |
| } |
|
|
| |
| [$success, $messages] = FileIntegrity::getFileIntegrityInformation(); |
|
|
| if (!$success) { |
| $this->warningMessages[] = Piwik::translate('General_FileIntegrityWarning'); |
| } |
| if (count($messages) > 0) { |
| $this->warningMessages = array_merge($this->warningMessages, $messages); |
| } |
| Filesystem::deleteAllCacheOnUpdate(); |
|
|
| sort($dimensionsToUpdate); |
|
|
| $view->coreError = $this->coreError; |
| $view->warningMessages = $this->warningMessages; |
| $view->errorMessages = $this->errorMessages; |
| $view->current_piwik_version = $currentVersion; |
| $view->pluginNamesToUpdate = $pluginNamesToUpdate; |
| $view->dimensionsToUpdate = $dimensionsToUpdate; |
| $view->coreToUpdate = $coreToUpdate; |
| } |
|
|
| private function doExecuteUpdates($view, DbUpdater $updater, $componentsWithUpdateFile) |
| { |
| $result = $updater->updateComponents($componentsWithUpdateFile); |
|
|
| $this->coreError = $result['coreError']; |
| $this->warningMessages = $result['warnings']; |
| $this->errorMessages = $result['errors']; |
| $this->deactivatedPlugins = $result['deactivatedPlugins']; |
| $view->coreError = $this->coreError; |
| $view->warningMessages = $this->warningMessages; |
| $view->errorMessages = $this->errorMessages; |
| $view->deactivatedPlugins = $this->deactivatedPlugins; |
| } |
|
|
| private function getIncompatiblePlugins($piwikVersion) |
| { |
| return PluginManager::getInstance()->getIncompatiblePlugins($piwikVersion); |
| } |
|
|
| private function checkUpdateDetailsToken(): bool |
| { |
| $config = Config::getInstance(); |
|
|
| if (empty($config->General['update_details_token'])) { |
| return false; |
| } |
|
|
| return $config->General['update_details_token'] === Request::fromRequest()->getStringParameter('updateDetailsToken', ''); |
| } |
|
|
| private function refreshUpdateDetailsToken(): string |
| { |
| $config = Config::getInstance(); |
| $token = Common::generateUniqId(); |
|
|
| $config->General['update_details_token'] = $token; |
| $config->forceSave(); |
|
|
| return $token; |
| } |
|
|
| private function getUpdateDetailsToken(): ?string |
| { |
| $config = Config::getInstance(); |
|
|
| if (empty($config->General['update_details_token'])) { |
| return null; |
| } |
|
|
| return $config->General['update_details_token']; |
| } |
|
|
| private function removeUpdateDetailsToken(): void |
| { |
| $config = Config::getInstance(); |
|
|
| if (empty($config->General['update_details_token'])) { |
| return; |
| } |
|
|
| unset($config->General['update_details_token']); |
|
|
| $config->forceSave(); |
| } |
| } |
|
|