Compare commits
19 Commits
b62ef7a1d7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a441c4b8af | ||
|
|
dc44e38e32 | ||
|
|
24f16e454e | ||
|
|
251575c8b4 | ||
|
|
ede523b02c | ||
|
|
80bc0bb728 | ||
|
|
3df01289a5 | ||
|
|
f840b68c9c | ||
|
|
9950aa25fd | ||
|
|
b536712ea7 | ||
|
|
ec13c9680d | ||
|
|
d1e3ccc00c | ||
|
|
91b59f7bc0 | ||
|
|
69ba3f1595 | ||
|
|
3dc07b9fb2 | ||
|
|
65ea9f6e30 | ||
|
|
ecd7d2b341 | ||
|
|
7e3c186419 | ||
|
|
1c4baed125 |
287
index.php
287
index.php
@@ -44,6 +44,10 @@ $defaultConfig = [
|
||||
|
||||
'subject' => 'Wiki Newsletter - {MONAT}',
|
||||
'firstname' => 'zusammen',
|
||||
'editor_tip' => '',
|
||||
'highlight_url_1' => '',
|
||||
'highlight_url_2' => '',
|
||||
'highlight_url_3' => '',
|
||||
'reply_to' => '',
|
||||
'unsubscribe_url' => '#',
|
||||
'cron_token' => bin2hex(random_bytes(24)),
|
||||
@@ -72,11 +76,18 @@ function loadJsonFile(string $file, array $default): array
|
||||
|
||||
function saveJsonFile(string $file, array $data): void
|
||||
{
|
||||
file_put_contents(
|
||||
$file,
|
||||
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
LOCK_EX
|
||||
$json = json_encode(
|
||||
$data,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE
|
||||
);
|
||||
if ($json === false) {
|
||||
throw new RuntimeException('Konfiguration konnte nicht kodiert werden.');
|
||||
}
|
||||
|
||||
$written = @file_put_contents($file, $json, LOCK_EX);
|
||||
if ($written === false) {
|
||||
throw new RuntimeException('Konfiguration konnte nicht gespeichert werden: ' . $file . ' (Dateirechte prüfen).');
|
||||
}
|
||||
}
|
||||
|
||||
function isAdminLoggedIn(): bool
|
||||
@@ -170,6 +181,10 @@ function fetchLatestArticles(string $wikiUrl, int $limit = 150): array
|
||||
$nodes = $xpath->query('//li');
|
||||
|
||||
foreach ($nodes as $li) {
|
||||
if (!$li instanceof DOMElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = trim(preg_replace('/\s+/', ' ', $li->textContent ?? ''));
|
||||
|
||||
if (!preg_match('/\((\d{2}\.\d{2}\.\d{4})\)/', $text, $dateMatch)) {
|
||||
@@ -281,7 +296,7 @@ function newsletterPeriod(string $mode): array
|
||||
|
||||
if ($mode === 'current_month') {
|
||||
$start = $now->modify('first day of this month')->setTime(0, 0, 0);
|
||||
$end = $now->modify('first day of next month')->setTime(0, 0, 0);
|
||||
$end = $now->modify('tomorrow')->setTime(0, 0, 0);
|
||||
} else {
|
||||
$start = $now->modify('first day of previous month')->setTime(0, 0, 0);
|
||||
$end = $now->modify('first day of this month')->setTime(0, 0, 0);
|
||||
@@ -340,17 +355,81 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
$replyEmail = trim((string)($config['reply_to'] ?: $config['from_email']));
|
||||
$replyHref = 'mailto:' . $replyEmail;
|
||||
$summaryText = $count === 1
|
||||
? 'Im ' . h($periodLabel) . ' wurde 1 neuer Wiki-Artikel veröffentlicht.'
|
||||
: 'Im ' . h($periodLabel) . ' wurden ' . $count . ' neue Wiki-Artikel veröffentlicht.';
|
||||
? 'Es wurde ein neuer Wiki-Artikel veröffentlicht.'
|
||||
: 'Es wurden ' . $count . ' neue Wiki-Artikel veröffentlicht.';
|
||||
$heroHeadline = 'Die neuesten Wiki-Artikel';
|
||||
$editorTip = trim((string)($config['editor_tip'] ?? ''));
|
||||
|
||||
$manualHighlightUrls = [];
|
||||
foreach (['highlight_url_1', 'highlight_url_2', 'highlight_url_3'] as $highlightField) {
|
||||
$url = trim((string)($config[$highlightField] ?? ''));
|
||||
if ($url !== '') {
|
||||
$manualHighlightUrls[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
$highlightArticles = [];
|
||||
if (!empty($manualHighlightUrls)) {
|
||||
foreach ($manualHighlightUrls as $manualUrl) {
|
||||
foreach ($articles as $article) {
|
||||
if (($article['url'] ?? '') === $manualUrl) {
|
||||
$highlightArticles[] = $article;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[$periodStart, $periodEnd] = newsletterPeriod((string)$config['period_mode']);
|
||||
$rangeEnd = $periodEnd;
|
||||
if (($config['period_mode'] ?? '') === 'current_month') {
|
||||
$rangeEnd = new DateTimeImmutable('today');
|
||||
}
|
||||
$dateRangeText = 'vom ' . $periodStart->format('d.m') . ' bis ' . $rangeEnd->format('d.m');
|
||||
|
||||
$highlightsRows = '';
|
||||
if (!empty($highlightArticles)) {
|
||||
$maxHighlights = min(3, count($highlightArticles));
|
||||
for ($i = 0; $i < $maxHighlights; $i++) {
|
||||
$highlightsRows .= '
|
||||
<tr>
|
||||
<td style="padding:0 0 8px 0;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:22px;color:#2f3237;">
|
||||
<span style="display:inline-block;min-width:22px;font-weight:700;color:#ff7d00;">' . ($i + 1) . '.</span>
|
||||
<a href="' . h($highlightArticles[$i]['url']) . '" target="_blank" style="color:#2f3237;text-decoration:none;">' . h($highlightArticles[$i]['title']) . '</a>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
$highlightsSection = '';
|
||||
if ($highlightsRows !== '') {
|
||||
$highlightsSection = '
|
||||
<tr>
|
||||
<td style="padding:0 32px 20px 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#fff7f0;border:1px solid #ffd6b0;border-radius:12px;">
|
||||
<tr>
|
||||
<td style="padding:16px 18px 8px 18px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:15px;color:#8e4e12;font-weight:700;letter-spacing:.8px;text-transform:uppercase;">
|
||||
Top Highlights
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 18px 12px 18px;">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0">' . $highlightsRows . '</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
$articleRows = '';
|
||||
if ($count === 0) {
|
||||
$articleRows = '
|
||||
<tr>
|
||||
<td style="padding:0 26px 18px 26px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;border:1px solid #d9dfe7;background:#ffffff;">
|
||||
<td style="padding:0 32px 24px 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;border:1px solid #d8e0ea;background:#ffffff;border-radius:14px;">
|
||||
<tr>
|
||||
<td style="padding:18px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:22px;color:#2b3340;">
|
||||
<td style="padding:22px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:23px;color:#2e3b4d;">
|
||||
Für diesen Zeitraum wurden keine neuen Artikel gefunden.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -361,29 +440,34 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
foreach ($articles as $article) {
|
||||
$articleRows .= '
|
||||
<tr>
|
||||
<td style="padding:0 26px 16px 26px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#ffffff;border:1px solid #e2e7ee;border-left:4px solid #ef7d00;">
|
||||
<td style="padding:0 32px 16px 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#ffffff;border:1px solid #d7dee8;border-radius:14px;">
|
||||
<tr>
|
||||
<td style="padding:14px 16px 0 16px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:17px;color:#5c6675;">
|
||||
Thomas-Krenn-Wiki
|
||||
<td style="padding:18px 22px 4px 22px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;color:#5f6d82;font-weight:700;letter-spacing:.7px;text-transform:uppercase;">
|
||||
Wiki-Beitrag
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:4px 16px 6px 16px;font-family:Arial,Helvetica,sans-serif;font-size:21px;line-height:28px;font-weight:700;color:#ef7d00;">
|
||||
<a href="' . h($article['url']) . '" target="_blank" style="color:#ef7d00;text-decoration:none;">' . h($article['title']) . '</a>
|
||||
<td class="mobile-card-title" style="padding:0 22px 6px 22px;font-family:Arial,Helvetica,sans-serif;font-size:24px;line-height:32px;font-weight:700;color:#2f3237;">
|
||||
<a href="' . h($article['url']) . '" target="_blank" style="color:#2f3237;text-decoration:none;">' . h($article['title']) . '</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 16px 12px 16px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#313844;">
|
||||
<td style="padding:0 22px 8px 22px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:24px;color:#40444b;">
|
||||
Kompakte technische Einordnung und direkte Handlungsempfehlungen aus dem Thomas-Krenn-Wiki.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 22px 16px 22px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;color:#667086;">
|
||||
Veröffentlicht am ' . h($article['date_de']) . ' von ' . h($article['author']) . '.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 16px 15px 16px;">
|
||||
<td style="padding:0 22px 20px 22px;">
|
||||
<table role="presentation" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ef7d00" style="background:#ef7d00;">
|
||||
<a href="' . h($article['url']) . '" target="_blank" style="display:inline-block;padding:8px 14px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:16px;font-weight:700;color:#ffffff;text-decoration:none;">Jetzt Artikel lesen</a>
|
||||
<td bgcolor="#ff7d00" style="background:#ff7d00;">
|
||||
<a href="' . h($article['url']) . '" target="_blank" style="display:inline-block;padding:11px 18px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:16px;font-weight:700;color:#ffffff;text-decoration:none;">Zum Beitrag</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -397,10 +481,10 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
|
||||
$testBadge = $isTest
|
||||
? '<tr>
|
||||
<td style="padding:0 26px 12px 26px;" class="mobile-padding">
|
||||
<td style="padding:0 32px 14px 32px;" class="mobile-padding">
|
||||
<table role="presentation" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#fff3cd" style="background:#fff3cd;border:1px solid #f3d38a;padding:6px 10px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:14px;font-weight:700;color:#7a5200;">
|
||||
<td bgcolor="#fff4e6" style="background:#fff4e6;border:1px solid #ffd2a3;padding:8px 12px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:14px;font-weight:700;color:#a34f00;">
|
||||
TESTVERSAND
|
||||
</td>
|
||||
</tr>
|
||||
@@ -412,7 +496,28 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
$unsubscribeFooter = '';
|
||||
$unsubscribeUrl = trim((string)($config['unsubscribe_url'] ?? ''));
|
||||
if ($unsubscribeUrl !== '' && $unsubscribeUrl !== '#' && filter_var($unsubscribeUrl, FILTER_VALIDATE_URL)) {
|
||||
$unsubscribeFooter = '<br><a href="' . h($unsubscribeUrl) . '" target="_blank" style="color:#ef7d00;text-decoration:underline;">Abmelden</a>';
|
||||
$unsubscribeFooter = '<br><a href="' . h($unsubscribeUrl) . '" target="_blank" style="color:#ff7d00;text-decoration:underline;">Abmelden</a>';
|
||||
}
|
||||
|
||||
$editorTipSection = '';
|
||||
if ($editorTip !== '') {
|
||||
$editorTipSection = '
|
||||
<tr>
|
||||
<td style="padding:8px 32px 24px 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#f7f8fa;border:1px solid #e1e5eb;border-radius:12px;">
|
||||
<tr>
|
||||
<td style="padding:16px 18px 8px 18px;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:15px;color:#5f6064;font-weight:700;letter-spacing:.8px;text-transform:uppercase;">
|
||||
Redaktionstipp
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 18px 16px 18px;font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:24px;color:#32363d;">
|
||||
' . nl2br(h($editorTip)) . '
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
return '<!doctype html>
|
||||
@@ -423,7 +528,7 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Thomas-Krenn Wiki Newsletter</title>
|
||||
<style type="text/css">
|
||||
html, body { margin:0 !important; padding:0 !important; width:100% !important; background:#dadada; }
|
||||
html, body { margin:0 !important; padding:0 !important; width:100% !important; background:#eceef1; }
|
||||
table, td { border-collapse:collapse; mso-table-lspace:0pt; mso-table-rspace:0pt; }
|
||||
img { border:0; outline:none; text-decoration:none; -ms-interpolation-mode:bicubic; display:block; }
|
||||
a { text-decoration:none; }
|
||||
@@ -431,74 +536,76 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
.preheader { display:none !important; visibility:hidden; opacity:0; color:transparent; height:0; width:0; max-height:0; max-width:0; overflow:hidden; mso-hide:all; }
|
||||
@media only screen and (max-width: 680px) {
|
||||
.email-container { width:100% !important; }
|
||||
.mobile-padding { padding-left:14px !important; padding-right:14px !important; }
|
||||
.mobile-intro { font-size:16px !important; line-height:24px !important; }
|
||||
.mobile-padding { padding-left:16px !important; padding-right:16px !important; }
|
||||
.mobile-intro { font-size:17px !important; line-height:27px !important; }
|
||||
.mobile-headline { font-size:30px !important; line-height:37px !important; }
|
||||
.mobile-card-title { font-size:22px !important; line-height:29px !important; }
|
||||
.mobile-hero-copy { font-size:17px !important; line-height:26px !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:#dadada;">
|
||||
<body style="margin:0;padding:0;background:#eceef1;">
|
||||
<div class="preheader">' . h($preheader) . ' ‌ ‌ ‌</div>
|
||||
<center role="article" aria-roledescription="email" lang="de" style="width:100%;background:#dadada;">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#dadada" style="width:100%;background:#dadada;">
|
||||
<center role="article" aria-roledescription="email" lang="de" style="width:100%;background:#eceef1;">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#eceef1" style="width:100%;background:#eceef1;">
|
||||
<tr>
|
||||
<td align="center" style="padding:12px 8px;">
|
||||
<table role="presentation" width="690" border="0" cellspacing="0" cellpadding="0" class="email-container" style="width:100%;max-width:690px;background:#ffffff;">
|
||||
<td align="center" style="padding:22px 10px;">
|
||||
<table role="presentation" width="700" border="0" cellspacing="0" cellpadding="0" class="email-container" style="width:100%;max-width:700px;background:#ffffff;border-top:4px solid #ff7d00;">
|
||||
<tr>
|
||||
<td align="center" style="padding:10px 26px 7px 26px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#4e5561;">
|
||||
Sollte diese E-Mail nicht einwandfrei dargestellt werden, klicken Sie bitte hier.
|
||||
<td align="center" style="padding:24px 32px 18px 32px;background:#5f6064;" class="mobile-padding">
|
||||
<img src="https://www.thomas-krenn.com/res/pics/tk_logo_340px.png" width="190" alt="Thomas-Krenn Logo" style="display:block;margin:0 auto;border:0;outline:none;text-decoration:none;height:auto;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding:5px 26px 12px 26px;font-family:Arial,Helvetica,sans-serif;font-size:52px;line-height:46px;font-weight:700;color:#4c5461;letter-spacing:0.6px;">
|
||||
THOMAS<br>KRENN
|
||||
</td>
|
||||
</tr>
|
||||
<td style="padding:0 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#5f6064;">
|
||||
<tr>
|
||||
<td style="padding:0 26px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#10233a;">
|
||||
<tr>
|
||||
<td style="padding:18px 18px 20px 18px;font-family:Arial,Helvetica,sans-serif;">
|
||||
<div style="font-size:30px;line-height:16px;font-weight:700;color:#ef7d00;letter-spacing:0.7px;">NEWSLETTER</div>
|
||||
<div style="font-size:45px;line-height:30px;font-weight:700;color:#ffffff;margin-top:6px;">' . h($periodLabel) . '</div>
|
||||
<td style="padding:30px 26px;font-family:Arial,Helvetica,sans-serif;">
|
||||
<div style="font-size:12px;line-height:16px;font-weight:700;color:#ff7d00;letter-spacing:1px;text-transform:uppercase;">Thomas-Krenn Wiki Update</div>
|
||||
<div class="mobile-headline" style="font-size:34px;line-height:40px;font-weight:700;color:#ffffff;margin-top:10px;">' . h($heroHeadline) . '</div>
|
||||
<div class="mobile-hero-copy" style="font-size:18px;line-height:27px;color:#ffffff;margin-top:10px;">' . h($periodLabel) . ' | ' . h($summaryText) . '</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:14px 26px 12px 26px;background:#eef0f3;" class="mobile-padding">
|
||||
<td style="padding:22px 32px 22px 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td class="mobile-intro" style="font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:27px;color:#1e2530;">
|
||||
Guten Tag,<br><br>
|
||||
hier ist Ihre aktuelle Übersicht aus dem Thomas-Krenn-Wiki.<br>
|
||||
' . $summaryText . '<br><br>
|
||||
Viel Spaß beim Lesen wünscht<br>Ihr Team von Thomas-Krenn
|
||||
<td class="mobile-intro" style="font-family:Arial,Helvetica,sans-serif;font-size:18px;line-height:29px;color:#22324a;">
|
||||
Servus,<br><br>
|
||||
hier ist deine Übersicht mit den neuesten Beiträgen aus dem Thomas-Krenn-Wiki ' . $dateRangeText . '.<br>
|
||||
Wir hoffen, dass dir diese Beiträge gefallen.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
' . $highlightsSection . '
|
||||
' . $testBadge . '
|
||||
' . $articleRows . '
|
||||
' . $editorTipSection . '
|
||||
<tr>
|
||||
<td style="padding:4px 26px 20px 26px;" class="mobile-padding">
|
||||
<td style="padding:0 32px 28px 32px;" class="mobile-padding">
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:22px;color:#2a303b;">
|
||||
Haben Sie Fragen oder einen Themenwunsch?
|
||||
<a href="' . h($replyHref) . '" style="color:#ef7d00;text-decoration:underline;">Kontaktieren Sie uns gerne direkt.</a>
|
||||
<td style="font-family:Arial,Helvetica,sans-serif;font-size:15px;line-height:24px;color:#2a3950;">
|
||||
Hast du Fragen oder einen Themenwunsch?
|
||||
<a href="' . h($replyHref) . '" style="color:#ff7d00;text-decoration:underline;">Schreib uns gerne direkt.</a><br><br>
|
||||
Viele Grüße<br>
|
||||
Euer Thomas-Krenn-Wiki Team
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="background:#eceef1;padding:20px 26px;text-align:center;font-family:Arial,Helvetica,sans-serif;">
|
||||
<div style="font-size:12px;line-height:20px;color:#646d7b;">
|
||||
Tel.: +49 8551 9150 0 | Fax: +49 8551 9150 55<br>
|
||||
<a href="https://www.thomas-krenn.com/de/wiki/Hauptseite" target="_blank" style="color:#ef7d00;text-decoration:underline;">thomas-krenn.com</a><br><br>
|
||||
Thomas-Krenn.AG | Speltenbach-Steinäcker 1 | D-94078 Freyung
|
||||
<td style="background:#5f6064;padding:22px 20px;text-align:center;font-family:Arial,Helvetica,sans-serif;">
|
||||
<div style="font-size:12px;line-height:19px;color:#eef0f2;">
|
||||
Thomas-Krenn.AG | Speltenbach-Steinäcker 1 | D-94078 Freyung<br>
|
||||
Tel.: +49 8551 9150 0 | Fax: +49 8551 9150 55 |
|
||||
<a href="https://www.thomas-krenn.com/de/wiki/Hauptseite" target="_blank" style="color:#ff7d00;text-decoration:underline;">thomas-krenn.com</a>
|
||||
' . $unsubscribeFooter . '
|
||||
</div>
|
||||
</td>
|
||||
@@ -627,12 +734,10 @@ function newsletterData(array $config, bool $testMode = false): array
|
||||
[$start, $end, $periodLabel, $periodKey] = newsletterPeriod($config['period_mode']);
|
||||
$allArticles = fetchLatestArticles($config['wiki_url'], (int)$config['article_fetch_limit']);
|
||||
|
||||
if ($testMode) {
|
||||
$articles = array_slice($allArticles, 0, (int)$config['article_limit']);
|
||||
$periodLabel = 'aktuelle Übersicht';
|
||||
$periodKey = 'test-' . date('Y-m-d-H-i-s');
|
||||
} else {
|
||||
// Testversand nutzt denselben Zeitraum wie der Liveversand.
|
||||
$articles = filterArticlesForPeriod($allArticles, $start, $end, (int)$config['article_limit']);
|
||||
if ($testMode) {
|
||||
$periodKey = 'test-' . date('Y-m-d-H-i-s');
|
||||
}
|
||||
|
||||
return [$articles, $periodLabel, $periodKey];
|
||||
@@ -688,7 +793,8 @@ function configFromPost(array $current): array
|
||||
'wiki_url', 'article_limit', 'article_fetch_limit', 'period_mode',
|
||||
'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_user',
|
||||
'from_email', 'from_name', 'recipient_email', 'test_recipient_email',
|
||||
'subject', 'reply_to', 'unsubscribe_url', 'cron_token'
|
||||
'subject', 'editor_tip', 'highlight_url_1', 'highlight_url_2', 'highlight_url_3',
|
||||
'reply_to', 'unsubscribe_url', 'cron_token'
|
||||
];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
@@ -726,6 +832,15 @@ $error = '';
|
||||
$previewHtml = '';
|
||||
$previewClipboardHtml = '';
|
||||
|
||||
$flash = $_SESSION['wiki_newsletter_flash'] ?? null;
|
||||
if (is_array($flash)) {
|
||||
$message = (string)($flash['message'] ?? '');
|
||||
$error = (string)($flash['error'] ?? '');
|
||||
$previewHtml = (string)($flash['preview_html'] ?? '');
|
||||
$previewClipboardHtml = (string)($flash['preview_clipboard_html'] ?? '');
|
||||
unset($_SESSION['wiki_newsletter_flash']);
|
||||
}
|
||||
|
||||
$isCron = (PHP_SAPI === 'cli' && cliArg('cron') !== null) || isset($_GET['cron']);
|
||||
|
||||
if ($isCron) {
|
||||
@@ -755,6 +870,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'login
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') !== 'login') {
|
||||
$redirectUrl = strtok($_SERVER['REQUEST_URI'], '?');
|
||||
|
||||
try {
|
||||
requireAdmin();
|
||||
$config = configFromPost($config);
|
||||
@@ -774,9 +891,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') !== 'login
|
||||
} elseif ($action === 'monthly_send') {
|
||||
$message = sendMonthlyIfDue($config);
|
||||
}
|
||||
|
||||
$_SESSION['wiki_newsletter_flash'] = [
|
||||
'message' => $message,
|
||||
'error' => '',
|
||||
'preview_html' => $previewHtml,
|
||||
'preview_clipboard_html' => $previewClipboardHtml,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
$_SESSION['wiki_newsletter_flash'] = [
|
||||
'message' => '',
|
||||
'error' => $e->getMessage(),
|
||||
'preview_html' => '',
|
||||
'preview_clipboard_html' => '',
|
||||
];
|
||||
}
|
||||
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
$isLoggedIn = isAdminLoggedIn();
|
||||
@@ -832,7 +964,7 @@ $isLoggedIn = isAdminLoggedIn();
|
||||
.card.full { grid-column: 1 / -1; }
|
||||
h2 { margin: 0 0 18px; font-size: 21px; letter-spacing: -.02em; }
|
||||
label { display: block; margin: 14px 0 7px; color: #374151; font-size: 14px; font-weight: 700; }
|
||||
input, select {
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
padding: 0 12px;
|
||||
@@ -843,7 +975,14 @@ $isLoggedIn = isAdminLoggedIn();
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
input:focus, select:focus { border-color: rgba(37,99,235,.55); box-shadow: 0 0 0 4px rgba(37,99,235,.08); }
|
||||
textarea {
|
||||
min-height: 110px;
|
||||
height: auto;
|
||||
padding: 10px 12px;
|
||||
resize: vertical;
|
||||
line-height: 1.5;
|
||||
}
|
||||
input:focus, select:focus, textarea:focus { border-color: rgba(37,99,235,.55); box-shadow: 0 0 0 4px rgba(37,99,235,.08); }
|
||||
.hint { margin: 7px 0 0; color: var(--muted); font-size: 13px; line-height: 1.5; }
|
||||
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
|
||||
button {
|
||||
@@ -949,6 +1088,20 @@ $isLoggedIn = isAdminLoggedIn();
|
||||
<input name="subject" value="<?= h($config['subject']) ?>">
|
||||
<p class="hint">Platzhalter: <code>{MONAT}</code>, <code>{ANZAHL}</code></p>
|
||||
|
||||
<label>Redaktionstipp (optional)</label>
|
||||
<textarea name="editor_tip" placeholder="Optionaler Hinweis für den Newsletter..."><?= h((string)($config['editor_tip'] ?? '')) ?></textarea>
|
||||
<p class="hint">Wird nur im Newsletter angezeigt, wenn ein Text hinterlegt ist.</p>
|
||||
|
||||
<label>Top Highlight 1 URL (optional)</label>
|
||||
<input name="highlight_url_1" value="<?= h((string)($config['highlight_url_1'] ?? '')) ?>" placeholder="https://www.thomas-krenn.com/de/wiki/...">
|
||||
|
||||
<label>Top Highlight 2 URL (optional)</label>
|
||||
<input name="highlight_url_2" value="<?= h((string)($config['highlight_url_2'] ?? '')) ?>" placeholder="https://www.thomas-krenn.com/de/wiki/...">
|
||||
|
||||
<label>Top Highlight 3 URL (optional)</label>
|
||||
<input name="highlight_url_3" value="<?= h((string)($config['highlight_url_3'] ?? '')) ?>" placeholder="https://www.thomas-krenn.com/de/wiki/...">
|
||||
<p class="hint">Wenn gesetzt, werden diese 3 Artikel in genau dieser Reihenfolge als Highlights verwendet. Sind alle Felder leer, wird kein Highlights-Block angezeigt.</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
|
||||
84
wiki.php
84
wiki.php
@@ -72,11 +72,18 @@ function loadJsonFile(string $file, array $default): array
|
||||
|
||||
function saveJsonFile(string $file, array $data): void
|
||||
{
|
||||
file_put_contents(
|
||||
$file,
|
||||
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
|
||||
LOCK_EX
|
||||
$json = json_encode(
|
||||
$data,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE
|
||||
);
|
||||
if ($json === false) {
|
||||
throw new RuntimeException('Konfiguration konnte nicht kodiert werden.');
|
||||
}
|
||||
|
||||
$written = @file_put_contents($file, $json, LOCK_EX);
|
||||
if ($written === false) {
|
||||
throw new RuntimeException('Konfiguration konnte nicht gespeichert werden: ' . $file . ' (Dateirechte prüfen).');
|
||||
}
|
||||
}
|
||||
|
||||
function isAdminLoggedIn(): bool
|
||||
@@ -170,6 +177,10 @@ function fetchLatestArticles(string $wikiUrl, int $limit = 150): array
|
||||
$nodes = $xpath->query('//li');
|
||||
|
||||
foreach ($nodes as $li) {
|
||||
if (!$li instanceof DOMElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$text = trim(preg_replace('/\s+/', ' ', $li->textContent ?? ''));
|
||||
|
||||
if (!preg_match('/\((\d{2}\.\d{2}\.\d{4})\)/', $text, $dateMatch)) {
|
||||
@@ -281,7 +292,7 @@ function newsletterPeriod(string $mode): array
|
||||
|
||||
if ($mode === 'current_month') {
|
||||
$start = $now->modify('first day of this month')->setTime(0, 0, 0);
|
||||
$end = $now->modify('first day of next month')->setTime(0, 0, 0);
|
||||
$end = $now->modify('tomorrow')->setTime(0, 0, 0);
|
||||
} else {
|
||||
$start = $now->modify('first day of previous month')->setTime(0, 0, 0);
|
||||
$end = $now->modify('first day of this month')->setTime(0, 0, 0);
|
||||
@@ -340,8 +351,15 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
$replyEmail = trim((string)($config['reply_to'] ?: $config['from_email']));
|
||||
$replyHref = 'mailto:' . $replyEmail;
|
||||
$summaryText = $count === 1
|
||||
? 'Im ' . h($periodLabel) . ' wurde 1 neuer Wiki-Artikel veröffentlicht.'
|
||||
: 'Im ' . h($periodLabel) . ' wurden ' . $count . ' neue Wiki-Artikel veröffentlicht.';
|
||||
? 'In diesem Zeitraum wurde 1 neuer Wiki-Artikel veröffentlicht.'
|
||||
: 'In diesem Zeitraum wurden ' . $count . ' neue Wiki-Artikel veröffentlicht.';
|
||||
|
||||
[$periodStart, $periodEnd] = newsletterPeriod((string)$config['period_mode']);
|
||||
$rangeEnd = $periodEnd;
|
||||
if (($config['period_mode'] ?? '') === 'current_month') {
|
||||
$rangeEnd = new DateTimeImmutable('today');
|
||||
}
|
||||
$dateRangeText = 'vom ' . $periodStart->format('d.m') . ' bis ' . $rangeEnd->format('d.m');
|
||||
|
||||
$articleRows = '';
|
||||
if ($count === 0) {
|
||||
@@ -444,13 +462,8 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
<td align="center" style="padding:12px 8px;">
|
||||
<table role="presentation" width="690" border="0" cellspacing="0" cellpadding="0" class="email-container" style="width:100%;max-width:690px;background:#ffffff;">
|
||||
<tr>
|
||||
<td align="center" style="padding:10px 26px 7px 26px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#4e5561;">
|
||||
Sollte diese E-Mail nicht einwandfrei dargestellt werden, klicken Sie bitte hier.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding:5px 26px 12px 26px;font-family:Arial,Helvetica,sans-serif;font-size:52px;line-height:46px;font-weight:700;color:#4c5461;letter-spacing:0.6px;">
|
||||
THOMAS<br>KRENN
|
||||
<td align="center" style="padding:8px 26px 14px 26px;">
|
||||
<img src="https://www.thomas-krenn.com/res/pics/tk_logo_340px.png" width="170" alt="Thomas-Krenn Logo" style="display:block;margin:0 auto;border:0;outline:none;text-decoration:none;height:auto;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -458,8 +471,8 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="width:100%;background:#10233a;">
|
||||
<tr>
|
||||
<td style="padding:18px 18px 20px 18px;font-family:Arial,Helvetica,sans-serif;">
|
||||
<div style="font-size:30px;line-height:16px;font-weight:700;color:#ef7d00;letter-spacing:0.7px;">NEWSLETTER</div>
|
||||
<div style="font-size:45px;line-height:30px;font-weight:700;color:#ffffff;margin-top:6px;">' . h($periodLabel) . '</div>
|
||||
<div style="font-size:13px;line-height:18px;font-weight:700;color:#ef7d00;letter-spacing:0.7px;">NEWSLETTER</div>
|
||||
<div style="font-size:20px;line-height:26px;font-weight:700;color:#ffffff;margin-top:2px;">' . h($periodLabel) . '</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -471,10 +484,11 @@ function buildNewsletterHtml(array $config, array $articles, string $periodLabel
|
||||
<tr>
|
||||
<td class="mobile-intro" style="font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:27px;color:#1e2530;">
|
||||
Guten Tag,<br><br>
|
||||
hier ist Ihre aktuelle Übersicht aus dem Thomas-Krenn-Wiki.<br>
|
||||
hier erhalten Sie Ihre aktuelle Übersicht aus dem Thomas-Krenn-Wiki, ' . $dateRangeText . '.<br>
|
||||
' . $summaryText . '<br><br>
|
||||
Viel Spaß beim Lesen wünscht<br>Ihr Team von Thomas-Krenn
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
@@ -628,12 +642,10 @@ function newsletterData(array $config, bool $testMode = false): array
|
||||
[$start, $end, $periodLabel, $periodKey] = newsletterPeriod($config['period_mode']);
|
||||
$allArticles = fetchLatestArticles($config['wiki_url'], (int)$config['article_fetch_limit']);
|
||||
|
||||
if ($testMode) {
|
||||
$articles = array_slice($allArticles, 0, (int)$config['article_limit']);
|
||||
$periodLabel = 'aktuelle Übersicht';
|
||||
$periodKey = 'test-' . date('Y-m-d-H-i-s');
|
||||
} else {
|
||||
// Testversand nutzt denselben Zeitraum wie der Liveversand.
|
||||
$articles = filterArticlesForPeriod($allArticles, $start, $end, (int)$config['article_limit']);
|
||||
if ($testMode) {
|
||||
$periodKey = 'test-' . date('Y-m-d-H-i-s');
|
||||
}
|
||||
|
||||
return [$articles, $periodLabel, $periodKey];
|
||||
@@ -727,6 +739,15 @@ $error = '';
|
||||
$previewHtml = '';
|
||||
$previewClipboardHtml = '';
|
||||
|
||||
$flash = $_SESSION['wiki_newsletter_flash'] ?? null;
|
||||
if (is_array($flash)) {
|
||||
$message = (string)($flash['message'] ?? '');
|
||||
$error = (string)($flash['error'] ?? '');
|
||||
$previewHtml = (string)($flash['preview_html'] ?? '');
|
||||
$previewClipboardHtml = (string)($flash['preview_clipboard_html'] ?? '');
|
||||
unset($_SESSION['wiki_newsletter_flash']);
|
||||
}
|
||||
|
||||
$isCron = (PHP_SAPI === 'cli' && cliArg('cron') !== null) || isset($_GET['cron']);
|
||||
|
||||
if ($isCron) {
|
||||
@@ -756,6 +777,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'login
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') !== 'login') {
|
||||
$redirectUrl = strtok($_SERVER['REQUEST_URI'], '?');
|
||||
|
||||
try {
|
||||
requireAdmin();
|
||||
$config = configFromPost($config);
|
||||
@@ -775,9 +798,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') !== 'login
|
||||
} elseif ($action === 'monthly_send') {
|
||||
$message = sendMonthlyIfDue($config);
|
||||
}
|
||||
|
||||
$_SESSION['wiki_newsletter_flash'] = [
|
||||
'message' => $message,
|
||||
'error' => '',
|
||||
'preview_html' => $previewHtml,
|
||||
'preview_clipboard_html' => $previewClipboardHtml,
|
||||
];
|
||||
} catch (Throwable $e) {
|
||||
$error = $e->getMessage();
|
||||
$_SESSION['wiki_newsletter_flash'] = [
|
||||
'message' => '',
|
||||
'error' => $e->getMessage(),
|
||||
'preview_html' => '',
|
||||
'preview_clipboard_html' => '',
|
||||
];
|
||||
}
|
||||
|
||||
header('Location: ' . $redirectUrl);
|
||||
exit;
|
||||
}
|
||||
|
||||
$isLoggedIn = isAdminLoggedIn();
|
||||
|
||||
Reference in New Issue
Block a user