Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
346 changes: 346 additions & 0 deletions .git-hooks-matomo/pre-push

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ jobs:
uses: matomo-org/plugin-ci-workflows/.github/workflows/plugin-ci.yml@main
with:
plugin-name: TrackingSpamPrevention
verify-hook: true
2 changes: 1 addition & 1 deletion BlockedGeoIp.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function isExcludedCountry($ip, $language, $excludedCountries, $includedC
return true;
}

if (!empty($excludedCountries) && in_array($countryCode, $excludedCountries, true)) {
if (in_array($countryCode, $excludedCountries, true)) {
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion BlockedIpRanges/DigitalOcean.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function getRanges(): array
{
$digitalOcean = Http::sendHttpRequest('https://www.digitalocean.com/geo/google.csv', 120, null, null, 0, false, false, true);

if (empty($digitalOcean) || empty($digitalOcean['status']) || $digitalOcean['status'] != 200) {
if (empty($digitalOcean['status']) || $digitalOcean['status'] != 200) {
throw new \Exception('Failed to retrieve digital ocean IP ranges');
}

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Changelog

# 5.2.1 - 2026-09-28
- Fixed the 5.0.11 plugin update failing on Matomo versions before 5.9

# 5.2.0 - 2026-08-10
- Added an "Organisation block list" setting to the UI (General Settings, shown while "Block tracking requests from the cloud" is enabled) to manage the organisations blocked by that feature. Existing `block_geoip_organisations` config values are migrated to the new `organisation_block_list` system setting and removed from the config file; an emptied config list is migrated as an empty list, keeping organisation blocking disabled, while a removed config key results in the default block list applying again
- The `trackingspamprevention:block-geo-ip-organisation` command now saves the organisation to the new system setting instead of the config file
Expand Down
16 changes: 12 additions & 4 deletions SystemSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@ private function makeIpRangeListSetting(string $name, string $titleKey, string $
return [];
}
$ips = array_map('trim', $value);
$ips = array_filter($ips, 'strlen');
$ips = array_filter($ips, function ($ip) {
return $ip !== '';
});
return array_values(array_unique($ips));
};
});
Expand All @@ -260,7 +262,9 @@ private function makeOrganisationBlockListSetting(): Setting
$organisations = array_map(function ($organisation) {
return mb_strtolower(trim((string) $organisation));
}, $value);
$organisations = array_filter($organisations, 'strlen');
$organisations = array_filter($organisations, function ($organisation) {
return $organisation !== '';
});
return array_values(array_unique($organisations));
};
});
Expand Down Expand Up @@ -296,7 +300,9 @@ private function settingToIpRanges(Setting $setting): array
}

// values set through a config file override skip the setting's transform, so clean them up here too
return array_values(array_filter(array_map('trim', $value), 'strlen'));
return array_values(array_filter(array_map('trim', $value), function ($ip) {
return $ip !== '';
}));
}

public function getBlockedOrganisations(): array
Expand All @@ -312,7 +318,9 @@ public function getBlockedOrganisations(): array
return mb_strtolower(trim((string) $organisation));
}, $value);

return array_values(array_filter($organisations, 'strlen'));
return array_values(array_filter($organisations, function ($organisation) {
return $organisation !== '';
}));
}

public function getExcludedCountryCodes()
Expand Down
14 changes: 10 additions & 4 deletions Updates/5.0.11.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Piwik\Plugins\TrackingSpamPrevention;

use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Settings\Storage\Factory;
use Piwik\Updater;
use Piwik\Updates as PiwikUpdates;
Expand All @@ -24,13 +25,18 @@ public function doUpdate(Updater $updater)
return;
}

$backend = (new Factory())->getPluginStorage('TrackingSpamPrevention', '')->getBackend();
$currentValue = $backend->loadValue('block_headless', null);
// the container's storage is the copy SystemSettings reads and saves, so the 5.1.0 and 5.2.0
// updates that can follow in the same run keep this value. SystemSettings::save() itself is
// avoided because it can start a cloud IP range sync.
$storage = StaticContainer::get(Factory::class)->getPluginStorage('TrackingSpamPrevention', '');

if ($currentValue !== null) {
// a setting reads as its default when nothing is stored, so check the stored rows
// (load() rather than loadValue(), which only exists from Matomo 5.9)
if (array_key_exists('block_headless', $storage->getBackend()->load())) {
return;
}

$backend->saveValue('block_headless', false);
$storage->setValue('block_headless', false);
$storage->save();
}
}
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 0
level: 5
# This plugin is on the Matomo 5 line, whose floor is PHP 7.2 rather than the 8.1 the Matomo 6
# plugins analyse against. PHPStan parses at this version, so it rejects syntax the plugin
# could not actually run on.
Expand Down
6 changes: 6 additions & 0 deletions phpstan/phpstan.created.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
includes:
- ../phpstan.neon
parameters:
# new files carry no pre-existing debt, so hold them to the strictest level
level: 9
tmpDir: /tmp/phpstan/TrackingSpamPrevention/created
4 changes: 4 additions & 0 deletions phpstan/phpstan.modified.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
includes:
- ../phpstan.neon
parameters:
tmpDir: /tmp/phpstan/TrackingSpamPrevention/modified
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "TrackingSpamPrevention",
"description": "This plugin offers various options to prevent spammers and bots from making your data inaccurate so you can rely on your data again.",
"version": "5.2.0",
"version": "5.2.1",
"theme": false,
"require": {
"matomo": ">=5.0.0-b1,<6.0.0-b1"
Expand Down
77 changes: 77 additions & 0 deletions tests/Integration/UpdatesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
namespace Piwik\Plugins\TrackingSpamPrevention\tests\Integration;

use Piwik\Config;
use Piwik\Container\StaticContainer;
use Piwik\Plugins\TrackingSpamPrevention\BlockedIpRanges;
use Piwik\Plugins\TrackingSpamPrevention\Configuration;
use Piwik\Plugins\TrackingSpamPrevention\SystemSettings;
use Piwik\Plugins\TrackingSpamPrevention\Updates_5_0_11;
use Piwik\Plugins\TrackingSpamPrevention\Updates_5_1_0;
use Piwik\Plugins\TrackingSpamPrevention\Updates_5_2_0;
use Piwik\Settings\Storage\Factory;
use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
use Piwik\Updater;

require_once PIWIK_INCLUDE_PATH . '/plugins/TrackingSpamPrevention/Updates/5.0.11.php';
require_once PIWIK_INCLUDE_PATH . '/plugins/TrackingSpamPrevention/Updates/5.1.0.php';
require_once PIWIK_INCLUDE_PATH . '/plugins/TrackingSpamPrevention/Updates/5.2.0.php';

Expand Down Expand Up @@ -76,6 +81,63 @@ public function test_update_doesNotOverwriteExistingSettingValue()
$this->assertArrayNotHasKey(Configuration::KEY_RANGE_ALLOW_LIST, Config::getInstance()->TrackingSpamPrevention);
}

public function test_update5011_storesBlockHeadlessDisabledAndKeepsOtherSettings()
{
$settings = $this->makeSettings();
$settings->ipAllowList->setValue(['20.20.0.0/21']);
$settings->save();
Config::getInstance()->TrackingSpamPrevention = [];

$this->runUpdate5011();

$stored = $this->loadStoredSettings();
$this->assertSame('0', $stored['block_headless'] ?? null);
$this->assertSame(['20.20.0.0/21'], $stored['ip_allow_list']);
}

public function test_update5011_thenUpdate510_keepsBlockHeadlessDisabled()
{
// loads the container's copy before the updates run, as an earlier read in the request would
$this->makeSettings()->blockHeadless->getValue();
Config::getInstance()->TrackingSpamPrevention = [
Configuration::KEY_RANGE_ALLOW_LIST => ['10.10.0.0/21'],
];

$this->runUpdate5011();
$this->runUpdate();

$stored = $this->loadStoredSettings();
$this->assertSame('0', $stored['block_headless'] ?? null);
$this->assertSame(['10.10.0.0/21'], $stored['ip_allow_list']);
}

public function test_update5011_doesNotSyncCloudIpRanges()
{
$storage = StaticContainer::get(Factory::class)->getPluginStorage('TrackingSpamPrevention', '');
$storage->setValue('block_clouds', true);
$storage->save();
$ranges = $this->createMock(BlockedIpRanges::class);
$ranges->expects($this->never())->method('updateBlockedIpRanges');
StaticContainer::getContainer()->set(BlockedIpRanges::class, $ranges);
Config::getInstance()->TrackingSpamPrevention = [];

$this->runUpdate5011();

$this->assertSame('0', $this->loadStoredSettings()['block_headless'] ?? null);
}

public function test_update5011_doesNotOverwriteStoredBlockHeadless()
{
$settings = $this->makeSettings();
$settings->blockHeadless->setValue(true);
$settings->save();
Config::getInstance()->TrackingSpamPrevention = [];

$this->runUpdate5011();

$this->assertSame('1', $this->loadStoredSettings()['block_headless'] ?? null);
}

public function test_update520_migratesCustomOrganisationsToSystemSetting()
{
Config::getInstance()->TrackingSpamPrevention = [
Expand Down Expand Up @@ -163,12 +225,27 @@ private function runUpdate()
$update->doUpdate(new Updater());
}

private function runUpdate5011()
{
$update = new Updates_5_0_11();
$update->doUpdate(new Updater());
}

private function runUpdate520()
{
$update = new Updates_5_2_0();
$update->doUpdate(new Updater());
}

/**
* Reads the stored rows: a setting returns its default when nothing is stored, so it cannot show
* whether the update wrote a value.
*/
private function loadStoredSettings(): array
{
return (new Factory())->getPluginStorage('TrackingSpamPrevention', '')->getBackend()->load();
}

private function makeSettings(): SystemSettings
{
return new SystemSettings();
Expand Down
Loading