1
0
قرینه از https://github.com/matomo-org/matomo.git synced 2025-08-21 22:47:43 +00:00
Files
matomo/plugins/CoreUpdater/Controller.php
Marc Neudert 200584887c Require token to display migration details during core update (#23318)
* Require token to display migration details during core update

* Update expected screenshot

* apply review feedback

---------

Co-authored-by: Matt <1169490+caddoo@users.noreply.github.com>
Co-authored-by: Stefan Giehl <stefan@matomo.org>
2025-05-26 15:46:11 +02:00

506 خطوط
16 KiB
PHP

<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
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();
/**
* @var Updater
*/
private $updater;
/**
* @var Plugins
*/
private $marketplacePlugins;
public function __construct(Updater $updater, ?Plugins $marketplacePlugins = null)
{
$this->updater = $updater;
$this->marketplacePlugins = $marketplacePlugins;
parent::__construct();
}
/**
* Return the base.less compiled to css
*
* @return string
*/
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);
}
/**
* Return the base.less compiled to css
*
* @return string
*/
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);
} 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 = $this->createUpdateDetailsTokenIfMissing();
$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)) {
// remove token if updater screen is not displayed
$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();
}
// Web
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;
// handle case of existing database with no tables
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;
}
}
}
// check file integrity
[$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 createUpdateDetailsTokenIfMissing(): ?string
{
$config = Config::getInstance();
if (!empty($config->General['update_details_token'])) {
return null;
}
$token = Common::generateUniqId();
$config->General['update_details_token'] = $token;
$config->forceSave();
return $token;
}
private function removeUpdateDetailsToken(): void
{
$config = Config::getInstance();
if (empty($config->General['update_details_token'])) {
return;
}
unset($config->General['update_details_token']);
$config->forceSave();
}
}