Add per-recipient relationship labels in newsletter

Each featured person now carries a parenthetical label relative
to the recipient: "Jane Doe (your mother) — 45th birthday",
"Karl Müller (your 4th great-grandfather) — death". Labels are
italic, muted, and only appear when a path can be computed.

- New RelationshipPathFinder service mirrors webtrees'
  RelationshipService::getCloseRelationship BFS but with a
  configurable depth (default 14 hops ≈ 7 generations) so it
  reaches great-great-grandparents and beyond. Results are
  memoised per (recipient xref, target xref) within one
  dispatch run.
- nameFromPath() formatting is delegated to webtrees so the
  label honours the configured UI language (German, English,
  etc.) and gendered/inflected forms.
- The recipient's tree-bound Individual is looked up via
  Tree::getUserPreference(user, PREF_TREE_ACCOUNT_XREF). External
  admin-added recipients (no webtrees account, no linked record)
  silently get no labels — names render plain.
- Trade-off: the view now renders once per recipient (instead of
  once per language group), because the relationship map is
  personalised. For typical subscriber counts the extra string-
  concat cost is negligible compared to the SMTP send itself.
This commit is contained in:
2026-05-15 12:53:22 +02:00
parent a065d64c67
commit 3bc25a2bdb
3 changed files with 313 additions and 31 deletions
+14 -3
View File
@@ -20,7 +20,8 @@ use Illuminate\Support\Collection;
* @var int $lookahead_days
* @var int $historical_lookahead
* @var int $generated_at
* @var array<string,string> $avatar_cids xref => CID name
* @var array<string,string> $avatar_cids xref => CID name
* @var array<string,string> $relationships xref => "your mother" etc. (per-recipient)
* @var string $account_url
*/
@@ -50,14 +51,24 @@ $avatar_size = 56;
// ─── Helpers ────────────────────────────────────────────────────────────
$linked_name = static function (Individual $individual) use ($palette): string {
$relationships = $relationships ?? [];
$linked_name = static function (Individual $individual) use ($palette, $relationships): string {
$name = strip_tags($individual->fullName());
$url = $individual->url();
$style = 'color:' . $palette['ink'] . ';text-decoration:none;'
. 'border-bottom:1px solid ' . $palette['border'] . ';'
. 'padding-bottom:1px;';
return '<a href="' . e($url) . '" style="' . $style . '">' . e($name) . '</a>';
$html = '<a href="' . e($url) . '" style="' . $style . '">' . e($name) . '</a>';
if (isset($relationships[$individual->xref()])) {
$html .= ' <span style="color:' . $palette['ink3'] . ';font-style:italic;font-weight:400;font-size:13px;">('
. e(strip_tags($relationships[$individual->xref()]))
. ')</span>';
}
return $html;
};
$record_label = static function (Fact $fact) use ($linked_name): string {