Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
76 changes: 76 additions & 0 deletions .github/workflows/matomo-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Action for running tests
# This file has been automatically created.
# To recreate it you can run this command
# ./console generate:test-action --plugin="Slack" --php-versions="7.2,8.4" --schedule-cron="0 5 * * 6"

name: Plugin Slack Tests

on:
pull_request:
types: [opened, synchronize]
push:
branches:
- '**.x-dev'
workflow_dispatch:
schedule:
- cron: "0 5 * * 6"

permissions:
actions: read
checks: none
contents: read
deployments: none
issues: read
packages: none
pull-requests: read
repository-projects: none
security-events: none
statuses: none

concurrency:
group: php-${{ github.ref }}
cancel-in-progress: true

jobs:
PluginTests:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
php: [ '7.2', '8.4' ]
target: ['minimum_required_matomo', 'maximum_supported_matomo']
steps:
- uses: actions/checkout@v3
with:
lfs: true
persist-credentials: false
- name: Install package ripgrep
run: sudo apt-get install ripgrep
- name: Run tests
uses: matomo-org/github-action-tests@main
with:
plugin-name: 'Slack'
php-version: ${{ matrix.php }}
test-type: 'PluginTests'
matomo-test-branch: ${{ matrix.target }}
redis-service: true
artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
upload-artifacts: ${{ matrix.php == '7.2' && matrix.target == 'maximum_supported_matomo' }}
UI:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3
with:
lfs: true
persist-credentials: false
- name: running tests
uses: matomo-org/github-action-tests@main
with:
plugin-name: 'Slack'
matomo-test-branch: 'maximum_supported_matomo'
test-type: 'UI'
php-version: '7.2'
node-version: '16'
redis-service: true
artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
upload-artifacts: true
43 changes: 43 additions & 0 deletions .github/workflows/phpcs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: PHPCS 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

jobs:
phpcs:
name: PHPCS
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
lfs: false
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
tools: cs2pr
- name: Install dependencies
run:
composer init --name=matomo/slack --quiet;
composer --no-plugins config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true -n;
composer config repositories.matomo-coding-standards vcs https://github.com/matomo-org/matomo-coding-standards -n;
composer require matomo-org/matomo-coding-standards:dev-master;
composer install --dev --prefer-dist --no-progress --no-suggest
- name: Check PHP code styles
id: phpcs
run: ./vendor/bin/phpcs --report-full --standard=phpcs.xml --report-checkstyle=./phpcs-report.xml
- name: Show PHPCS results in PR
if: ${{ always() && steps.phpcs.outcome == 'failure' }}
run: cs2pr ./phpcs-report.xml --prepend-filename
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/tests/System/processed/
/tests/Integration/Importers/processed/
/tests/UI/processed-ui-screenshots/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Changelog

5.0.0 - 2025-09-01
- Initial release to send scheduled reports to a Slack channel
156 changes: 156 additions & 0 deletions ScheduleReportSlack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?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\Slack;

use Piwik\Container\StaticContainer;
use Piwik\Http;
use Piwik\Log\LoggerInterface;

class ScheduleReportSlack
{
private $subject;
private $filename;

private $fileContents;

private $channel;

private $fileID;

private $token;

private const SLACK_UPLOAD_URL_EXTERNAL = 'https://slack.com/api/files.getUploadURLExternal';
private const SLACK_COMPLETE_UPLOAD_EXTERNAL = 'https://slack.com/api/files.completeUploadExternal';

private const SLACK_TIMEOUT = 5000;

private $logger;

public function send(
string $subject,
string $fileName,
string $fileContents,
string $channel,
#[\SensitiveParameter]
string $token
) {
$this->subject = $subject;
$this->filename = $fileName;
$this->fileContents = $fileContents;
$this->channel = $channel;
$this->token = $token;
$this->logger = StaticContainer::get(LoggerInterface::class);

$uploadURL = $this->getUploadURLExternal();
if (!empty($uploadURL) && $this->sendFile($uploadURL)) {
return $this->completeUploadExternal();
}

$this->logger->debug('Unable to send ' . $fileName . ' report to Slack');

return false;
}

public function getUploadURLExternal(): string
{
try {
$response = $this->sendHttpRequest(
self::SLACK_UPLOAD_URL_EXTERNAL,
self::SLACK_TIMEOUT,
[
'token' => $this->token,
'filename' => $this->filename,
'length' => strlen($this->fileContents),
],
['Content-Type' => 'multipart/form-data']
);
} catch (\Exception $e) {
$this->logger->debug($e->getMessage());
return '';
}

$data = json_decode($response, true);
if (!empty($data) && !empty($data['upload_url']) && !empty($data['file_id'])) {
$this->fileID = $data['file_id'];

return $data['upload_url'];
}

return '';
}

public function sendFile(string $uploadURL): bool
{
try {
$response = $this->sendHttpRequest(
$uploadURL,
self::SLACK_TIMEOUT,
[$this->fileContents],
[],
true
);
} catch (\Exception $e) {
$this->logger->debug($e->getMessage());
return false;
}

return stripos($response, 'OK') !== false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick - better to use getExtended info and check the return code? Or something more robust than checking for "OK" in the string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to do a stricter comparison, please check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? That's the same isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to below, as the expected response is ok - {fileLength} from Slack for a successful upload

return strtolower($response) === ('ok - ' . strlen($fileContents));

}

public function completeUploadExternal(): bool
{
try {
$response = $this->sendHttpRequest(
self::SLACK_COMPLETE_UPLOAD_EXTERNAL,
self::SLACK_TIMEOUT,
[
'token' => $this->token,
'files' => json_encode([['id' => $this->fileID]]),
'channel_id' => $this->channel,
'initial_comment' => $this->subject
],
['Content-Type' => 'multipart/form-data']
);
} catch (\Exception $e) {
$this->logger->debug($e->getMessage());
return false;
}

$data = json_decode($response, true);

return !empty($data['ok']);
}

private function sendHttpRequest(string $url, int $timeout, array $requestBody, array $additionalHeaders = [], $requestBodyAsString = false)
{
if ($requestBodyAsString && !empty($requestBody[0])) {
$requestBody = $requestBody[0];
}

return Http::sendHttpRequestBy(
Http::getTransportMethod(),
$url,
$timeout,
$userAgent = null,
$destinationPath = null,
$file = null,
$followDepth = 0,
$acceptLanguage = false,
$acceptInvalidSslCertificate = false,
$byteRange = false,
$getExtendedInfo = false,
$httpMethod = 'POST',
$httpUsername = null,
$httpPassword = null,
$requestBody,
$additionalHeaders
);
}
}
Loading