From 996ff2ea7a94832bed8faa569320941c944bb30a Mon Sep 17 00:00:00 2001 From: Altamash Shaikh Date: Tue, 3 Feb 2026 06:13:10 +0530 Subject: [PATCH 01/15] Adds PHPStan, [no_release] --- .git-hooks-matomo/pre-push | 106 +++++++++++++++++++ .github/workflows/.git-hooks-matomo/pre-push | 106 +++++++++++++++++++ .github/workflows/phpstan.yml | 83 +++++++++++++++ phpstan.neon | 21 ++++ phpstan/phpstan.created.neon | 5 + phpstan/phpstan.modified.neon | 5 + 6 files changed, 326 insertions(+) create mode 100755 .git-hooks-matomo/pre-push create mode 100755 .github/workflows/.git-hooks-matomo/pre-push create mode 100644 .github/workflows/phpstan.yml create mode 100644 phpstan.neon create mode 100644 phpstan/phpstan.created.neon create mode 100644 phpstan/phpstan.modified.neon diff --git a/.git-hooks-matomo/pre-push b/.git-hooks-matomo/pre-push new file mode 100755 index 00000000..17ac8c23 --- /dev/null +++ b/.git-hooks-matomo/pre-push @@ -0,0 +1,106 @@ +#!/bin/bash + +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# + + + +### Check we're running in the context of a plugin and get helpful dir variables ### + +REPO_DIR="$(git rev-parse --show-toplevel)" +echo "Running pre-commit hook in repo: $REPO_DIR" + +if [[ "$REPO_DIR" =~ /plugins/(.*) ]]; then + PLUGIN_PATH="plugins/${BASH_REMATCH[1]}/" +else + echo "Not a plugin, not running any further checks" + exit 1 +fi +MATOMO_DIR=$(echo "$REPO_DIR" | sed -E 's|/plugins/.*$||') + + + +### Figure out how to run PHPStan - ddev or not. ### + +COMMAND="" +# Use local PHP if setup +if command -v php >/dev/null 2>&1; then + if [ -f "${MATOMO_DIR}/vendor/bin/phpstan" ]; then + COMMAND="${MATOMO_DIR}/vendor/bin/phpstan" + PLUGIN_PATH='' + fi +elif command -v ddev >/dev/null 2>&1; then + # Use ddev if setup (overridding local setup) + if [ -d "$MATOMO_DIR/.ddev" ]; then + cd "$MATOMO_DIR" || exit 1 + if ddev status 2>&1 > /dev/null; then + COMMAND="ddev exec phpstan" + fi + fi +fi +# If no command, exit +if [[ -z "$COMMAND" ]]; then + echo "No way to run phpstan found." + exit 1 +fi + + + +# Basic setup +cd "$REPO_DIR" +STATUS=0 + + + + +### Run PHPStan on newly created files. ### + +PHPSTAN_CREATED_CONFIG=phpstan/phpstan.created.neon +MAIN_BRANCH='5.x-dev' +if [[ -f "$PHPSTAN_CREATED_CONFIG" ]]; then + CHANGED_FILES=$(git diff --name-only ${MAIN_BRANCH} --diff-filter=A | grep '\.php$' || true) + if [ -z "$CHANGED_FILES" ]; then + echo "No created PHP files" + else + echo "Running PHPstan at a very high level on new files" + CHANGED_FILES=`echo "$CHANGED_FILES" | sed -e 's/^\(.*\)$/"\1"/' | xargs -I{} echo "${PLUGIN_PATH}{}"` + echo "$CHANGED_FILES" | xargs $COMMAND analyse -c ${PLUGIN_PATH}${PHPSTAN_CREATED_CONFIG} || STATUS=1 + fi +fi + + + +### Run PHPStan on modified files. ### +PHPSTAN_MODIFIED_CONFIG=phpstan/phpstan.modified.neon +if [[ -f "$PHPSTAN_MODIFIED_CONFIG" ]]; then + CHANGED_FILES=$(git diff --name-only ${MAIN_BRANCH} --diff-filter=CM | grep '\.php$' || true) + if [ -z "$CHANGED_FILES" ]; then + echo "No changed PHP files" + else + echo "Running PHPstan on modified files" + CHANGED_FILES=`echo "$CHANGED_FILES" | sed -e 's/^\(.*\)$/"\1"/' | xargs -I{} echo "${PLUGIN_PATH}{}"` + echo "$CHANGED_FILES" | xargs $COMMAND analyse -c ${PLUGIN_PATH}${PHPSTAN_MODIFIED_CONFIG} || STATUS=1 + fi +fi + +# Don't bother running the full check, as we check changes files already, and +# can assume that the unchanged files don't need rechecking. +# +# Github will check this anyway. +# +# PHPSTAN_BASE_CONFIG=phpstan.neon +# if [[ -f "$PHPSTAN_BASE_CONFIG" ]]; then +# echo "Running PHPstan at a base level on all plugin files" +# $COMMAND analyse -c ${PLUGIN_PATH}/${PHPSTAN_BASE_CONFIG} || STATUS=1 +# fi + +exit $STATUS diff --git a/.github/workflows/.git-hooks-matomo/pre-push b/.github/workflows/.git-hooks-matomo/pre-push new file mode 100755 index 00000000..17ac8c23 --- /dev/null +++ b/.github/workflows/.git-hooks-matomo/pre-push @@ -0,0 +1,106 @@ +#!/bin/bash + +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# + + + +### Check we're running in the context of a plugin and get helpful dir variables ### + +REPO_DIR="$(git rev-parse --show-toplevel)" +echo "Running pre-commit hook in repo: $REPO_DIR" + +if [[ "$REPO_DIR" =~ /plugins/(.*) ]]; then + PLUGIN_PATH="plugins/${BASH_REMATCH[1]}/" +else + echo "Not a plugin, not running any further checks" + exit 1 +fi +MATOMO_DIR=$(echo "$REPO_DIR" | sed -E 's|/plugins/.*$||') + + + +### Figure out how to run PHPStan - ddev or not. ### + +COMMAND="" +# Use local PHP if setup +if command -v php >/dev/null 2>&1; then + if [ -f "${MATOMO_DIR}/vendor/bin/phpstan" ]; then + COMMAND="${MATOMO_DIR}/vendor/bin/phpstan" + PLUGIN_PATH='' + fi +elif command -v ddev >/dev/null 2>&1; then + # Use ddev if setup (overridding local setup) + if [ -d "$MATOMO_DIR/.ddev" ]; then + cd "$MATOMO_DIR" || exit 1 + if ddev status 2>&1 > /dev/null; then + COMMAND="ddev exec phpstan" + fi + fi +fi +# If no command, exit +if [[ -z "$COMMAND" ]]; then + echo "No way to run phpstan found." + exit 1 +fi + + + +# Basic setup +cd "$REPO_DIR" +STATUS=0 + + + + +### Run PHPStan on newly created files. ### + +PHPSTAN_CREATED_CONFIG=phpstan/phpstan.created.neon +MAIN_BRANCH='5.x-dev' +if [[ -f "$PHPSTAN_CREATED_CONFIG" ]]; then + CHANGED_FILES=$(git diff --name-only ${MAIN_BRANCH} --diff-filter=A | grep '\.php$' || true) + if [ -z "$CHANGED_FILES" ]; then + echo "No created PHP files" + else + echo "Running PHPstan at a very high level on new files" + CHANGED_FILES=`echo "$CHANGED_FILES" | sed -e 's/^\(.*\)$/"\1"/' | xargs -I{} echo "${PLUGIN_PATH}{}"` + echo "$CHANGED_FILES" | xargs $COMMAND analyse -c ${PLUGIN_PATH}${PHPSTAN_CREATED_CONFIG} || STATUS=1 + fi +fi + + + +### Run PHPStan on modified files. ### +PHPSTAN_MODIFIED_CONFIG=phpstan/phpstan.modified.neon +if [[ -f "$PHPSTAN_MODIFIED_CONFIG" ]]; then + CHANGED_FILES=$(git diff --name-only ${MAIN_BRANCH} --diff-filter=CM | grep '\.php$' || true) + if [ -z "$CHANGED_FILES" ]; then + echo "No changed PHP files" + else + echo "Running PHPstan on modified files" + CHANGED_FILES=`echo "$CHANGED_FILES" | sed -e 's/^\(.*\)$/"\1"/' | xargs -I{} echo "${PLUGIN_PATH}{}"` + echo "$CHANGED_FILES" | xargs $COMMAND analyse -c ${PLUGIN_PATH}${PHPSTAN_MODIFIED_CONFIG} || STATUS=1 + fi +fi + +# Don't bother running the full check, as we check changes files already, and +# can assume that the unchanged files don't need rechecking. +# +# Github will check this anyway. +# +# PHPSTAN_BASE_CONFIG=phpstan.neon +# if [[ -f "$PHPSTAN_BASE_CONFIG" ]]; then +# echo "Running PHPstan at a base level on all plugin files" +# $COMMAND analyse -c ${PLUGIN_PATH}/${PHPSTAN_BASE_CONFIG} || STATUS=1 +# fi + +exit $STATUS diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 00000000..e49c8310 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,83 @@ +name: PHPStan check + +on: pull_request + +permissions: + actions: read + checks: read + contents: read + deployments: none + issues: read + packages: none + pull-requests: read + repository-projects: none + security-events: none + statuses: read + +env: + PLUGIN_NAME: CustomAlerts + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + lfs: false + persist-credentials: false + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.2' + + - name: Check out github-action-tests repository + uses: actions/checkout@v4 + with: + repository: matomo-org/github-action-tests + ref: main + path: github-action-tests + + - name: checkout matomo for plugin builds + shell: bash + run: ${{ github.workspace }}/github-action-tests/scripts/bash/checkout_matomo.sh + env: + PLUGIN_NAME: ${{ env.PLUGIN_NAME }} + WORKSPACE: ${{ github.workspace }} + ACTION_PATH: ${{ github.workspace }}/github-action-tests + MATOMO_TEST_TARGET: maximum_supported_matomo + + - name: prepare setup + shell: bash + run: | + cd ${{ github.workspace }}/matomo + echo -e "composer install" + composer install --ignore-platform-reqs + + - name: checkout additional plugins + if: ${{ env.DEPENDENT_PLUGINS != '' }} + shell: bash + working-directory: ${{ github.workspace }}/matomo + run: ${{ github.workspace }}/github-action-tests/scripts/bash/checkout_dependent_plugins.sh + + env: + GITHUB_USER_TOKEN: ${{ secrets.TESTS_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} + + - name: "Restore result cache" + uses: actions/cache/restore@v4 + with: + path: /tmp/phpstan # same as in phpstan.neon + key: "phpstan-result-cache-${{ github.run_id }}" + restore-keys: | + phpstan-result-cache- + + - name: PHPStan whole repo + id: phpstan-all + run: cd ${{ github.workspace }}/matomo && composer run phpstan -- -vvv -c plugins/${{ env.PLUGIN_NAME }}/phpstan.neon + + - name: "Save result cache" + uses: actions/cache/save@v4 + if: ${{ !cancelled() }} + with: + path: /tmp/phpstan # same as in phpstan.neon + key: "phpstan-result-cache-${{ github.run_id }}" \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..517ef7a8 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,21 @@ +parameters: + level: 5 + phpVersion: 70200 + tmpDir: /tmp/phpstan/CustomAlerts/main + paths: + - . + excludePaths: + - tests/* + - github-action-tests + bootstrapFiles: + - ../../bootstrap-phpstan.php + universalObjectCratesClasses: + - Piwik\Config + - Piwik\View + - Piwik\ViewDataTable\Config + scanDirectories: + # ../../ does not actually seem to give us anything + # that ../plugins/ does not, but including it for + # completeness. It does not seem to slow down performance. + - . + diff --git a/phpstan/phpstan.created.neon b/phpstan/phpstan.created.neon new file mode 100644 index 00000000..d52a84c0 --- /dev/null +++ b/phpstan/phpstan.created.neon @@ -0,0 +1,5 @@ +includes: + - ../phpstan.neon +parameters: + level: 2 + tmpDir: /tmp/phpstan/CustomAlerts/created \ No newline at end of file diff --git a/phpstan/phpstan.modified.neon b/phpstan/phpstan.modified.neon new file mode 100644 index 00000000..3a5a66ea --- /dev/null +++ b/phpstan/phpstan.modified.neon @@ -0,0 +1,5 @@ +includes: + - ../phpstan.neon +parameters: + level: 2 + tmpDir: /tmp/phpstan/CustomAlerts/modified \ No newline at end of file From d3d37dd0bf08fc273aaa03a5950d19f3488d2c82 Mon Sep 17 00:00:00 2001 From: Altamash Shaikh Date: Tue, 3 Feb 2026 06:29:32 +0530 Subject: [PATCH 02/15] changed to level 1 --- phpstan/phpstan.created.neon | 2 +- phpstan/phpstan.modified.neon | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan/phpstan.created.neon b/phpstan/phpstan.created.neon index d52a84c0..cef683da 100644 --- a/phpstan/phpstan.created.neon +++ b/phpstan/phpstan.created.neon @@ -1,5 +1,5 @@ includes: - ../phpstan.neon parameters: - level: 2 + level: 1 tmpDir: /tmp/phpstan/CustomAlerts/created \ No newline at end of file diff --git a/phpstan/phpstan.modified.neon b/phpstan/phpstan.modified.neon index 3a5a66ea..d8c13164 100644 --- a/phpstan/phpstan.modified.neon +++ b/phpstan/phpstan.modified.neon @@ -1,5 +1,5 @@ includes: - ../phpstan.neon parameters: - level: 2 + level: 1 tmpDir: /tmp/phpstan/CustomAlerts/modified \ No newline at end of file From d0e95d44620bf75e0920a9277a9fff1e6e537a2e Mon Sep 17 00:00:00 2001 From: Altamash Shaikh Date: Tue, 3 Feb 2026 06:38:49 +0530 Subject: [PATCH 03/15] changed to level 0 --- API.php | 2 +- Controller.php | 6 +++--- Model.php | 4 ++-- phpstan/phpstan.created.neon | 2 +- phpstan/phpstan.modified.neon | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/API.php b/API.php index 87473e22..62447933 100755 --- a/API.php +++ b/API.php @@ -271,7 +271,7 @@ public function deleteAlert($idAlert) /** * Get triggered alerts. * - * @param int[] idSites + * @param int[] $idSites * * @return array */ diff --git a/Controller.php b/Controller.php index 63fc44f0..7e7cdda2 100755 --- a/Controller.php +++ b/Controller.php @@ -118,7 +118,7 @@ public function historyTriggeredAlerts() $idSites = $this->getSiteIdsHavingAccess(); $alerts = API::getInstance()->getTriggeredAlerts($idSites); - array_slice($alerts, 0, 100); + $alerts = array_slice($alerts, 0, 100); $alerts = array_reverse($alerts); $view->alertsFormatted = $this->formatAlerts($alerts, 'html_extended'); @@ -280,9 +280,9 @@ private function addBasicCreateAndEditVariables($view, $alert) $view->supportsSMS = $this->supportsPlugin('MobileMessaging'); $supportsSlack = $this->supportsPlugin('Slack'); $isSlackOAuthTokenAdded = false; - if ($supportsSlack) { + if ($supportsSlack && class_exists(\Piwik\Plugins\Slack\SystemSettings::class)) { $slackSettings = StaticContainer::get(\Piwik\Plugins\Slack\SystemSettings::class); - $isSlackOAuthTokenAdded = !empty($slackSettings->slackOauthToken->getValue()); + $isSlackOAuthTokenAdded = $slackSettings && !empty($slackSettings->slackOauthToken->getValue()); } $view->isSlackOAuthTokenAdded = $isSlackOAuthTokenAdded; $view->periodOptions = array( diff --git a/Model.php b/Model.php index a1811be3..26e4cf84 100755 --- a/Model.php +++ b/Model.php @@ -112,7 +112,7 @@ public function getAlerts($idSites, $login = false) /** * @param $idSites - * @return array + * @return string */ protected function getInnerSiteQuery($idSites) { @@ -141,7 +141,7 @@ private function completeAlerts($alerts) private function getDefinedSiteIds($idAlert) { $sql = "SELECT idsite FROM " . Common::prefixTable('alert_site') . " WHERE idalert = ?"; - $sites = Db::fetchAll($sql, $idAlert, \PDO::FETCH_COLUMN); + $sites = Db::fetchAll($sql, $idAlert); $idSites = array(); foreach ($sites as $site) { diff --git a/phpstan/phpstan.created.neon b/phpstan/phpstan.created.neon index cef683da..726034d5 100644 --- a/phpstan/phpstan.created.neon +++ b/phpstan/phpstan.created.neon @@ -1,5 +1,5 @@ includes: - ../phpstan.neon parameters: - level: 1 + level: 0 tmpDir: /tmp/phpstan/CustomAlerts/created \ No newline at end of file diff --git a/phpstan/phpstan.modified.neon b/phpstan/phpstan.modified.neon index d8c13164..95846cb2 100644 --- a/phpstan/phpstan.modified.neon +++ b/phpstan/phpstan.modified.neon @@ -1,5 +1,5 @@ includes: - ../phpstan.neon parameters: - level: 1 + level: 0 tmpDir: /tmp/phpstan/CustomAlerts/modified \ No newline at end of file From a2c031ce552d982567eaf5659aa15e061151864a Mon Sep 17 00:00:00 2001 From: Altamash Shaikh Date: Tue, 3 Feb 2026 06:51:24 +0530 Subject: [PATCH 04/15] changed to level 1 --- phpstan.neon | 2 +- phpstan/phpstan.created.neon | 2 +- phpstan/phpstan.modified.neon | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 517ef7a8..98223ba4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 1 phpVersion: 70200 tmpDir: /tmp/phpstan/CustomAlerts/main paths: diff --git a/phpstan/phpstan.created.neon b/phpstan/phpstan.created.neon index 726034d5..8086320a 100644 --- a/phpstan/phpstan.created.neon +++ b/phpstan/phpstan.created.neon @@ -1,5 +1,5 @@ includes: - ../phpstan.neon parameters: - level: 0 + level: 5 tmpDir: /tmp/phpstan/CustomAlerts/created \ No newline at end of file diff --git a/phpstan/phpstan.modified.neon b/phpstan/phpstan.modified.neon index 95846cb2..1cd88dfa 100644 --- a/phpstan/phpstan.modified.neon +++ b/phpstan/phpstan.modified.neon @@ -1,5 +1,5 @@ includes: - ../phpstan.neon parameters: - level: 0 + level: 5 tmpDir: /tmp/phpstan/CustomAlerts/modified \ No newline at end of file From e80c1c673427780996420dae76882d48d24e4c28 Mon Sep 17 00:00:00 2001 From: Altamash Shaikh Date: Tue, 3 Feb 2026 06:52:14 +0530 Subject: [PATCH 05/15] fixes error --- Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Controller.php b/Controller.php index 7e7cdda2..d4880f50 100755 --- a/Controller.php +++ b/Controller.php @@ -282,7 +282,7 @@ private function addBasicCreateAndEditVariables($view, $alert) $isSlackOAuthTokenAdded = false; if ($supportsSlack && class_exists(\Piwik\Plugins\Slack\SystemSettings::class)) { $slackSettings = StaticContainer::get(\Piwik\Plugins\Slack\SystemSettings::class); - $isSlackOAuthTokenAdded = $slackSettings && !empty($slackSettings->slackOauthToken->getValue()); + $isSlackOAuthTokenAdded = !empty($slackSettings->slackOauthToken->getValue()); } $view->isSlackOAuthTokenAdded = $isSlackOAuthTokenAdded; $view->periodOptions = array( From 65c3e696e8d59326cd56a13e5b1fc6354c18dc9f Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 10:50:34 +1300 Subject: [PATCH 06/15] Fixes --- CustomAlerts.php | 22 ++++++++++++++++++++++ Model.php | 17 +++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/CustomAlerts.php b/CustomAlerts.php index 4e58a1cb..41af4245 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -41,6 +41,7 @@ public function registerEvents() 'Request.dispatch' => 'checkControllerPermission', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', 'UsersManager.deleteUser' => 'deleteAlertsForLogin', + 'UsersManager.removeSiteAccess' => 'removeSiteAccessForUser', 'SitesManager.deleteSite.end' => 'deleteAlertsForSite', 'Db.getTablesInstalled' => 'getTablesInstalled', 'ScheduledTasks.execute' => 'startingScheduledTask', @@ -295,4 +296,25 @@ public function validateReportParameters($parameters, $alertMedium) throw new \Exception(Piwik::translate('CustomAlerts_InvalidPhoneNumberReportParameter')); } } + + + public function removeSiteAccessForUser($userLogin, $idSites) + { + $model = $this->getModel(); + $alerts = $model->getAlerts($idSites, $userLogin); + + foreach ($alerts as $alert) { + $alertId = $alert['idalert']; + $alertSites = $alert['id_sites']; + if (count($alertSites) === 1 || empty(array_diff($alertSites, $idSites))) { + $model->deleteAlert($alertId); + } else { + $model->deleteTriggeredAlertsForUser($alertId, $userLogin); + $model->deleteAlertSitesForSites($alertId, $idSites); + } + } + + + + } } diff --git a/Model.php b/Model.php index 26e4cf84..d064a627 100755 --- a/Model.php +++ b/Model.php @@ -453,4 +453,21 @@ public function deleteTriggeredAlertsForSite($idSite) { $this->getDb()->query("DELETE FROM " . Common::prefixTable("alert_triggered") . " WHERE idsite = ?", $idSite); } + + public function deleteTriggeredAlertsForUser($idAlert, $login) + { + $db = $this->getDb(); + $db->query("DELETE FROM " . Common::prefixTable("alert_triggered") . " WHERE idalert = ? AND login = ?", array($idAlert, $login)); + } + + public function deleteAlertSitesForSites($idAlert, $idSites) + { + $db = $this->getDb(); + $placeholders = Common::getSqlStringFieldsArray($idSites); + $bind = array_merge(array($idAlert), $idSites); + $db->query( + "DELETE FROM " . Common::prefixTable("alert_site") . " WHERE idalert = ? AND idsite in (" . $placeholders . " )", + $bind + ); + } } From 2f1d85743182a19c22e0be32cc50eec669bb64ea Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 10:51:02 +1300 Subject: [PATCH 07/15] phpcs --- CustomAlerts.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/CustomAlerts.php b/CustomAlerts.php index 41af4245..29b906a1 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -313,8 +313,5 @@ public function removeSiteAccessForUser($userLogin, $idSites) $model->deleteAlertSitesForSites($alertId, $idSites); } } - - - } } From b135044a54978ae042efa0b6ffb7e3ee478287a3 Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 10:53:19 +1300 Subject: [PATCH 08/15] changed name --- CustomAlerts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CustomAlerts.php b/CustomAlerts.php index 29b906a1..894c77de 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -41,7 +41,7 @@ public function registerEvents() 'Request.dispatch' => 'checkControllerPermission', 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', 'UsersManager.deleteUser' => 'deleteAlertsForLogin', - 'UsersManager.removeSiteAccess' => 'removeSiteAccessForUser', + 'UsersManager.removeSiteAccess' => 'removeAlertsForUser', 'SitesManager.deleteSite.end' => 'deleteAlertsForSite', 'Db.getTablesInstalled' => 'getTablesInstalled', 'ScheduledTasks.execute' => 'startingScheduledTask', @@ -298,7 +298,7 @@ public function validateReportParameters($parameters, $alertMedium) } - public function removeSiteAccessForUser($userLogin, $idSites) + public function removeAlertsForUser($userLogin, $idSites) { $model = $this->getModel(); $alerts = $model->getAlerts($idSites, $userLogin); From 26b5acb1da6f4ca641f567609277db6f43087bb5 Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 12:22:29 +1300 Subject: [PATCH 09/15] Fix --- CustomAlerts.php | 4 ++++ tests/Integration/ModelTest.php | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/CustomAlerts.php b/CustomAlerts.php index 894c77de..e25277c0 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -300,6 +300,10 @@ public function validateReportParameters($parameters, $alertMedium) public function removeAlertsForUser($userLogin, $idSites) { + if (empty($idSites) || empty($userLogin)) { + return; + } + $model = $this->getModel(); $alerts = $model->getAlerts($idSites, $userLogin); diff --git a/tests/Integration/ModelTest.php b/tests/Integration/ModelTest.php index 9bc3ac90..ed200254 100644 --- a/tests/Integration/ModelTest.php +++ b/tests/Integration/ModelTest.php @@ -431,6 +431,47 @@ public function test_deleteTriggeredAlertsForSite() $this->assertEquals(3, $alerts[1]['idtriggered']); } + public function test_deleteTriggeredAlertsForUser_shouldRemoveOnlyMatchingAlertAndLogin() + { + $alertIdA = $this->createAlert('UserAlertA', 'day', array($this->idSite, $this->idSite2), 'nb_visits', 'MultiSites_getOne', 'userA'); + $alertIdB = $this->createAlert('UserAlertB', 'day', array($this->idSite), 'nb_visits', 'MultiSites_getOne', 'userA'); + + $now = Date::now()->getDatetime(); + $this->model->triggerAlert($alertIdA, $this->idSite, 11, 5, $now); + $this->model->triggerAlert($alertIdA, $this->idSite2, 12, 6, $now); + $this->model->triggerAlert($alertIdB, $this->idSite, 13, 7, $now); + + $alerts = $this->model->getTriggeredAlerts(array($this->idSite, $this->idSite2), 'userA'); + $this->assertCount(3, $alerts); + + $this->model->deleteTriggeredAlertsForUser($alertIdA, 'userA'); + + $alerts = $this->model->getTriggeredAlerts(array($this->idSite, $this->idSite2), 'userA'); + $this->assertCount(1, $alerts); + $this->assertEquals($alertIdB, $alerts[0]['idalert']); + } + + public function test_deleteAlertSitesForSites_shouldRemoveOnlySpecifiedSites() + { + $alertId = $this->createAlert('SiteAlert', 'week', array($this->idSite, $this->idSite2)); + + $this->model->deleteAlertSitesForSites($alertId, array($this->idSite2)); + + $alert = $this->model->getAlert($alertId); + $expectedSites = array($this->idSite); + $actualSites = $alert['id_sites']; + sort($expectedSites); + sort($actualSites); + $this->assertEquals($expectedSites, $actualSites); + + $otherAlert = $this->model->getAlert(2); + $expectedOtherSites = array($this->idSite, $this->idSite2); + $actualOtherSites = $otherAlert['id_sites']; + sort($expectedOtherSites); + sort($actualOtherSites); + $this->assertEquals($expectedOtherSites, $actualOtherSites); + } + public function testGetTriggeredAlertsFromPastNHours() { $now = Date::now(); From e1f594ef6d16d472f99e3f56482d4145844020b4 Mon Sep 17 00:00:00 2001 From: Altamash Shaikh Date: Wed, 4 Feb 2026 05:49:57 +0530 Subject: [PATCH 10/15] Removed unwanted file --- .github/workflows/.git-hooks-matomo/pre-push | 106 ------------------- 1 file changed, 106 deletions(-) delete mode 100755 .github/workflows/.git-hooks-matomo/pre-push diff --git a/.github/workflows/.git-hooks-matomo/pre-push b/.github/workflows/.git-hooks-matomo/pre-push deleted file mode 100755 index 17ac8c23..00000000 --- a/.github/workflows/.git-hooks-matomo/pre-push +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/bash - -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# - - - -### Check we're running in the context of a plugin and get helpful dir variables ### - -REPO_DIR="$(git rev-parse --show-toplevel)" -echo "Running pre-commit hook in repo: $REPO_DIR" - -if [[ "$REPO_DIR" =~ /plugins/(.*) ]]; then - PLUGIN_PATH="plugins/${BASH_REMATCH[1]}/" -else - echo "Not a plugin, not running any further checks" - exit 1 -fi -MATOMO_DIR=$(echo "$REPO_DIR" | sed -E 's|/plugins/.*$||') - - - -### Figure out how to run PHPStan - ddev or not. ### - -COMMAND="" -# Use local PHP if setup -if command -v php >/dev/null 2>&1; then - if [ -f "${MATOMO_DIR}/vendor/bin/phpstan" ]; then - COMMAND="${MATOMO_DIR}/vendor/bin/phpstan" - PLUGIN_PATH='' - fi -elif command -v ddev >/dev/null 2>&1; then - # Use ddev if setup (overridding local setup) - if [ -d "$MATOMO_DIR/.ddev" ]; then - cd "$MATOMO_DIR" || exit 1 - if ddev status 2>&1 > /dev/null; then - COMMAND="ddev exec phpstan" - fi - fi -fi -# If no command, exit -if [[ -z "$COMMAND" ]]; then - echo "No way to run phpstan found." - exit 1 -fi - - - -# Basic setup -cd "$REPO_DIR" -STATUS=0 - - - - -### Run PHPStan on newly created files. ### - -PHPSTAN_CREATED_CONFIG=phpstan/phpstan.created.neon -MAIN_BRANCH='5.x-dev' -if [[ -f "$PHPSTAN_CREATED_CONFIG" ]]; then - CHANGED_FILES=$(git diff --name-only ${MAIN_BRANCH} --diff-filter=A | grep '\.php$' || true) - if [ -z "$CHANGED_FILES" ]; then - echo "No created PHP files" - else - echo "Running PHPstan at a very high level on new files" - CHANGED_FILES=`echo "$CHANGED_FILES" | sed -e 's/^\(.*\)$/"\1"/' | xargs -I{} echo "${PLUGIN_PATH}{}"` - echo "$CHANGED_FILES" | xargs $COMMAND analyse -c ${PLUGIN_PATH}${PHPSTAN_CREATED_CONFIG} || STATUS=1 - fi -fi - - - -### Run PHPStan on modified files. ### -PHPSTAN_MODIFIED_CONFIG=phpstan/phpstan.modified.neon -if [[ -f "$PHPSTAN_MODIFIED_CONFIG" ]]; then - CHANGED_FILES=$(git diff --name-only ${MAIN_BRANCH} --diff-filter=CM | grep '\.php$' || true) - if [ -z "$CHANGED_FILES" ]; then - echo "No changed PHP files" - else - echo "Running PHPstan on modified files" - CHANGED_FILES=`echo "$CHANGED_FILES" | sed -e 's/^\(.*\)$/"\1"/' | xargs -I{} echo "${PLUGIN_PATH}{}"` - echo "$CHANGED_FILES" | xargs $COMMAND analyse -c ${PLUGIN_PATH}${PHPSTAN_MODIFIED_CONFIG} || STATUS=1 - fi -fi - -# Don't bother running the full check, as we check changes files already, and -# can assume that the unchanged files don't need rechecking. -# -# Github will check this anyway. -# -# PHPSTAN_BASE_CONFIG=phpstan.neon -# if [[ -f "$PHPSTAN_BASE_CONFIG" ]]; then -# echo "Running PHPstan at a base level on all plugin files" -# $COMMAND analyse -c ${PLUGIN_PATH}/${PHPSTAN_BASE_CONFIG} || STATUS=1 -# fi - -exit $STATUS From e195d78f5563c9c7f2f9a341accef057673ba482 Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 13:29:11 +1300 Subject: [PATCH 11/15] Fixed --- CustomAlerts.php | 14 ++++++++++++-- Model.php | 9 +++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CustomAlerts.php b/CustomAlerts.php index e25277c0..a548744c 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -298,7 +298,17 @@ public function validateReportParameters($parameters, $alertMedium) } - public function removeAlertsForUser($userLogin, $idSites) + /** + * Remove alerts associated with user + * + * If an alert is related to multiple sites that aren't in idSites, we + * won't delete the site, just remove the alert_site link and triggers + * + * @param string $userLogin Username of + * @param array $idSites + * @return void + */ + public function removeAlertsForUser($userLogin, $idSites): void { if (empty($idSites) || empty($userLogin)) { return; @@ -313,7 +323,7 @@ public function removeAlertsForUser($userLogin, $idSites) if (count($alertSites) === 1 || empty(array_diff($alertSites, $idSites))) { $model->deleteAlert($alertId); } else { - $model->deleteTriggeredAlertsForUser($alertId, $userLogin); + $model->deleteTriggeredAlertsForUserAndSites($alertId, $idSites, $userLogin); $model->deleteAlertSitesForSites($alertId, $idSites); } } diff --git a/Model.php b/Model.php index d064a627..796eb768 100755 --- a/Model.php +++ b/Model.php @@ -454,10 +454,15 @@ public function deleteTriggeredAlertsForSite($idSite) $this->getDb()->query("DELETE FROM " . Common::prefixTable("alert_triggered") . " WHERE idsite = ?", $idSite); } - public function deleteTriggeredAlertsForUser($idAlert, $login) + public function deleteTriggeredAlertsForUserAndSites($idAlert, $idSites, $login) { $db = $this->getDb(); - $db->query("DELETE FROM " . Common::prefixTable("alert_triggered") . " WHERE idalert = ? AND login = ?", array($idAlert, $login)); + $placeholders = Common::getSqlStringFieldsArray($idSites); + $bind = array_merge(array($idAlert, $login), $idSites); + $db->query( + "DELETE FROM " . Common::prefixTable("alert_triggered") . " WHERE idalert = ? AND login = ? AND idsite in (" . $placeholders . " )", + $bind + ); } public function deleteAlertSitesForSites($idAlert, $idSites) From be51fb7618cba3a126e2fa0a5401cf16ee619ef9 Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 13:37:09 +1300 Subject: [PATCH 12/15] changed test --- tests/Integration/ModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/ModelTest.php b/tests/Integration/ModelTest.php index ed200254..6bc2935f 100644 --- a/tests/Integration/ModelTest.php +++ b/tests/Integration/ModelTest.php @@ -444,7 +444,7 @@ public function test_deleteTriggeredAlertsForUser_shouldRemoveOnlyMatchingAlertA $alerts = $this->model->getTriggeredAlerts(array($this->idSite, $this->idSite2), 'userA'); $this->assertCount(3, $alerts); - $this->model->deleteTriggeredAlertsForUser($alertIdA, 'userA'); + $this->model->deleteTriggeredAlertsForUserAndSites($alertIdA, array($this->idSite, $this->idSite2), 'userA'); $alerts = $this->model->getTriggeredAlerts(array($this->idSite, $this->idSite2), 'userA'); $this->assertCount(1, $alerts); From 65ebb90764a5112c9f49ec3a1e2183280b44b2d0 Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 13:48:24 +1300 Subject: [PATCH 13/15] Fixed docs --- CustomAlerts.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CustomAlerts.php b/CustomAlerts.php index a548744c..a69e0dcc 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -302,10 +302,10 @@ public function validateReportParameters($parameters, $alertMedium) * Remove alerts associated with user * * If an alert is related to multiple sites that aren't in idSites, we - * won't delete the site, just remove the alert_site link and triggers + * won't delete the alert, just remove the alert_site link and triggers * - * @param string $userLogin Username of - * @param array $idSites + * @param string $userLogin Username of user who had access removed + * @param array $idSites List of website IDs * @return void */ public function removeAlertsForUser($userLogin, $idSites): void From 3875c498cd9a42680f1526a751c14bf8b606f1da Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 15:07:35 +1300 Subject: [PATCH 14/15] removed redundant logic --- CustomAlerts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CustomAlerts.php b/CustomAlerts.php index a69e0dcc..43511f7c 100755 --- a/CustomAlerts.php +++ b/CustomAlerts.php @@ -320,7 +320,7 @@ public function removeAlertsForUser($userLogin, $idSites): void foreach ($alerts as $alert) { $alertId = $alert['idalert']; $alertSites = $alert['id_sites']; - if (count($alertSites) === 1 || empty(array_diff($alertSites, $idSites))) { + if (empty(array_diff($alertSites, $idSites))) { $model->deleteAlert($alertId); } else { $model->deleteTriggeredAlertsForUserAndSites($alertId, $idSites, $userLogin); From 80320160c7b55c167751029c040250b1943d7e70 Mon Sep 17 00:00:00 2001 From: Lachlan Reynolds Date: Wed, 4 Feb 2026 16:37:53 +1300 Subject: [PATCH 15/15] Added test cases --- tests/Integration/CustomAlertsTest.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/Integration/CustomAlertsTest.php b/tests/Integration/CustomAlertsTest.php index 5e6142af..bf2e3a56 100644 --- a/tests/Integration/CustomAlertsTest.php +++ b/tests/Integration/CustomAlertsTest.php @@ -200,6 +200,26 @@ public function test_deleteAlertsForLogin() $this->assertEquals('Initial6', $alerts[2]['name']); } + public function test_removeSiteAccessEventRemovesAlertsForSitesAndLogin() + { + $this->createAlert('DeleteMe', array(), array(1), 'userLogin'); + + $this->assertCount(1, $this->model->getAlerts(array(1), 'userLogin')); + Piwik::postEvent('UsersManager.removeSiteAccess', array('userLogin', array(1))); + $this->assertCount(0, $this->model->getAlerts(array(1), 'userLogin')); + } + + public function test_removeSiteAccessEventRemovesSiteAndTriggersButKeepsAlert() + { + $alert = $this->createAlert('KeepMe', array(), array(1, 2), 'userLogin'); + $this->model->triggerAlert($alert['idalert'], 1, 99, 48, Date::now()->getDatetime()); + + Piwik::postEvent('UsersManager.removeSiteAccess', array('userLogin', array(1))); + + $this->assertNotNull($this->model->getAlert($alert['idalert'])); + $this->assertCount(0, $this->model->getTriggeredAlerts(array(1), 'userLogin')); + } + public function testStartingScheduledTask() { $this->checkOptionStringValue(true);