diff options
22 files changed, 460 insertions, 26 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d5ecdaa3..281a670082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,11 @@ The Product Changelog at **[matomo.org/changelog](https://matomo.org/changelog)* * Support for tracking and reporting of these browser plugins has been discontinued: Gears, Director * Added new event `Db.getTablesInstalled`, plugins should use to register the tables they create. +## Matomo 3.13.6 + +### API Changes +* The first parameter `userLogin` in the `UsersManager.getUserPreference` method is now optional and defaults to the currently authenticated user login. + ## Matomo 3.13.5 ### New API @@ -932,6 +937,7 @@ We are using `@since` annotations in case we are introducing new API's to make i ### Breaking Changes ### Deprecations +### API Changes ### New features ### New APIs ### New commands diff --git a/core/CliMulti/RequestCommand.php b/core/CliMulti/RequestCommand.php index ad75427566..725ac6ecf1 100644 --- a/core/CliMulti/RequestCommand.php +++ b/core/CliMulti/RequestCommand.php @@ -81,6 +81,10 @@ class RequestCommand extends ConsoleCommand if (!empty($process)) { $process->finishProcess(); } + + while (ob_get_level()) { + echo ob_get_clean(); + } } private function isTestModeEnabled() diff --git a/core/Config/IniFileChain.php b/core/Config/IniFileChain.php index b3028fde09..2801cbeb26 100644 --- a/core/Config/IniFileChain.php +++ b/core/Config/IniFileChain.php @@ -97,6 +97,11 @@ class IniFileChain */ public function set($name, $value) { + $name = $this->replaceSectionInvalidChars($name); + if ($value !== null) { + $value = $this->replaceInvalidChars($value); + } + $this->mergedSettings[$name] = $value; } @@ -538,4 +543,27 @@ class IniFileChain $writer = new IniWriter(); return $writer->writeToString($values, $header); } + + private function replaceInvalidChars($value) + { + if (is_array($value)) { + $result = []; + foreach ($value as $key => $arrayValue) { + $key = $this->replaceInvalidChars($key); + if (is_array($arrayValue)) { + $arrayValue = $this->replaceInvalidChars($arrayValue); + } + + $result[$key] = $arrayValue; + } + return $result; + } else { + return preg_replace('/[^a-zA-Z0-9_\[\]-]/', '', $value); + } + } + + private function replaceSectionInvalidChars($value) + { + return preg_replace('/[^a-zA-Z0-9_-]/', '', $value); + } } diff --git a/core/DataAccess/ArchiveSelector.php b/core/DataAccess/ArchiveSelector.php index 3588335431..83670433a3 100644 --- a/core/DataAccess/ArchiveSelector.php +++ b/core/DataAccess/ArchiveSelector.php @@ -96,6 +96,10 @@ class ArchiveSelector $minDatetimeArchiveProcessedUTC = Date::factory($minDatetimeArchiveProcessedUTC); } + if (!empty($minDatetimeArchiveProcessedUTC) && !is_object($minDatetimeArchiveProcessedUTC)) { + $minDatetimeArchiveProcessedUTC = Date::factory($minDatetimeArchiveProcessedUTC); + } + // the archive is too old if ($minDatetimeArchiveProcessedUTC && isset($result['idarchive']) diff --git a/core/Db/Adapter.php b/core/Db/Adapter.php index 21769b37bc..7beccd9c24 100644 --- a/core/Db/Adapter.php +++ b/core/Db/Adapter.php @@ -25,7 +25,7 @@ class Adapter public static function factory($adapterName, & $dbInfos, $connect = true) { if ($connect) { - if ($dbInfos['port'][0] === '/') { + if (isset($dbInfos['port']) && is_string($dbInfos['port']) && $dbInfos['port'][0] === '/') { $dbInfos['unix_socket'] = $dbInfos['port']; unset($dbInfos['host']); unset($dbInfos['port']); diff --git a/core/Theme.php b/core/Theme.php index 833c0c1aec..4417dbac45 100644 --- a/core/Theme.php +++ b/core/Theme.php @@ -146,7 +146,7 @@ class Theme foreach (Manager::getAlternativeWebRootDirectories() as $absDir => $webRootDirectory) { $withoutPlugins = str_replace('plugins/', '', $pathAsset); if (file_exists($absDir . '/' . $withoutPlugins)) { - return $webRootDirectory . $withoutPlugins; + return str_replace($pathAsset, $webRootDirectory . $withoutPlugins, $source); } } diff --git a/core/Tracker/FingerprintSalt.php b/core/Tracker/FingerprintSalt.php new file mode 100644 index 0000000000..9896c074ca --- /dev/null +++ b/core/Tracker/FingerprintSalt.php @@ -0,0 +1,88 @@ +<?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\Tracker; + +use Piwik\Common; +use Piwik\Date; +use Piwik\Exception\InvalidRequestParameterException; +use Piwik\Exception\UnexpectedWebsiteFoundException; +use Piwik\Option; +use Piwik\Piwik; +use Piwik\SettingsServer; +use Piwik\Site; +use Piwik\Db as PiwikDb; + +class FingerprintSalt +{ + const OPTION_PREFIX = 'fingerprint_salt_'; + const DELETE_FINGERPRINT_OLDER_THAN_SECONDS = 432000; // 5 days in seconds + + public function generateSalt() + { + return Common::getRandomString(32); + } + + public function deleteOldSalts() + { + // we want to make sure to delete salts that were created more than three days ago as they are likely not in + // use anymore. We should delete them to ensure the fingerprint is truly random for each day because if we used + // eg the regular salt then it would technically still be possible to try and regenerate the fingerprint based + // on certain information. + // Typically, only the salts for today and yesterday are used. However, if someone was to import historical data + // for the same day and this takes more than five days, then it could technically happen that we delete a + // fingerprint that is still in use now and as such after deletion a few visitors would have a new configId + // within one visit and such a new visit would be created. That should be very much edge case though. + $deleteSaltsCreatedBefore = Date::getNowTimestamp() - self::DELETE_FINGERPRINT_OLDER_THAN_SECONDS; + $options = Option::getLike(self::OPTION_PREFIX . '%'); + $deleted = array(); + foreach ($options as $name => $value) { + $value = $this->decode($value); + if (empty($value['time']) || $value['time'] < $deleteSaltsCreatedBefore) { + Option::delete($name); + $deleted[] = $name; + } + } + + return $deleted; + } + + public function getDateString(Date $date, $timezone) + { + $dateString = Date::factory($date->getTimestampUTC(), $timezone)->toString(); + return $dateString; + } + + private function encode($value) + { + return json_encode($value); + } + + private function decode($value) + { + return @json_decode($value, true); + } + + public function getSalt($dateString, $idSite) + { + $fingerprintSaltKey = self::OPTION_PREFIX . (int) $idSite . '_' . $dateString; + $salt = Option::get($fingerprintSaltKey); + if (!empty($salt)) { + $salt = $this->decode($salt); + } + if (empty($salt['value'])) { + $salt = array( + 'value' => $this->generateSalt(), + 'time' => Date::getNowTimestamp() + ); + Option::set($fingerprintSaltKey, $this->encode($salt)); + } + return $salt['value']; + } +} diff --git a/core/Tracker/Settings.php b/core/Tracker/Settings.php index fd492a5734..656d07dcca 100644 --- a/core/Tracker/Settings.php +++ b/core/Tracker/Settings.php @@ -8,7 +8,9 @@ */ namespace Piwik\Tracker; +use Piwik\Config; use Piwik\Container\StaticContainer; +use Piwik\Date; use Piwik\Tracker; use Piwik\DeviceDetector\DeviceDetectorFactory; use Piwik\SettingsPiwik; @@ -56,6 +58,31 @@ class Settings // TODO: merge w/ visitor recognizer or make it it's own service. } $browserLang = substr($request->getBrowserLanguage(), 0, 20); // limit the length of this string to match db + $trackerConfig = Config::getInstance()->Tracker; + + $fingerprintSalt = ''; + + // fingerprint salt won't work when across multiple sites since all sites could have different timezones + // also cant add fingerprint salt for a specific day when we dont create new visit after midnight + if (!$this->isSameFingerprintsAcrossWebsites && !empty($trackerConfig['create_new_visit_after_midnight'])) { + $cache = Cache::getCacheWebsiteAttributes($request->getIdSite()); + $date = Date::factory((int) $request->getCurrentTimestamp()); + $fingerprintSaltKey = new FingerprintSalt(); + $dateString = $fingerprintSaltKey->getDateString($date, $cache['timezone']); + + if (!empty($cache[FingerprintSalt::OPTION_PREFIX . $dateString])) { + $fingerprintSalt = $cache[FingerprintSalt::OPTION_PREFIX . $dateString]; + } else { + // we query the DB directly for requests older than 2-3 days... + $fingerprintSalt = $fingerprintSaltKey->getSalt($dateString, $request->getIdSite()); + } + + $fingerprintSalt .= $dateString; + + if (defined('PIWIK_TEST_MODE') && PIWIK_TEST_MODE) { + $fingerprintSalt = ''; // use fixed value so they don't change randomly in tests + } + } return $this->getConfigHash( $request, @@ -71,7 +98,8 @@ class Settings // TODO: merge w/ visitor recognizer or make it it's own service. $plugin_Silverlight, $plugin_Cookie, $ipAddress, - $browserLang); + $browserLang, + $fingerprintSalt); } /** @@ -91,12 +119,13 @@ class Settings // TODO: merge w/ visitor recognizer or make it it's own service. * @param $plugin_Cookie * @param $ip * @param $browserLang + * @param $fingerprintHash * @return string */ protected function getConfigHash(Request $request, $os, $browserName, $browserVersion, $plugin_Flash, $plugin_Java, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, $plugin_WindowsMedia, $plugin_Silverlight, $plugin_Cookie, $ip, - $browserLang) + $browserLang, $fingerprintHash) { // prevent the config hash from being the same, across different Piwik instances // (limits ability of different Piwik instances to cross-match users) @@ -109,7 +138,8 @@ class Settings // TODO: merge w/ visitor recognizer or make it it's own service. . $plugin_WindowsMedia . '0' . $plugin_Silverlight . $plugin_Cookie . $ip . $browserLang - . $salt; + . $salt + . $fingerprintHash; if (!$this->isSameFingerprintsAcrossWebsites) { $configString .= $request->getIdSite(); diff --git a/plugins/CoreAdminHome/Tasks.php b/plugins/CoreAdminHome/Tasks.php index c3afd5562d..aa026de425 100644 --- a/plugins/CoreAdminHome/Tasks.php +++ b/plugins/CoreAdminHome/Tasks.php @@ -32,6 +32,7 @@ use Piwik\Scheduler\Schedule\SpecificTime; use Piwik\Settings\Storage\Backend\MeasurableSettingsTable; use Piwik\Tracker\Failures; use Piwik\Site; +use Piwik\Tracker\FingerprintSalt; use Piwik\Tracker\Visit\ReferrerSpamFilter; use Psr\Log\LoggerInterface; use Piwik\SettingsPiwik; @@ -67,6 +68,8 @@ class Tasks extends \Piwik\Plugin\Tasks // sure all archives that need to be invalidated get invalidated $this->daily('invalidateOutdatedArchives', null, self::HIGH_PRIORITY); + $this->daily('deleteOldFingerprintSalts', null, self::HIGH_PRIORITY); + // general data purge on older archive tables, executed daily $this->daily('purgeOutdatedArchives', null, self::HIGH_PRIORITY); @@ -89,6 +92,11 @@ class Tasks extends \Piwik\Plugin\Tasks $this->scheduleTrackingCodeReminderChecks(); } + public function deleteOldFingerprintSalts() + { + StaticContainer::get(FingerprintSalt::class)->deleteOldSalts(); + } + public function invalidateOutdatedArchives() { if (!Rules::isBrowserTriggerEnabled()) { @@ -278,6 +286,12 @@ class Tasks extends \Piwik\Plugin\Tasks if (empty($purgedDates[$yesterdayStr])) { $this->archivePurger->purgeInvalidatedArchivesFrom($yesterday); } + + // handle year start table + $yearStart = $today->toString('Y-01'); + if (empty($purgedDates[$yearStart])) { + $this->archivePurger->purgeInvalidatedArchivesFrom(Date::factory($yearStart . '-01')); + } } public function optimizeArchiveTable() diff --git a/plugins/CoreAdminHome/tests/Integration/TasksTest.php b/plugins/CoreAdminHome/tests/Integration/TasksTest.php index 68459951b6..e3611596f0 100644 --- a/plugins/CoreAdminHome/tests/Integration/TasksTest.php +++ b/plugins/CoreAdminHome/tests/Integration/TasksTest.php @@ -133,6 +133,7 @@ class TasksTest extends IntegrationTestCase $expected = [ 'invalidateOutdatedArchives.', + 'deleteOldFingerprintSalts.', 'purgeOutdatedArchives.', 'purgeInvalidatedArchives.', 'purgeOrphanedArchives.', diff --git a/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php b/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php index 9928932e26..98fee4696f 100644 --- a/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php +++ b/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php @@ -41,9 +41,7 @@ class PluginTrackerFiles $dirs = array(); $manager = Plugin\Manager::getInstance(); foreach ($manager->getPluginsLoadedAndActivated() as $pluginName => $plugin) { - if ($plugin->isTrackerPlugin()) { - $dirs[$pluginName] = rtrim(Plugin\Manager::getPluginDirectory($pluginName), '/') . '/'; - } + $dirs[$pluginName] = rtrim(Plugin\Manager::getPluginDirectory($pluginName), '/') . '/'; } return $dirs; } diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php index 4452c914b4..b39e03c621 100644 --- a/plugins/Installation/Controller.php +++ b/plugins/Installation/Controller.php @@ -290,6 +290,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin $newsletterPiwikORG, $newsletterProfessionalServices ); + Onboarding::sendSysAdminMail($email); $this->redirectToNextStep(__FUNCTION__); } catch (Exception $e) { diff --git a/plugins/Installation/Onboarding.php b/plugins/Installation/Onboarding.php new file mode 100644 index 0000000000..90bc2f12d8 --- /dev/null +++ b/plugins/Installation/Onboarding.php @@ -0,0 +1,77 @@ +<?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\Plugins\Installation; + +use Piwik\Mail; +use Piwik\Option; +use Piwik\Piwik; + +class Onboarding +{ + const OPTION_NAME_INSTALL_MAIL = 'install_mail_sent'; + + public static function sendSysAdminMail($email) + { + if (!Piwik::isValidEmailString($email)) { + return; + } + if (Option::get(self::OPTION_NAME_INSTALL_MAIL)) { + return; + } + Option::set(self::OPTION_NAME_INSTALL_MAIL, 1); + + $message = 'Hey there,<br> +<br> +Thank you for installing Matomo On-Premises, the #1 Google Analytics alternative that protects your data.<br> +<br> +You’re receiving this email from your Matomo instance because you\'re the Super User and you have just finished installing Matomo On-Premise. You’re the only person who will receive this email. The mail was sent from your Matomo.<br> +<br> +It’s now our job to ensure you get the best possible Matomo experience without any disruptions, so we hope to answer the three most common problems we find users ask when starting out with Matomo.<br> +<br> +<strong>1. Speed up your Matomo by generating your reports in the background</strong><br> +This is a common problem for first time users that can easily be fixed in a few minutes. What you’ll need to do is set up auto-archiving of your reports. I have provided you with a link of step-by-step instructions on how to do this below:<br> +<a href="https://matomo.org/docs/setup-auto-archiving/">>> Set up auto-archiving of your reports</a><br><br> +<strong>2. Get the server size right for your traffic</strong><br> +Matomo is a platform designed to be fast, no matter the size of your database and how many visits you’re tracking. Here we can recommend the best infrastructure to host your Matomo.<br> +<a href="https://matomo.org/docs/requirements/#recommended-servers-sizing-cpu-ram-disks">>> Learn the recommended server configuration and sizing to run Matomo with speed</a><br> +<br> +<strong>3. Next, make sure your data is secure</strong><br> +Privacy and security are of utmost importance to the Matomo team and we want to make sure security is up to the standard you need.<br> +Below is a link that will give your Matomo administrator a list of tips and best practices to improve security.<br> +<a href="https://matomo.org/docs/security/">>> Tips for staying secure in Matomo</a><br> +<br> +<strong>Need more help?</strong><br> +<ul><li>Join our forum</li> +<li>If there is a feature you’d like to see in Matomo, submit a request through Github</li> +<li>And if you want first-hand assistance from our expert team, support plans are available for your business</li> +</ul> +<br> +It’s so great to have you be part of the Matomo community! We hope to deliver you the valuable insights you need to make better data-driven decisions for your website.<br> +<br> +Happy Analytics,<br> +<br> +Matthieu<br> +Matomo Founder<br> +<br> +'; + + $mail = new Mail(); + $mail->addTo($email); + $mail->setSubject('Congratulations for setting up Matomo'); + $mail->setBodyHtml($message); + + try { + $mail->send(); + } catch (\Exception $e) { + // Mail might not be configured yet and it won't work... + } + } + + +} diff --git a/plugins/Live/javascripts/live.js b/plugins/Live/javascripts/live.js index 3e329e8183..80bce4098f 100644 --- a/plugins/Live/javascripts/live.js +++ b/plugins/Live/javascripts/live.js @@ -74,6 +74,8 @@ that._parseResponse(r); } + that.options.interval = parseInt(that.options.interval, 10); + // add default interval to last interval if not updated or reset to default if so if (!that.updated) { that.currentInterval += that.options.interval; @@ -168,7 +170,7 @@ return; } - this.currentInterval = this.options.interval; + this.currentInterval = parseInt(this.options.interval, 10); if (0 === $(this.element).parents('.widget').length) { var $rootScope = piwikHelper.getAngularDependency('$rootScope'); diff --git a/plugins/Morpheus/stylesheets/uibase/_header.less b/plugins/Morpheus/stylesheets/uibase/_header.less index f9d8d2d19f..47c2e31475 100644 --- a/plugins/Morpheus/stylesheets/uibase/_header.less +++ b/plugins/Morpheus/stylesheets/uibase/_header.less @@ -18,17 +18,6 @@ } } -// IE 10+ -@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - #root #logo { - width: 80px; - } - - #root #logo img.default-piwik-logo { - width: 100%; - } -} - #javascriptDisabled, #javascriptDisabled a { font-weight: bold; diff --git a/plugins/SitesManager/SitesManager.php b/plugins/SitesManager/SitesManager.php index 0644e4df37..fbf6af7278 100644 --- a/plugins/SitesManager/SitesManager.php +++ b/plugins/SitesManager/SitesManager.php @@ -13,12 +13,14 @@ use Piwik\API\Request; use Piwik\Common; use Piwik\Config; use Piwik\Container\StaticContainer; +use Piwik\Date; use Piwik\Exception\UnexpectedWebsiteFoundException; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugins\CoreHome\SystemSummary; use Piwik\Settings\Storage\Backend\MeasurableSettingsTable; use Piwik\Tracker\Cache; +use Piwik\Tracker\FingerprintSalt; use Piwik\Tracker\Model as TrackerModel; use Piwik\Session\SessionNamespace; @@ -197,6 +199,16 @@ class SitesManager extends \Piwik\Plugin $array['timezone'] = $this->getTimezoneFromWebsite($website); $array['ts_created'] = $website['ts_created']; $array['type'] = $website['type']; + + // we make sure to have the fingerprint salts for the last 3 days incl tmrw in the cache so we don't need to + // query the DB directly for these days + $datesToGenerateSalt = array(Date::now()->addDay(1), Date::now(), Date::now()->subDay(1), Date::now()->subDay(2)); + + $fingerprintSaltKey = new FingerprintSalt(); + foreach ($datesToGenerateSalt as $date) { + $dateString = $fingerprintSaltKey->getDateString($date, $array['timezone']); + $array[FingerprintSalt::OPTION_PREFIX . $dateString] = $fingerprintSaltKey->getSalt($dateString, $idSite); + } } public function setTrackerCacheGeneral(&$cache) diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php index 697dfa5566..bc4cb9e08d 100644 --- a/plugins/UsersManager/API.php +++ b/plugins/UsersManager/API.php @@ -193,12 +193,18 @@ class API extends \Piwik\Plugin\API /** * Gets a user preference - * @param string $userLogin + * @param string $userLogin Optional, defaults to current user log in. * @param string $preferenceName * @return bool|string */ - public function getUserPreference($userLogin, $preferenceName) + public function getUserPreference($userLogin = false, $preferenceName) { + if ($userLogin === false) { + // the default value for first parameter is there to have it an optional parameter in the HTTP API + // in PHP it won't be optional. Could move parameter to the end of the method but did not want to break + // BC + $userLogin = Piwik::getCurrentUserLogin(); + } Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); $optionValue = $this->getPreferenceValue($userLogin, $preferenceName); diff --git a/plugins/UsersManager/UsersManager.php b/plugins/UsersManager/UsersManager.php index 797590bc98..bcb3c7dc15 100644 --- a/plugins/UsersManager/UsersManager.php +++ b/plugins/UsersManager/UsersManager.php @@ -201,8 +201,9 @@ class UsersManager extends \Piwik\Plugin public static function getPasswordHash($password) { - self::checkBasicPasswordStrength($password); - + if (SettingsPiwik::isUserCredentialsSanityCheckEnabled()) { + self::checkBasicPasswordStrength($password); + } // if change here, should also edit the installation process // to change how the root pwd is saved in the config file return md5($password); diff --git a/plugins/UsersManager/tests/System/ApiTest.php b/plugins/UsersManager/tests/System/ApiTest.php index e96dbf9016..580a69e59f 100644 --- a/plugins/UsersManager/tests/System/ApiTest.php +++ b/plugins/UsersManager/tests/System/ApiTest.php @@ -9,6 +9,8 @@ namespace Piwik\Plugins\UsersManager\tests\System; use Piwik\Date; +use Piwik\API\Request; +use Piwik\Piwik; use Piwik\Plugins\UsersManager\API; use Piwik\Plugins\UsersManager\Model; use Piwik\Plugins\UsersManager\tests\Fixtures\ManyUsers; @@ -65,6 +67,35 @@ class ApiTest extends SystemTestCase } } + public function test_getUserPreference_loginIsOptional() + { + $response = Request::processRequest('UsersManager.getUserPreference', array( + 'preferenceName' => API::PREFERENCE_DEFAULT_REPORT + )); + $this->assertEquals('1', $response); + + $response = Request::processRequest('UsersManager.getUserPreference', array( + 'preferenceName' => API::PREFERENCE_DEFAULT_REPORT_DATE + )); + $this->assertEquals('yesterday', $response); + } + + public function test_getUserPreference_loginCanBeSet() + { + $response = Request::processRequest('UsersManager.getUserPreference', array( + 'userLogin' => Piwik::getCurrentUserLogin(), + 'preferenceName' => API::PREFERENCE_DEFAULT_REPORT_DATE + )); + $this->assertEquals('yesterday', $response); + + // user not exists + $response = Request::processRequest('UsersManager.getUserPreference', array( + 'userLogin' => 'foo', + 'preferenceName' => API::PREFERENCE_DEFAULT_REPORT_DATE + )); + $this->assertEquals('yesterday', $response); + } + public function getApiForTesting() { $apiToTest = array( diff --git a/tests/PHPUnit/Integration/Tracker/FingerprintSaltTest.php b/tests/PHPUnit/Integration/Tracker/FingerprintSaltTest.php new file mode 100644 index 0000000000..087ba6ff8a --- /dev/null +++ b/tests/PHPUnit/Integration/Tracker/FingerprintSaltTest.php @@ -0,0 +1,109 @@ +<?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\Tests\Integration\Tracker; + +use Piwik\Config; +use Piwik\Date; +use Piwik\Plugins\SitesManager\API; +use Piwik\Tests\Framework\Mock\FakeAccess; +use Piwik\Tracker\Action; +use Piwik\Tracker\FingerprintSalt; +use Piwik\Tracker\PageUrl; +use Piwik\Tracker\Request; +use Piwik\Translate; +use Piwik\Plugin\Manager as PluginManager; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; + +/** + * @group Core + * @group ActionTest + */ +class FingerprintSaltTest extends IntegrationTestCase +{ + /** + * @var FingerprintSalt + */ + private $fingerprintSalt; + + public function setUp(): void + { + parent::setUp(); + + $this->fingerprintSalt = new FingerprintSalt(); + } + + public function test_generateSalt() + { + $salt = $this->fingerprintSalt->generateSalt(); + $this->assertEquals(32, strlen($salt)); + $this->assertTrue(ctype_alnum($salt)); + } + + public function test_generateSalt_isRandom() + { + $this->assertNotSame($this->fingerprintSalt->generateSalt(), $this->fingerprintSalt->generateSalt()); + } + + public function test_getDateString() + { + $date = Date::factory('2020-05-05 14:04:05'); + $this->assertSame('2020-05-06',$this->fingerprintSalt->getDateString($date, 'Pacific/Auckland')); + $this->assertSame('2020-05-05',$this->fingerprintSalt->getDateString($date, 'Europe/Berlin')); + } + + public function test_getSalt_remembersSaltPerSite() + { + $salt05_1 = $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 1); + $salt06_1 = $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 1); + $salt05_2 = $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 2); + $salt06_2 = $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 2); + + $this->assertNotSame($salt05_1, $salt06_1); + $this->assertNotSame($salt05_2, $salt06_2); + $this->assertNotSame($salt06_1, $salt06_2); + + $this->assertSame($salt05_1, $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 1)); + $this->assertSame($salt06_1, $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 1)); + $this->assertSame($salt05_2, $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 2)); + } + + public function test_deleteOldSalts_whenNothingToDelete() + { + $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 1); + $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 1); + + Date::$now = time() - FingerprintSalt::DELETE_FINGERPRINT_OLDER_THAN_SECONDS + 30;// they would expire in 30 seconds + $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 2); + $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 2); + + Date::$now = time(); + $this->assertSame(array(), $this->fingerprintSalt->deleteOldSalts()); + } + + public function test_deleteOldSalts_someToBeDeleted() + { + $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 1); + + Date::$now = time() - FingerprintSalt::DELETE_FINGERPRINT_OLDER_THAN_SECONDS - 30; // these entries should be expired + $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 1); + $this->fingerprintSalt->getSalt('2020-05-05', $idSite = 2); + $this->fingerprintSalt->getSalt('2020-05-06', $idSite = 2); + + Date::$now = time(); + $this->assertSame(array( + 'fingerprint_salt_1_2020-05-06', + 'fingerprint_salt_2_2020-05-05', + 'fingerprint_salt_2_2020-05-06' + ), $this->fingerprintSalt->deleteOldSalts()); + + // executing it again wont delete anything + $this->assertSame(array(), $this->fingerprintSalt->deleteOldSalts()); + } + +} diff --git a/tests/PHPUnit/Unit/Config/IniFileChainTest.php b/tests/PHPUnit/Unit/Config/IniFileChainTest.php index adfd203092..0274d12fba 100644 --- a/tests/PHPUnit/Unit/Config/IniFileChainTest.php +++ b/tests/PHPUnit/Unit/Config/IniFileChainTest.php @@ -8,6 +8,7 @@ namespace Piwik\Tests\Unit\Config; use PHPUnit\Framework\TestCase; +use Piwik\Config; use Piwik\Config\IniFileChain; /** @@ -379,4 +380,36 @@ class IniFileChainTest extends TestCase $actualOutput = $fileChain->dumpChanges($header); $this->assertEquals($expectedDumpChanges, $actualOutput); } + + + public function test_dump_handlesSpecialCharsCorrectly() + { + $config = new IniFileChain(); + $config->set('first', ["a[]\n\n[d]\n\nb=4" => "\n\n[def]\na=b"]); + $config->set('second', ["a[]\n\n[d]b=4" => 'b']); + $config->set('thir][d]', ['a' => 'b']); + $config->set("four]\n\n[def]\n", ['d[]' => 'e']); + $out = $config->dump(); + + $expected = <<<END +[first] +a[][d]b4 = " + +[def] +a=b" + +[second] +a[][d]b4 = "b" + +[third] +a = "b" + +[fourdef] +d[] = "e" + + +END; + + $this->assertEquals($expected, $out); + } }
\ No newline at end of file diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png index 0b9237c8d1..b7fd2edbb6 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c1090b268fc8079be7251734ee6a3758453124e50bc6b7da48caf8a7bf7b31d -size 4934942 +oid sha256:bc2a3504a68bca4c62f46bd42299297eca4849eb4e52263175ccbbf0d705d585 +size 4935210 |