File: /var/www/html/wp-content/plugins/matomo/app/core/SettingsPiwik.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;
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'];
}
}