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

use Exception;
use Piwik\Cache as PiwikCache;
use Piwik\Config\GeneralConfig;
use Piwik\Container\StaticContainer;
/**
 * Contains helper methods that can be used to get common Piwik settings.
 *
 */
class SettingsPiwik
{
    public const OPTION_PIWIK_URL = 'piwikUrl';
    /**
     * Get salt from [General] section. Should ONLY be used as a seed to create hashes
     *
     * NOTE: Keep this salt secret! Never output anywhere or share it etc.
     *
     */
    public static function getSalt() : ?string
    {
        static $salt = null;
        if (is_null($salt)) {
            $salt = \Piwik\Config::getInstance()->General['salt'] ?? '';
        }
        return $salt;
    }
    /**
     * Should Piwik check that the login & password have minimum length and valid characters?
     *
     * @return bool  True if checks enabled; false otherwise
     */
    public static function isUserCredentialsSanityCheckEnabled() : bool
    {
        return \Piwik\Config::getInstance()->General['disable_checks_usernames_attributes'] == 0;
    }
    /**
     * Should Piwik show the update notification to superusers only?
     *
     * @return bool  True if show to superusers only; false otherwise
     */
    public static function isShowUpdateNotificationToSuperUsersOnlyEnabled() : bool
    {
        return \Piwik\Config::getInstance()->General['show_update_notification_to_superusers_only'] == 1;
    }
    /**
     * Returns every stored segment to pre-process for each site during cron archiving.
     *
     * @return array The list of stored segments that apply to all sites.
     */
    public static function getKnownSegmentsToArchive() : array
    {
        $cacheId = 'KnownSegmentsToArchive';
        $cache = PiwikCache::getTransientCache();
        if ($cache->contains($cacheId)) {
            return $cache->fetch($cacheId);
        }
        $segments = \Piwik\Config::getInstance()->Segments;
        $segmentsToProcess = isset($segments['Segments']) ? $segments['Segments'] : array();
        /**
         * Triggered during the cron archiving process to collect segments that
         * should be pre-processed for all websites. The archiving process will be launched
         * for each of these segments when archiving data.
         *
         * This event can be used to add segments to be pre-processed. If your plugin depends
         * on data from a specific segment, this event could be used to provide enhanced
         * performance.
         *
         * _Note: If you just want to add a segment that is managed by the user, use the
         * SegmentEditor API._
         *
         * **Example**
         *
         *     Piwik::addAction('Segments.getKnownSegmentsToArchiveAllSites', function (&$segments) {
         *         $segments[] = 'country=jp;city=Tokyo';
         *     });
         *
         * @param array &$segmentsToProcess List of segment definitions, eg,
         *
         *                                      array(
         *                                          'browserCode=ff;resolution=800x600',
         *                                          'country=jp;city=Tokyo'
         *                                      )
         *
         *                                  Add segments to this array in your event handler.
         */
        \Piwik\Piwik::postEvent('Segments.getKnownSegmentsToArchiveAllSites', array(&$segmentsToProcess));
        $segmentsToProcess = array_unique($segmentsToProcess);
        $cache->save($cacheId, $segmentsToProcess);
        return $segmentsToProcess;
    }
    /**
     * Returns the list of stored segments to pre-process for an individual site when executing
     * cron archiving.
     *
     * @param int $idSite The ID of the site to get stored segments for.
     * @return string[] The list of stored segments that apply to the requested site.
     */
    public static function getKnownSegmentsToArchiveForSite($idSite) : array
    {
        $cacheId = 'KnownSegmentsToArchiveForSite' . $idSite;
        $cache = PiwikCache::getTransientCache();
        if ($cache->contains($cacheId)) {
            return $cache->fetch($cacheId);
        }
        $segments = array();
        /**
         * Triggered during the cron archiving process to collect segments that
         * should be pre-processed for one specific site. The archiving process will be launched
         * for each of these segments when archiving data for that one site.
         *
         * This event can be used to add segments to be pre-processed for one site.
         *
         * _Note: If you just want to add a segment that is managed by the user, you should use the
         * SegmentEditor API._
         *
         * **Example**
         *
         *     Piwik::addAction('Segments.getKnownSegmentsToArchiveForSite', function (&$segments, $idSite) {
         *         $segments[] = 'country=jp;city=Tokyo';
         *     });
         *
         * @param array &$segmentsToProcess List of segment definitions, eg,
         *
         *                                      array(
         *                                          'browserCode=ff;resolution=800x600',
         *                                          'country=JP;city=Tokyo'
         *                                      )
         *
         *                                  Add segments to this array in your event handler.
         * @param int $idSite The ID of the site to get segments for.
         */
        \Piwik\Piwik::postEvent('Segments.getKnownSegmentsToArchiveForSite', array(&$segments, $idSite));
        $segments = array_unique($segments);
        $cache->save($cacheId, $segments);
        return $segments;
    }
    /**
     * Number of websites to show in the Website selector
     *
     */
    public static function getWebsitesCountToDisplay() : int
    {
        $count = max(\Piwik\Config::getInstance()->General['site_selector_max_sites'], \Piwik\Config::getInstance()->General['autocomplete_min_sites']);
        return (int) $count;
    }
    /**
     * Returns the URL to this Piwik instance, eg. **https://demo.piwik.org/** or **https://example.org/piwik/**.
     *
     * @return string|false return false if no value is configured and we are in PHP CLI mode
     * @api
     */
    public static function getPiwikUrl()
    {
        $url = \Piwik\Option::get(self::OPTION_PIWIK_URL);
        $isPiwikCoreDispatching = defined('PIWIK_ENABLE_DISPATCH') && PIWIK_ENABLE_DISPATCH;
        if (\Piwik\Common::isPhpCliMode() || \Piwik\SettingsServer::isArchivePhpTriggered() || !$isPiwikCoreDispatching) {
            return $url;
        }
        $currentUrl = \Piwik\Common::sanitizeInputValue(\Piwik\Url::getCurrentUrlWithoutFileName());
        // when script is called from /misc/cron/archive.php, Piwik URL is /index.php
        $currentUrl = str_replace("/misc/cron", "", $currentUrl);
        if (empty($url) || $currentUrl !== $url) {
            $host = \Piwik\Url::getHostFromUrl($currentUrl);
            if (strlen($currentUrl) >= strlen('http://a/') && \Piwik\Url::isValidHost($host) && !\Piwik\Url::isLocalHost($host)) {
                self::overwritePiwikUrl($currentUrl);
            }
            $url = $currentUrl;
        }
        if (\Piwik\ProxyHttp::isHttps()) {
            $url = str_replace("http://", "https://", $url);
        }
        return $url;
    }
    public static function isMatomoInstalled() : bool
    {
        $config = \Piwik\Config::getInstance()->getLocalPath();
        $exists = file_exists($config);
        // Piwik is not installed if the config file is not found
        if (!$exists) {
            return \false;
        }
        $general = \Piwik\Config::getInstance()->General;
        $isInstallationInProgress = \false;
        if (array_key_exists('installation_in_progress', $general)) {
            $isInstallationInProgress = (bool) $general['installation_in_progress'];
        }
        if ($isInstallationInProgress) {
            return \false;
        }
        // Check that the database section is really set, ie. file is not empty
        if (empty(\Piwik\Config::getInstance()->database['username'])) {
            return \false;
        }
        return \true;
    }
    /**
     * Check if outgoing internet connections are enabled
     * This is often disable in an intranet environment
     *
     */
    public static function isInternetEnabled() : bool
    {
        return (bool) \Piwik\Config::getInstance()->General['enable_internet_features'];
    }
    /**
     * Detect whether user has enabled auto updates. Please note this config is a bit misleading. It is currently
     * actually used for 2 things: To disable making any connections back to Piwik, and to actually disable the auto
     * update of core and plugins.
     */
    public static function isAutoUpdateEnabled() : bool
    {
        $enableAutoUpdate = (bool) \Piwik\Config::getInstance()->General['enable_auto_update'];
        if (self::isInternetEnabled() === \true && $enableAutoUpdate === \true) {
            return \true;
        }
        return \false;
    }
    /**
     * Detects whether an auto update can be made. An update is possible if the user is not on multiple servers and if
     * automatic updates are actually enabled. If a user is running Piwik on multiple servers an update is not possible
     * as it would be installed only on one server instead of all of them. Also if a user has disabled automatic updates
     * we cannot perform any automatic updates.
     *
     */
    public static function isAutoUpdatePossible() : bool
    {
        return !self::isMultiServerEnvironment() && self::isAutoUpdateEnabled();
    }
    /**
     * Returns `true` if Piwik is running on more than one server. For example in a load balanced environment. In this
     * case we should not make changes to the config and not install a plugin via the UI as it would be only executed
     * on one server.
     */
    public static function isMultiServerEnvironment() : bool
    {
        $is = \Piwik\Config::getInstance()->General['multi_server_environment'];
        return !empty($is);
    }
    /**
     * Returns `true` if segmentation is allowed for this user, `false` if otherwise.
     *
     * @api
     */
    public static function isSegmentationEnabled() : bool
    {
        return !\Piwik\Piwik::isUserIsAnonymous() || \Piwik\Config::getInstance()->General['anonymous_user_enable_use_segments_API'];
    }
    /**
     * Returns true if unique visitors should be processed for the given period type.
     *
     * Unique visitor processing is controlled by the `[General] enable_processing_unique_visitors_...`
     * INI config options. By default, unique visitors are processed only for day/week/month periods.
     *
     * @param string $periodLabel `"day"`, `"week"`, `"month"`, `"year"` or `"range"`
     * @api
     */
    public static function isUniqueVisitorsEnabled(string $periodLabel) : bool
    {
        $generalSettings = \Piwik\Config::getInstance()->General;
        $settingName = "enable_processing_unique_visitors_{$periodLabel}";
        $result = !empty($generalSettings[$settingName]) && $generalSettings[$settingName] == 1;
        // check enable_processing_unique_visitors_year_and_range for backwards compatibility
        if (($periodLabel === 'year' || $periodLabel === 'range') && isset($generalSettings['enable_processing_unique_visitors_year_and_range'])) {
            $result |= $generalSettings['enable_processing_unique_visitors_year_and_range'] == 1;
        }
        return $result;
    }
    /**
     * If Piwik uses per-domain config file, make sure CustomLogo is unique
     * @throws \Piwik\Exception\DI\DependencyException
     * @throws \Piwik\Exception\DI\NotFoundException
     * @throws Exception
     */
    public static function rewriteMiscUserPathWithInstanceId(string $path) : string
    {
        $tmp = StaticContainer::get('path.misc.user');
        $path = self::rewritePathAppendPiwikInstanceId($path, $tmp);
        return $path;
    }
    /**
     * Returns true if the Piwik server appears to be working.
     *
     * If the Piwik server is in an error state (eg. some directories are not writable and Piwik displays error message),
     * or if the Piwik server is "offline",
     * this will return false..
     *
     * @throws Exception
     */
    public static function checkPiwikServerWorking(string $piwikServerUrl, bool $acceptInvalidSSLCertificates = \false) : void
    {
        // Now testing if the webserver is running
        try {
            $fetched = \Piwik\Http::sendHttpRequestBy('curl', $piwikServerUrl, $timeout = 45, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0, $acceptLanguage = \false, $acceptInvalidSSLCertificates);
        } catch (Exception $e) {
            $fetched = "ERROR fetching: " . $e->getMessage();
        }
        // this will match when Piwik not installed yet, or favicon not customised
        $expectedStringAlt = 'plugins/CoreHome/images/favicon.png';
        // this will match when Piwik is installed and favicon has been customised
        $expectedString = 'misc/user/';
        // see checkPiwikIsNotInstalled()
        $expectedStringAlreadyInstalled = 'piwik-is-already-installed';
        $expectedStringNotFound = strpos($fetched, $expectedString) === \false && strpos($fetched, $expectedStringAlt) === \false && strpos($fetched, $expectedStringAlreadyInstalled) === \false;
        $hasError = \false !== strpos($fetched, PAGE_TITLE_WHEN_ERROR);
        if ($hasError || $expectedStringNotFound) {
            throw new Exception("\nMatomo should be running at: " . $piwikServerUrl . " but this URL returned an unexpected response: '" . $fetched . "'\n\n");
        }
    }
    /**
     * Returns true if Piwik is deployed using git
     * FAQ: https://piwik.org/faq/how-to-install/faq_18271/
     *
     */
    public static function isGitDeployment() : bool
    {
        return file_exists(PIWIK_INCLUDE_PATH . '/.git/HEAD');
    }
    public static function getCurrentGitBranch() : string
    {
        $file = PIWIK_INCLUDE_PATH . '/.git/HEAD';
        if (!file_exists($file)) {
            return '';
        }
        $firstLineOfGitHead = file($file);
        if (empty($firstLineOfGitHead)) {
            return '';
        }
        $firstLineOfGitHead = $firstLineOfGitHead[0];
        $parts = explode('/', $firstLineOfGitHead);
        if (empty($parts[2])) {
            return '';
        }
        $currentGitBranch = trim($parts[2]);
        return $currentGitBranch;
    }
    /**
     * @throws Exception
     */
    protected static function rewritePathAppendPiwikInstanceId(string $pathToRewrite, string $leadingPathToAppendHostnameTo) : string
    {
        $instanceId = self::getPiwikInstanceId();
        if (empty($instanceId)) {
            return $pathToRewrite;
        }
        if (($posTmp = strrpos($pathToRewrite, $leadingPathToAppendHostnameTo)) === \false) {
            throw new Exception("The path {$pathToRewrite} was expected to contain the string  {$leadingPathToAppendHostnameTo}");
        }
        $tmpToReplace = $leadingPathToAppendHostnameTo . $instanceId . '/';
        // replace only the latest occurrence (in case path contains twice /tmp)
        $pathToRewrite = substr_replace($pathToRewrite, $tmpToReplace, $posTmp, strlen($leadingPathToAppendHostnameTo));
        return $pathToRewrite;
    }
    /**
     * @throws Exception
     * @return string|false return string or false if not set
     */
    public static function getPiwikInstanceId()
    {
        // until Matomo is installed, we use hostname as instance_id
        if (!self::isMatomoInstalled() && \Piwik\Common::isPhpCliMode()) {
            // enterprise:install use case
            return \Piwik\Config::getHostname();
        }
        // config.ini.php not ready yet, instance_id will not be set
        if (!\Piwik\Config::getInstance()->existsLocalConfig()) {
            return \false;
        }
        $instanceId = GeneralConfig::getConfigValue('instance_id');
        if (!empty($instanceId)) {
            return preg_replace('/[^\\w\\.-]/', '', $instanceId);
        }
        // do not rewrite the path as Matomo uses the standard config.ini.php file
        return \false;
    }
    public static function overwritePiwikUrl(string $currentUrl) : void
    {
        \Piwik\Option::set(self::OPTION_PIWIK_URL, $currentUrl, $autoLoad = \true);
    }
    public static function isHttpsForced() : bool
    {
        if (!self::isMatomoInstalled()) {
            // Only enable this feature after Piwik is already installed
            return \false;
        }
        return \Piwik\Config::getInstance()->General['force_ssl'] == 1;
    }
    /**
     * Note: this config settig is also checked in the InterSites plugin
     *
     */
    public static function isSameFingerprintAcrossWebsites() : bool
    {
        return (bool) \Piwik\Config::getInstance()->Tracker['enable_fingerprinting_across_websites'];
    }
}
