initial try
This commit is contained in:
commit
18d56ceef9
|
|
@ -0,0 +1,147 @@
|
||||||
|
# flarum-msteams-webhook
|
||||||
|
|
||||||
|
Spiegelt Flarum-**Web-Benachrichtigungen** (die Glocke im Forum) in den **Microsoft Teams Activity Feed**.
|
||||||
|
|
||||||
|
## Zielbild
|
||||||
|
|
||||||
|
Diese erste Version registriert **keinen eigenen Teams-Kanal in der Flarum-UI**. Stattdessen spiegelt die Erweiterung exakt die Benachrichtigungen, die ein Benutzer bereits im **Web/Alert-Kanal** aktiviert hat, nach Teams.
|
||||||
|
|
||||||
|
Das ist für Flarum 1.8.x die pragmatischste v1, weil dadurch:
|
||||||
|
|
||||||
|
- die bestehende Flarum-Benachrichtigungslogik unverändert bleibt,
|
||||||
|
- keine Anpassung der Notification-Grid-UI notwendig ist,
|
||||||
|
- und Teams nur die Benachrichtigungen erhält, die im Forum ohnehin als Web-Alert ankommen würden.
|
||||||
|
|
||||||
|
## Kompatibilität
|
||||||
|
|
||||||
|
- Flarum: **^1.8**
|
||||||
|
- PHP: **^8.1**
|
||||||
|
- Getestetes Zielsystem laut Projekt-Hinweis: **Flarum 1.8.15**, **PHP 8.3.6**
|
||||||
|
|
||||||
|
## Was die Extension macht
|
||||||
|
|
||||||
|
1. Hängt sich per `Extend\Notification()->beforeSending(...)` in den Flarum-Notification-Flow ein.
|
||||||
|
2. Prüft für jeden Empfänger, ob die betreffende Notification im **Web-Kanal (`alert`)** aktiviert ist.
|
||||||
|
3. Löst dafür einen **Queue-Job** aus.
|
||||||
|
4. Der Queue-Job holt per **Client Credentials Flow** ein Graph-Token.
|
||||||
|
5. Anschließend wird per Microsoft Graph eine **Teams Activity Feed Notification** an den Benutzer gesendet.
|
||||||
|
|
||||||
|
## Voraussetzungen in Microsoft 365 / Entra / Teams
|
||||||
|
|
||||||
|
### 1) Entra App Registration
|
||||||
|
|
||||||
|
Benötigte Konfiguration:
|
||||||
|
|
||||||
|
- `tenant_id`
|
||||||
|
- `client_id`
|
||||||
|
- `client_secret`
|
||||||
|
|
||||||
|
Benötigte Microsoft Graph **Application Permission**:
|
||||||
|
|
||||||
|
- bevorzugt: `TeamsActivity.Send.User`
|
||||||
|
- alternativ: `TeamsActivity.Send`
|
||||||
|
|
||||||
|
### 2) Teams App
|
||||||
|
|
||||||
|
Die Ziel-Benutzer müssen die Teams-App installiert haben, für die Benachrichtigungen verschickt werden.
|
||||||
|
|
||||||
|
Empfehlung für v1:
|
||||||
|
|
||||||
|
- Activity Type: `systemDefault`
|
||||||
|
- Teams App ID in der Extension-Konfiguration hinterlegen
|
||||||
|
|
||||||
|
### 3) Benutzer-Mapping
|
||||||
|
|
||||||
|
Diese v1 unterstützt folgende Auflösungsstrategien:
|
||||||
|
|
||||||
|
- `email` → `POST /users/{email}/teamwork/sendActivityNotification`
|
||||||
|
- `upn` → UPN aus User-Preference oder Fallback auf E-Mail
|
||||||
|
- `preference` → frei konfigurierbarer User-Preference-Key
|
||||||
|
|
||||||
|
Wenn euer Entra-/SSO-Login bereits mit identischer E-Mail / UPN arbeitet, reicht meist `email` oder `upn`.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Composer
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require sbp-jm/flarum-msteams-webhook:dev-main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flarum Cache leeren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php flarum cache:clear
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
Da diese v1 noch kein Admin-UI mitliefert, werden die Settings direkt in der Flarum-`settings`-Tabelle gepflegt.
|
||||||
|
|
||||||
|
### Pflicht-Settings
|
||||||
|
|
||||||
|
```sql
|
||||||
|
REPLACE INTO settings (`key`, `value`) VALUES
|
||||||
|
('sbp-jm-msteams-webhook.enabled', '1'),
|
||||||
|
('sbp-jm-msteams-webhook.tenant_id', 'DEIN-TENANT-ID'),
|
||||||
|
('sbp-jm-msteams-webhook.client_id', 'DEINE-CLIENT-ID'),
|
||||||
|
('sbp-jm-msteams-webhook.client_secret', 'DEIN-CLIENT-SECRET'),
|
||||||
|
('sbp-jm-msteams-webhook.forum_base_url', 'https://forum.example.tld'),
|
||||||
|
('sbp-jm-msteams-webhook.user_lookup_strategy', 'email'),
|
||||||
|
('sbp-jm-msteams-webhook.activity_type', 'systemDefault'),
|
||||||
|
('sbp-jm-msteams-webhook.timeout_seconds', '15');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optionale Settings
|
||||||
|
|
||||||
|
```sql
|
||||||
|
REPLACE INTO settings (`key`, `value`) VALUES
|
||||||
|
('sbp-jm-msteams-webhook.teams_app_id', 'DEINE-TEAMS-APP-ID'),
|
||||||
|
('sbp-jm-msteams-webhook.icon_id', ''),
|
||||||
|
('sbp-jm-msteams-webhook.upn_preference_key', 'sbp-jm-msteams-webhook.upn'),
|
||||||
|
('sbp-jm-msteams-webhook.user_preference_key', 'sbp-jm-msteams-webhook.target');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Queue / Scheduler
|
||||||
|
|
||||||
|
Die Extension ist für asynchrone Zustellung ausgelegt.
|
||||||
|
|
||||||
|
Empfohlen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php flarum queue:work
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn ihr bereits den **database** Queue Driver und einen aktiven Scheduler verwendet, passt diese Erweiterung sehr gut in euer bestehendes Setup.
|
||||||
|
|
||||||
|
## Bekannte Einschränkungen der v1
|
||||||
|
|
||||||
|
- Noch **kein Admin-UI** zum Setzen der Settings
|
||||||
|
- Noch **kein User-UI** für persönliches Teams-Ziel / Opt-out
|
||||||
|
- Noch **keine Teams-Spalte** im Flarum-Notification-Grid
|
||||||
|
- Activity Feed setzt voraus, dass die passende Teams-App im Zielkontext installiert ist
|
||||||
|
|
||||||
|
## Nächste sinnvolle Schritte
|
||||||
|
|
||||||
|
1. Admin-UI für Settings ergänzen
|
||||||
|
2. User-UI für Ziel-UPN / Opt-out ergänzen
|
||||||
|
3. Optional eine echte `Teams`-Spalte im Notification Grid ergänzen
|
||||||
|
4. Test-Command (`php flarum teams:test-user <userId>`) hinzufügen
|
||||||
|
5. Persistentes Delivery-Log ergänzen
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```text
|
||||||
|
extend.php
|
||||||
|
src/
|
||||||
|
Listener/
|
||||||
|
MirrorAlertsToTeams.php
|
||||||
|
Job/
|
||||||
|
SendTeamsActivityNotificationJob.php
|
||||||
|
Service/
|
||||||
|
GraphTokenProvider.php
|
||||||
|
TeamsActivityNotifier.php
|
||||||
|
Support/
|
||||||
|
NotificationPayloadFactory.php
|
||||||
|
TargetResolver.php
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "sbp-jm/flarum-msteams-webhook",
|
||||||
|
"description": "Mirror Flarum web notifications to the Microsoft Teams activity feed.",
|
||||||
|
"type": "flarum-extension",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"flarum",
|
||||||
|
"flarum-extension",
|
||||||
|
"teams",
|
||||||
|
"microsoft-teams",
|
||||||
|
"notifications",
|
||||||
|
"entra-id"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jörg Mühlberger"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
|
"flarum/core": "^1.8",
|
||||||
|
"guzzlehttp/guzzle": "^7.8"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SbpJm\FlarumMSTeamsWebhook\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"flarum-extension": {
|
||||||
|
"title": "MS Teams Webhook",
|
||||||
|
"icon": {
|
||||||
|
"name": "fab fa-microsoft",
|
||||||
|
"backgroundColor": "#6264A7",
|
||||||
|
"color": "#ffffff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Flarum\Extend;
|
||||||
|
use SbpJm\FlarumMSTeamsWebhook\Listener\MirrorAlertsToTeams;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(new Extend\Locales(__DIR__ . '/locale')),
|
||||||
|
|
||||||
|
(new Extend\Notification())
|
||||||
|
->beforeSending(MirrorAlertsToTeams::class),
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
sbp-jm-msteams-webhook:
|
||||||
|
admin:
|
||||||
|
title: MS Teams Activity Feed
|
||||||
|
description: Mirror Flarum web notifications to Microsoft Teams.
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SbpJm\FlarumMSTeamsWebhook\Job;
|
||||||
|
|
||||||
|
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
use SbpJm\FlarumMSTeamsWebhook\Service\TeamsActivityNotifier;
|
||||||
|
|
||||||
|
class SendTeamsActivityNotificationJob
|
||||||
|
{
|
||||||
|
public int $tries = 3;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public BlueprintInterface $blueprint,
|
||||||
|
public User $user
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(TeamsActivityNotifier $notifier): void
|
||||||
|
{
|
||||||
|
$notifier->notify($this->user, $this->blueprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SbpJm\FlarumMSTeamsWebhook\Listener;
|
||||||
|
|
||||||
|
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
use Illuminate\Contracts\Queue\Queue;
|
||||||
|
use SbpJm\FlarumMSTeamsWebhook\Job\SendTeamsActivityNotificationJob;
|
||||||
|
use SbpJm\FlarumMSTeamsWebhook\Support\TargetResolver;
|
||||||
|
|
||||||
|
class MirrorAlertsToTeams
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected SettingsRepositoryInterface $settings,
|
||||||
|
protected Queue $queue,
|
||||||
|
protected TargetResolver $targetResolver
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User[] $newRecipients
|
||||||
|
* @return User[]
|
||||||
|
*/
|
||||||
|
public function __invoke(BlueprintInterface $blueprint, array $newRecipients): array
|
||||||
|
{
|
||||||
|
if (!$this->isEnabled()) {
|
||||||
|
return $newRecipients;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($newRecipients as $user) {
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->shouldMirrorForUser($user, $blueprint)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->targetResolver->resolve($user) === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->queue->push(new SendTeamsActivityNotificationJob($blueprint, $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newRecipients;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isEnabled(): bool
|
||||||
|
{
|
||||||
|
return filter_var($this->settings->get('sbp-jm-msteams-webhook.enabled', '0'), FILTER_VALIDATE_BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function shouldMirrorForUser(User $user, BlueprintInterface $blueprint): bool
|
||||||
|
{
|
||||||
|
$webPreferenceKey = User::getNotificationPreferenceKey($blueprint::getType(), 'alert');
|
||||||
|
$webEnabled = (bool) $user->getPreference($webPreferenceKey, false);
|
||||||
|
|
||||||
|
if (!$webEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$globalOptOutKey = 'sbp-jm-msteams-webhook.user_disabled';
|
||||||
|
|
||||||
|
return !(bool) $user->getPreference($globalOptOutKey, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SbpJm\FlarumMSTeamsWebhook\Service;
|
||||||
|
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use GuzzleHttp\ClientInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class GraphTokenProvider
|
||||||
|
{
|
||||||
|
protected ?string $accessToken = null;
|
||||||
|
protected int $expiresAt = 0;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected ClientInterface $http,
|
||||||
|
protected SettingsRepositoryInterface $settings
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken(): string
|
||||||
|
{
|
||||||
|
if ($this->accessToken !== null && time() < ($this->expiresAt - 60)) {
|
||||||
|
return $this->accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tenantId = trim((string) $this->settings->get('sbp-jm-msteams-webhook.tenant_id', ''));
|
||||||
|
$clientId = trim((string) $this->settings->get('sbp-jm-msteams-webhook.client_id', ''));
|
||||||
|
$clientSecret = (string) $this->settings->get('sbp-jm-msteams-webhook.client_secret', '');
|
||||||
|
|
||||||
|
if ($tenantId === '' || $clientId === '' || $clientSecret === '') {
|
||||||
|
throw new RuntimeException('Missing Microsoft Graph credentials in Flarum settings.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->http->request('POST', sprintf('https://login.microsoftonline.com/%s/oauth2/v2.0/token', rawurlencode($tenantId)), [
|
||||||
|
'form_params' => [
|
||||||
|
'grant_type' => 'client_credentials',
|
||||||
|
'client_id' => $clientId,
|
||||||
|
'client_secret' => $clientSecret,
|
||||||
|
'scope' => 'https://graph.microsoft.com/.default',
|
||||||
|
],
|
||||||
|
'timeout' => (int) $this->settings->get('sbp-jm-msteams-webhook.timeout_seconds', '15'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$payload = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
if (!is_array($payload) || empty($payload['access_token'])) {
|
||||||
|
throw new RuntimeException('Microsoft Graph token response did not contain an access token.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->accessToken = (string) $payload['access_token'];
|
||||||
|
$this->expiresAt = time() + (int) ($payload['expires_in'] ?? 3600);
|
||||||
|
|
||||||
|
return $this->accessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SbpJm\FlarumMSTeamsWebhook\Service;
|
||||||
|
|
||||||
|
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
use GuzzleHttp\ClientInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
use SbpJm\FlarumMSTeamsWebhook\Support\NotificationPayloadFactory;
|
||||||
|
use SbpJm\FlarumMSTeamsWebhook\Support\TargetResolver;
|
||||||
|
|
||||||
|
class TeamsActivityNotifier
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected ClientInterface $http,
|
||||||
|
protected SettingsRepositoryInterface $settings,
|
||||||
|
protected GraphTokenProvider $tokenProvider,
|
||||||
|
protected TargetResolver $targetResolver,
|
||||||
|
protected NotificationPayloadFactory $payloadFactory
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notify(User $user, BlueprintInterface $blueprint): void
|
||||||
|
{
|
||||||
|
$target = $this->targetResolver->resolve($user);
|
||||||
|
|
||||||
|
if ($target === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = $this->payloadFactory->make($user, $blueprint);
|
||||||
|
$token = $this->tokenProvider->getAccessToken();
|
||||||
|
|
||||||
|
$response = $this->http->request(
|
||||||
|
'POST',
|
||||||
|
sprintf('https://graph.microsoft.com/v1.0/users/%s/teamwork/sendActivityNotification', rawurlencode($target)),
|
||||||
|
[
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $token,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'json' => $payload,
|
||||||
|
'timeout' => (int) $this->settings->get('sbp-jm-msteams-webhook.timeout_seconds', '15'),
|
||||||
|
'http_errors' => false,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$status = $response->getStatusCode();
|
||||||
|
|
||||||
|
if ($status < 200 || $status >= 300) {
|
||||||
|
throw new RuntimeException(sprintf(
|
||||||
|
'Teams activity notification failed for user %s with status %s: %s',
|
||||||
|
$user->id,
|
||||||
|
$status,
|
||||||
|
(string) $response->getBody()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SbpJm\FlarumMSTeamsWebhook\Support;
|
||||||
|
|
||||||
|
use Flarum\Discussion\Discussion;
|
||||||
|
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||||
|
use Flarum\Post\Post;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class NotificationPayloadFactory
|
||||||
|
{
|
||||||
|
public function __construct(protected SettingsRepositoryInterface $settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string,mixed>
|
||||||
|
*/
|
||||||
|
public function make(User $recipient, BlueprintInterface $blueprint): array
|
||||||
|
{
|
||||||
|
$activityType = trim((string) $this->settings->get('sbp-jm-msteams-webhook.activity_type', 'systemDefault'));
|
||||||
|
$forumName = trim((string) $this->settings->get('forum_title', 'Flarum')) ?: 'Flarum';
|
||||||
|
$topicUrl = $this->resolveSubjectUrl($blueprint) ?? rtrim((string) $this->settings->get('sbp-jm-msteams-webhook.forum_base_url', ''), '/');
|
||||||
|
$previewText = $this->buildPreviewText($blueprint);
|
||||||
|
$teamsAppId = trim((string) $this->settings->get('sbp-jm-msteams-webhook.teams_app_id', ''));
|
||||||
|
$iconId = trim((string) $this->settings->get('sbp-jm-msteams-webhook.icon_id', ''));
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'topic' => [
|
||||||
|
'source' => 'text',
|
||||||
|
'value' => $forumName,
|
||||||
|
'webUrl' => $topicUrl,
|
||||||
|
],
|
||||||
|
'activityType' => $activityType === '' ? 'systemDefault' : $activityType,
|
||||||
|
'previewText' => [
|
||||||
|
'content' => $previewText,
|
||||||
|
],
|
||||||
|
'chainId' => $this->buildChainId($recipient, $blueprint),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($teamsAppId !== '') {
|
||||||
|
$payload['teamsAppId'] = $teamsAppId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iconId !== '') {
|
||||||
|
$payload['iconId'] = $iconId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($payload['activityType'] ?? 'systemDefault') !== 'systemDefault') {
|
||||||
|
$payload['templateParameters'] = [
|
||||||
|
[
|
||||||
|
'name' => 'notificationText',
|
||||||
|
'value' => $previewText,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildPreviewText(BlueprintInterface $blueprint): string
|
||||||
|
{
|
||||||
|
$actor = $blueprint->getFromUser();
|
||||||
|
$actorName = $actor instanceof User ? (string) ($actor->display_name ?? $actor->username ?? 'Jemand') : 'Jemand';
|
||||||
|
$type = $blueprint::getType();
|
||||||
|
$subject = $blueprint->getSubject();
|
||||||
|
|
||||||
|
$subjectTitle = null;
|
||||||
|
if ($subject instanceof Discussion) {
|
||||||
|
$subjectTitle = (string) $subject->title;
|
||||||
|
} elseif ($subject instanceof Post && $subject->discussion) {
|
||||||
|
$subjectTitle = (string) $subject->discussion->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = match ($type) {
|
||||||
|
'newPost' => sprintf('%s hat in einer beobachteten Diskussion geantwortet', $actorName),
|
||||||
|
'postMentioned' => sprintf('%s hat dich in einem Beitrag erwähnt', $actorName),
|
||||||
|
'userMentioned' => sprintf('%s hat dich erwähnt', $actorName),
|
||||||
|
'postLiked' => sprintf('%s hat einen Beitrag von dir geliked', $actorName),
|
||||||
|
'discussionRenamed' => sprintf('%s hat eine Diskussion umbenannt', $actorName),
|
||||||
|
default => sprintf('%s: neue Flarum-Benachrichtigung (%s)', $actorName, $type),
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($subjectTitle) {
|
||||||
|
$text .= ' – ' . $subjectTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_substr($text, 0, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveSubjectUrl(BlueprintInterface $blueprint): ?string
|
||||||
|
{
|
||||||
|
$baseUrl = rtrim((string) $this->settings->get('sbp-jm-msteams-webhook.forum_base_url', ''), '/');
|
||||||
|
if ($baseUrl === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subject = $blueprint->getSubject();
|
||||||
|
|
||||||
|
if ($subject instanceof Discussion) {
|
||||||
|
return sprintf('%s/d/%s', $baseUrl, $subject->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($subject instanceof Post) {
|
||||||
|
$discussionId = $subject->discussion_id;
|
||||||
|
$postNumber = $subject->number;
|
||||||
|
|
||||||
|
if ($discussionId !== null && $postNumber !== null) {
|
||||||
|
return sprintf('%s/d/%s/%s', $baseUrl, $discussionId, $postNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildChainId(User $recipient, BlueprintInterface $blueprint): int
|
||||||
|
{
|
||||||
|
$subject = $blueprint->getSubject();
|
||||||
|
$subjectId = is_object($subject) && isset($subject->id) ? (string) $subject->id : '0';
|
||||||
|
|
||||||
|
return abs(crc32(implode('|', [
|
||||||
|
(string) $recipient->id,
|
||||||
|
$blueprint::getType(),
|
||||||
|
$subjectId,
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SbpJm\FlarumMSTeamsWebhook\Support;
|
||||||
|
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class TargetResolver
|
||||||
|
{
|
||||||
|
public function __construct(protected SettingsRepositoryInterface $settings)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolve(User $user): ?string
|
||||||
|
{
|
||||||
|
$strategy = trim((string) $this->settings->get('sbp-jm-msteams-webhook.user_lookup_strategy', 'email'));
|
||||||
|
|
||||||
|
return match ($strategy) {
|
||||||
|
'email' => $this->clean($user->email),
|
||||||
|
'upn' => $this->resolveUpn($user),
|
||||||
|
'preference' => $this->clean($user->getPreference((string) $this->settings->get('sbp-jm-msteams-webhook.user_preference_key', 'sbp-jm-msteams-webhook.target'))),
|
||||||
|
default => $this->clean($user->email),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveUpn(User $user): ?string
|
||||||
|
{
|
||||||
|
$preferenceKey = (string) $this->settings->get('sbp-jm-msteams-webhook.upn_preference_key', 'sbp-jm-msteams-webhook.upn');
|
||||||
|
$upn = $this->clean($user->getPreference($preferenceKey));
|
||||||
|
|
||||||
|
if ($upn !== null) {
|
||||||
|
return $upn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->clean($user->email);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function clean(mixed $value): ?string
|
||||||
|
{
|
||||||
|
if (!is_string($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = trim($value);
|
||||||
|
|
||||||
|
return $value === '' ? null : $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue