Per-user intro versioning + admin pending-delivery view
Replaces "clear intro after first send", which dropped the message for any subscriber still queued on a slower cadence. - Each non-empty admin save bumps a per-locale version counter on the tree. The dispatcher includes the intro only for recipients whose last-seen version is behind, then advances their watermark after a successful send. Webtrees users get a per-user watermark; external addresses share one tree-level watermark. Re-saving the same text is a no-op. - The preferences page now shows delivery progress per locale: "Delivered to X of Y subscriber(s)" plus a collapsible Pending list with name + email of each subscriber who hasn't received the current intro yet, and a single "External recipients (N)" row when the external watermark is behind. - README rewritten to reflect every feature shipped since the initial commit (BockenTheme skin, embedded avatars, relationship labels, kin-distance filter, per-user cadence, bilingual subject prefix, locale-aware subject date, SiteUser as From, three subscriber sources, Markdown intro with personalisation tokens).
This commit is contained in:
@@ -173,7 +173,7 @@ use Illuminate\Support\Collection;
|
||||
</legend>
|
||||
<div class="col-sm-9">
|
||||
<small class="form-text text-muted d-block mb-2">
|
||||
<?= I18N::translate('Shown once, above the upcoming events. Cleared automatically after the next successful send.') ?>
|
||||
<?= I18N::translate('Delivered once to every subscriber on their own cadence. Edit and save the text to send a new intro to everyone.') ?>
|
||||
<br>
|
||||
<?= I18N::translate('Formatted as Markdown — e.g. %1$s for emphasis, %2$s for a link.', '<code>**bold**</code>', '<code>[label](https://example.org)</code>') ?>
|
||||
<br>
|
||||
@@ -183,17 +183,92 @@ use Illuminate\Support\Collection;
|
||||
<code>{{username}}</code>,
|
||||
<code>{{email}}</code>
|
||||
</small>
|
||||
<?php
|
||||
// Subscribers for this tree — used by the
|
||||
// per-locale "who has seen it?" block
|
||||
// below. Same approved/verified gate that
|
||||
// the dispatcher applies, so the admin's
|
||||
// numbers match the actual run.
|
||||
$tree_subscribers = $all_users->filter(static function (User $user) use ($tree): bool {
|
||||
if ($user->getPreference(\Fisharebest\Webtrees\Contracts\UserInterface::PREF_IS_ACCOUNT_APPROVED) !== '1') {
|
||||
return false;
|
||||
}
|
||||
if ($user->getPreference(\Fisharebest\Webtrees\Contracts\UserInterface::PREF_IS_EMAIL_VERIFIED) !== '1') {
|
||||
return false;
|
||||
}
|
||||
return $tree->getUserPreference($user, Configuration::USER_PREF_SUBSCRIBED) === '1';
|
||||
});
|
||||
$external_addresses = Configuration::extraRecipients($tree);
|
||||
?>
|
||||
<?php foreach (Configuration::supportedSubjectLocales() as $code => $label) : ?>
|
||||
<?php
|
||||
$field = 'intro-' . $id . '-' . $code;
|
||||
$val = Configuration::introForLocale($module, $tree, $code);
|
||||
$field = 'intro-' . $id . '-' . $code;
|
||||
$val = Configuration::introForLocale($module, $tree, $code);
|
||||
$current_v = Configuration::introVersion($module, $tree, $code);
|
||||
$external_v = Configuration::externalIntroVersion($module, $tree, $code);
|
||||
$locale_subs = $tree_subscribers->filter(static function (User $user) use ($code): bool {
|
||||
$pref = $user->getPreference(\Fisharebest\Webtrees\Contracts\UserInterface::PREF_LANGUAGE, '');
|
||||
return Configuration::canonicalSubjectLocale($pref) === $code;
|
||||
});
|
||||
// Status counts — only meaningful once
|
||||
// the intro has been bumped to v ≥ 1.
|
||||
$seen_users = 0;
|
||||
$pending = [];
|
||||
foreach ($locale_subs as $user) {
|
||||
if (Configuration::userIntroVersion($tree, $user, $code) >= $current_v) {
|
||||
$seen_users++;
|
||||
} else {
|
||||
$pending[] = $user;
|
||||
}
|
||||
}
|
||||
$externals_seen = $current_v > 0 && $external_v >= $current_v;
|
||||
$externals_pending = $current_v > 0 && !$externals_seen && $external_addresses !== [];
|
||||
$total = $locale_subs->count() + ($external_addresses === [] ? 0 : 1);
|
||||
$done = $seen_users + ($externals_seen ? 1 : 0);
|
||||
?>
|
||||
<div class="mb-2">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted mb-1" for="<?= e($field) ?>">
|
||||
<?= e($label) ?>
|
||||
</label>
|
||||
<textarea class="form-control" rows="6"
|
||||
id="<?= e($field) ?>" name="<?= e($field) ?>"><?= e($val) ?></textarea>
|
||||
|
||||
<?php if ($val !== '' && $current_v > 0) : ?>
|
||||
<div class="small text-muted mt-1">
|
||||
<?php if ($done === $total) : ?>
|
||||
<span class="text-success">✓</span>
|
||||
<?= I18N::translate('Delivered to all %d subscriber(s).', $total) ?>
|
||||
<?php else : ?>
|
||||
<?= I18N::translate('Delivered to %1$d of %2$d subscriber(s).', $done, $total) ?>
|
||||
<?php if ($pending !== [] || $externals_pending) : ?>
|
||||
<details class="mt-1">
|
||||
<summary class="text-muted" style="cursor:pointer;">
|
||||
<?= I18N::translate('Pending') ?>
|
||||
</summary>
|
||||
<ul class="list-unstyled small mb-0 mt-1 ps-2">
|
||||
<?php foreach ($pending as $user) : ?>
|
||||
<li>
|
||||
<span class="text-warning">⏳</span>
|
||||
<?= e($user->realName()) ?>
|
||||
<span class="text-muted"><<?= e($user->email()) ?>></span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
<?php if ($externals_pending) : ?>
|
||||
<li>
|
||||
<span class="text-warning">⏳</span>
|
||||
<?= I18N::translate('External recipients (%d)', count($external_addresses)) ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</details>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php elseif ($val !== '' && $current_v === 0) : ?>
|
||||
<div class="small text-muted mt-1">
|
||||
<?= I18N::translate('Save to schedule delivery.') ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user