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
3 changes: 2 additions & 1 deletion API.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ private function filterPhoneNumbers($phoneNumbers)
return array_values($phoneNumbers);
}

private function checkAlert($idSites, $name, $description, $period, &$emailMe, &$additionalEmails, &$phoneNumbers, &$slackChannelID, &$msTeamsWebhookUrl, $metricCondition, $metricValue, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums)
private function checkAlert($idSites, $name, $description, $period, &$emailMe, &$additionalEmails, &$phoneNumbers, &$slackChannelID, &$msTeamsWebhookUrl, $metricCondition, $metricValue, $comparedTo, $reportCondition, &$reportUniqueId, $reportMediums)
{
Piwik::checkUserHasViewAccess($idSites);
$reportUniqueId = $this->validator->getRenamedReportUniqueId($idSites, $reportUniqueId);
$additionalEmails = in_array('email', $reportMediums) ? $this->filterAdditionalEmails($additionalEmails) : [];
$phoneNumbers = in_array('mobile', $reportMediums) ? $this->filterPhoneNumbers($phoneNumbers) : [];
$emailMe = in_array('email', $reportMediums) && $emailMe;
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Changelog

* 6.0.1 - Alerts on the Goals Overview reports that carried `idGoal=0` are moved to the ecommerce order report where every site of the alert has ecommerce enabled, otherwise to the all-goals report. An alert covering sites both with and without ecommerce is therefore evaluated on all-goals figures for every site, including those that were evaluated on ecommerce order figures before. Triggered alerts keep the report they were triggered for. The move depends only on the sites' ecommerce setting, not on whether the Ecommerce plugin is active during the update; while the plugin is inactive, an alert on the ecommerce order report is not evaluated until the plugin is activated again. Those reports can no longer be selected, and `CustomAlerts.addAlert` and `CustomAlerts.editAlert` requests that still use one resolve it the same way, so such a request is rejected while the Ecommerce plugin is inactive and every site of the alert has ecommerce enabled.
* 6.0.0 - 2026-08-09 - Compatibility with Matomo 6
* 5.3.3 - 2026-07-27 - Added strict comparison check
* 5.3.2 - 2026-06-29 - Added code to improve the access check before sending an alert
Expand Down
85 changes: 85 additions & 0 deletions Updates/6.0.1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?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\CustomAlerts;

use Piwik\Common;
use Piwik\Db;
use Piwik\Updater;
use Piwik\Updater\Migration\Factory as MigrationFactory;
use Piwik\Updates;

/**
* Moves alerts off the retired `idGoal=0` report unique ids, to the ecommerce order report only where every
* site of the alert has ecommerce enabled, read from the site setting. Triggered alerts keep their report.
*/
class Updates_6_0_1 extends Updates
{
/**
* @var MigrationFactory
*/
private $migration;

public function __construct(MigrationFactory $factory)
{
$this->migration = $factory;
}

public function doUpdate(Updater $updater): void
{
$updater->executeMigrations(__FILE__, $this->getMigrations($updater));
}

public function getMigrations(Updater $updater): array
{
$alertTable = Common::prefixTable('alert');
$siteTable = Common::prefixTable('site');

$hasRetiredReports = Db::fetchOne(
"SELECT 1 FROM `$alertTable` WHERE report IN (?, ?, ?) LIMIT 1",
['Goals_get_idGoal--0', 'Goals_getVisitsUntilConversion_idGoal--0', 'Goals_getDaysToConversion_idGoal--0']
);

if (!$hasRetiredReports) {
return [];
}

$alertSiteTable = Common::prefixTable('alert_site');

$migrations = [
$this->migration->db->boundSql(
"UPDATE `$alertTable` a SET a.report = ?
WHERE a.report = ?
AND EXISTS (SELECT 1 FROM `$alertSiteTable` s WHERE s.idalert = a.idalert)
AND NOT EXISTS (
SELECT 1 FROM `$alertSiteTable` s
LEFT JOIN `$siteTable` site ON site.idsite = s.idsite
WHERE s.idalert = a.idalert AND (site.ecommerce IS NULL OR site.ecommerce <> 1)
)",
['Goals_get_idGoal--ecommerceOrder', 'Goals_get_idGoal--0']
),
];

$replacements = [
'Goals_get_idGoal--0' => 'Goals_get',
'Goals_getVisitsUntilConversion_idGoal--0' => 'Goals_getVisitsUntilConversion',
'Goals_getDaysToConversion_idGoal--0' => 'Goals_getDaysToConversion',
];

foreach ($replacements as $retired => $replacement) {
$migrations[] = $this->migration->db->boundSql(
"UPDATE `$alertTable` SET report = ? WHERE report = ?",
[$replacement, $retired]
);
}

return $migrations;
}
}
41 changes: 41 additions & 0 deletions Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,29 @@
use Piwik\Context;
use Piwik\Piwik;
use Piwik\Plugins\API\ProcessedReport;
use Piwik\Site;

/**
*
*/
class Validator
{
/**
* Used only where every site of the alert has ecommerce enabled.
*/
private const ECOMMERCE_ORDER_REPLACEMENTS = [
'Goals_get_idGoal--0' => 'Goals_get_idGoal--ecommerceOrder',
];

/**
* Mirrors core's `ProcessedReport::getRenamedReportUniqueIds()`, which older Matomo 6 releases lack.
*/
private const ALL_GOALS_REPLACEMENTS = [
'Goals_get_idGoal--0' => 'Goals_get',
'Goals_getVisitsUntilConversion_idGoal--0' => 'Goals_getVisitsUntilConversion',
'Goals_getDaysToConversion_idGoal--0' => 'Goals_getDaysToConversion',
];

/**
* @var ProcessedReport
*/
Expand Down Expand Up @@ -175,6 +192,30 @@ public function checkApiMethodAndMetric($idSite, $apiMethodUniqueId, $metric)
});
}

/**
* Picks the ecommerce order report only when every site has ecommerce enabled, as the 6.0.1 update does.
*
* @param int[] $idSites
* @param mixed $apiMethodUniqueId
* @return mixed
*/
public function getRenamedReportUniqueId(array $idSites, $apiMethodUniqueId)
{
if (!is_string($apiMethodUniqueId) || !isset(self::ALL_GOALS_REPLACEMENTS[$apiMethodUniqueId])) {
return $apiMethodUniqueId;
}

if (isset(self::ECOMMERCE_ORDER_REPLACEMENTS[$apiMethodUniqueId]) && !empty($idSites)) {
$sitesWithEcommerce = array_filter($idSites, [Site::class, 'isEcommerceEnabledFor']);

if (count($sitesWithEcommerce) === count($idSites)) {
return self::ECOMMERCE_ORDER_REPLACEMENTS[$apiMethodUniqueId];
}
}

return self::ALL_GOALS_REPLACEMENTS[$apiMethodUniqueId];
}

/**
* @param $alert
* @throws \Exception
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "CustomAlerts",
"description": "Create custom Alerts to be notified of important changes on your website or app! ",
"version": "6.0.0",
"version": "6.0.1",
"require": {
"matomo": ">=6.0.0-b1,<7.0.0-b1"
},
Expand Down
69 changes: 69 additions & 0 deletions tests/Integration/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
namespace Piwik\Plugins\CustomAlerts\tests\Integration;

use Piwik\Date;
use Piwik\Plugin\Manager;
use Piwik\Tests\Framework\Fixture;
use Piwik\Tests\Framework\Mock\FakeAccess;

/**
Expand Down Expand Up @@ -105,6 +107,62 @@ public function test_addAlert_ShouldFail_IfInvalidReportProvided()
$this->createAlert('InvalidReport', 'week', null, 'nb_visits', 'IkReport_NotExisTing');
}

/**
* @dataProvider getRetiredReports
*/
public function test_addAlert_ShouldStoreTheReportHoldingTheFiguresOfARetiredReport($report, $ecommerceSites, $expectedReport)
{
$this->setSuperUser();
$idSites = [$this->idSite];

if ($ecommerceSites) {
$idSites = [
Fixture::createWebsite('2012-08-11 11:22:33', $ecommerce = 1),
Fixture::createWebsite('2012-08-12 11:22:33', $ecommerce = 1),
];
}

$id = $this->createAlert('RetiredReport', 'week', $idSites, 'nb_conversions', $report);

$this->assertSame($expectedReport, $this->api->getAlert($id)['report']);
}

public function getRetiredReports()
{
return [
['Goals_get_idGoal--0', true, 'Goals_get_idGoal--ecommerceOrder'],
['Goals_get_idGoal--0', false, 'Goals_get'],
['Goals_getVisitsUntilConversion_idGoal--0', true, 'Goals_getVisitsUntilConversion'],
['Goals_getDaysToConversion_idGoal--0', false, 'Goals_getDaysToConversion'],
];
}

public function test_addAlert_ShouldStoreTheAllGoalsReport_IfOnlySomeSitesHaveEcommerce()
{
$this->setSuperUser();
$idSites = [$this->idSite, Fixture::createWebsite('2012-08-11 11:22:33', $ecommerce = 1)];

$id = $this->createAlert('RetiredReport', 'week', $idSites, 'nb_conversions', 'Goals_get_idGoal--0');

$this->assertSame('Goals_get', $this->api->getAlert($id)['report']);
}

public function test_addAlert_ShouldFail_IfAllSitesHaveEcommerce_WhileTheEcommercePluginIsInactive()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('CustomAlerts_InvalidReport');

$this->setSuperUser();
$idSites = [
Fixture::createWebsite('2012-08-11 11:22:33', $ecommerce = 1),
Fixture::createWebsite('2012-08-12 11:22:33', $ecommerce = 1),
];

Manager::getInstance()->loadPlugins(array_diff(Manager::getInstance()->getActivatedPlugins(), ['Ecommerce']));

$this->createAlert('RetiredReport', 'week', $idSites, 'nb_conversions', 'Goals_get_idGoal--0');
}

public function test_addAlert_ShouldFail_ShouldFailIfPeriodNotValid()
{
$this->expectException(\Exception::class);
Expand Down Expand Up @@ -362,6 +420,17 @@ public function test_editAlert_ShouldFail_IfInvalidEmail()
$this->editAlert($id, 'InvalidEmail', 'week', null, 'nb_visits', 'MultiSites_getOne', 'less_than', 'matches_any', array('test@example.com', 'inv+34i32s?y', 'test2@example.com'));
}

public function test_editAlert_ShouldStoreTheReportHoldingTheFiguresOfARetiredReport()
{
$this->setSuperUser();
$idSite = Fixture::createWebsite('2012-08-11 11:22:33', $ecommerce = 1);

$id = $this->createAlert('MyAlert', 'week', [$idSite]);
$this->editAlert($id, 'MyAlert', 'week', [$idSite], 'nb_conversions', 'Goals_get_idGoal--0');

$this->assertSame('Goals_get_idGoal--ecommerceOrder', $this->api->getAlert($id)['report']);
}

public function test_editAlert_ShouldUpdateExistingEntry()
{
$this->setSuperUser();
Expand Down
Loading
Loading