site-accueil-insa/matomo/core/SettingsPiwik.php

520 lines
18 KiB
PHP

<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://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.
*
* @return string|null
*/
public static function getSalt(): ?string
{
static $salt = null;
if (is_null($salt)) {
$salt = 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 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 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 = 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::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::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
*
* @return int
*/
public static function getWebsitesCountToDisplay(): int
{
$count = max(Config::getInstance()->General['site_selector_max_sites'],
Config::getInstance()->General['autocomplete_min_sites']);
return (int)$count;
}
/**
* Returns the URL to this Piwik instance, eg. **http://demo.piwik.org/** or **http://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 = Option::get(self::OPTION_PIWIK_URL);
$isPiwikCoreDispatching = defined('PIWIK_ENABLE_DISPATCH') && PIWIK_ENABLE_DISPATCH;
if (Common::isPhpCliMode()
// in case core:archive command is triggered (often with localhost domain)
|| SettingsServer::isArchivePhpTriggered()
// When someone else than core is dispatching this request then we return the URL as it is read only
|| !$isPiwikCoreDispatching
) {
return $url;
}
$currentUrl = Common::sanitizeInputValue(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)
// if URL changes, always update the cache
|| $currentUrl !== $url
) {
$host = Url::getHostFromUrl($currentUrl);
if (strlen($currentUrl) >= strlen('http://a/')
&& Url::isValidHost($host)
&& !Url::isLocalHost($host)) {
self::overwritePiwikUrl($currentUrl);
}
$url = $currentUrl;
}
if (ProxyHttp::isHttps()) {
$url = str_replace("http://", "https://", $url);
}
return $url;
}
/**
* @return bool
*/
public static function isMatomoInstalled(): bool
{
$config = Config::getInstance()->getLocalPath();
$exists = file_exists($config);
// Piwik is not installed if the config file is not found
if (!$exists) {
return false;
}
$general = 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(Config::getInstance()->database['username'])) {
return false;
}
return true;
}
/**
* Check if outgoing internet connections are enabled
* This is often disable in an intranet environment
*
* @return bool
*/
public static function isInternetEnabled(): bool
{
return (bool) 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.
* @return bool
*/
public static function isAutoUpdateEnabled(): bool
{
$enableAutoUpdate = (bool) 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.
*
* @return bool
*/
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.
* @return bool
*/
public static function isMultiServerEnvironment(): bool
{
$is = Config::getInstance()->General['multi_server_environment'];
return !empty($is);
}
/**
* Returns `true` if segmentation is allowed for this user, `false` if otherwise.
*
* @return bool
* @api
*/
public static function isSegmentationEnabled(): bool
{
return !Piwik::isUserIsAnonymous()
|| 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"`
* @return bool
* @api
*/
public static function isUniqueVisitorsEnabled(string $periodLabel): bool
{
$generalSettings = 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
* @param string $path
* @return string
* @throws \DI\DependencyException
* @throws \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..
*
* @param string $piwikServerUrl
* @param bool $acceptInvalidSSLCertificates
* @return void
* @throws Exception
*/
public static function checkPiwikServerWorking(string $piwikServerUrl, bool $acceptInvalidSSLCertificates = false): void
{
// Now testing if the webserver is running
try {
$fetched = 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: http://piwik.org/faq/how-to-install/faq_18271/
*
* @return bool
*/
public static function isGitDeployment(): bool
{
return file_exists(PIWIK_INCLUDE_PATH . '/.git/HEAD');
}
/**
* @return string
*/
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;
}
/**
* @param string $pathToRewrite
* @param string $leadingPathToAppendHostnameTo
* @return string
* @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() && Common::isPhpCliMode()) {
// enterprise:install use case
return Config::getHostname();
}
// config.ini.php not ready yet, instance_id will not be set
if (!Config::getInstance()->existsLocalConfig()) {
return false;
}
$instanceId = GeneralConfig::getConfigValue('instance_id');
if (!empty($instanceId)) {
return $instanceId;
}
// do not rewrite the path as Matomo uses the standard config.ini.php file
return false;
}
/**
* @param string $currentUrl
*/
public static function overwritePiwikUrl(string $currentUrl): void
{
Option::set(self::OPTION_PIWIK_URL, $currentUrl, $autoLoad = true);
}
/**
* @return bool
*/
public static function isHttpsForced(): bool
{
if (!self::isMatomoInstalled()) {
// Only enable this feature after Piwik is already installed
return false;
}
return Config::getInstance()->General['force_ssl'] == 1;
}
/**
* Note: this config settig is also checked in the InterSites plugin
*
* @return bool
*/
public static function isSameFingerprintAcrossWebsites(): bool
{
return (bool)Config::getInstance()->Tracker['enable_fingerprinting_across_websites'];
}
}