Skip to content
Open
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
19 changes: 11 additions & 8 deletions API.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ public function getAlerts($idSites, $ifSuperUserReturnAllAlerts = false)
* @param string $msTeamsWebhookUrl Microsoft Teams webhook URL when the teams channel is enabled.
* @return int ID of the newly created alert.
*/
public function addAlert($name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '', string $msTeamsWebhookUrl = '')
public function addAlert($name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '', string $msTeamsWebhookUrl = '', string $description = '')
{
$idSites = Site::getIdSitesFromIdSitesString($idSites);

$this->checkAlert($idSites, $name, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $msTeamsWebhookUrl, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);
$this->checkAlert($idSites, $name, $description, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $msTeamsWebhookUrl, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);

$name = Common::unsanitizeInputValue($name);
$name = Common::unsanitizeInputValue($name);
$description = Common::unsanitizeInputValue($description);
$login = Piwik::getCurrentUserLogin();

if (empty($reportCondition) || empty($reportValue)) {
Expand All @@ -161,7 +162,7 @@ public function addAlert($name, $idSites, $period, $emailMe, $additionalEmails,

$metricValue = Common::forceDotAsSeparatorForDecimalPoint((float)$metricValue);

return $this->getModel()->createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl);
return $this->getModel()->createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl, $description);
}

private function filterAdditionalEmails($additionalEmails)
Expand Down Expand Up @@ -195,7 +196,7 @@ private function filterPhoneNumbers($phoneNumbers)
return array_values($phoneNumbers);
}

private function checkAlert($idSites, $name, $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);
$additionalEmails = in_array('email', $reportMediums) ? $this->filterAdditionalEmails($additionalEmails) : [];
Expand All @@ -205,6 +206,7 @@ private function checkAlert($idSites, $name, $period, &$emailMe, &$additionalEma
$msTeamsWebhookUrl = in_array('teams', $reportMediums) ? $msTeamsWebhookUrl : '';

$this->validator->checkName($name);
$this->validator->checkDescription($description);
$this->validator->checkPeriod($period);
$this->validator->checkComparedTo($period, $comparedTo);
$this->validator->checkMetricCondition($metricCondition);
Expand Down Expand Up @@ -255,16 +257,17 @@ private function checkAlert($idSites, $name, $period, &$emailMe, &$additionalEma
*
* @return int Updated alert ID.
*/
public function editAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '', string $msTeamsWebhookUrl = '')
public function editAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition = false, $reportValue = false, array $reportMediums = [], string $slackChannelID = '', string $msTeamsWebhookUrl = '', string $description = '')
{
// make sure alert exists and user has permission to read
$this->getAlert($idAlert);

$idSites = Site::getIdSitesFromIdSitesString($idSites);

$this->checkAlert($idSites, $name, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $msTeamsWebhookUrl, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);
$this->checkAlert($idSites, $name, $description, $period, $emailMe, $additionalEmails, $phoneNumbers, $slackChannelID, $msTeamsWebhookUrl, $metricCondition, $metric, $comparedTo, $reportCondition, $reportUniqueId, $reportMediums);

$name = Common::unsanitizeInputValue($name);
$description = Common::unsanitizeInputValue($description);

if (empty($reportCondition) || empty($reportValue)) {
$reportCondition = null;
Expand All @@ -273,7 +276,7 @@ public function editAlert($idAlert, $name, $idSites, $period, $emailMe, $additio

$metricValue = Common::forceDotAsSeparatorForDecimalPoint((float)$metricValue);

return $this->getModel()->updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl);
return $this->getModel()->updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl, $description);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Changelog

* 5.2.6 - 2026-04-27 - Updated API documentation
* 5.3.0 - 2026-05-04 - Added alert description and helptexts
* 5.2.5 - 2026-03-30 - Added escaping for report_matched value
* 5.2.4 - 2026-03-02 - Updated API documentation
* 5.2.3 - 2026-02-05 - Alerts now get deleted when a user's site access is revoked
Expand Down
6 changes: 6 additions & 0 deletions CustomAlerts.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ public function getClientSideTranslationKeys(&$translations)
$translations[] = 'CustomAlerts_CreateNewAlert';
$translations[] = 'CustomAlerts_AlertsHistory';
$translations[] = 'CustomAlerts_AlertName';
$translations[] = 'CustomAlerts_AlertNameInlineHelp';
$translations[] = 'CustomAlerts_AlertNamePlaceholder';
$translations[] = 'CustomAlerts_AlertDescriptionOptional';
$translations[] = 'CustomAlerts_AlertDescriptionInlineHelp';
$translations[] = 'CustomAlerts_AlertDescriptionPlaceholder';
$translations[] = 'CustomAlerts_ApplyTo';
$translations[] = 'ScheduledReports_SendReportTo';
$translations[] = 'ScheduledReports_SentToMe';
Expand All @@ -268,6 +273,7 @@ public function getClientSideTranslationKeys(&$translations)
$translations[] = 'CustomAlerts_ThisAppliesToHelp';
$translations[] = 'General_Yes';
$translations[] = 'General_No';
$translations[] = 'General_Description';
$translations[] = 'CustomAlerts_MediumTitle';
$translations[] = 'CustomAlerts_MediumDescription';
$translations[] = 'CustomAlerts_ManageTooltip';
Expand Down
10 changes: 7 additions & 3 deletions Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static function install()
{
$tableAlert = "`idalert` INT NOT NULL PRIMARY KEY ,
`name` VARCHAR(100) NOT NULL ,
`description` VARCHAR(255) NOT NULL DEFAULT '',
`login` VARCHAR(100) NOT NULL ,
`period` VARCHAR(5) NOT NULL ,
`report` VARCHAR(150) NOT NULL ,
Expand Down Expand Up @@ -56,6 +57,7 @@ public static function install()
`value_old` DECIMAL (20,3) DEFAULT NULL,
`value_new` DECIMAL (20,3) DEFAULT NULL,
`name` VARCHAR(100) NOT NULL ,
`description` VARCHAR(255) NOT NULL DEFAULT '',
`login` VARCHAR(100) NOT NULL ,
`period` VARCHAR(5) NOT NULL ,
`report` VARCHAR(150) NOT NULL ,
Expand Down Expand Up @@ -261,13 +263,14 @@ public function getAllAlertsForPeriod($period)
* @return int ID of new Alert
* @throws \Exception
*/
public function createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl)
public function createAlert($name, $idSites, $login, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl, $description = '')
{
$idAlert = $this->getNextAlertId();

$newAlert = array(
'idalert' => $idAlert,
'name' => $name,
'description' => $description,
'period' => $period,
'login' => $login,
'email_me' => $emailMe ? 1 : 0,
Expand Down Expand Up @@ -346,10 +349,11 @@ private function removeAllSites($idAlert)
* @return int
* @throws \Exception
*/
public function updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl)
public function updateAlert($idAlert, $name, $idSites, $period, $emailMe, $additionalEmails, $phoneNumbers, $metric, $metricCondition, $metricValue, $comparedTo, $reportUniqueId, $reportCondition, $reportValue, $reportMediums, $slackChannelID, $msTeamsWebhookUrl, $description = '')
{
$alert = array(
'name' => $name,
'description' => $description,
'period' => $period,
'email_me' => $emailMe ? 1 : 0,
'additional_emails' => json_encode($additionalEmails),
Expand Down Expand Up @@ -393,7 +397,7 @@ public function triggerAlert($idAlert, $idSite, $valueNew, $valueOld, $datetime)
{
$alert = $this->getAlert($idAlert);

$keysToKeep = array('idalert', 'name', 'login', 'period', 'metric', 'metric_condition', 'metric_matched', 'report', 'report_condition', 'report_matched', 'report_mediums', 'compared_to', 'email_me', 'additional_emails', 'phone_numbers', 'slack_channel_id', 'ms_teams_webhook_url');
$keysToKeep = array('idalert', 'name', 'description', 'login', 'period', 'metric', 'metric_condition', 'metric_matched', 'report', 'report_condition', 'report_matched', 'report_mediums', 'compared_to', 'email_me', 'additional_emails', 'phone_numbers', 'slack_channel_id', 'ms_teams_webhook_url');

$triggeredAlert = array();
foreach ($keysToKeep as $key) {
Expand Down
43 changes: 43 additions & 0 deletions Updates/5.3.0.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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\Plugins\CustomAlerts;

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

/**
*/
class Updates_5_3_0 extends Updates
{
/**
* @var MigrationFactory
*/
private $migration;

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

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

public function getMigrations(Updater $updater): array
{
return array(
$this->migration->db->addColumn('alert', 'description', "VARCHAR(255) NOT NULL DEFAULT ''", 'name'),
$this->migration->db->addColumn('alert_triggered', 'description', "VARCHAR(255) NOT NULL DEFAULT ''", 'name'),
);
}
}
10 changes: 8 additions & 2 deletions Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace Piwik\Plugins\CustomAlerts;

use Exception;
use Piwik\Common;
use Piwik\Context;
use Piwik\Piwik;
use Piwik\Plugins\API\ProcessedReport;
Expand Down Expand Up @@ -72,11 +71,18 @@ public function checkName($name)
throw new Exception(Piwik::translate("General_PleaseSpecifyValue", "name"));
}

if (Common::mb_strlen($name) > 100) {
if (mb_strlen($name, 'UTF-8') > 100) {
throw new Exception(Piwik::translate("CustomAlerts_ParmeterIsTooLong", array(Piwik::translate('General_Name'), 100)));
}
}

public function checkDescription($description)
{
if (mb_strlen($description, 'UTF-8') > 255) {
throw new Exception(Piwik::translate("CustomAlerts_ParmeterIsTooLong", array(Piwik::translate('General_Description'), 255)));
}
}

public function isValidComparableDate($period, $comparedToDate)
{
$dates = Processor::getComparablesDates();
Expand Down
5 changes: 5 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
"AlertCondition": "Alert Condition",
"AlertDoesNotExist": "Alert with ID %d does not exist!",
"AlertMeWhen": "Alert me when",
"AlertDescriptionOptional": "Description (optional)",
"AlertDescriptionInlineHelp": "Enter a description to provide additional context, such as the purpose or usage.",
"AlertDescriptionPlaceholder": "Alert me when visits decrease by 40%",
"AlertName": "Alert Name",
"AlertNameInlineHelp": "Enter a unique name to clearly identify this custom alert.",
"AlertNamePlaceholder": "Visits drop detected",
"Alerts": "Alerts",
"AlertsHistory": "History of triggered alerts",
"ApplyTo": "Apply to",
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": "5.2.6",
"version": "5.3.0",
"require": {
"matomo": ">=5.0.0-b1,<6.0.0-b1"
},
Expand Down
36 changes: 27 additions & 9 deletions tests/Integration/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ protected function createAlert(
$metricCondition = 'less_than',
$reportCondition = 'matches_exactly',
$emails = array('test1@example.com', 'test2@example.com'),
$comparedTo = 1
$comparedTo = 1,
$description = ''
) {
if (is_null($idSites)) {
$idSites = $this->idSite;
Expand All @@ -63,7 +64,8 @@ protected function createAlert(
'Piwik',
['email', 'mobile'],
'',
''
'',
$description
);
return $id;
}
Expand Down Expand Up @@ -188,10 +190,19 @@ public function test_addAlert_ShouldCreateANewAlert()
{
$this->setSuperUser();

$id = $this->createAlert('MyCustomAlert', 'week');
$id = $this->createAlert('MyCustomAlert', 'week', null, 'nb_visits', 'MultiSites_getOne', 'less_than', 'matches_exactly', array('test1@example.com', 'test2@example.com'), 1, 'Description text');
$this->assertGreaterThan(3, $id);

$this->assertIsAlert($id, 'MyCustomAlert', 'week');
$this->assertIsAlert($id, 'MyCustomAlert', 'week', null, 'superUserLogin', 'nb_visits', 'less_than', 5, 'MultiSites_getOne', 'matches_exactly', 'Piwik', 'Description text');
}

public function test_addAlert_ShouldFail_IfDescriptionTooLong()
{
$this->setSuperUser();
$this->expectException(\Exception::class);
$this->expectExceptionMessage('CustomAlerts_ParmeterIsTooLong');

$this->createAlert('MyCustomAlert', 'week', null, 'nb_visits', 'MultiSites_getOne', 'less_than', 'matches_exactly', array('test1@example.com', 'test2@example.com'), 1, str_repeat('x', 256));
}

protected function assertIsAlert(
Expand All @@ -205,7 +216,8 @@ protected function assertIsAlert(
$metricMatched = 5,
$report = 'MultiSites_getOne',
$reportCondition = 'matches_exactly',
$reportMatched = 'Piwik'
$reportMatched = 'Piwik',
$description = ''
) {
if (is_null($idSites)) {
$idSites = array($this->idSite);
Expand All @@ -216,6 +228,7 @@ protected function assertIsAlert(
$expected = array(
'idalert' => $id,
'name' => $name,
'description' => $description,
'login' => $login,
'period' => $period,
'report' => $report,
Expand Down Expand Up @@ -254,7 +267,8 @@ protected function editAlert(
$report = 'MultiSites_getOne',
$metricCondition = 'less_than',
$reportCondition = 'matches_exactly',
$emails = array('test1@example.com', 'test2@example.com')
$emails = array('test1@example.com', 'test2@example.com'),
$description = ''
) {
if (is_null($idSites)) {
$idSites = $this->idSite;
Expand All @@ -279,7 +293,10 @@ protected function editAlert(
$report,
$reportCondition,
'Piwik',
['email', 'mobile']
['email', 'mobile'],
'',
'',
$description
);
return $id;
}
Expand Down Expand Up @@ -349,10 +366,10 @@ public function test_editAlert_ShouldUpdateExistingEntry()
{
$this->setSuperUser();

$id = $this->editAlert(2, 'MyCustomAlert', 'day');
$id = $this->editAlert(2, 'MyCustomAlert', 'day', null, 'nb_visits', 'MultiSites_getOne', 'less_than', 'matches_exactly', array('test1@example.com', 'test2@example.com'), 'Updated description');
$this->assertEquals(2, $id);

$this->assertIsAlert(2, 'MyCustomAlert', 'day', array(1));
$this->assertIsAlert(2, 'MyCustomAlert', 'day', array(1), 'superUserLogin', 'nb_visits', 'less_than', 5, 'MultiSites_getOne', 'matches_exactly', 'Piwik', 'Updated description');
}

public function test_getAlert_ShouldLoadAlertAndRelatedWebsiteIds_IfExists()
Expand Down Expand Up @@ -572,6 +589,7 @@ public function test_triggerAlert_getTriggeredAlertsForPeriod_ShouldMarkAlertAsT
'idsite' => 1,
'ts_last_sent' => null,
'name' => 'Initial2',
'description' => '',
'period' => 'week',
'login' => 'superUserLogin',
'report' => 'MultiSites_getOne',
Expand Down
Loading
Loading