*/ 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, ], ], ]; } /** * Resolve the discussion title from the notification subject. */ 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', ''), '/' ); 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; } /** * English reason text for Activity Feed (systemDefault). */ protected function buildReasonTextEn(BlueprintInterface $blueprint): string { 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', }; } /** * 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); } }