| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Plugins\CoreAdminHome; |
|
|
| use Piwik\Common; |
| use Piwik\Nonce; |
| use Piwik\Piwik; |
| use Piwik\Plugins\LanguagesManager\API as APILanguagesManager; |
| use Piwik\Plugins\LanguagesManager\LanguagesManager; |
| use Piwik\Plugins\PrivacyManager\DoNotTrackHeaderChecker; |
| use Piwik\Request; |
| use Piwik\Tracker\IgnoreCookie; |
| use Piwik\Url; |
| use Piwik\UrlHelper; |
| use Piwik\View; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class OptOutManager |
| { |
| |
| private $doNotTrackHeaderChecker; |
|
|
| |
| private $javascripts; |
|
|
| |
| private $stylesheets; |
|
|
| |
| private $title; |
|
|
| |
| private $view; |
|
|
| |
| private $queryParameters = array(); |
|
|
| public function __construct(?DoNotTrackHeaderChecker $doNotTrackHeaderChecker = null) |
| { |
| $this->doNotTrackHeaderChecker = $doNotTrackHeaderChecker ?: new DoNotTrackHeaderChecker(); |
|
|
| $this->javascripts = array( |
| 'inline' => array(), |
| 'external' => array(), |
| ); |
|
|
| $this->stylesheets = array( |
| 'inline' => array(), |
| 'external' => array(), |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function addJavaScript($javascript, $inline = true) |
| { |
| $type = $inline ? 'inline' : 'external'; |
| $this->javascripts[$type][] = $javascript; |
| } |
|
|
| |
| |
| |
| public function getJavaScripts() |
| { |
| return $this->javascripts; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function addStylesheet($stylesheet, $inline = true) |
| { |
| $type = $inline ? 'inline' : 'external'; |
| $this->stylesheets[$type][] = $stylesheet; |
| } |
|
|
| |
| |
| |
| public function getStylesheets() |
| { |
| return $this->stylesheets; |
| } |
|
|
| |
| |
| |
| public function getTitle() |
| { |
| return $this->title; |
| } |
|
|
| |
| |
| |
| public function setTitle($title) |
| { |
| $this->title = $title; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function addQueryParameter($key, $value, $override = true) |
| { |
| if (!isset($this->queryParameters[$key]) || true === $override) { |
| $this->queryParameters[$key] = $value; |
| return true; |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| public function addQueryParameters(array $items, $override = true) |
| { |
| foreach ($items as $key => $value) { |
| $this->addQueryParameter($key, $value, $override); |
| } |
| } |
|
|
| |
| |
| |
| public function removeQueryParameter($key) |
| { |
| unset($this->queryParameters[$key]); |
| } |
|
|
| |
| |
| |
| public function getQueryParameters() |
| { |
| return $this->queryParameters; |
| } |
|
|
| |
| |
| |
| public function getOptOutJSEmbedCode( |
| string $matomoUrl, |
| string $language, |
| string $backgroundColor, |
| string $fontColor, |
| string $fontSize, |
| string $fontFamily, |
| bool $applyStyling, |
| bool $showIntro |
| ): string { |
| $parsedUrl = parse_url($matomoUrl); |
|
|
| if ( |
| (!empty($matomoUrl) && false === $parsedUrl) |
| || (!empty($parsedUrl['scheme']) && !in_array(strtolower($parsedUrl['scheme']), ['http', 'https'])) |
| || (empty($parsedUrl['host']) || !Url::isValidHost($parsedUrl['host'])) |
| ) { |
| throw new \Piwik\Exception\Exception('The provided URL is invalid.'); |
| } |
|
|
| |
| |
| $matomoUrl = (strpos($matomoUrl, '//') === 0 ? '//' : '') . UrlHelper::getParseUrlReverse($parsedUrl); |
|
|
| return '<div id="matomo-opt-out"></div> |
| <script src="' . rtrim($matomoUrl, '/') . '/index.php?module=CoreAdminHome&action=optOutJS&divId=matomo-opt-out&language=' . $language . ($applyStyling ? '&backgroundColor=' . $backgroundColor . '&fontColor=' . $fontColor . '&fontSize=' . $fontSize . '&fontFamily=' . $fontFamily : '') . '&showIntro=' . ($showIntro ? '1' : '0') . '"></script>'; |
| } |
|
|
| |
| |
| |
| public function getOptOutSelfContainedEmbedCode( |
| string $backgroundColor, |
| string $fontColor, |
| string $fontSize, |
| string $fontFamily, |
| bool $applyStyling, |
| bool $showIntro |
| ): string { |
|
|
| $cookiePath = Common::getRequestVar('cookiePath', '', 'string'); |
| $cookieDomain = Common::getRequestVar('cookieDomain', '', 'string'); |
|
|
| $settings = [ |
| 'showIntro' => $showIntro, |
| 'divId' => 'matomo-opt-out', |
| 'useSecureCookies' => true, |
| 'cookiePath' => ($cookiePath !== '' ? $cookiePath : null), |
| 'cookieDomain' => ($cookieDomain !== '' ? $cookieDomain : null), |
| 'cookieSameSite' => Common::getRequestVar('cookieSameSite', 'Lax', 'string'), |
| ]; |
|
|
| |
| $settings = array_merge($settings, $this->getTranslations()); |
| $settingsString = 'var settings = ' . json_encode($settings) . ';'; |
|
|
| $styleSheet = $this->optOutStyling($fontSize, $fontColor, $fontFamily, $backgroundColor, true); |
|
|
| $code = <<<HTML |
| <div id="matomo-opt-out" style=""></div> |
| <script> |
| var settings = {}; |
| document.addEventListener('DOMContentLoaded', function() { |
| window.MatomoConsent.init(settings.useSecureCookies, settings.cookiePath, settings.cookieDomain, settings.cookieSameSite); |
| showContent(window.MatomoConsent.hasConsent()); |
| }); |
| |
| window.MatomoConsent = { }; |
| </script> |
| HTML; |
| return str_replace( |
| 'window.MatomoConsent = { };', |
| $this->getOptOutCommonJS(), |
| str_replace( |
| 'style=""', |
| ($applyStyling ? 'style="' . $styleSheet . '"' : ''), |
| str_replace("var settings = {};", $settingsString, $code) |
| ) |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function getOptOutJS(): string |
| { |
|
|
| $language = Common::getRequestVar('language', 'auto', 'string'); |
| $showIntro = Common::getRequestVar('showIntro', 1, 'int'); |
| $divId = Common::getRequestVar('divId', 'matomo-opt-out', 'string'); |
| $useCookiesIfNoTracker = Common::getRequestVar('useCookiesIfNoTracker', 1, 'int'); |
| $useCookiesTimeout = Common::getRequestVar('useCookiesTimeout', 10, 'int'); |
| $useSecureCookies = Common::getRequestVar('useSecureCookies', 1, 'int'); |
| $cookiePath = Common::getRequestVar('cookiePath', '', 'string'); |
| $cookieDomain = Common::getRequestVar('cookieDomain', '', 'string'); |
|
|
| |
| if ($language === 'auto') { |
| $language = Common::extractLanguageAndRegionCodeFromBrowserLanguage( |
| Common::getBrowserLanguage(), |
| APILanguagesManager::getInstance()->getAvailableLanguages() |
| ); |
| } |
|
|
| $settings = [ |
| 'showIntro' => $showIntro, |
| 'divId' => $divId, |
| 'useSecureCookies' => $useSecureCookies, |
| 'cookiePath' => ($cookiePath !== '' ? $cookiePath : null), |
| 'cookieDomain' => ($cookieDomain !== '' ? $cookieDomain : null), |
| 'useCookiesIfNoTracker' => $useCookiesIfNoTracker, |
| 'useCookiesTimeout' => $useCookiesTimeout, |
| ]; |
|
|
| |
| $translations = $this->getTranslations($language); |
| $translations['OptOutErrorNoTracker'] = Piwik::translate('CoreAdminHome_OptOutErrorNoTracker', [], $language); |
| $settings = array_merge($settings, $translations); |
| $settingsString = 'var settings = ' . json_encode($settings) . ';'; |
|
|
| $styleSheet = $this->optOutStyling(null, null, null, null, true); |
|
|
| |
| $code = <<<JS |
| |
| var settings = {}; |
| var checkForTrackerTried = 0; |
| var checkForTrackerTries = (settings.useCookiesTimeout * 4); |
| var checkForTrackerInterval = 250; |
| var optOutDiv = null; |
| |
| function optOutInit() { |
| optOutDiv = document.getElementById(settings.divId); |
| if (optOutDiv) { |
| optOutDiv.style.cssText += 'stylecss'; // Appending css to avoid overwritting existing inline div styles |
| } else { |
| showContent(false, null, true); // will show unable to find opt-out div error |
| return; |
| } |
| checkForMatomoTracker(); |
| } |
| |
| function checkForMatomoTracker() { |
| if (typeof _paq !== 'undefined') { |
| showOptOutTracker(); |
| return; |
| } |
| |
| if (checkForTrackerTried < checkForTrackerTries) { |
| setTimeout(checkForMatomoTracker, checkForTrackerInterval); |
| checkForTrackerTried++; |
| return; |
| } |
| |
| if (settings.useCookiesIfNoTracker) { |
| showOptOutDirect(); |
| return; |
| } |
| |
| console.log('Matomo OptOutJS: failed to find Matomo tracker after '+(checkForTrackerTries*checkForTrackerInterval / 1000)+' seconds'); |
| } |
| |
| function showOptOutTracker() { |
| _paq.push([function () { |
| if (settings.cookieDomain) { |
| _paq.push(['setCookieDomain', settings.cookieDomain]); |
| } |
| if (settings.cookiePath) { |
| _paq.push(['setCookiePath', settings.cookiePath]); |
| } |
| if (this.isUserOptedOut()) { |
| showContent(false, null, true); |
| } else { |
| showContent(true, null, true); |
| } |
| }]); |
| } |
| |
| function showOptOutDirect() { |
| window.MatomoConsent.init(settings.useSecureCookies, settings.cookiePath, settings.cookieDomain, settings.cookieSameSite); |
| showContent(window.MatomoConsent.hasConsent()); |
| } |
| |
| document.addEventListener('DOMContentLoaded', optOutInit()); |
| |
| window.MatomoConsent = { }; |
| JS; |
|
|
| return str_replace( |
| 'window.MatomoConsent = { };', |
| $this->getOptOutCommonJS(), |
| str_replace( |
| 'stylecss', |
| $styleSheet, |
| str_replace("var settings = {};", $settingsString, $code) |
| ) |
| ); |
| } |
|
|
| |
| |
| |
| private function getOptOutCommonJS(): string |
| { |
| |
| return <<<JS |
| |
| function showContent(consent, errorMessage = null, useTracker = false) { |
| |
| var errorBlock = '<p style="color: red; font-weight: bold;">'; |
| |
| var div = document.getElementById(settings.divId); |
| if (!div) { |
| var warningDiv = document.createElement("div"); |
| var msg = 'Unable to find opt-out content div: "'+settings.divId+'"'; |
| warningDiv.id = settings.divId+'-warning'; |
| warningDiv.innerHTML = errorBlock+msg+'</p>'; |
| document.body.insertBefore(warningDiv, document.body.firstChild); |
| console.log(msg); |
| return; |
| } |
| |
| if (!navigator || !navigator.cookieEnabled) { |
| div.innerHTML = errorBlock+settings.OptOutErrorNoCookies+'</p>'; |
| return; |
| } |
| |
| if (errorMessage !== null) { |
| div.innerHTML = errorBlock+errorMessage+'</p>'; |
| return; |
| } |
| |
| var content = ''; |
| |
| if (location.protocol !== 'https:') { |
| content += errorBlock + settings.OptOutErrorNotHttps + '</p>'; |
| } |
| |
| if (consent) { |
| if (settings.showIntro) { |
| content += '<p>'+settings.YouMayOptOut2+' '+settings.YouMayOptOut3+'</p>'; |
| } |
| content += '<input id="trackVisits" type="checkbox" checked="checked" />'; |
| content += '<label for="trackVisits"><strong><span>'+settings.YouAreNotOptedOut+' '+settings.UncheckToOptOut+'</span></strong></label>'; |
| } else { |
| if (settings.showIntro) { |
| content += '<p>'+settings.OptOutComplete+' '+settings.OptOutCompleteBis+'</p>'; |
| } |
| content += '<input id="trackVisits" type="checkbox" />'; |
| content += '<label for="trackVisits"><strong><span>'+settings.YouAreOptedOut+' '+settings.CheckToOptIn+'</span></strong></label>'; |
| } |
| div.innerHTML = content; |
| |
| var tV = document.getElementById('trackVisits'); |
| if (consent) { |
| if (useTracker) { |
| tV.addEventListener("click", function (e) { |
| _paq.push(['optUserOut']); |
| showContent(false, null, true); |
| }); |
| } else { |
| tV.addEventListener("click", function (e) { |
| window.MatomoConsent.consentRevoked(); |
| showContent(false); |
| }); |
| } |
| } else { |
| if (useTracker) { |
| tV.addEventListener("click", function (e) { |
| _paq.push(['forgetUserOptOut']); |
| showContent(true, null, true); |
| }); |
| } else { |
| tV.addEventListener("click", function (e) { |
| window.MatomoConsent.consentGiven(); |
| showContent(true); |
| }); |
| } |
| } |
| }; |
| |
| window.MatomoConsent = { |
| cookiesDisabled: (!navigator || !navigator.cookieEnabled), |
| CONSENT_COOKIE_NAME: 'mtm_consent', CONSENT_REMOVED_COOKIE_NAME: 'mtm_consent_removed', |
| cookieIsSecure: false, useSecureCookies: true, cookiePath: '', cookieDomain: '', cookieSameSite: 'Lax', |
| init: function(useSecureCookies, cookiePath, cookieDomain, cookieSameSite) { |
| this.useSecureCookies = useSecureCookies; this.cookiePath = cookiePath; |
| this.cookieDomain = cookieDomain; this.cookieSameSite = cookieSameSite; |
| if(useSecureCookies && location.protocol !== 'https:') { |
| console.log('Error with setting useSecureCookies: You cannot use this option on http.'); |
| } else { |
| this.cookieIsSecure = useSecureCookies; |
| } |
| }, |
| hasConsent: function() { |
| var consentCookie = this.getCookie(this.CONSENT_COOKIE_NAME); |
| var removedCookie = this.getCookie(this.CONSENT_REMOVED_COOKIE_NAME); |
| if (!consentCookie && !removedCookie) { |
| return true; // No cookies set, so opted in |
| } |
| if (removedCookie && consentCookie) { |
| this.setCookie(this.CONSENT_COOKIE_NAME, '', -129600000); |
| return false; |
| } |
| return (consentCookie || consentCookie !== 0); |
| }, |
| consentGiven: function() { |
| this.setCookie(this.CONSENT_REMOVED_COOKIE_NAME, '', -129600000); |
| this.setCookie(this.CONSENT_COOKIE_NAME, new Date().getTime(), 946080000000); |
| }, |
| consentRevoked: function() { |
| this.setCookie(this.CONSENT_COOKIE_NAME, '', -129600000); |
| this.setCookie(this.CONSENT_REMOVED_COOKIE_NAME, new Date().getTime(), 946080000000); |
| }, |
| getCookie: function(cookieName) { |
| var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)'), cookieMatch = cookiePattern.exec(document.cookie); |
| return cookieMatch ? window.decodeURIComponent(cookieMatch[2]) : 0; |
| }, |
| setCookie: function(cookieName, value, msToExpire) { |
| var expiryDate = new Date(); |
| expiryDate.setTime((new Date().getTime()) + msToExpire); |
| document.cookie = cookieName + '=' + window.encodeURIComponent(value) + |
| (msToExpire ? ';expires=' + expiryDate.toGMTString() : '') + |
| ';path=' + (this.cookiePath || '/') + |
| (this.cookieDomain ? ';domain=' + this.cookieDomain : '') + |
| (this.cookieIsSecure ? ';secure' : '') + |
| ';SameSite=' + this.cookieSameSite; |
| if ((!msToExpire || msToExpire >= 0) && this.getCookie(cookieName) !== String(value)) { |
| console.log('There was an error setting cookie `' + cookieName + '`. Please check domain and path.'); |
| } |
| } |
| }; |
| JS; |
| } |
|
|
| |
| |
| |
| |
| |
| private function getTranslations(?string $language = null): array |
| { |
| return [ |
| 'OptOutComplete' => Piwik::translate('CoreAdminHome_OptOutComplete', [], $language), |
| 'OptOutCompleteBis' => Piwik::translate('CoreAdminHome_OptOutCompleteBis', [], $language), |
| 'YouMayOptOut2' => Piwik::translate('CoreAdminHome_YouMayOptOut2', [], $language), |
| 'YouMayOptOut3' => Piwik::translate('CoreAdminHome_YouMayOptOut3', [], $language), |
| 'OptOutErrorNoCookies' => Piwik::translate('CoreAdminHome_OptOutErrorNoCookies', [], $language), |
| 'OptOutErrorNotHttps' => Piwik::translate('CoreAdminHome_OptOutErrorNotHttps', [], $language), |
| 'YouAreNotOptedOut' => Piwik::translate('CoreAdminHome_YouAreNotOptedOut', [], $language), |
| 'UncheckToOptOut' => Piwik::translate('CoreAdminHome_UncheckToOptOut', [], $language), |
| 'YouAreOptedOut' => Piwik::translate('CoreAdminHome_YouAreOptedOut', [], $language), |
| 'CheckToOptIn' => Piwik::translate('CoreAdminHome_CheckToOptIn', [], $language), |
| ]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function getOptOutViewIFrame() |
| { |
| if ($this->view) { |
| return $this->view; |
| } |
|
|
| $trackVisits = !IgnoreCookie::isIgnoreCookieFound(); |
| $dntFound = $this->getDoNotTrackHeaderChecker()->isDoNotTrackFound(); |
|
|
| $setCookieInNewWindow = Common::getRequestVar('setCookieInNewWindow', false, 'int'); |
| if ($setCookieInNewWindow) { |
| $nonce = Common::getRequestVar('nonce', false); |
|
|
| if ($nonce !== false && !Nonce::verifyNonce('Piwik_OptOut', $nonce)) { |
| Nonce::discardNonce('Piwik_OptOut'); |
| $nonce = ''; |
| } |
|
|
| $reloadUrl = Url::getCurrentQueryStringWithParametersModified(array( |
| 'showConfirmOnly' => 1, |
| 'setCookieInNewWindow' => 0, |
| 'nonce' => $nonce ? : '', |
| )); |
| } else { |
| $reloadUrl = false; |
|
|
| $requestNonce = Common::getRequestVar('nonce', false); |
| if ($requestNonce !== false && Nonce::verifyNonce('Piwik_OptOut', $requestNonce)) { |
| Nonce::discardNonce('Piwik_OptOut'); |
| IgnoreCookie::setIgnoreCookie(); |
| $trackVisits = !$trackVisits; |
| } |
| } |
|
|
| $language = Common::getRequestVar('language', '', 'string'); |
| $lang = APILanguagesManager::getInstance()->isLanguageAvailable($language) |
| ? $language |
| : LanguagesManager::getLanguageCodeForCurrentUser(); |
|
|
| $nonce = Nonce::getNonce('Piwik_OptOut', 3600); |
|
|
| $this->addQueryParameters(array( |
| 'module' => 'CoreAdminHome', |
| 'action' => 'optOut', |
| 'language' => $lang, |
| 'setCookieInNewWindow' => 1, |
| 'nonce' => $nonce, |
| ), false); |
|
|
| if (Common::getRequestVar('applyStyling', 1, 'int')) { |
| $this->addStylesheet($this->optOutStyling()); |
| } |
|
|
| $this->view = new View("@CoreAdminHome/optOut"); |
|
|
| $this->addJavaScript('plugins/CoreAdminHome/javascripts/optOut.js', false); |
|
|
| $this->view->setXFrameOptions('allow'); |
| $this->view->dntFound = $dntFound; |
| $this->view->trackVisits = $trackVisits; |
| $this->view->nonce = $nonce; |
| $this->view->language = $lang; |
| $this->view->showIntro = Common::getRequestVar('showIntro', 1, 'int'); |
| $this->view->showConfirmOnly = Common::getRequestVar('showConfirmOnly', false, 'int'); |
| $this->view->reloadUrl = $reloadUrl; |
| $this->view->javascripts = $this->getJavaScripts(); |
| $this->view->stylesheets = $this->getStylesheets(); |
| $this->view->title = $this->getTitle(); |
| $this->view->queryParameters = $this->getQueryParameters(); |
| return $this->view; |
| } |
|
|
| |
| |
| |
| |
| |
| private function optOutStyling( |
| ?string $fontSize = null, |
| ?string $fontColor = null, |
| ?string $fontFamily = null, |
| ?string $backgroundColor = null, |
| bool $noBody = false |
| ): string { |
| $cssfontsize = ($fontSize ? : Request::fromRequest()->getStringParameter('fontSize', '')); |
| $cssfontcolour = ($fontColor ? : Request::fromRequest()->getStringParameter('fontColor', '')); |
| $cssfontfamily = ($fontFamily ? : Request::fromRequest()->getStringParameter('fontFamily', '')); |
| $cssbackgroundcolor = ($backgroundColor ? : Request::fromRequest()->getStringParameter('backgroundColor', '')); |
|
|
| if (!$noBody) { |
| $cssbody = 'body { '; |
| } else { |
| $cssbody = ''; |
| } |
|
|
| $hexstrings = array( |
| 'fontColor' => $cssfontcolour, |
| 'backgroundColor' => $cssbackgroundcolor, |
| ); |
| foreach ($hexstrings as $key => $testcase) { |
| if ($testcase && !(ctype_xdigit($testcase) && in_array(strlen($testcase), array(3,6), true))) { |
| throw new \Exception("The URL parameter $key value of '$testcase' is not valid. Expected value is for example 'ffffff' or 'fff'.\n"); |
| } |
| } |
|
|
| |
| if ($cssfontsize && (preg_match("/^[0-9]+[\.]?[0-9]*(px|pt|em|rem|%)$/", $cssfontsize))) { |
| $cssbody .= 'font-size: ' . $cssfontsize . '; '; |
| } elseif ($cssfontsize) { |
| throw new \Exception("The URL parameter fontSize value of '$cssfontsize' is not valid. Expected value is for example '15pt', '1.2em' or '13px'.\n"); |
| } |
|
|
| |
| if ($cssfontfamily && (preg_match('/^[a-zA-Z0-9-\ ,\'"]+$/', $cssfontfamily))) { |
| $cssbody .= 'font-family: ' . $cssfontfamily . '; '; |
| } elseif ($cssfontfamily) { |
| throw new \Exception("The URL parameter fontFamily value of '$cssfontfamily' is not valid. Expected value is for example 'sans-serif' or 'Monaco, monospace'.\n"); |
| } |
|
|
| if ($cssfontcolour) { |
| $cssbody .= 'color: #' . $cssfontcolour . '; '; |
| } |
| if ($cssbackgroundcolor) { |
| $cssbody .= 'background-color: #' . $cssbackgroundcolor . '; '; |
| } |
|
|
| if (!$noBody) { |
| $cssbody .= '}'; |
| } |
| return $cssbody; |
| } |
| |
| |
| |
| protected function getDoNotTrackHeaderChecker() |
| { |
| return $this->doNotTrackHeaderChecker; |
| } |
| } |
|
|