diff --git a/composer.json b/composer.json
index 82f3e80..60cee43 100755
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,7 @@
{
"name": "ssh-w020abd8/staging",
"require": {
+ "phpmailer/phpmailer": "^6.9",
"tijsverkoyen/css-to-inline-styles": "^2.3"
}
}
diff --git a/partials/landingpage/accountsetup/settings.php b/partials/landingpage/accountsetup/settings.php
index e661357..bd8d6ba 100755
--- a/partials/landingpage/accountsetup/settings.php
+++ b/partials/landingpage/accountsetup/settings.php
@@ -68,6 +68,43 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
+
diff --git a/public/assets/js/ui-user.js b/public/assets/js/ui-user.js
index 9d790b1..63d9b62 100755
--- a/public/assets/js/ui-user.js
+++ b/public/assets/js/ui-user.js
@@ -448,6 +448,15 @@ function fillSettingsForm(settings) {
: 0;
settingsForm.versions_retention.value = String(Math.max(0, retention));
}
+ if (settingsForm.smtp_enabled) settingsForm.smtp_enabled.checked = Number(settings.smtp_enabled) === 1;
+ if (settingsForm.smtp_host) settingsForm.smtp_host.value = settings.smtp_host || '';
+ if (settingsForm.smtp_port) settingsForm.smtp_port.value = settings.smtp_port ? String(settings.smtp_port) : '';
+ if (settingsForm.smtp_user) settingsForm.smtp_user.value = settings.smtp_user || '';
+ if (settingsForm.smtp_pass) settingsForm.smtp_pass.value = settings.smtp_pass || '';
+ if (settingsForm.smtp_secure) settingsForm.smtp_secure.value = settings.smtp_secure || '';
+ if (settingsForm.smtp_from_email) settingsForm.smtp_from_email.value = settings.smtp_from_email || '';
+ if (settingsForm.smtp_from_name) settingsForm.smtp_from_name.value = settings.smtp_from_name || '';
+ if (settingsForm.smtp_reply_to) settingsForm.smtp_reply_to.value = settings.smtp_reply_to || '';
refreshAdminTables(settings.bridge_setup?.tables || [], settings.bridge_tables || []);
}
@@ -501,6 +510,18 @@ async function submitSettingsForm(ev) {
const parsed = raw === '' ? 0 : Number(raw);
data.versions_retention = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : 0;
}
+ if (settingsForm.smtp_enabled) data.smtp_enabled = settingsForm.smtp_enabled.checked ? 1 : 0;
+ if (settingsForm.smtp_host) data.smtp_host = settingsForm.smtp_host.value.trim();
+ if (settingsForm.smtp_port) {
+ const rawPort = settingsForm.smtp_port.value.trim();
+ data.smtp_port = rawPort === '' ? 0 : Number(rawPort);
+ }
+ if (settingsForm.smtp_user) data.smtp_user = settingsForm.smtp_user.value.trim();
+ if (settingsForm.smtp_pass) data.smtp_pass = settingsForm.smtp_pass.value;
+ if (settingsForm.smtp_secure) data.smtp_secure = settingsForm.smtp_secure.value;
+ if (settingsForm.smtp_from_email) data.smtp_from_email = settingsForm.smtp_from_email.value.trim();
+ if (settingsForm.smtp_from_name) data.smtp_from_name = settingsForm.smtp_from_name.value.trim();
+ if (settingsForm.smtp_reply_to) data.smtp_reply_to = settingsForm.smtp_reply_to.value.trim();
if (adminTablesAllSelect && adminTablesSelectedSelect) {
const bridgeTables = normalizeTableList(state.settings.bridge_tables || []);
data.bridge_tables = bridgeTables;
diff --git a/schema.sql b/schema.sql
index f54d64d..8dc95a0 100755
--- a/schema.sql
+++ b/schema.sql
@@ -110,6 +110,15 @@ CREATE TABLE IF NOT EXISTS `emailtemplate_customer_settings` (
`sender_token` varchar(255) DEFAULT NULL,
`external_api_token` varchar(255) DEFAULT NULL,
`editor_default` varchar(32) DEFAULT NULL,
+ `smtp_enabled` tinyint(1) DEFAULT 0,
+ `smtp_host` varchar(255) DEFAULT NULL,
+ `smtp_port` int(10) unsigned DEFAULT NULL,
+ `smtp_user` varchar(255) DEFAULT NULL,
+ `smtp_pass` varchar(255) DEFAULT NULL,
+ `smtp_secure` varchar(16) DEFAULT NULL,
+ `smtp_from_email` varchar(255) DEFAULT NULL,
+ `smtp_from_name` varchar(255) DEFAULT NULL,
+ `smtp_reply_to` varchar(255) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`customer_id`)
diff --git a/src/ApiKernel.php b/src/ApiKernel.php
index 32a716f..ba8844f 100755
--- a/src/ApiKernel.php
+++ b/src/ApiKernel.php
@@ -2,6 +2,8 @@
declare(strict_types=1);
use TijsVerkoyen\CssToInlineStyles\CssToInlineStyles;
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\Exception as PHPMailerException;
// 💡 NEUE KORREKTUR: Starte Output Buffering so früh wie möglich, um Whitespace/Errors
// von inkludierten Dateien (AuthService.php, config.php) abzufangen.
@@ -25,6 +27,7 @@ class ApiKernel
private array $tableMap;
private AuthService $authService;
private array $tableExistsCache = [];
+ private ?string $lastMailError = null;
// --- Initialisierung & Konstruktor (Optimiert) ---
@@ -2289,7 +2292,7 @@ class ApiKernel
}
}
- if (!$this->dispatchTestMail($recipient, $subject, $html, $sender)) {
+ if (!$this->dispatchTestMail($recipient, $subject, $html, $sender, $customerId)) {
$this->writeDebugLog('templates_test_send', [
'time' => date(DATE_ATOM),
'template_id' => $templateId,
@@ -2299,6 +2302,7 @@ class ApiKernel
'from_email' => $sender['from_email'] ?? ($this->conf['smtp']['from_email'] ?? null),
'from_name' => $sender['from_name'] ?? ($this->conf['smtp']['from_name'] ?? null),
'html_length' => strlen($html),
+ 'mail_error' => $this->lastMailError,
]);
$this->fail('Send failed', null, 500);
}
@@ -2867,16 +2871,80 @@ class ApiKernel
return $map ? strtr($html, $map) : $html;
}
- private function dispatchTestMail(string $to, string $subject, string $html, ?array $sender = null): bool
+ private function dispatchTestMail(string $to, string $subject, string $html, ?array $sender = null, ?int $customerId = null): bool
{
+ $this->lastMailError = null;
+ $smtpConf = $this->conf['smtp'] ?? [];
+ $settings = ($customerId && $customerId > 0) ? $this->getCustomerSettings($customerId) : [];
+ $smtp = array_merge($smtpConf, array_filter([
+ 'host' => $settings['smtp_host'] ?? null,
+ 'port' => $settings['smtp_port'] ?? null,
+ 'user' => $settings['smtp_user'] ?? null,
+ 'pass' => $settings['smtp_pass'] ?? null,
+ 'secure' => $settings['smtp_secure'] ?? null,
+ 'from_email' => $settings['smtp_from_email'] ?? null,
+ 'from_name' => $settings['smtp_from_name'] ?? null,
+ 'reply_to' => $settings['smtp_reply_to'] ?? null,
+ 'enabled' => $settings['smtp_enabled'] ?? null,
+ ], static fn($v) => $v !== null && $v !== ''));
+
+ $smtpEnabled = !empty($smtp['enabled']);
+ $smtpHost = trim((string)($smtp['host'] ?? ''));
+ $smtpUser = trim((string)($smtp['user'] ?? ''));
+ $smtpPass = (string)($smtp['pass'] ?? '');
+ $smtpSecure = strtolower(trim((string)($smtp['secure'] ?? '')));
+ $smtpPort = (int)($smtp['port'] ?? 0);
+
+ $fromEmail = $sender['from_email'] ?? ($smtp['from_email'] ?? ($smtpConf['from_email'] ?? 'no-reply@example.com'));
+ $fromName = $sender['from_name'] ?? ($sender['label'] ?? ($smtp['from_name'] ?? ($smtpConf['from_name'] ?? 'EmailTemplate')));
+ $replyTo = $sender['reply_to'] ?? ($smtp['reply_to'] ?? '');
+
+ if ($smtpEnabled && $smtpHost !== '' && class_exists(PHPMailer::class)) {
+ try {
+ $mailer = new PHPMailer(true);
+ $mailer->CharSet = 'UTF-8';
+ $mailer->isSMTP();
+ $mailer->Host = $smtpHost;
+ $mailer->SMTPAuth = ($smtpUser !== '' || $smtpPass !== '');
+ if ($smtpUser !== '') $mailer->Username = $smtpUser;
+ if ($smtpPass !== '') $mailer->Password = $smtpPass;
+ if ($smtpSecure === 'ssl') {
+ $mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
+ if ($smtpPort <= 0) $smtpPort = 465;
+ } elseif ($smtpSecure === 'tls') {
+ $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
+ if ($smtpPort <= 0) $smtpPort = 587;
+ } else {
+ $mailer->SMTPSecure = '';
+ $mailer->SMTPAutoTLS = false;
+ if ($smtpPort <= 0) $smtpPort = 25;
+ }
+ if ($smtpPort > 0) $mailer->Port = $smtpPort;
+
+ $mailer->setFrom($fromEmail, $fromName);
+ $mailer->addAddress($to);
+ if ($replyTo !== '') {
+ $mailer->addReplyTo($replyTo, $fromName ?: $fromEmail);
+ }
+ $mailer->isHTML(true);
+ $mailer->Subject = $subject;
+ $mailer->Body = $html;
+ $mailer->AltBody = trim(strip_tags($html));
+ return $mailer->send();
+ } catch (PHPMailerException $e) {
+ $this->lastMailError = $e->getMessage();
+ return false;
+ } catch (Throwable $e) {
+ $this->lastMailError = $e->getMessage();
+ return false;
+ }
+ }
+
if (!function_exists('mail')) {
+ $this->lastMailError = 'PHP mail() not available';
return false;
}
- $smtp = $this->conf['smtp'] ?? [];
- $fromEmail = $sender['from_email'] ?? ($smtp['from_email'] ?? 'no-reply@example.com');
- $fromName = $sender['from_name'] ?? ($sender['label'] ?? ($smtp['from_name'] ?? 'EmailTemplate'));
- $replyTo = $sender['reply_to'] ?? '';
$headers = [
'MIME-Version: 1.0',
'Content-Type: text/html; charset=UTF-8',
@@ -2889,7 +2957,11 @@ class ApiKernel
$encodedSubject = function_exists('mb_encode_mimeheader')
? mb_encode_mimeheader($subject, 'UTF-8')
: $subject;
- return @mail($to, $encodedSubject, $html, implode("\r\n", $headers));
+ $sent = @mail($to, $encodedSubject, $html, implode("\r\n", $headers));
+ if (!$sent) {
+ $this->lastMailError = 'mail() returned false';
+ }
+ return $sent;
}
private function formatEmailAddress(string $email, string $name): string
@@ -3757,11 +3829,22 @@ class ApiKernel
$hasVersionsRetention = array_key_exists('versions_retention', $this->in);
$hasListSort = array_key_exists('list_sort', $this->in);
$hasBridgeTables = array_key_exists('bridge_tables', $this->in);
+ $hasSmtpEnabled = array_key_exists('smtp_enabled', $this->in);
+ $hasSmtpHost = array_key_exists('smtp_host', $this->in);
+ $hasSmtpPort = array_key_exists('smtp_port', $this->in);
+ $hasSmtpUser = array_key_exists('smtp_user', $this->in);
+ $hasSmtpPass = array_key_exists('smtp_pass', $this->in);
+ $hasSmtpSecure = array_key_exists('smtp_secure', $this->in);
+ $hasSmtpFromEmail = array_key_exists('smtp_from_email', $this->in);
+ $hasSmtpFromName = array_key_exists('smtp_from_name', $this->in);
+ $hasSmtpReplyTo = array_key_exists('smtp_reply_to', $this->in);
$rotateBridge = !empty($this->in['rotate_bridge_token']);
$rotateSender = !empty($this->in['rotate_sender_token']);
$rotateExternal = !empty($this->in['rotate_external_token']);
$onlyListSort = $hasListSort && !$hasBridgeUrl && !$hasBridgeToken && !$hasSenderToken && !$hasExternalToken
- && !$hasEditorDefault && !$hasBridgeTables && !$hasVersionsRetention && !$rotateBridge && !$rotateSender && !$rotateExternal;
+ && !$hasEditorDefault && !$hasBridgeTables && !$hasVersionsRetention && !$rotateBridge && !$rotateSender && !$rotateExternal
+ && !$hasSmtpEnabled && !$hasSmtpHost && !$hasSmtpPort && !$hasSmtpUser && !$hasSmtpPass && !$hasSmtpSecure
+ && !$hasSmtpFromEmail && !$hasSmtpFromName && !$hasSmtpReplyTo;
if (!$onlyListSort) {
$this->ensureRole($user, ['owner', 'admin']);
@@ -3777,6 +3860,15 @@ class ApiKernel
$versionsRetention = $hasVersionsRetention ? (int)($this->in['versions_retention'] ?? 0) : (int)($settings['versions_retention'] ?? 0);
$listSort = $hasListSort ? strtolower(trim((string)($this->in['list_sort'] ?? ''))) : '';
$bridgeTables = $hasBridgeTables ? $this->normalizeBridgeTables($this->in['bridge_tables'] ?? []) : ($settings['bridge_tables'] ?? []);
+ $smtpEnabled = $hasSmtpEnabled ? (int)($this->in['smtp_enabled'] ?? 0) : (int)($settings['smtp_enabled'] ?? 0);
+ $smtpHost = $hasSmtpHost ? trim((string)($this->in['smtp_host'] ?? '')) : (string)($settings['smtp_host'] ?? '');
+ $smtpPort = $hasSmtpPort ? (int)($this->in['smtp_port'] ?? 0) : (int)($settings['smtp_port'] ?? 0);
+ $smtpUser = $hasSmtpUser ? trim((string)($this->in['smtp_user'] ?? '')) : (string)($settings['smtp_user'] ?? '');
+ $smtpPass = $hasSmtpPass ? (string)($this->in['smtp_pass'] ?? '') : (string)($settings['smtp_pass'] ?? '');
+ $smtpSecure = $hasSmtpSecure ? strtolower(trim((string)($this->in['smtp_secure'] ?? ''))) : strtolower((string)($settings['smtp_secure'] ?? ''));
+ $smtpFromEmail = $hasSmtpFromEmail ? trim((string)($this->in['smtp_from_email'] ?? '')) : (string)($settings['smtp_from_email'] ?? '');
+ $smtpFromName = $hasSmtpFromName ? trim((string)($this->in['smtp_from_name'] ?? '')) : (string)($settings['smtp_from_name'] ?? '');
+ $smtpReplyTo = $hasSmtpReplyTo ? trim((string)($this->in['smtp_reply_to'] ?? '')) : (string)($settings['smtp_reply_to'] ?? '');
if ($bridgeUrl && !filter_var($bridgeUrl, FILTER_VALIDATE_URL)) {
$this->fail('Ungültige Bridge-URL', null, 422);
@@ -3786,6 +3878,24 @@ class ApiKernel
$this->fail('Ungültige Sortierung', null, 422);
}
+ if ($smtpEnabled) {
+ if ($smtpHost === '') {
+ $this->fail('SMTP-Host erforderlich', null, 422);
+ }
+ if ($smtpPort < 0 || $smtpPort > 65535) {
+ $this->fail('Ungültiger SMTP-Port', null, 422);
+ }
+ if ($smtpSecure !== '' && !in_array($smtpSecure, ['tls', 'ssl', 'none'], true)) {
+ $this->fail('Ungültige SMTP-Sicherheit', null, 422);
+ }
+ }
+ if ($smtpFromEmail !== '' && !filter_var($smtpFromEmail, FILTER_VALIDATE_EMAIL)) {
+ $this->fail('Ungültige SMTP-Absenderadresse', null, 422);
+ }
+ if ($smtpReplyTo !== '' && !filter_var($smtpReplyTo, FILTER_VALIDATE_EMAIL)) {
+ $this->fail('Ungültige SMTP-Reply-To-Adresse', null, 422);
+ }
+
if (!$onlyListSort) {
if ($rotateBridge || $bridgeToken === '') $bridgeToken = $this->generateToken();
if ($rotateSender || $senderToken === '') $senderToken = $this->generateToken();
@@ -3797,6 +3907,9 @@ class ApiKernel
if ($versionsRetention < 0) {
$this->fail('Ungültiger Aufbewahrungswert', null, 422);
}
+ if ($smtpSecure === 'none') {
+ $smtpSecure = '';
+ }
$settings = $this->saveCustomerSettings($customerId, [
'bridge_url' => $bridgeUrl,
@@ -3806,6 +3919,15 @@ class ApiKernel
'editor_default' => $editorDefault ?: null,
'bridge_tables' => $bridgeTables,
'versions_retention' => $versionsRetention,
+ 'smtp_enabled' => $smtpEnabled ? 1 : 0,
+ 'smtp_host' => $smtpHost ?: null,
+ 'smtp_port' => $smtpPort > 0 ? $smtpPort : null,
+ 'smtp_user' => $smtpUser ?: null,
+ 'smtp_pass' => $smtpPass !== '' ? $smtpPass : null,
+ 'smtp_secure' => $smtpSecure ?: null,
+ 'smtp_from_email' => $smtpFromEmail ?: null,
+ 'smtp_from_name' => $smtpFromName ?: null,
+ 'smtp_reply_to' => $smtpReplyTo ?: null,
]);
} else {
$settings = $customerId ? $this->ensureSettingsTokens($customerId, $settings) : $settings;
@@ -4367,7 +4489,25 @@ class ApiKernel
{
if ($customerId <= 0) return [];
$this->ensureCustomerSettingsTableExists();
- $allowed = ['bridge_url', 'bridge_token', 'sender_token', 'external_api_token', 'editor_default', 'bridge_tables', 'bridge_setup', 'versions_retention'];
+ $allowed = [
+ 'bridge_url',
+ 'bridge_token',
+ 'sender_token',
+ 'external_api_token',
+ 'editor_default',
+ 'bridge_tables',
+ 'bridge_setup',
+ 'versions_retention',
+ 'smtp_enabled',
+ 'smtp_host',
+ 'smtp_port',
+ 'smtp_user',
+ 'smtp_pass',
+ 'smtp_secure',
+ 'smtp_from_email',
+ 'smtp_from_name',
+ 'smtp_reply_to',
+ ];
$fields = array_intersect_key($data, array_flip($allowed));
if (!$fields) return $this->getCustomerSettings($customerId);
if (array_key_exists('versions_retention', $fields)) {
@@ -4450,6 +4590,16 @@ class ApiKernel
} else {
$row['versions_retention'] = max(0, (int)$row['versions_retention']);
}
+ if (!isset($row['smtp_enabled'])) {
+ $row['smtp_enabled'] = 0;
+ } else {
+ $row['smtp_enabled'] = (int)$row['smtp_enabled'] ? 1 : 0;
+ }
+ if (isset($row['smtp_port'])) {
+ $row['smtp_port'] = (int)$row['smtp_port'];
+ } else {
+ $row['smtp_port'] = 0;
+ }
return $row;
}
@@ -4727,6 +4877,15 @@ CREATE TABLE IF NOT EXISTS `$table` (
`editor_default` varchar(32) DEFAULT NULL,
`bridge_tables` text DEFAULT NULL,
`versions_retention` int(10) unsigned DEFAULT 0,
+ `smtp_enabled` tinyint(1) DEFAULT 0,
+ `smtp_host` varchar(255) DEFAULT NULL,
+ `smtp_port` int(10) unsigned DEFAULT NULL,
+ `smtp_user` varchar(255) DEFAULT NULL,
+ `smtp_pass` varchar(255) DEFAULT NULL,
+ `smtp_secure` varchar(16) DEFAULT NULL,
+ `smtp_from_email` varchar(255) DEFAULT NULL,
+ `smtp_from_name` varchar(255) DEFAULT NULL,
+ `smtp_reply_to` varchar(255) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`customer_id`)
@@ -4770,6 +4929,33 @@ SQL;
if (!in_array('versions_retention', $columns, true)) {
$missing[] = 'ADD COLUMN `versions_retention` int(10) unsigned DEFAULT 0';
}
+ if (!in_array('smtp_enabled', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_enabled` tinyint(1) DEFAULT 0';
+ }
+ if (!in_array('smtp_host', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_host` varchar(255) DEFAULT NULL';
+ }
+ if (!in_array('smtp_port', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_port` int(10) unsigned DEFAULT NULL';
+ }
+ if (!in_array('smtp_user', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_user` varchar(255) DEFAULT NULL';
+ }
+ if (!in_array('smtp_pass', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_pass` varchar(255) DEFAULT NULL';
+ }
+ if (!in_array('smtp_secure', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_secure` varchar(16) DEFAULT NULL';
+ }
+ if (!in_array('smtp_from_email', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_from_email` varchar(255) DEFAULT NULL';
+ }
+ if (!in_array('smtp_from_name', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_from_name` varchar(255) DEFAULT NULL';
+ }
+ if (!in_array('smtp_reply_to', $columns, true)) {
+ $missing[] = 'ADD COLUMN `smtp_reply_to` varchar(255) DEFAULT NULL';
+ }
if (!$missing) {
return;