Skip to content

Commit 136011f

Browse files
committed
Add Mattermost webhook payload support
1 parent c698bae commit 136011f

File tree

4 files changed

+182
-4
lines changed

4 files changed

+182
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Liens Morts Detector est une extension WordPress qui détecte les liens et image
2323
- Actions en ligne et groupées : modifier une URL, proposer/appliquer une redirection détectée, re-vérifier, ignorer/restaurer ou dissocier un lien en conservant l’ancre.【F:liens-morts-detector-jlg/includes/class-blc-links-list-table.php†L530-L881】
2424

2525
### Notifications et suivi
26-
- Notifications par e-mail ou webhook personnalisable, avec choix du canal, du gabarit de message et des catégories de statuts HTTP qui déclenchent un envoi.【F:liens-morts-detector-jlg/includes/blc-settings-fields.php†L295-L366】【F:liens-morts-detector-jlg/includes/blc-settings-fields.php†L659-L1669
26+
- Notifications par e-mail ou webhook personnalisable, avec choix du canal (générique JSON, Slack, Microsoft Teams ou Mattermost), du gabarit de message et des catégories de statuts HTTP qui déclenchent un envoi.【F:liens-morts-detector-jlg/includes/blc-settings-fields.php†L295-L366】【F:liens-morts-detector-jlg/includes/blc-notification-payloads.php†L1-L318
2727
- Résumés post-scan envoyés automatiquement depuis le cœur du scanner dès que des destinataires ou un webhook sont configurés.【F:liens-morts-detector-jlg/includes/blc-scanner.php†L1231-L1250】
2828
- Chaque lot publie des métriques (durée, progression, réussite/échec) stockées côté WordPress et exposées via un hook pour alimenter des tableaux de bord externes.【F:liens-morts-detector-jlg/includes/blc-scanner.php†L3214-L3250】
2929

liens-morts-detector-jlg/includes/blc-notification-payloads.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ function blc_build_notification_webhook_payload($channel, $message, array $summa
2222
return blc_build_slack_notification_payload($message, $summary, $settings);
2323
case 'teams':
2424
return blc_build_teams_notification_payload($message, $summary, $settings);
25+
case 'mattermost':
26+
return blc_build_mattermost_notification_payload($message, $summary, $settings);
2527
case 'generic':
2628
default:
2729
return blc_build_generic_notification_payload($message, $summary);
@@ -343,6 +345,135 @@ function blc_build_teams_notification_payload($message, array $summary, array $s
343345
);
344346
}
345347

348+
/**
349+
* Build an attachment based payload for Mattermost.
350+
*
351+
* Mattermost est compatible avec le format Slack des pièces jointes. On
352+
* capitalise sur ce support pour proposer un rendu riche sans multiplier les
353+
* formats de sortie tout en conservant une structure adaptée au canal.
354+
*
355+
* @param string $message Summary message used as fallback text.
356+
* @param array<string, mixed> $summary Structured summary metadata.
357+
* @param array<string, mixed> $settings Webhook settings.
358+
*
359+
* @return array<string, mixed>
360+
*/
361+
function blc_build_mattermost_notification_payload($message, array $summary, array $settings = array()) {
362+
$subject = isset($summary['subject']) ? blc_trim_slack_plain_text((string) $summary['subject']) : '';
363+
$site_name = isset($summary['site_name']) ? blc_trim_slack_plain_text((string) $summary['site_name']) : '';
364+
$dataset_label = isset($summary['dataset_label']) ? blc_trim_slack_plain_text((string) $summary['dataset_label']) : '';
365+
$dataset_type = isset($summary['dataset_type']) ? (string) $summary['dataset_type'] : '';
366+
$broken_count = isset($summary['broken_count']) ? (int) $summary['broken_count'] : 0;
367+
$report_url = isset($summary['report_url']) ? (string) $summary['report_url'] : '';
368+
$difference = array_key_exists('difference', $summary) ? $summary['difference'] : null;
369+
$previous = array_key_exists('previous_count', $summary) ? $summary['previous_count'] : null;
370+
$top_issues = isset($summary['top_issues']) && is_array($summary['top_issues']) ? $summary['top_issues'] : array();
371+
$filters = isset($summary['status_filters']) && is_array($summary['status_filters']) ? $summary['status_filters'] : array();
372+
$filter_labels = blc_translate_notification_status_filters($filters);
373+
374+
if ($dataset_label === '' && $dataset_type !== '') {
375+
$dataset_label = $dataset_type;
376+
}
377+
378+
$trend_label = blc_format_notification_trend($difference, $previous);
379+
380+
$fields = array(
381+
array(
382+
'short' => true,
383+
'title' => __('Site', 'liens-morts-detector-jlg'),
384+
'value' => $site_name !== '' ? blc_escape_slack_text($site_name) : __('Inconnu', 'liens-morts-detector-jlg'),
385+
),
386+
array(
387+
'short' => true,
388+
'title' => __('Analyse', 'liens-morts-detector-jlg'),
389+
'value' => $dataset_label !== '' ? blc_escape_slack_text($dataset_label) : __('N/A', 'liens-morts-detector-jlg'),
390+
),
391+
array(
392+
'short' => true,
393+
'title' => __('Éléments cassés', 'liens-morts-detector-jlg'),
394+
'value' => blc_escape_slack_text((string) $broken_count),
395+
),
396+
array(
397+
'short' => true,
398+
'title' => __('Tendance', 'liens-morts-detector-jlg'),
399+
'value' => blc_escape_slack_text($trend_label),
400+
),
401+
);
402+
403+
if ($filter_labels !== array()) {
404+
$fields[] = array(
405+
'short' => false,
406+
'title' => __('Filtres actifs', 'liens-morts-detector-jlg'),
407+
'value' => blc_escape_slack_text(implode('', $filter_labels)),
408+
);
409+
}
410+
411+
$issue_lines = array();
412+
if ($top_issues !== array()) {
413+
foreach ($top_issues as $issue) {
414+
if (!is_array($issue)) {
415+
continue;
416+
}
417+
418+
$url = isset($issue['url']) ? (string) $issue['url'] : '';
419+
$http_status = isset($issue['http_status']) ? $issue['http_status'] : null;
420+
$occurrences = isset($issue['occurrence_count']) ? (int) $issue['occurrence_count'] : null;
421+
$title = isset($issue['post_title']) ? (string) $issue['post_title'] : '';
422+
423+
$details = array();
424+
if ($http_status !== null && $http_status !== '') {
425+
$details[] = sprintf(__('statut : %s', 'liens-morts-detector-jlg'), $http_status);
426+
}
427+
if ($occurrences !== null) {
428+
$details[] = sprintf(__('occurrences : %d', 'liens-morts-detector-jlg'), $occurrences);
429+
}
430+
if ($title !== '') {
431+
$details[] = sprintf(__('contenu : %s', 'liens-morts-detector-jlg'), blc_escape_slack_text($title));
432+
}
433+
434+
$issue_label = $url !== ''
435+
? sprintf('<%1$s|%1$s>', blc_escape_slack_url($url))
436+
: __('URL inconnue', 'liens-morts-detector-jlg');
437+
438+
$issue_lines[] = sprintf('• %s — %s', $issue_label, blc_escape_slack_text(implode('', $details)));
439+
}
440+
}
441+
442+
$color = $broken_count > 0 ? '#D0382D' : '#2E8540';
443+
$title = $subject !== '' ? $subject : __('Résumé de scan Liens Morts Detector', 'liens-morts-detector-jlg');
444+
445+
$attachment = array(
446+
'fallback' => $message,
447+
'color' => $color,
448+
'title' => $title,
449+
'text' => $message,
450+
'fields' => $fields,
451+
'mrkdwn_in' => array('text', 'fields'),
452+
);
453+
454+
if ($issue_lines !== array()) {
455+
$attachment['text'] .= sprintf("\n\n*%s*\n%s",
456+
__('Problèmes principaux', 'liens-morts-detector-jlg'),
457+
implode("\n", $issue_lines)
458+
);
459+
}
460+
461+
if ($report_url !== '') {
462+
$attachment['actions'] = array(
463+
array(
464+
'name' => __('Ouvrir le rapport', 'liens-morts-detector-jlg'),
465+
'type' => 'button',
466+
'url' => $report_url,
467+
),
468+
);
469+
}
470+
471+
return array(
472+
'text' => $message,
473+
'attachments' => array($attachment),
474+
);
475+
}
476+
346477
/**
347478
* Translate status filter identifiers into human readable labels.
348479
*

liens-morts-detector-jlg/includes/blc-settings-fields.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,9 +2920,10 @@ function blc_sanitize_notification_recipients_option($value) {
29202920
function blc_get_notification_webhook_channel_choices() {
29212921
return array(
29222922
'disabled' => __('Désactivé', 'liens-morts-detector-jlg'),
2923-
'generic' => __('Webhook générique (JSON)', 'liens-morts-detector-jlg'),
2924-
'slack' => __('Slack', 'liens-morts-detector-jlg'),
2925-
'teams' => __('Microsoft Teams', 'liens-morts-detector-jlg'),
2923+
'generic' => __('Webhook générique (JSON)', 'liens-morts-detector-jlg'),
2924+
'slack' => __('Slack', 'liens-morts-detector-jlg'),
2925+
'teams' => __('Microsoft Teams', 'liens-morts-detector-jlg'),
2926+
'mattermost' => __('Mattermost', 'liens-morts-detector-jlg'),
29262927
);
29272928
}
29282929

tests/BlcWebhookNotificationsTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,52 @@ public function test_build_teams_payload_adds_facts_and_actions(): void
133133
$this->assertStringContainsString('occurrences : 5', $payload['sections'][1]['text']);
134134
}
135135

136+
public function test_build_mattermost_payload_includes_attachments_and_actions(): void
137+
{
138+
require_once __DIR__ . '/../liens-morts-detector-jlg/includes/blc-notification-payloads.php';
139+
140+
$summary = array(
141+
'subject' => 'Synthèse scan liens',
142+
'site_name' => 'Site Mattermost',
143+
'dataset_label' => 'Analyse globale',
144+
'dataset_type' => 'link',
145+
'broken_count' => 12,
146+
'report_url' => 'https://example.test/report.csv',
147+
'difference' => 4,
148+
'previous_count' => 8,
149+
'status_filters' => array('status_404_410'),
150+
'top_issues' => array(
151+
array(
152+
'url' => 'https://example.test/500',
153+
'http_status' => 500,
154+
'occurrence_count' => 6,
155+
'post_title' => 'Page 500',
156+
),
157+
),
158+
);
159+
160+
$payload = blc_build_notification_webhook_payload('mattermost', 'fallback message', $summary);
161+
162+
$this->assertArrayHasKey('attachments', $payload);
163+
$this->assertNotEmpty($payload['attachments']);
164+
165+
$attachment = $payload['attachments'][0];
166+
$this->assertSame('fallback message', $attachment['fallback']);
167+
$this->assertArrayHasKey('fields', $attachment);
168+
$this->assertGreaterThanOrEqual(4, count($attachment['fields']));
169+
170+
$field_titles = array_column($attachment['fields'], 'title');
171+
$this->assertContains(__('Éléments cassés', 'liens-morts-detector-jlg'), $field_titles);
172+
$this->assertContains(__('Tendance', 'liens-morts-detector-jlg'), $field_titles);
173+
174+
$this->assertArrayHasKey('actions', $attachment);
175+
$this->assertSame('button', $attachment['actions'][0]['type']);
176+
$this->assertSame('https://example.test/report.csv', $attachment['actions'][0]['url']);
177+
178+
$this->assertStringContainsString('https://example.test/500', $attachment['text']);
179+
$this->assertStringContainsString('occurrences : 6', $attachment['text']);
180+
}
181+
136182
public function test_unknown_channel_falls_back_to_generic_payload(): void
137183
{
138184
require_once __DIR__ . '/../liens-morts-detector-jlg/includes/blc-notification-payloads.php';

0 commit comments

Comments
 (0)