diff --git a/src/Support/NotificationPayloadFactory.php b/src/Support/NotificationPayloadFactory.php index 2965bbf..f3e883a 100644 --- a/src/Support/NotificationPayloadFactory.php +++ b/src/Support/NotificationPayloadFactory.php @@ -12,135 +12,84 @@ use Flarum\User\User; class NotificationPayloadFactory { - public function __construct(protected SettingsRepositoryInterface $settings) + public function __construct( + protected SettingsRepositoryInterface $settings + ) {} + + /** + * Build the payload for Microsoft Teams Activity Feed. + * + * Informational only (systemDefault). + * No clickable navigation from the Activity Feed is expected. + * + * @return array + */ + public function make(User $recipient, BlueprintInterface $blueprint): array { + // Activity Feed is informational only + $activityType = 'systemDefault'; + + $forumName = trim((string) $this->settings->get('forum_title', 'Flarum')) ?: 'Flarum'; + + // Resolve dynamic data + $discussionTitle = $this->resolveDiscussionTitle($blueprint) ?? $forumName; + $discussionUrl = $this->resolveSubjectUrl($blueprint) + ?? rtrim((string) $this->settings->get('sbp-jm-msteams-webhook.forum_base_url', ''), '/'); + + $previewSnippet = $this->buildPreviewText($blueprint); + $reasonText = $this->buildReasonTextEn($blueprint); + + // Teams requires /l/ URLs for topic.source = text + // /l/browser is the official, neutral deep link for external pages + $teamsBrowserLink = 'https://teams.microsoft.com/l/browser/' . rawurlencode($discussionUrl); + + return [ + 'topic' => [ + 'source' => 'text', + 'value' => $discussionTitle, + 'webUrl' => $teamsBrowserLink, + ], + 'activityType' => $activityType, + 'previewText' => [ + 'content' => mb_substr($previewSnippet, 0, 150), + ], + 'templateParameters' => [ + [ + 'name' => 'systemDefaultText', + 'value' => $reasonText, + ], + ], + ]; } /** - * @return array + * Resolve the discussion title from the notification subject. */ -/** - * @return array - */ - 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'; - $iconId = trim((string) $this->settings->get('sbp-jm-msteams-webhook.icon_id', '')); - - // Diskussionstitel + URL dynamisch aus dem Subject ziehen - // $discussionTitle = $this->resolveDiscussionTitle($blueprint) ?? $forumName; - // $discussionUrl = $this->resolveSubjectUrl($blueprint) ?? 'https://forum.sbp.de'; - // $previewSnippet = $this->buildPreviewText($blueprint); - - // $discussionTitle = 'Backup-Strategie für Forum'; - // $discussionUrl = 'https://forum.sbp.de/d/34-which-file-extensions-are-allowed-for-uploads/2'; - // $previewSnippet = 'Bitte prüft, ob wir tägliche Snapshots brauchen.'; - // $reasonText = 'Neue Antwort in beobachteter Diskussion'; - - - - // // Freie Actor+Reason-Zeile für systemDefault - // $reasonText = match ($blueprint::getType()) { - // 'newPost' => 'There was a new answer in one of your folloed posts', - // 'postMentioned' => 'Your post has been mentioned', - // 'userMentioned' => 'You have been mentioned', - // 'postLiked' => 'Your post has been liked', - // default => 'New sbp forum update', - // }; - $discussionTitle = 'Backup-Strategie für Forum'; - $discussionUrl = 'https://forum.sbp.de/d/34-which-file-extensions-are-allowed-for-uploads/2'; - $previewSnippet = 'Bitte prüft, ob wir tägliche Snapshots brauchen.'; - $reasonText = 'Neue Antwort in beobachteter Diskussion'; - - // ✅ Teams Browser Deep Link (offiziell unterstützt) - $teamsBrowserLink = 'https://teams.microsoft.com/l/browser/' . rawurlencode($discussionUrl); - - $payload = [ - 'topic' => [ - 'source' => 'text', - 'value' => $discussionTitle, - 'webUrl' => $teamsBrowserLink, // ✅ MUSS /l/ sein - ], - 'activityType' => 'systemDefault', - 'previewText' => [ - 'content' => mb_substr($previewSnippet, 0, 150), - ], - 'templateParameters' => [ - [ - 'name' => 'systemDefaultText', - 'value' => $reasonText, - ], - ], - ]; - - if ($iconId !== '') { - $payload['iconId'] = $iconId; - } - - if (($payload['activityType'] ?? 'systemDefault') !== 'systemDefault') { - $payload['templateParameters'] = [ - [ - 'name' => 'notificationText', - 'value' => mb_substr($previewSnippet, 0, 150), - ], - ]; - } - - return $payload; - } - protected function buildPreviewText(BlueprintInterface $blueprint): string + protected function resolveDiscussionTitle(BlueprintInterface $blueprint): ?string { - if ($blueprint::getType() === 'teamsTest') { - return 'Flarum ➜ Teams Test erfolgreich angestoßen'; - } - - $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; + return (string) $subject->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; + if ($subject instanceof Post && $subject->discussion) { + return (string) $subject->discussion->title; } - return mb_substr($text, 0, 150); + return null; } - - - protected function resolveDiscussionTitle(BlueprintInterface $blueprint): ?string - { - $subject = $blueprint->getSubject(); - if ($subject instanceof Discussion) { - return (string) $subject->title; - } - - if ($subject instanceof Post && $subject->discussion) { - return (string) $subject->discussion->title; - } - - return null; - } + /** + * Resolve the URL to the discussion or specific post. + */ protected function resolveSubjectUrl(BlueprintInterface $blueprint): ?string { - $baseUrl = rtrim((string) $this->settings->get('sbp-jm-msteams-webhook.forum_base_url', ''), '/'); + $baseUrl = rtrim( + (string) $this->settings->get('sbp-jm-msteams-webhook.forum_base_url', ''), + '/' + ); + if ($baseUrl === '') { return null; } @@ -153,7 +102,7 @@ class NotificationPayloadFactory if ($subject instanceof Post) { $discussionId = $subject->discussion_id; - $postNumber = $subject->number; + $postNumber = $subject->number; if ($discussionId !== null && $postNumber !== null) { return sprintf('%s/d/%s/%s', $baseUrl, $discussionId, $postNumber); @@ -163,15 +112,58 @@ class NotificationPayloadFactory return $baseUrl; } - protected function buildChainId(User $recipient, BlueprintInterface $blueprint): int + /** + * English reason text for Activity Feed (systemDefault). + */ + protected function buildReasonTextEn(BlueprintInterface $blueprint): string { - $subject = $blueprint->getSubject(); - $subjectId = is_object($subject) && isset($subject->id) ? (string) $subject->id : '0'; + return match ($blueprint::getType()) { + 'newPost' => 'There was a new reply in a discussion you are following', + 'postMentioned' => 'You were mentioned in a post', + 'userMentioned' => 'You were mentioned', + 'postLiked' => 'Your post was liked', + 'discussionRenamed' => 'A discussion was renamed', + default => 'New forum activity', + }; + } - return abs(crc32(implode('|', [ - (string) $recipient->id, - $blueprint::getType(), - $subjectId, - ]))); + /** + * Build a short preview snippet shown in the Activity Feed. + */ + protected function buildPreviewText(BlueprintInterface $blueprint): string + { + if ($blueprint::getType() === 'teamsTest') { + return 'Flarum → Teams test notification sent successfully'; + } + + $actor = $blueprint->getFromUser(); + $actorName = $actor instanceof User + ? (string) ($actor->display_name ?? $actor->username ?? 'Someone') + : 'Someone'; + + $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 replied to a discussion', $actorName), + 'postMentioned' => sprintf('%s mentioned you in a post', $actorName), + 'userMentioned' => sprintf('%s mentioned you', $actorName), + 'postLiked' => sprintf('%s liked your post', $actorName), + 'discussionRenamed' => sprintf('%s renamed a discussion', $actorName), + default => sprintf('%s: new forum activity', $actorName), + }; + + if ($subjectTitle) { + $text .= ' – ' . $subjectTitle; + } + + return mb_substr($text, 0, 150); } }