<?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\Plugin;

use Piwik\Config as PiwikConfig;
use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Date;
use Piwik\Db\Schema;
use Piwik\Development;
use Piwik\Exception\MissingFilePermissionException;
use Piwik\Menu\MenuAdmin;
use Piwik\Menu\MenuTop;
use Piwik\Notification;
use Piwik\Notification\Manager as NotificationManager;
use Piwik\Piwik;
use Piwik\Plugins\CorePluginsAdmin\CorePluginsAdmin;
use Piwik\Plugins\Marketplace\Marketplace;
use Piwik\Tracker\TrackerConfig;
use Piwik\Url;
use Piwik\Version;
use Piwik\View;
use Piwik\ProxyHttp;
use Piwik\SettingsPiwik;
/**
 * Base class of plugin controllers that provide administrative functionality.
 *
 * See {@link Controller} to learn more about Piwik controllers.
 *
 */
abstract class ControllerAdmin extends \Piwik\Plugin\Controller
{
    private const NEXT_REQUIRED_MINIMUM_PHP = '8.1';
    private const NEXT_REQUIRED_MINIMUM_MYSQL = '8.0';
    private const NEXT_REQUIRED_MINIMUM_MARIADB = '10.6';
    private static function notifyWhenTrackingStatisticsDisabled()
    {
        $statsEnabled = PiwikConfig::getInstance()->Tracker['record_statistics'];
        if ($statsEnabled == "0") {
            $notification = new Notification(Piwik::translate('General_StatisticsAreNotRecorded'));
            $notification->context = Notification::CONTEXT_INFO;
            Notification\Manager::notify('ControllerAdmin_StatsAreNotRecorded', $notification);
        }
    }
    private static function notifyAnyInvalidLicense()
    {
        if (!Marketplace::isMarketplaceEnabled()) {
            return;
        }
        if (Piwik::isUserIsAnonymous()) {
            return;
        }
        if (!Piwik::isUserHasSomeAdminAccess()) {
            return;
        }
        if (Development::isEnabled()) {
            return;
        }
        $expired = StaticContainer::get('Piwik\\Plugins\\Marketplace\\Plugins\\InvalidLicenses');
        $messageLicenseMissing = $expired->getMessageNoLicense();
        if (!empty($messageLicenseMissing)) {
            $notification = new Notification($messageLicenseMissing);
            $notification->raw = \true;
            $notification->context = Notification::CONTEXT_ERROR;
            $notification->title = Piwik::translate('Marketplace_LicenseMissing');
            Notification\Manager::notify('ControllerAdmin_LicenseMissingWarning', $notification);
        }
        $messageExceeded = $expired->getMessageExceededLicenses();
        if (!empty($messageExceeded)) {
            $notification = new Notification($messageExceeded);
            $notification->raw = \true;
            $notification->context = Notification::CONTEXT_WARNING;
            $notification->title = Piwik::translate('Marketplace_LicenseExceeded');
            Notification\Manager::notify('ControllerAdmin_LicenseExceededWarning', $notification);
        }
        $messageExpired = $expired->getMessageExpiredLicenses();
        if (!empty($messageExpired)) {
            $notification = new Notification($messageExpired);
            $notification->raw = \true;
            $notification->context = Notification::CONTEXT_WARNING;
            $notification->title = Piwik::translate('Marketplace_LicenseExpired');
            Notification\Manager::notify('ControllerAdmin_LicenseExpiredWarning', $notification);
        }
    }
    private static function notifyAnyInvalidPlugin()
    {
        if (!Piwik::hasUserSuperUserAccess()) {
            return;
        }
        $missingPlugins = \Piwik\Plugin\Manager::getInstance()->getMissingPlugins();
        if (empty($missingPlugins)) {
            return;
        }
        $invalidPluginsWarning = Piwik::translate('CoreAdminHome_InvalidPluginsWarning', [self::getPiwikVersion(), '<strong>' . implode('</strong>,&nbsp;<wbr><strong>', $missingPlugins) . '</strong>']);
        if (CorePluginsAdmin::isPluginsAdminEnabled()) {
            $pluginsLink = Url::getCurrentQueryStringWithParametersModified(['module' => 'CorePluginsAdmin', 'action' => 'plugins']);
            $invalidPluginsWarning .= '<br/>' . Piwik::translate('CoreAdminHome_InvalidPluginsYouCanUninstall', ['<a href="' . $pluginsLink . '">', '</a>']);
        } else {
            $invalidPluginsWarning .= '<br/>' . Piwik::translate('CoreAdminHome_InvalidPluginsAdminDisabled');
        }
        $notification = new Notification($invalidPluginsWarning);
        $notification->raw = \true;
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->title = Piwik::translate('General_Warning');
        Notification\Manager::notify('ControllerAdmin_InvalidPluginsWarning', $notification);
    }
    /**
     * Calls {@link setBasicVariablesNoneAdminView()} and {@link setBasicVariablesAdminView()}
     * using the supplied view.
     *
     * @param View $view
     * @param string $viewType If 'admin', the admin variables are set as well as basic ones.
     */
    protected function setBasicVariablesViewAs($view, $viewType = 'admin')
    {
        $this->setBasicVariablesNoneAdminView($view);
        if ($viewType === 'admin') {
            self::setBasicVariablesAdminView($view);
        }
    }
    private static function notifyIfURLIsNotSecure()
    {
        $isURLSecure = ProxyHttp::isHttps();
        if ($isURLSecure) {
            return;
        }
        if (!Piwik::hasUserSuperUserAccess()) {
            return;
        }
        if (Url::isLocalHost(Url::getCurrentHost())) {
            return;
        }
        if (Development::isEnabled()) {
            return;
        }
        $message = Piwik::translate('General_CurrentlyUsingUnsecureHttp');
        $message .= " ";
        $message .= Piwik::translate('General_ReadThisToLearnMore', [Url::getExternalLinkTag('https://matomo.org/faq/how-to/faq_91/'), '</a>']);
        $notification = new Notification($message);
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->raw = \true;
        Notification\Manager::notify('ControllerAdmin_HttpIsUsed', $notification);
    }
    private static function notifyIfDevelopmentModeOnButNotInstalledThroughGit()
    {
        if (!Piwik::hasUserSuperUserAccess()) {
            return;
        }
        if (!Development::isEnabled()) {
            return;
        }
        if (SettingsPiwik::isGitDeployment()) {
            return;
        }
        $message = Piwik::translate('General_WarningDevelopmentModeOnButNotGitInstalled');
        $notification = new Notification($message);
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->raw = \true;
        $notification->flags = Notification::FLAG_CLEAR;
        Notification\Manager::notify('ControllerAdmin_DevelopmentModeOn', $notification);
    }
    /**
     * @ignore
     */
    public static function displayWarningIfConfigFileNotWritable()
    {
        try {
            PiwikConfig::getInstance()->checkConfigIsWritable();
        } catch (MissingFilePermissionException $exception) {
            $notification = new Notification($exception->getMessage());
            $notification->raw = \true;
            $notification->context = Notification::CONTEXT_WARNING;
            Notification\Manager::notify('ControllerAdmin_ConfigNotWriteable', $notification);
        }
    }
    private static function notifyIfEAcceleratorIsUsed()
    {
        $isEacceleratorUsed = ini_get('eaccelerator.enable');
        if (empty($isEacceleratorUsed)) {
            return;
        }
        $message = sprintf("You are using the PHP accelerator & optimizer eAccelerator which is known to be not compatible with Matomo.\n            We have disabled eAccelerator, which might affect the performance of Matomo.\n            Read the %srelated ticket%s for more information and how to fix this problem.", '<a rel="noreferrer noopener" target="_blank" href="https://github.com/matomo-org/matomo/issues/4439">', '</a>');
        $notification = new Notification($message);
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->raw = \true;
        Notification\Manager::notify('ControllerAdmin_EacceleratorIsUsed', $notification);
    }
    /**
     * PHP Version required by the next major Matomo version
     * @return string
     */
    private static function getNextRequiredMinimumPHP()
    {
        return self::NEXT_REQUIRED_MINIMUM_PHP;
    }
    private static function isUsingPhpVersionCompatibleWithNextPiwik($phpVersion = \PHP_VERSION)
    {
        return version_compare($phpVersion, self::getNextRequiredMinimumPHP(), '>=');
    }
    private static function notifyWhenPhpVersionIsNotCompatibleWithNextMajorPiwik()
    {
        if (defined('PIWIK_TEST_MODE')) {
            // to avoid changing every admin UI test
            return;
        }
        if (!Piwik::hasUserSuperUserAccess()) {
            return;
        }
        if (self::isUsingPhpVersionCompatibleWithNextPiwik()) {
            return;
        }
        $youMustUpgradePHP = Piwik::translate('General_YouMustUpgradePhpVersionToReceiveNextMajorMatomo');
        $message = Piwik::translate('General_MatomoCannotBeUpgradedToNextMajorBecausePhpIsTooOld') . ' ' . sprintf(Piwik::translate('General_PleaseUpgradeYourPhpVersionForNextMajorMatomo'), self::getNextRequiredMinimumPHP());
        $notification = new Notification($message);
        $notification->title = $youMustUpgradePHP;
        $notification->priority = Notification::PRIORITY_LOW;
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->type = Notification::TYPE_TRANSIENT;
        $notification->flags = Notification::FLAG_NO_CLEAR;
        NotificationManager::notify('PHPVersionTooOldForNewestPiwikCheck', $notification);
    }
    private static function getNextRequiredMinimumDatabaseVersion(string $databaseType, string $databaseVersion) : ?string
    {
        $databaseType = self::getEffectiveDatabaseType($databaseType, $databaseVersion);
        if ($databaseType === 'mysql') {
            return self::NEXT_REQUIRED_MINIMUM_MYSQL;
        }
        if ($databaseType === 'mariadb') {
            return self::NEXT_REQUIRED_MINIMUM_MARIADB;
        }
        return null;
    }
    private static function isUsingDatabaseVersionCompatibleWithNextPiwik(string $databaseType, string $databaseVersion) : bool
    {
        $requiredVersion = self::getNextRequiredMinimumDatabaseVersion($databaseType, $databaseVersion);
        if ($requiredVersion === null) {
            return \true;
        }
        return version_compare(self::getComparableDatabaseVersion($databaseVersion), $requiredVersion, '>=');
    }
    private static function getEffectiveDatabaseType(string $databaseType, string $databaseVersion) : string
    {
        if (stripos($databaseVersion, 'mariadb') !== \false) {
            return 'mariadb';
        }
        return strtolower($databaseType);
    }
    private static function getComparableDatabaseVersion(string $databaseVersion) : string
    {
        if (stripos($databaseVersion, 'mariadb') !== \false && preg_match('/5\\.5\\.5-(\\d+(?:\\.\\d+){0,2})-MariaDB/i', $databaseVersion, $matches)) {
            return $matches[1];
        }
        if (preg_match('/\\d+(?:\\.\\d+){0,2}/', $databaseVersion, $matches)) {
            return $matches[0];
        }
        return $databaseVersion;
    }
    private static function notifyWhenDatabaseVersionIsNotCompatibleWithNextMajorPiwik() : void
    {
        if (defined('PIWIK_TEST_MODE')) {
            // to avoid changing every admin UI test
            return;
        }
        if (!Piwik::hasUserSuperUserAccess()) {
            return;
        }
        $databaseType = Schema::getInstance()->getDatabaseType();
        $databaseVersion = Schema::getInstance()->getVersion();
        if (self::isUsingDatabaseVersionCompatibleWithNextPiwik($databaseType, $databaseVersion)) {
            return;
        }
        $requiredVersion = self::getNextRequiredMinimumDatabaseVersion($databaseType, $databaseVersion);
        $databaseType = self::getEffectiveDatabaseType($databaseType, $databaseVersion) === 'mariadb' ? 'MariaDB' : 'MySQL';
        $message = Piwik::translate('General_MatomoCannotBeUpgradedToNextMajorBecauseDatabaseIsTooOld') . ' ' . Piwik::translate('General_PleaseUpgradeYourDatabaseVersionForNextMajorMatomo', [$databaseType, $requiredVersion]);
        $notification = new Notification($message);
        $notification->title = Piwik::translate('General_YouMustUpgradeDatabaseVersionToReceiveNextMajorMatomo');
        $notification->priority = Notification::PRIORITY_LOW;
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->type = Notification::TYPE_TRANSIENT;
        $notification->flags = Notification::FLAG_NO_CLEAR;
        NotificationManager::notify('DatabaseVersionTooOldForNewestPiwikCheck', $notification);
    }
    private static function notifyWhenPhpVersionIsEOL()
    {
        if (defined('PIWIK_TEST_MODE')) {
            // to avoid changing every admin UI test
            return;
        }
        $notifyPhpIsEOL = Piwik::hasUserSuperUserAccess() && self::isPhpVersionEOL();
        if (!$notifyPhpIsEOL) {
            return;
        }
        $deprecatedMajorPhpVersion = \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION;
        $message = '';
        if (version_compare(\PHP_VERSION, self::getNextRequiredMinimumPHP(), '<')) {
            $message = Piwik::translate('General_WarningPiwikWillStopSupportingPHPVersion', [$deprecatedMajorPhpVersion, self::getNextRequiredMinimumPHP()]) . '<br/>';
        }
        $message .= Piwik::translate('General_WarningPhpVersionXIsTooOld', $deprecatedMajorPhpVersion);
        $notification = new Notification($message);
        $notification->raw = \true;
        $notification->title = Piwik::translate('General_Warning');
        $notification->priority = Notification::PRIORITY_LOW;
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->type = Notification::TYPE_TRANSIENT;
        $notification->flags = Notification::FLAG_NO_CLEAR;
        NotificationManager::notify('PHPVersionCheck', $notification);
    }
    private static function notifyWhenDatabaseVersionIsEOL() : void
    {
        if (defined('PIWIK_TEST_MODE')) {
            // to avoid changing every admin UI test
            return;
        }
        $isEOL = Piwik::hasUserSuperUserAccess() && Schema::getInstance()->hasReachedEOL();
        if (!$isEOL) {
            return;
        }
        $databaseVersion = Schema::getInstance()->getVersion();
        $message = Piwik::translate('General_WarningDatabaseVersionXIsTooOld', [Schema::getInstance()->getDatabaseType(), $databaseVersion]);
        $notification = new Notification($message);
        $notification->raw = \true;
        $notification->title = Piwik::translate('General_Warning');
        $notification->priority = Notification::PRIORITY_LOW;
        $notification->context = Notification::CONTEXT_WARNING;
        $notification->type = Notification::TYPE_TRANSIENT;
        $notification->flags = Notification::FLAG_NO_CLEAR;
        NotificationManager::notify('DatabaseVersionCheck', $notification);
    }
    private static function notifyWhenDebugOnDemandIsEnabled($trackerSetting)
    {
        if (!Development::isEnabled() && Piwik::hasUserSuperUserAccess() && TrackerConfig::getConfigValue($trackerSetting)) {
            $message = Piwik::translate('General_WarningDebugOnDemandEnabled');
            $message = sprintf($message, '"' . $trackerSetting . '"', '"[Tracker] ' . $trackerSetting . '"', '"0"', '"config/config.ini.php"');
            $notification = new Notification($message);
            $notification->title = Piwik::translate('General_Warning');
            $notification->priority = Notification::PRIORITY_LOW;
            $notification->context = Notification::CONTEXT_WARNING;
            $notification->type = Notification::TYPE_TRANSIENT;
            $notification->flags = Notification::FLAG_NO_CLEAR;
            NotificationManager::notify('Tracker' . $trackerSetting, $notification);
        }
    }
    /**
     * Assigns view properties that would be useful to views that render admin pages.
     *
     * Assigns the following variables:
     *
     * - **statisticsNotRecorded** - Set to true if the `[Tracker] record_statistics` INI
     *                               config is `0`. If not `0`, this variable will not be defined.
     * - **topMenu** - The result of `MenuTop::getInstance()->getMenu()`.
     * - **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If
     *                    true, {@link Piwik\View::setXFrameOptions()} is called on the view.
     * - **isSuperUser** - Whether the current user is a superuser or not.
     * - **usingOldGeoIPPlugin** - Whether this Piwik install is currently using the old GeoIP
     *                             plugin or not.
     * - **invalidPluginsWarning** - Set if some of the plugins to load (determined by INI configuration)
     *                               are invalid or missing.
     * - **phpVersion** - The current PHP version.
     * - **phpIsNewEnough** - Whether the current PHP version is new enough to run Piwik.
     * - **adminMenu** - The result of `MenuAdmin::getInstance()->getMenu()`.
     *
     * @api
     */
    public static function setBasicVariablesAdminView(View $view)
    {
        self::notifyWhenTrackingStatisticsDisabled();
        self::notifyIfEAcceleratorIsUsed();
        self::notifyIfURLIsNotSecure();
        self::notifyIfDevelopmentModeOnButNotInstalledThroughGit();
        $view->topMenu = MenuTop::getInstance()->getMenu();
        $view->isDataPurgeSettingsEnabled = self::isDataPurgeSettingsEnabled();
        $enableFrames = PiwikConfig::getInstance()->General['enable_framed_settings'];
        $view->enableFrames = $enableFrames;
        if (!$enableFrames) {
            $view->setXFrameOptions('sameorigin');
        }
        $view->isSuperUser = Piwik::hasUserSuperUserAccess();
        self::notifyAnyInvalidLicense();
        self::notifyAnyInvalidPlugin();
        self::notifyWhenPhpVersionIsEOL();
        self::notifyWhenPhpVersionIsNotCompatibleWithNextMajorPiwik();
        self::notifyWhenDatabaseVersionIsNotCompatibleWithNextMajorPiwik();
        self::notifyWhenDatabaseVersionIsEOL();
        self::notifyWhenDebugOnDemandIsEnabled('debug');
        self::notifyWhenDebugOnDemandIsEnabled('debug_on_demand');
        /**
         * Posted when rendering an admin page and notifications about any warnings or errors should be triggered.
         * You can use it for example when you have a plugin that needs to be configured in order to work and the
         * plugin has not been configured yet. It can be also used to cancel / remove other notifications by calling
         * eg `Notification\Manager::cancel($notificationId)`.
         *
         * **Example**
         *
         *     public function onTriggerAdminNotifications(Piwik\Widget\WidgetsList $list)
         *     {
         *         if ($pluginFooIsNotConfigured) {
         *              $notification = new Notification('The plugin foo has not been configured yet');
         *              $notification->context = Notification::CONTEXT_WARNING;
         *              Notification\Manager::notify('fooNotConfigured', $notification);
         *         }
         *     }
         *
         */
        Piwik::postEvent('Controller.triggerAdminNotifications');
        $view->adminMenu = MenuAdmin::getInstance()->getMenu();
        $notifications = $view->notifications;
        if (empty($notifications)) {
            $view->notifications = NotificationManager::getAllNotificationsToDisplay();
            NotificationManager::cancelAllNonPersistent();
        }
    }
    public static function isDataPurgeSettingsEnabled()
    {
        return (bool) Config::getInstance()->General['enable_delete_old_data_settings_admin'];
    }
    protected static function getPiwikVersion()
    {
        return "Matomo " . Version::VERSION;
    }
    private static function isPhpVersionEOL()
    {
        $phpEOL = '8.0';
        // End of security update for certain PHP versions as of https://www.php.net/supported-versions
        if (Date::today()->isLater(Date::factory('2025-12-31'))) {
            $phpEOL = '8.1';
        }
        if (Date::today()->isLater(Date::factory('2026-12-31'))) {
            $phpEOL = '8.2';
        }
        if (Date::today()->isLater(Date::factory('2027-12-31'))) {
            $phpEOL = '8.3';
        }
        if (Date::today()->isLater(Date::factory('2028-12-31'))) {
            $phpEOL = '8.4';
        }
        return version_compare(\PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION, $phpEOL, '<=');
    }
}
