| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Plugins\CorePluginsAdmin; |
|
|
| use Exception; |
| use Piwik\Access; |
| use Piwik\API\Request; |
| use Piwik\Common; |
| use Piwik\Container\StaticContainer; |
| use Piwik\Exception\MissingFilePermissionException; |
| use Piwik\ExceptionHandler; |
| use Piwik\Filechecks; |
| use Piwik\Filesystem; |
| use Piwik\Nonce; |
| use Piwik\Notification; |
| use Piwik\Piwik; |
| use Piwik\Plugin; |
| use Piwik\Plugins\CorePluginsAdmin\Model\TagManagerTeaser; |
| use Piwik\Plugins\Login\PasswordVerifier; |
| use Piwik\Plugins\Marketplace\Marketplace; |
| use Piwik\Plugins\Marketplace\Controller as MarketplaceController; |
| use Piwik\Plugins\Marketplace\Plugins; |
| use Piwik\SettingsPiwik; |
| use Piwik\SettingsServer; |
| use Piwik\Translation\Translator; |
| use Piwik\Url; |
| use Piwik\Version; |
| use Piwik\View; |
|
|
| class Controller extends Plugin\ControllerAdmin |
| { |
| public const ACTIVATE_NONCE = 'CorePluginsAdmin.activatePlugin'; |
| public const DEACTIVATE_NONCE = 'CorePluginsAdmin.deactivatePlugin'; |
| public const UNINSTALL_NONCE = 'CorePluginsAdmin.uninstallPlugin'; |
|
|
| |
| |
| |
| private $translator; |
|
|
| |
| |
| |
| private $settingsProvider; |
|
|
| |
| |
| |
| private $pluginInstaller; |
| |
| |
| |
| private $pluginManager; |
|
|
| |
| |
| |
| private $marketplacePlugins; |
|
|
| |
| |
| |
| private $passwordVerify; |
|
|
| |
| |
| |
| |
| public function __construct( |
| Translator $translator, |
| Plugin\SettingsProvider $settingsProvider, |
| PluginInstaller $pluginInstaller, |
| PasswordVerifier $passwordVerify, |
| $marketplacePlugins = null |
| ) { |
| $this->translator = $translator; |
| $this->settingsProvider = $settingsProvider; |
| $this->pluginInstaller = $pluginInstaller; |
| $this->pluginManager = Plugin\Manager::getInstance(); |
| $this->passwordVerify = $passwordVerify; |
|
|
| if (!empty($marketplacePlugins)) { |
| $this->marketplacePlugins = $marketplacePlugins; |
| } elseif (Marketplace::isMarketplaceEnabled()) { |
| |
| $this->marketplacePlugins = StaticContainer::get('Piwik\Plugins\Marketplace\Plugins'); |
| } |
|
|
| parent::__construct(); |
| } |
|
|
| public function uploadPlugin() |
| { |
| static::dieIfPluginsAdminIsDisabled(); |
| Piwik::checkUserHasSuperUserAccess(); |
|
|
| if (!CorePluginsAdmin::isPluginUploadEnabled()) { |
| throw new \Exception('Plugin upload disabled by config'); |
| } |
|
|
| Nonce::checkNonce(MarketplaceController::INSTALL_NONCE); |
|
|
| if ( |
| !$this->passwordVerify->isPasswordCorrect( |
| Piwik::getCurrentUserLogin(), |
| \Piwik\Request::fromRequest()->getStringParameter('confirmPassword') |
| ) |
| ) { |
| throw new \Exception($this->translator->translate('Login_LoginPasswordNotCorrect')); |
| } |
|
|
| if (empty($_FILES['pluginZip'])) { |
| throw new \Exception('You did not specify a ZIP file.'); |
| } |
|
|
| if (!empty($_FILES['pluginZip']['error'])) { |
| throw new \Exception('Something went wrong during the plugin file upload. Please try again.'); |
| } |
|
|
| $file = $_FILES['pluginZip']['tmp_name']; |
| if (!file_exists($file)) { |
| throw new \Exception('Something went wrong during the plugin file upload. Please try again.'); |
| } |
|
|
| $view = $this->configureView('@CorePluginsAdmin/uploadPlugin'); |
|
|
| $pluginMetadata = $this->pluginInstaller->installOrUpdatePluginFromFile($file); |
|
|
| $view->nonce = Nonce::getNonce(static::ACTIVATE_NONCE); |
| $view->plugin = array( |
| 'name' => $pluginMetadata->name, |
| 'version' => $pluginMetadata->version, |
| 'isTheme' => !empty($pluginMetadata->theme), |
| 'isActivated' => $this->pluginManager->isPluginActivated($pluginMetadata->name), |
| ); |
|
|
| return $view->render(); |
| } |
|
|
| public function tagManagerTeaser() |
| { |
| $this->dieIfPluginsAdminIsDisabled(); |
| Piwik::checkUserHasSomeAdminAccess(); |
|
|
| $tagManagerTeaser = new TagManagerTeaser(Piwik::getCurrentUserLogin()); |
|
|
| if (!$tagManagerTeaser->shouldShowTeaser()) { |
| $this->redirectToIndex('CoreHome', 'index'); |
| return; |
| } |
|
|
| $nonce = ''; |
| if (Piwik::hasUserSuperUserAccess()) { |
| $nonce = Nonce::getNonce(static::ACTIVATE_NONCE); |
| } |
|
|
| $view = new View('@CorePluginsAdmin/tagManagerTeaser'); |
| $this->setGeneralVariablesView($view); |
| $view->contactEmail = implode(',', Piwik::getContactEmailAddresses()); |
| $view->nonce = $nonce; |
| return $view->render(); |
| } |
|
|
| public function disableActivateTagManagerPage() |
| { |
| $this->dieIfPluginsAdminIsDisabled(); |
| Piwik::checkUserHasSomeAdminAccess(); |
|
|
| $tagManagerTeaser = new TagManagerTeaser(Piwik::getCurrentUserLogin()); |
|
|
| if (Piwik::hasUserSuperUserAccess()) { |
| $tagManagerTeaser->disableGlobally(); |
| } else { |
| $tagManagerTeaser->disableForUser(); |
| } |
|
|
| $date = Common::getRequestVar('date', false); |
| $this->redirectToIndex('CoreHome', 'index', $websiteId = null, $defaultPeriod = null, $date); |
| } |
|
|
| private function dieIfPluginsAdminIsDisabled() |
| { |
| Piwik::checkUserIsNotAnonymous(); |
| if (!CorePluginsAdmin::isPluginsAdminEnabled()) { |
| throw new \Exception('Enabling, disabling and uninstalling plugins has been disabled by Piwik admins. |
| Please contact your Piwik admins with your request so they can assist you.'); |
| } |
| } |
|
|
| private function createPluginsOrThemesView($template, $themesOnly) |
| { |
| Piwik::checkUserHasSuperUserAccess(); |
|
|
| $view = $this->configureView('@CorePluginsAdmin/' . $template); |
|
|
| $this->securityPolicy->addPolicy('img-src', '*.matomo.org'); |
| $this->securityPolicy->addPolicy('default-src', '*.matomo.org'); |
|
|
| $view->updateNonce = Nonce::getNonce(MarketplaceController::UPDATE_NONCE); |
| $view->activateNonce = Nonce::getNonce(static::ACTIVATE_NONCE); |
| $view->uninstallNonce = Nonce::getNonce(static::UNINSTALL_NONCE); |
| $view->deactivateNonce = Nonce::getNonce(static::DEACTIVATE_NONCE); |
| $view->pluginsInfo = $this->getPluginsInfo($themesOnly); |
|
|
| $users = Request::processRequest('UsersManager.getUsers', array('filter_limit' => '-1')); |
| $view->otherUsersCount = count($users) - 1; |
| $view->themeEnabled = $this->pluginManager->getThemeEnabled()->getPluginName(); |
|
|
| $view->pluginNamesHavingSettings = array_keys($this->settingsProvider->getAllSystemSettings()); |
| $view->isMarketplaceEnabled = Marketplace::isMarketplaceEnabled(); |
| $view->isPluginsAdminEnabled = CorePluginsAdmin::isPluginsAdminEnabled(); |
|
|
| $view->marketplacePluginNames = []; |
| $view->pluginsHavingUpdate = []; |
| $view->pluginUpdateNonces = []; |
|
|
| if (Marketplace::isMarketplaceEnabled() && $this->marketplacePlugins) { |
| try { |
| $view->marketplacePluginNames = $this->marketplacePlugins->getAvailablePluginNames($themesOnly); |
| $view->pluginsHavingUpdate = $this->marketplacePlugins->getPluginsHavingUpdate(); |
| foreach ($view->pluginsHavingUpdate as $name => $plugin) { |
| $view->pluginUpdateNonces[$name] = Nonce::getNonce($plugin['name']); |
| } |
| } catch (Exception $e) { |
| |
| } |
| } |
|
|
| $view->isPluginUploadEnabled = CorePluginsAdmin::isPluginUploadEnabled(); |
| $view->uploadLimit = SettingsServer::getPostMaxUploadSize(); |
| $view->installNonce = Nonce::getNonce(MarketplaceController::INSTALL_NONCE); |
|
|
| return $view; |
| } |
|
|
| public function plugins() |
| { |
| $view = $this->createPluginsOrThemesView('plugins', $themesOnly = false); |
| return $view->render(); |
| } |
|
|
| public function themes() |
| { |
| $view = $this->createPluginsOrThemesView('themes', $themesOnly = true); |
| return $view->render(); |
| } |
|
|
| protected function configureView($template) |
| { |
| Piwik::checkUserIsNotAnonymous(); |
|
|
| $view = new View($template); |
| $this->setBasicVariablesView($view); |
|
|
| |
| if (CorePluginsAdmin::isPluginsAdminEnabled()) { |
| $this->displayWarningIfConfigFileNotWritable(); |
| } |
|
|
| $view->errorMessage = ''; |
|
|
| return $view; |
| } |
|
|
| protected function getPluginsInfo($themesOnly = false) |
| { |
| $plugins = $this->pluginManager->loadAllPluginsAndGetTheirInfo(); |
|
|
| foreach ($plugins as $pluginName => &$plugin) { |
| $plugin['isCorePlugin'] = $this->pluginManager->isPluginBundledWithCore($pluginName); |
| $plugin['isOfficialPlugin'] = false; |
|
|
| if (isset($plugin['info']) && isset($plugin['info']['authors'])) { |
| foreach ($plugin['info']['authors'] as $author) { |
| if (in_array(strtolower($author['name']), array('piwik', 'innocraft', 'matomo', 'matomo-org'))) { |
| $plugin['isOfficialPlugin'] = true; |
| break; |
| } |
| } |
| } |
|
|
| if (!empty($plugin['info']['description'])) { |
| $plugin['info']['description'] = $this->translator->translate($plugin['info']['description']); |
| } |
|
|
| if (!isset($plugin['info'])) { |
| $suffix = $this->translator->translate('CorePluginsAdmin_PluginNotWorkingAlternative'); |
| |
| [$pluginNameRenamed, $methodName] = Request::getRenamedModuleAndAction($pluginName, 'index'); |
| if ($pluginName != $pluginNameRenamed) { |
| $suffix = "You may uninstall the plugin or manually delete the files in /path/to/matomo/plugins/$pluginName/"; |
| } |
|
|
| if ($this->pluginManager->isPluginInFilesystem($pluginName)) { |
| $description = '<strong>' |
| . $this->translator->translate( |
| 'CorePluginsAdmin_PluginNotCompatibleWith', |
| array($pluginName, self::getPiwikVersion()) |
| ) |
| . '</strong><br/>' |
| . $suffix; |
| } else { |
| $description = $this->translator->translate( |
| 'CorePluginsAdmin_PluginNotFound', |
| array($pluginName) |
| ) |
| . "\n" |
| . $this->translator->translate('CorePluginsAdmin_PluginNotFoundAlternative'); |
| } |
| $plugin['info'] = array( |
| 'description' => $description, |
| 'version' => $this->translator->translate('General_Unknown'), |
| 'theme' => false, |
| ); |
| } |
| } |
|
|
| $pluginsFiltered = $this->keepPluginsOrThemes($themesOnly, $plugins); |
| return $pluginsFiltered; |
| } |
|
|
| protected function keepPluginsOrThemes($themesOnly, $plugins) |
| { |
| $pluginsFiltered = array(); |
| foreach ($plugins as $name => $thisPlugin) { |
| $isTheme = false; |
| if (!empty($thisPlugin['info']['theme'])) { |
| $isTheme = (bool)$thisPlugin['info']['theme']; |
| } |
| if ( |
| ($themesOnly && $isTheme) |
| || (!$themesOnly && !$isTheme) |
| ) { |
| $pluginsFiltered[$name] = $thisPlugin; |
| } |
| } |
| return $pluginsFiltered; |
| } |
|
|
| public function safemode($lastError = array()) |
| { |
| if (ob_get_length()) { |
| ob_clean(); |
| } |
|
|
| $this->tryToRepairPiwik(); |
|
|
| if (empty($lastError) && defined('PIWIK_TEST_MODE') && PIWIK_TEST_MODE) { |
| $lastError = array( |
| 'message' => Common::getRequestVar('error_message', null, 'string'), |
| 'file' => Common::getRequestVar('error_file', null, 'string'), |
| 'line' => Common::getRequestVar('error_line', null, 'integer'), |
| ); |
| } elseif (empty($lastError)) { |
| throw new Exception('Safemode not available'); |
| } |
|
|
| $outputFormat = Common::getRequestVar('format', 'html', 'string'); |
| $outputFormat = strtolower($outputFormat); |
|
|
| if (!empty($outputFormat) && 'html' !== $outputFormat) { |
| $errorMessage = $lastError['message']; |
|
|
| if ( |
| !empty($lastError['backtrace']) |
| && ExceptionHandler::shouldPrintBackTraceWithMessage() |
| ) { |
| $errorMessage .= $lastError['backtrace']; |
| } |
|
|
| if (Piwik::isUserIsAnonymous()) { |
| $errorMessage = 'A fatal error occurred.'; |
| } |
|
|
| $response = new \Piwik\API\ResponseBuilder($outputFormat, [], false); |
| $message = $response->getResponseException(new Exception($errorMessage)); |
|
|
| return $message; |
| } |
|
|
| if (Common::isPhpCliMode()) { |
| throw new Exception("Error: " . var_export($lastError, true)); |
| } |
|
|
| if (!ExceptionHandler::shouldPrintBackTraceWithMessage()) { |
| unset($lastError['backtrace']); |
| } |
|
|
| $view = new View('@CorePluginsAdmin/safemode'); |
| $view->lastError = $lastError; |
| $view->isAllowedToTroubleshootAsSuperUser = $this->isAllowedToTroubleshootAsSuperUser(); |
| $view->isSuperUser = Piwik::hasUserSuperUserAccess(); |
| $view->isAnonymousUser = Piwik::isUserIsAnonymous(); |
| $view->plugins = $this->pluginManager->loadAllPluginsAndGetTheirInfo(); |
| $view->deactivateNonce = Nonce::getNonce(static::DEACTIVATE_NONCE); |
| $view->deactivateIAmSuperUserSalt = Common::getRequestVar('i_am_super_user', '', 'string'); |
| $view->uninstallNonce = Nonce::getNonce(static::UNINSTALL_NONCE); |
| $view->contactEmail = implode(',', Piwik::getContactEmailAddresses()); |
| $view->piwikVersion = Version::VERSION; |
| $view->showVersion = !Common::getRequestVar('tests_hide_piwik_version', 0); |
| $view->pluginCausesIssue = ''; |
|
|
| |
| |
| $view->disableCacheBuster(); |
|
|
| if (!empty($lastError['file'])) { |
| preg_match('/piwik\/plugins\/(.*)\//', $lastError['file'], $matches); |
|
|
| if (!empty($matches[1])) { |
| $view->pluginCausesIssue = $matches[1]; |
| } |
| } |
|
|
| return $view->render(); |
| } |
|
|
| public function activate($redirectAfter = true) |
| { |
| $this->dieIfPluginsAdminIsDisabled(); |
|
|
| $params = [ |
| 'module' => 'CorePluginsAdmin', |
| 'action' => 'activate', |
| 'pluginName' => Common::getRequestVar('pluginName'), |
| 'nonce' => Common::getRequestVar('nonce'), |
| 'redirectTo' => Common::getRequestVar('redirectTo', '', 'string'), |
| 'referrer' => urlencode(Url::getReferrer()), |
| ]; |
|
|
| if (!$this->passwordVerify->requirePasswordVerifiedRecently($params)) { |
| return; |
| } |
|
|
| $pluginName = $this->initPluginModification(static::ACTIVATE_NONCE); |
|
|
| $this->pluginManager->activatePlugin($pluginName); |
|
|
| if ($redirectAfter) { |
| $message = $this->translator->translate('CorePluginsAdmin_SuccessfullyActicated', array($pluginName)); |
|
|
| if ($this->settingsProvider->getSystemSettings($pluginName)) { |
| $target = sprintf( |
| '<a href="index.php%s#%s">', |
| Url::getCurrentQueryStringWithParametersModified(array('module' => 'CoreAdminHome', 'action' => 'generalSettings')), |
| $pluginName |
| ); |
| $message .= ' ' . $this->translator->translate('CorePluginsAdmin_ChangeSettingsPossible', array($target, '</a>')); |
| } |
|
|
| $notification = new Notification($message); |
| $notification->raw = true; |
| $notification->title = $this->translator->translate('General_WellDone'); |
| $notification->context = Notification::CONTEXT_SUCCESS; |
| Notification\Manager::notify('CorePluginsAdmin_PluginActivated', $notification); |
|
|
| $redirectTo = Common::getRequestVar('redirectTo', '', 'string'); |
| if (!empty($redirectTo) && $redirectTo === 'marketplace') { |
| $this->redirectToIndex('Marketplace', 'overview'); |
| } elseif (!empty($redirectTo) && $redirectTo === 'tagmanager') { |
| $this->redirectToIndex('TagManager', 'manageContainers'); |
| } elseif (!empty($redirectTo) && $redirectTo === 'referrer') { |
| $this->redirectAfterModification($redirectAfter); |
| } else { |
| $plugin = $this->pluginManager->loadPlugin($pluginName); |
|
|
| $actionToRedirect = 'plugins'; |
| if ($plugin->isTheme()) { |
| $actionToRedirect = 'themes'; |
| } |
|
|
| $this->redirectToIndex('CorePluginsAdmin', $actionToRedirect); |
| } |
| } |
| } |
|
|
| public function deactivate($redirectAfter = true) |
| { |
| $params = [ |
| 'module' => 'CorePluginsAdmin', |
| 'action' => 'deactivate', |
| 'pluginName' => Common::getRequestVar('pluginName'), |
| 'nonce' => Common::getRequestVar('nonce'), |
| 'redirectTo' => Common::getRequestVar('redirectTo'), |
| 'referrer' => urlencode(Url::getReferrer()), |
| ]; |
| if (!$this->passwordVerify->requirePasswordVerifiedRecently($params)) { |
| return; |
| } |
|
|
| if ($this->isAllowedToTroubleshootAsSuperUser()) { |
| Access::doAsSuperUser(function () use ($redirectAfter) { |
| $this->doDeactivatePlugin($redirectAfter); |
| }); |
| } else { |
| $this->doDeactivatePlugin($redirectAfter); |
| } |
| } |
|
|
| public function uninstall($redirectAfter = true) |
| { |
| $this->dieIfPluginsAdminIsDisabled(); |
|
|
| $params = [ |
| 'module' => 'CorePluginsAdmin', |
| 'action' => 'uninstall', |
| 'pluginName' => Common::getRequestVar('pluginName'), |
| 'nonce' => Common::getRequestVar('nonce'), |
| 'referrer' => urlencode(Url::getReferrer()), |
| ]; |
| if (!$this->passwordVerify->requirePasswordVerifiedRecently($params)) { |
| return; |
| } |
|
|
| $pluginName = $this->initPluginModification(static::UNINSTALL_NONCE); |
|
|
| $uninstalled = $this->pluginManager->uninstallPlugin($pluginName); |
|
|
| if (!$uninstalled) { |
| $path = Plugin\Manager::getPluginDirectory($pluginName) . '/'; |
|
|
| $messagePermissions = Filechecks::getErrorMessageMissingPermissions($path); |
|
|
| $messageIntro = $this->translator->translate( |
| "Warning: \"%s\" could not be uninstalled. Piwik did not have enough permission to delete the files in $path. ", |
| $pluginName |
| ); |
| $exitMessage = $messageIntro . "<br/><br/>" . $messagePermissions; |
| $exitMessage .= "<br> Or manually delete this directory (using FTP or SSH access)"; |
|
|
| $ex = new MissingFilePermissionException($exitMessage); |
| $ex->setIsHtmlMessage(); |
|
|
| throw $ex; |
| } |
|
|
| $this->redirectAfterModification($redirectAfter); |
| } |
|
|
| public function showLicense() |
| { |
| Piwik::checkUserHasSomeViewAccess(); |
|
|
| $pluginName = Common::getRequestVar('pluginName', null, 'string'); |
|
|
| if (!Plugin\Manager::getInstance()->isPluginInFilesystem($pluginName)) { |
| throw new Exception('Invalid plugin'); |
| } |
|
|
| $metadata = new Plugin\MetadataLoader($pluginName); |
| $license_file = $metadata->getPathToLicenseFile(); |
|
|
| $license = 'No license file found for this plugin.'; |
| if (!empty($license_file)) { |
| $license = file_get_contents($license_file); |
| $license = nl2br($license); |
| } |
|
|
| $view = $this->configureView('@CorePluginsAdmin/license'); |
| $view->pluginName = $pluginName; |
| $view->license = $license; |
| return $view->render(); |
| } |
|
|
| protected function initPluginModification($nonceName) |
| { |
| Piwik::checkUserHasSuperUserAccess(); |
|
|
| Nonce::checkNonce($nonceName); |
|
|
| $pluginName = Common::getRequestVar('pluginName', null, 'string'); |
|
|
| if (!$this->pluginManager->isValidPluginName($pluginName)) { |
| throw new Exception('Invalid plugin name'); |
| } |
|
|
| return $pluginName; |
| } |
|
|
| protected function redirectAfterModification($redirectAfter) |
| { |
| if (!$redirectAfter) { |
| return; |
| } |
|
|
| $referrer = Common::getRequestVar('referrer', false); |
| $referrer = Common::unsanitizeInputValue($referrer); |
| if ( |
| !empty($referrer) |
| && Url::isLocalUrl($referrer) |
| ) { |
| Url::redirectToUrl($referrer); |
| } else { |
| Url::redirectToReferrer(); |
| } |
| } |
|
|
| private function tryToRepairPiwik() |
| { |
| |
| |
| try { |
| Filesystem::deleteAllCacheOnUpdate(); |
| } catch (Exception $e) { |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| protected function isAllowedToTroubleshootAsSuperUser() |
| { |
| $isAllowedToTroubleshootAsSuperUser = false; |
| $salt = SettingsPiwik::getSalt(); |
| if (!empty($salt)) { |
| $saltFromRequest = Common::getRequestVar('i_am_super_user', '', 'string'); |
| $isAllowedToTroubleshootAsSuperUser = ($salt == $saltFromRequest); |
| } |
| return $isAllowedToTroubleshootAsSuperUser; |
| } |
|
|
| |
| |
| |
| |
| protected function doDeactivatePlugin($redirectAfter) |
| { |
| $pluginName = $this->initPluginModification(static::DEACTIVATE_NONCE); |
| $this->dieIfPluginsAdminIsDisabled(); |
|
|
| $this->pluginManager->deactivatePlugin($pluginName); |
| $this->redirectAfterModification($redirectAfter); |
| } |
| } |
|
|