Upload via GUI (452 Dateien)
This commit is contained in:
@@ -195,70 +195,118 @@ function render_email_template(string $key, array $vars, string $defaultSubject,
|
||||
// Einheitliches, modernes E-Mail-Design
|
||||
// ====================================================================
|
||||
// email_wrap() umschließt JEDEN Vorlagentext (Standard oder vom Admin
|
||||
// angepasst) mit demselben Branding-Rahmen, damit alle System-Mails
|
||||
// gleich aussehen. email_badge()/email_button() sind wiederverwendbare
|
||||
// Bausteine für die Standardtexte der einzelnen Vorlagen.
|
||||
// angepasst) mit demselben Rahmen, damit alle System-Mails gleich aussehen.
|
||||
// email_badge()/email_button()/email_p() sind wiederverwendbare Bausteine.
|
||||
// Alles ist tabellenbasiert mit Inline-Styles und für Outlook (Word-Engine),
|
||||
// Gmail, Apple Mail und Handys gebaut: feste Breite 600 px, die auf kleinen
|
||||
// Bildschirmen schrumpft; der Knopf ist in Outlook ein echter Knopf (VML).
|
||||
|
||||
/** Branding-Rahmen (Header, weiße Karte, Footer) um einen HTML-Textkörper. */
|
||||
const EMAIL_FONT = "'Segoe UI',-apple-system,BlinkMacSystemFont,Roboto,Helvetica,Arial,sans-serif";
|
||||
|
||||
/** Rahmen (Kopf mit Name oder Logo, weiße Karte, Fußzeile) um einen HTML-Textkörper. */
|
||||
function email_wrap(string $innerHtml): string
|
||||
{
|
||||
$siteName = e(get_setting('display_name', config()['site']['name'] ?? 'DeskHub Support'));
|
||||
$siteNameRaw = (string) get_setting('display_name', config()['site']['name'] ?? 'DeskHub Support');
|
||||
$siteName = e($siteNameRaw);
|
||||
$font = EMAIL_FONT;
|
||||
$logo = (string) get_setting('logo_path', '');
|
||||
$brand = '<span style="font-family:' . $font . ';color:#ffffff;font-size:20px;font-weight:700;letter-spacing:-.01em;">' . $siteName . '</span>';
|
||||
if ($logo !== '' && (str_starts_with($logo, '/') || preg_match('#^https://#i', $logo))) {
|
||||
$src = e(str_starts_with($logo, '/') ? base_url() . $logo : $logo);
|
||||
$img = '<img src="' . $src . '" alt="' . $siteName . '" height="34" style="display:inline-block;height:34px;width:auto;border:0;vertical-align:middle;">';
|
||||
$brand = get_setting('logo_hide_name', '0') === '1' ? $img : $img . '<span style="font-family:' . $font . ';color:#ffffff;font-size:20px;font-weight:700;vertical-align:middle;padding-left:12px;">' . $siteName . '</span>';
|
||||
}
|
||||
$home = e(base_url());
|
||||
return <<<HTML
|
||||
<div style="background:#f5f7f9;padding:32px 16px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:520px;margin:0 auto;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e2e8f0;">
|
||||
<tr>
|
||||
<td style="background:#0f6f6a;padding:20px 28px;">
|
||||
<span style="color:#ffffff;font-size:18px;font-weight:700;letter-spacing:-.01em;">{$siteName}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:32px 28px;">
|
||||
{$innerHtml}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:18px 28px;background:#f5f7f9;border-top:1px solid #e2e8f0;">
|
||||
<p style="margin:0;color:#94a3b8;font-size:12px;">Diese E-Mail wurde automatisch von {$siteName} versendet.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#eef2f6" style="background:#eef2f6;">
|
||||
<tr>
|
||||
<td align="center" style="padding:28px 12px;">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:600px;">
|
||||
<tr>
|
||||
<td bgcolor="#0f6f6a" style="background:#0f6f6a;border-radius:14px 14px 0 0;padding:24px 36px;">
|
||||
{$brand}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="4" bgcolor="#14958d" style="background:#14958d;font-size:0;line-height:0;height:4px;"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" style="background:#ffffff;padding:36px 36px 34px;font-family:{$font};color:#1f2937;font-size:15px;line-height:1.6;border-left:1px solid #e2e8f0;border-right:1px solid #e2e8f0;">
|
||||
{$innerHtml}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" style="background:#ffffff;border:1px solid #e2e8f0;border-top:1px solid #edf1f5;border-radius:0 0 14px 14px;padding:18px 36px;font-family:{$font};">
|
||||
<span style="color:#94a3b8;font-size:12px;line-height:1.5;">Diese E-Mail wurde automatisch von <a href="{$home}" style="color:#64748b;text-decoration:none;">{$siteName}</a> versendet. Bitte antworten Sie nur, wenn es um Ihr Ticket geht.</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/** Farbige Hervorhebungs-Box, z.B. für die Ticketnummer. $label/$value dürfen bereits ersetzte {platzhalter} enthalten. */
|
||||
/** Hervorgehobene Karte, z.B. für die Ticketnummer (volle Breite, farbiger Balken links). $label/$value dürfen {platzhalter} enthalten. */
|
||||
function email_badge(string $label, string $value, string $color = '#0f6f6a', string $bg = '#e6f4f3'): string
|
||||
{
|
||||
$font = EMAIL_FONT;
|
||||
return <<<HTML
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="margin:0 0 24px;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 22px;">
|
||||
<tr>
|
||||
<td style="background:{$bg};border-radius:8px;padding:14px 18px;">
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:{$color};margin-bottom:2px;">{$label}</div>
|
||||
<div style="font-size:20px;font-weight:700;color:{$color};font-family:'SFMono-Regular',Consolas,'Liberation Mono',monospace;">{$value}</div>
|
||||
<td width="5" bgcolor="{$color}" style="background:{$color};width:5px;font-size:0;line-height:0;"> </td>
|
||||
<td bgcolor="{$bg}" style="background:{$bg};padding:16px 20px;font-family:{$font};">
|
||||
<div style="font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:{$color};line-height:1.4;">{$label}</div>
|
||||
<div style="font-size:24px;font-weight:700;color:{$color};line-height:1.3;font-family:'SFMono-Regular',Consolas,'Liberation Mono',monospace;">{$value}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/** Button-artiger Call-to-Action-Link. */
|
||||
/** Knopf mit Link: in Outlook ein echter Knopf (VML), sonst ein abgerundeter Link. */
|
||||
function email_button(string $label, string $url, string $color = '#0f6f6a'): string
|
||||
{
|
||||
$font = EMAIL_FONT;
|
||||
$plain = trim(strip_tags($label));
|
||||
$w = max(180, (int) round(mb_strlen($plain) * 9.2 + 64));
|
||||
return <<<HTML
|
||||
<table role="presentation" cellpadding="0" cellspacing="0">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:6px 0 4px;">
|
||||
<tr>
|
||||
<td style="background:{$color};border-radius:8px;">
|
||||
<a href="{$url}" style="display:inline-block;padding:12px 24px;color:#ffffff;font-size:14px;font-weight:600;text-decoration:none;">{$label}</a>
|
||||
<td align="center" bgcolor="{$color}" style="background:{$color};border-radius:10px;">
|
||||
<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{$url}" style="height:48px;v-text-anchor:middle;width:{$w}px;" arcsize="20%" stroke="f" fillcolor="{$color}"><w:anchorlock/><center style="color:#ffffff;font-family:'Segoe UI',Arial,sans-serif;font-size:15px;font-weight:bold;">{$label}</center></v:roundrect><![endif]-->
|
||||
<!--[if !mso]><!-- --><a href="{$url}" style="display:inline-block;padding:14px 30px;font-family:{$font};color:#ffffff;font-size:15px;font-weight:600;line-height:1.2;text-decoration:none;border-radius:10px;background:{$color};">{$label}</a><!--<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/** Wiederverwendeter Inline-Style für normale Absätze in Mail-Vorlagen. */
|
||||
/** Normaler Absatz. */
|
||||
function email_p(string $html, string $marginBottom = '16px'): string
|
||||
{
|
||||
return "<p style=\"margin:0 0 {$marginBottom};color:#16202a;font-size:15px;line-height:1.5;\">{$html}</p>";
|
||||
return "<p style=\"margin:0 0 {$marginBottom};color:#1f2937;font-size:15px;line-height:1.6;font-family:" . EMAIL_FONT . ";\">{$html}</p>";
|
||||
}
|
||||
|
||||
/** Zitat-/Textkasten für die eigentliche Nachricht (zum Beispiel die Antwort des Supports). */
|
||||
function email_quote(string $html): string
|
||||
{
|
||||
return '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 24px;"><tr>'
|
||||
. '<td width="4" bgcolor="#0f6f6a" style="background:#0f6f6a;width:4px;font-size:0;line-height:0;"> </td>'
|
||||
. '<td bgcolor="#f6f9fb" style="background:#f6f9fb;padding:16px 20px;color:#1f2937;font-size:15px;line-height:1.65;font-family:' . EMAIL_FONT . ';">' . $html . '</td>'
|
||||
. '</tr></table>';
|
||||
}
|
||||
|
||||
/** Überschrift im Mailtext. */
|
||||
function email_h(string $text): string
|
||||
{
|
||||
return '<h2 style="margin:0 0 14px;color:#0f172a;font-size:22px;line-height:1.3;font-weight:700;font-family:' . EMAIL_FONT . ';">' . $text . '</h2>';
|
||||
}
|
||||
|
||||
/** Kleingedruckter Hinweis unter dem Knopf. */
|
||||
function email_small(string $html): string
|
||||
{
|
||||
return '<p style="margin:22px 0 0;color:#64748b;font-size:13px;line-height:1.55;font-family:' . EMAIL_FONT . ';">' . $html . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,25 +316,28 @@ function email_p(string $html, string $marginBottom = '16px'): string
|
||||
*/
|
||||
function email_ticket_created_default_body(): string
|
||||
{
|
||||
return email_p('Hallo {customer_name},')
|
||||
return email_h('Wir haben Ihre Anfrage erhalten')
|
||||
. email_p('Hallo {customer_name},')
|
||||
. email_p('vielen Dank für Ihre Anfrage. Wir haben Ihr Ticket erhalten und kümmern uns so schnell wie möglich darum.', '20px')
|
||||
. email_badge('Ihre Ticketnummer', '#{ticket_number}')
|
||||
. email_p('<strong>Betreff:</strong> {subject}', '24px')
|
||||
. email_button('Ticket ansehen', '{ticket_url}')
|
||||
. '<p style="margin:24px 0 0;color:#64748b;font-size:13px;line-height:1.5;">Bewahren Sie diese Ticketnummer gut auf – damit können Sie den Status Ihrer Anfrage jederzeit auch ohne Kundenkonto unter <a href="{lookup_url}" style="color:#0f6f6a;">{lookup_url}</a> prüfen.</p>';
|
||||
. email_small('Bewahren Sie diese Ticketnummer gut auf – damit können Sie den Status Ihrer Anfrage jederzeit auch ohne Kundenkonto unter <a href="{lookup_url}" style="color:#0f6f6a;">{lookup_url}</a> prüfen.');
|
||||
}
|
||||
|
||||
function email_new_reply_default_body(): string
|
||||
{
|
||||
return email_p('Hallo {customer_name},')
|
||||
. email_p('es gibt eine neue Antwort zu Ihrem Ticket <strong>#{ticket_number}</strong>:')
|
||||
. '<div style="background:#f8fafc;border-left:3px solid #0f6f6a;border-radius:0 6px 6px 0;padding:14px 18px;margin:0 0 24px;color:#16202a;font-size:14px;line-height:1.6;">{message}</div>'
|
||||
return email_h('Neue Antwort zu Ihrem Ticket')
|
||||
. email_p('Hallo {customer_name},')
|
||||
. email_p('es gibt eine neue Antwort zu Ihrem Ticket <strong>#{ticket_number}</strong>:', '18px')
|
||||
. email_quote('{message}')
|
||||
. email_button('Ticket ansehen', '{ticket_url}');
|
||||
}
|
||||
|
||||
function email_new_ticket_department_default_body(): string
|
||||
{
|
||||
return email_p('Ein neues Ticket wurde erstellt.')
|
||||
return email_h('Neues Ticket')
|
||||
. email_p('Ein neues Ticket wurde erstellt.', '18px')
|
||||
. email_badge('Ticket', '#{ticket_number}')
|
||||
. email_p('<strong>Von:</strong> {customer_name} ({customer_email})', '6px')
|
||||
. email_p('<strong>Abteilung:</strong> {department_name}', '6px')
|
||||
@@ -296,7 +347,8 @@ function email_new_ticket_department_default_body(): string
|
||||
|
||||
function email_ticket_overdue_default_body(): string
|
||||
{
|
||||
return email_p('Das folgende Ticket hat die SLA-Lösungsfrist überschritten und benötigt Aufmerksamkeit:')
|
||||
return email_h('SLA-Frist überschritten')
|
||||
. email_p('Das folgende Ticket hat die SLA-Lösungsfrist überschritten und benötigt Aufmerksamkeit:', '18px')
|
||||
. email_badge('Ticket', '#{ticket_number}', '#b91c1c', '#fee2e2')
|
||||
. email_p('<strong>Betreff:</strong> {subject}', '6px')
|
||||
. email_p('<strong>Abteilung:</strong> {department_name}', '24px')
|
||||
@@ -305,8 +357,9 @@ function email_ticket_overdue_default_body(): string
|
||||
|
||||
function email_live_chat_transcript_default_body(): string
|
||||
{
|
||||
return email_p('Hallo {customer_name},')
|
||||
. email_p('vielen Dank für Ihren Chat mit uns. Hier der vollständige Verlauf zu Ihrer Unterlage:', '20px')
|
||||
return email_h('Ihr Chat-Verlauf')
|
||||
. email_p('Hallo {customer_name},')
|
||||
. email_p('vielen Dank für Ihren Chat mit uns. Hier der vollständige Verlauf zu Ihrer Anfrage:', '20px')
|
||||
. '{transcript}'
|
||||
. email_p('Bei weiteren Fragen antworten Sie einfach direkt auf diese E-Mail.', '20px')
|
||||
. email_button('Ticket ansehen', '{ticket_url}');
|
||||
@@ -315,9 +368,10 @@ function email_live_chat_transcript_default_body(): string
|
||||
/** Fertige, versandbereite HTML-Mail für Passwort-zurücksetzen-Links (Kunden- und Mitarbeiterbereich). */
|
||||
function email_password_reset_body(string $link): string
|
||||
{
|
||||
$inner = email_p('Klicken Sie auf den folgenden Button, um Ihr Passwort zurückzusetzen. Der Link ist <strong>1 Stunde</strong> lang gültig.')
|
||||
$inner = email_h('Passwort zurücksetzen')
|
||||
. email_p('Klicken Sie auf den folgenden Knopf, um ein neues Passwort festzulegen. Der Link ist <strong>1 Stunde</strong> lang gültig.', '22px')
|
||||
. email_button('Passwort zurücksetzen', e($link))
|
||||
. '<p style="margin:20px 0 0;color:#64748b;font-size:13px;line-height:1.5;">Falls Sie dies nicht angefordert haben, ignorieren Sie diese E-Mail einfach – es wird nichts verändert.</p>';
|
||||
. email_small('Falls Sie dies nicht angefordert haben, ignorieren Sie diese E-Mail einfach – es wird nichts verändert.');
|
||||
return email_wrap($inner);
|
||||
}
|
||||
|
||||
@@ -349,10 +403,11 @@ function notify_customer_new_reply(array $ticket, string $message, string $signa
|
||||
/** Standardtext der Erinnerung an Kunden (siehe cron/remind_customers.php). */
|
||||
function email_customer_reminder_default_body(): string
|
||||
{
|
||||
return '<h2 style="margin:0 0 12px;font-size:20px;color:#0f172a;">Brauchen Sie noch Hilfe?</h2>'
|
||||
. '<p style="margin:0 0 12px;color:#334155;font-size:15px;line-height:1.6;">Hallo {customer_name}, wir haben Ihnen zu Ihrem Ticket <strong>#{ticket_number}</strong> („{subject}“) geantwortet und noch nichts von Ihnen gehört. Hat sich Ihr Anliegen erledigt, brauchen Sie nichts zu tun. Haben Sie noch Fragen, antworten Sie einfach.</p>'
|
||||
return email_h('Brauchen Sie noch Hilfe?')
|
||||
. email_p('Hallo {customer_name}, wir haben Ihnen zu Ihrem Ticket <strong>#{ticket_number}</strong> („{subject}“) geantwortet und noch nichts von Ihnen gehört. Hat sich Ihr Anliegen erledigt, brauchen Sie nichts zu tun. Haben Sie noch Fragen, antworten Sie einfach.', '16px')
|
||||
. '{close_note}'
|
||||
. '<p style="margin:20px 0 0;"><a href="{ticket_url}" style="background:#0f6f6a;color:#ffffff;text-decoration:none;padding:11px 20px;border-radius:8px;font-weight:600;display:inline-block;">Ticket ansehen</a></p>';
|
||||
. '<div style="height:8px;line-height:8px;font-size:0;"> </div>'
|
||||
. email_button('Ticket ansehen', '{ticket_url}');
|
||||
}
|
||||
|
||||
function notify_customer_ticket_created(array $ticket): void
|
||||
@@ -516,10 +571,20 @@ function notify_admins_overdue_ticket(array $ticket): void
|
||||
* Mitarbeiter die aktiven Administratoren. Nie die Postfach-Adresse der Abteilung: Sie wird vom E-Mail-Abruf gelesen und würde Tickets erzeugen.
|
||||
* @return list<string> E-Mail-Adressen
|
||||
*/
|
||||
/** Wer bei SLA-Überschreitung benachrichtigt wird: „admins“ (Standard: nur die Administratoren, nur per E-Mail) oder „assignee“ (Bearbeiter bzw. Abteilung, Mail und Push). */
|
||||
function sla_notify_mode(): string
|
||||
{
|
||||
return get_setting('sla_notify_mode', 'admins') === 'assignee' ? 'assignee' : 'admins';
|
||||
}
|
||||
|
||||
function sla_overdue_recipients(PDO $pdo, array $ticket): array
|
||||
{
|
||||
$emails = [];
|
||||
$assignee = (int) ($ticket['assigned_staff_id'] ?? 0);
|
||||
if (sla_notify_mode() === 'admins') {
|
||||
$assignee = 0; // nur Administratoren: weder der Bearbeiter noch die Abteilung
|
||||
$ticket['department_id'] = null;
|
||||
}
|
||||
if ($assignee > 0) {
|
||||
// Im Urlaub: an die Vertretung; ohne nutzbare Vertretung gar nicht an den Abwesenden (dann gehen die Abteilung bzw. die Administratoren vor)
|
||||
$target = staff_is_on_vacation($assignee) ? vacation_target($pdo, $assignee) : $assignee;
|
||||
|
||||
Reference in New Issue
Block a user