Files
webtrees_email_newsletter/resources/views/admin.phtml
T
Alexander 9458867d4d One-shot bilingual intro paragraph with markdown + author avatar
- Admin can set a per-locale intro paragraph for the next issue on
  the preferences page; cleared automatically after a successful
  send. Stored in module_setting (longText) so multi-paragraph
  notes fit.
- Intro is rendered via webtrees' CommonMark factory (same flavour
  as notes) with raw HTML escaped, supports {{first_name}},
  {{last_name}}, {{username}}, {{email}} substitution per recipient.
- Two-column intro layout: tree contact user's linked Individual
  becomes the editorial portrait on the left. Their avatar is
  added to the per-recipient embed set so the inline image always
  resolves rather than falling through to a tree-page login link.
- Masthead now shows the tree URL under the title.
- Avatar source dimensions bumped 96→192 px and JPEG quality 75→88
  so portraits stay crisp at retina display ratios.
2026-05-15 15:32:30 +02:00

283 lines
15 KiB
PHTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use EmailNewsletter\Configuration;
use EmailNewsletter\Module;
use Fisharebest\Webtrees\Http\RequestHandlers\ControlPanel;
use Fisharebest\Webtrees\Http\RequestHandlers\ModulesAllPage;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Tree;
use Fisharebest\Webtrees\User;
use Illuminate\Support\Collection;
/**
* @var Module $module
* @var Collection<int,Tree> $all_trees
* @var Collection<int,User> $all_users
* @var string $cron_token
* @var string $cron_url
* @var string $title
*/
?>
<?= view('components/breadcrumbs', [
'links' => [
route(ControlPanel::class) => I18N::translate('Control panel'),
route(ModulesAllPage::class) => I18N::translate('Modules'),
$title,
],
]) ?>
<h1><?= e($title) ?></h1>
<p>
<?= I18N::translate('Configure newsletter dispatch on a per-tree basis. The sender is the contact user of each tree (falling back to the site webmaster).') ?>
</p>
<form method="post">
<?= csrf_field() ?>
<?php foreach ($all_trees as $tree) : ?>
<?php
$id = $tree->id();
$enabled = Configuration::isEnabled($tree);
$frequency = Configuration::frequencyDays($tree);
$annivs = Configuration::includeAnniversaries($tree);
$subject = Configuration::subjectPrefix($tree);
$extras = $tree->getPreference(Configuration::PREF_EXTRA_RECIPIENTS, '');
$lineal = Configuration::linealDepth($tree);
$last_sent = Configuration::lastSentAt($tree);
?>
<fieldset class="card mb-4">
<legend class="card-header h4"><?= e($tree->title()) ?></legend>
<div class="card-body">
<div class="row mb-3">
<div class="offset-sm-3 col-sm-9">
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="enabled-<?= $id ?>" name="enabled-<?= $id ?>"
value="1" <?= $enabled ? 'checked' : '' ?>>
<label class="form-check-label" for="enabled-<?= $id ?>">
<?= I18N::translate('Enable newsletter for this tree') ?>
</label>
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label" for="frequency-<?= $id ?>">
<?= I18N::translate('Send newsletters every') ?>
</label>
<div class="col-sm-9">
<div class="input-group" style="max-width: 18rem;">
<input class="form-control" type="number"
id="frequency-<?= $id ?>" name="frequency-<?= $id ?>"
value="<?= e((string) $frequency) ?>"
min="<?= Configuration::MIN_FREQUENCY_DAYS ?>"
max="<?= Configuration::MAX_FREQUENCY_DAYS ?>" required>
<span class="input-group-text"><?= I18N::translate('days') ?></span>
</div>
<small class="form-text text-muted">
<?= I18N::translate('Each issue looks the same number of days ahead, for both living relatives and historical events of those who have passed away. Default 14.') ?>
</small>
</div>
</div>
<div class="row mb-3">
<div class="offset-sm-3 col-sm-9">
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="anniversaries-<?= $id ?>" name="anniversaries-<?= $id ?>"
value="1" <?= $annivs ? 'checked' : '' ?>>
<label class="form-check-label" for="anniversaries-<?= $id ?>">
<?= I18N::translate('Include marriage anniversaries') ?>
</label>
</div>
<small class="form-text text-muted">
<?= I18N::translate('Only intact marriages of still-living couples are included.') ?>
</small>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label" for="lineal-<?= $id ?>">
<?= I18N::translate('Detailed view distance') ?>
</label>
<div class="col-sm-9">
<input class="form-control" type="number" style="max-width: 18rem;"
id="lineal-<?= $id ?>" name="lineal-<?= $id ?>"
value="<?= e((string) $lineal) ?>"
min="<?= Configuration::MIN_LINEAL_DEPTH ?>"
max="<?= Configuration::MAX_LINEAL_DEPTH ?>" required>
<small class="form-text text-muted">
<?= I18N::translate('A person is shown in detail (avatar, icon, timeline) when they sit within this many descent-steps of the recipient\'s direct lineage. Examples relative to the recipient: a sibling is distance 1 (one step down from the recipient\'s parent), a great-aunt is distance 1 (one step down from a great-grandparent), a nephew is distance 2, a first cousin is distance 2. Spouses share their partner\'s distance. Everyone outside this radius appears as a compact text bullet at the bottom of each section. Set to 0 to render the whole newsletter as text; recipients with no linked tree record always see the full detailed view.') ?>
</small>
</div>
</div>
<fieldset class="row mb-3">
<legend class="col-sm-3 col-form-label">
<?= I18N::translate('Subject prefix') ?>
</legend>
<div class="col-sm-9">
<small class="form-text text-muted d-block mb-2">
<?= I18N::translate('Prepended to the email subject line. Leave a field blank to fall back to the generic prefix below.') ?>
</small>
<?php foreach (Configuration::supportedSubjectLocales() as $code => $label) : ?>
<?php
$field = 'subject-' . $id . '-' . $code;
$val = $tree->getPreference(
Configuration::PREF_SUBJECT_PREFIX_PREFIX . $code,
'',
);
?>
<div class="input-group input-group-sm mb-2">
<span class="input-group-text" style="min-width: 7rem;">
<?= e($label) ?>
</span>
<input class="form-control" type="text"
id="<?= e($field) ?>" name="<?= e($field) ?>"
value="<?= e($val) ?>"
placeholder="<?= e('[' . $tree->title() . '] ') ?>">
</div>
<?php endforeach ?>
<div class="input-group input-group-sm">
<span class="input-group-text" style="min-width: 7rem;">
<?= I18N::translate('Generic') ?>
</span>
<input class="form-control" type="text"
id="subject-<?= $id ?>" name="subject-<?= $id ?>"
value="<?= e($subject) ?>">
</div>
</div>
</fieldset>
<div class="row mb-3">
<label class="col-sm-3 col-form-label" for="extras-<?= $id ?>">
<?= I18N::translate('Extra recipient email addresses (one per line)') ?>
</label>
<div class="col-sm-9">
<textarea class="form-control" rows="4"
id="extras-<?= $id ?>" name="extras-<?= $id ?>"><?= e($extras) ?></textarea>
</div>
</div>
<fieldset class="row mb-3">
<legend class="col-sm-3 col-form-label">
<?= I18N::translate('Intro paragraph for the next email') ?>
</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.') ?>
<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>
<?= I18N::translate('Personalisation tokens:') ?>
<code>{{first_name}}</code>,
<code>{{last_name}}</code>,
<code>{{username}}</code>,
<code>{{email}}</code>
</small>
<?php foreach (Configuration::supportedSubjectLocales() as $code => $label) : ?>
<?php
$field = 'intro-' . $id . '-' . $code;
$val = Configuration::introForLocale($module, $tree, $code);
?>
<div class="mb-2">
<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>
</div>
<?php endforeach ?>
</div>
</fieldset>
<div class="row mb-3">
<label class="col-sm-3 col-form-label">
<?= I18N::translate('Subscribed users') ?>
</label>
<div class="col-sm-9">
<input type="hidden" name="users-submitted-<?= $id ?>" value="1">
<?php if ($all_users->isEmpty()) : ?>
<small class="form-text text-muted">
<?= I18N::translate('No users with email addresses found.') ?>
</small>
<?php else : ?>
<small class="form-text text-muted d-block mb-2">
<?= I18N::translate('Tick a user to subscribe them to this trees newsletter. Users can still adjust their own subscription on their account page.') ?>
</small>
<div class="border rounded p-2"
style="max-height: 18rem; overflow-y: auto;">
<?php foreach ($all_users as $user) : ?>
<?php
$field = 'subscribe-' . $id . '-' . $user->id();
$is_subbed = $tree->getUserPreference($user, Configuration::USER_PREF_SUBSCRIBED) === '1';
?>
<div class="form-check">
<input class="form-check-input" type="checkbox"
id="<?= e($field) ?>" name="<?= e($field) ?>"
value="1" <?= $is_subbed ? 'checked' : '' ?>>
<label class="form-check-label" for="<?= e($field) ?>">
<?= e($user->realName()) ?>
<small class="text-muted">
&lt;<?= e($user->email()) ?>&gt;
</small>
</label>
</div>
<?php endforeach ?>
</div>
<?php endif ?>
</div>
</div>
<?php if ($last_sent > 0) : ?>
<div class="row mb-3">
<div class="offset-sm-3 col-sm-9">
<small class="text-muted">
<?= I18N::translate('Last sent: %s', date('Y-m-d H:i', $last_sent)) ?>
</small>
</div>
</div>
<?php endif ?>
</div>
</fieldset>
<?php endforeach ?>
<fieldset class="card mb-4">
<legend class="card-header h4"><?= I18N::translate('Cron token') ?></legend>
<div class="card-body">
<p>
<?= I18N::translate('Configure your system cron, systemd timer, or any external scheduler to call the URL below. The schedule decides when newsletters are actually due — calling more frequently is safe.') ?>
</p>
<pre class="bg-light p-2"><?= e($cron_url) ?></pre>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="regenerate_token"
name="regenerate_token" value="1">
<label class="form-check-label" for="regenerate_token">
<?= I18N::translate('Regenerate token') ?>
</label>
</div>
</div>
</fieldset>
<button type="submit" class="btn btn-primary">
<?= view('icons/save') ?>
<?= I18N::translate('Save') ?>
</button>
</form>
<form method="post" action="<?= e(route('module', ['module' => $module->name(), 'action' => 'SendNowAdmin'])) ?>" class="mt-3">
<?= csrf_field() ?>
<button type="submit" class="btn btn-secondary"
onclick="return confirm('<?= e(I18N::translate('Send the newsletter now for every enabled tree?')) ?>');">
<?= I18N::translate('Send now') ?>
</button>
</form>