Add email sending logging functionality
Add petitionId parameter to email functions Update email sending calls to include petitionId Add error message parameter to internal email functions Improve error handling in email sending functions Update thank you email to use signature email from CSV Add SMTP connection error logging to error message Add SMTP config not found error logging Add SMTP disabled error logging Add signature not found error logging
This commit is contained in:
parent
b79bcd9fe9
commit
8f49bb17cd
1 changed files with 73 additions and 13 deletions
|
|
@ -584,6 +584,45 @@ function petitionFormatDate(int $timestamp, Context $ctx): string {
|
||||||
return "{$day}. {$month} {$year}, {$time}";
|
return "{$day}. {$month} {$year}, {$time}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log email send attempt to SMTP log file
|
||||||
|
* Used for tracking failed sends and enabling retry via CLI
|
||||||
|
*/
|
||||||
|
function petitionLogEmail(string $type, string $petitionId, string $email, bool $success, string $errorMessage = ''): void {
|
||||||
|
$logPath = dirname(__DIR__, 2) . '/data/smtp-log.csv';
|
||||||
|
$dir = dirname($logPath);
|
||||||
|
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0750, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$isNewFile = !file_exists($logPath);
|
||||||
|
|
||||||
|
$fp = fopen($logPath, 'a');
|
||||||
|
if (!$fp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flock($fp, LOCK_EX)) {
|
||||||
|
if ($isNewFile) {
|
||||||
|
fputcsv($fp, ['timestamp', 'type', 'petition_id', 'email', 'status', 'error_message'], ',', '"', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
fputcsv($fp, [
|
||||||
|
time(),
|
||||||
|
$type,
|
||||||
|
$petitionId,
|
||||||
|
$email,
|
||||||
|
$success ? 'success' : 'failed',
|
||||||
|
$errorMessage
|
||||||
|
], ',', '"', '');
|
||||||
|
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry wrapper for email sending with exponential backoff
|
* Retry wrapper for email sending with exponential backoff
|
||||||
* Retries up to maxRetries times with increasing delays
|
* Retries up to maxRetries times with increasing delays
|
||||||
|
|
@ -617,26 +656,33 @@ function petitionSendWithRetry(callable $sendFunction, int $maxRetries = 3): boo
|
||||||
/**
|
/**
|
||||||
* Send confirmation email (with retry wrapper)
|
* Send confirmation email (with retry wrapper)
|
||||||
*/
|
*/
|
||||||
function petitionSendConfirmationEmail(array $data, string $confirmUrl, string $petitionTitle, Context $ctx): bool {
|
function petitionSendConfirmationEmail(array $data, string $confirmUrl, string $petitionTitle, string $petitionId, Context $ctx): bool {
|
||||||
return petitionSendWithRetry(function() use ($data, $confirmUrl, $petitionTitle, $ctx) {
|
$result = petitionSendWithRetry(function() use ($data, $confirmUrl, $petitionTitle, $ctx, &$errorMessage) {
|
||||||
return petitionSendConfirmationEmailInternal($data, $confirmUrl, $petitionTitle, $ctx);
|
return petitionSendConfirmationEmailInternal($data, $confirmUrl, $petitionTitle, $ctx, $errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log the final result
|
||||||
|
petitionLogEmail('confirmation', $petitionId, $data['email'], $result, $result ? '' : ($errorMessage ?? 'Unknown error'));
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal: Send confirmation email (single attempt)
|
* Internal: Send confirmation email (single attempt)
|
||||||
*/
|
*/
|
||||||
function petitionSendConfirmationEmailInternal(array $data, string $confirmUrl, string $petitionTitle, Context $ctx): bool {
|
function petitionSendConfirmationEmailInternal(array $data, string $confirmUrl, string $petitionTitle, Context $ctx, ?string &$errorMessage = null): bool {
|
||||||
// Decode HTML entities for plain-text email (e.g., – → –)
|
// Decode HTML entities for plain-text email (e.g., – → –)
|
||||||
$petitionTitle = html_entity_decode($petitionTitle, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$petitionTitle = html_entity_decode($petitionTitle, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
$smtpConfigPath = dirname(__DIR__, 2) . '/smtp-config.php';
|
$smtpConfigPath = dirname(__DIR__, 2) . '/smtp-config.php';
|
||||||
if (!file_exists($smtpConfigPath)) {
|
if (!file_exists($smtpConfigPath)) {
|
||||||
|
$errorMessage = 'SMTP config not found';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = require $smtpConfigPath;
|
$config = require $smtpConfigPath;
|
||||||
|
|
||||||
if (!$config['enabled']) {
|
if (!$config['enabled']) {
|
||||||
|
$errorMessage = 'SMTP disabled';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -670,7 +716,7 @@ function petitionSendConfirmationEmailInternal(array $data, string $confirmUrl,
|
||||||
// Pre-flight check
|
// Pre-flight check
|
||||||
$fp = @fsockopen($config['host'], $config['port'], $errno, $errstr, 10);
|
$fp = @fsockopen($config['host'], $config['port'], $errno, $errstr, 10);
|
||||||
if (!$fp) {
|
if (!$fp) {
|
||||||
// Note: Logging SMTP config only, no user data
|
$errorMessage = "Connection failed: {$errno} - {$errstr}";
|
||||||
error_log("Petition SMTP pre-flight failed: {$config['host']}:{$config['port']} - {$errno} - {$errstr}");
|
error_log("Petition SMTP pre-flight failed: {$config['host']}:{$config['port']} - {$errno} - {$errstr}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -697,14 +743,14 @@ function petitionSendConfirmationEmailInternal(array $data, string $confirmUrl,
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
if (!$sent || stripos($output, 'error') !== false || stripos($output, '✗') !== false) {
|
if (!$sent || stripos($output, 'error') !== false || stripos($output, '✗') !== false) {
|
||||||
// Note: Output is already sanitized with strip_tags, no user data exposed
|
$errorMessage = strip_tags($output) ?: 'Send returned false';
|
||||||
error_log("Petition email send failed: " . strip_tags($output));
|
error_log("Petition email send failed: " . strip_tags($output));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// Note: Exception message doesn't contain user data
|
$errorMessage = $e->getMessage();
|
||||||
error_log("Petition email exception: " . $e->getMessage());
|
error_log("Petition email exception: " . $e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -750,32 +796,43 @@ function petitionGetSignatureByToken(string $csvPath, string $token): ?array {
|
||||||
/**
|
/**
|
||||||
* Send thank you email with delete link (with retry wrapper)
|
* Send thank you email with delete link (with retry wrapper)
|
||||||
*/
|
*/
|
||||||
function petitionSendThankYouEmail(string $token, string $deleteUrl, string $petitionTitle, string $csvPath, Context $ctx): bool {
|
function petitionSendThankYouEmail(string $token, string $deleteUrl, string $petitionTitle, string $petitionId, string $csvPath, Context $ctx): bool {
|
||||||
return petitionSendWithRetry(function() use ($token, $deleteUrl, $petitionTitle, $csvPath, $ctx) {
|
$signature = petitionGetSignatureByToken($csvPath, $token);
|
||||||
return petitionSendThankYouEmailInternal($token, $deleteUrl, $petitionTitle, $csvPath, $ctx);
|
$email = $signature['email'] ?? 'unknown';
|
||||||
|
|
||||||
|
$result = petitionSendWithRetry(function() use ($token, $deleteUrl, $petitionTitle, $csvPath, $ctx, &$errorMessage) {
|
||||||
|
return petitionSendThankYouEmailInternal($token, $deleteUrl, $petitionTitle, $csvPath, $ctx, $errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log the final result
|
||||||
|
petitionLogEmail('thankyou', $petitionId, $email, $result, $result ? '' : ($errorMessage ?? 'Unknown error'));
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal: Send thank you email with delete link (single attempt)
|
* Internal: Send thank you email with delete link (single attempt)
|
||||||
*/
|
*/
|
||||||
function petitionSendThankYouEmailInternal(string $token, string $deleteUrl, string $petitionTitle, string $csvPath, Context $ctx): bool {
|
function petitionSendThankYouEmailInternal(string $token, string $deleteUrl, string $petitionTitle, string $csvPath, Context $ctx, ?string &$errorMessage = null): bool {
|
||||||
// Decode HTML entities for plain-text email (e.g., – → –)
|
// Decode HTML entities for plain-text email (e.g., – → –)
|
||||||
$petitionTitle = html_entity_decode($petitionTitle, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
$petitionTitle = html_entity_decode($petitionTitle, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
|
|
||||||
$signature = petitionGetSignatureByToken($csvPath, $token);
|
$signature = petitionGetSignatureByToken($csvPath, $token);
|
||||||
if (!$signature) {
|
if (!$signature) {
|
||||||
|
$errorMessage = 'Signature not found';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$smtpConfigPath = dirname(__DIR__, 2) . '/smtp-config.php';
|
$smtpConfigPath = dirname(__DIR__, 2) . '/smtp-config.php';
|
||||||
if (!file_exists($smtpConfigPath)) {
|
if (!file_exists($smtpConfigPath)) {
|
||||||
|
$errorMessage = 'SMTP config not found';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = require $smtpConfigPath;
|
$config = require $smtpConfigPath;
|
||||||
|
|
||||||
if (!$config['enabled']) {
|
if (!$config['enabled']) {
|
||||||
|
$errorMessage = 'SMTP disabled';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -805,6 +862,7 @@ function petitionSendThankYouEmailInternal(string $token, string $deleteUrl, str
|
||||||
// Pre-flight check
|
// Pre-flight check
|
||||||
$fp = @fsockopen($config['host'], $config['port'], $errno, $errstr, 10);
|
$fp = @fsockopen($config['host'], $config['port'], $errno, $errstr, 10);
|
||||||
if (!$fp) {
|
if (!$fp) {
|
||||||
|
$errorMessage = "Connection failed: {$errno} - {$errstr}";
|
||||||
error_log("Petition SMTP pre-flight failed: {$config['host']}:{$config['port']} - {$errno} - {$errstr}");
|
error_log("Petition SMTP pre-flight failed: {$config['host']}:{$config['port']} - {$errno} - {$errstr}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -831,12 +889,14 @@ function petitionSendThankYouEmailInternal(string $token, string $deleteUrl, str
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
if (!$sent || stripos($output, 'error') !== false || stripos($output, '✗') !== false) {
|
if (!$sent || stripos($output, 'error') !== false || stripos($output, '✗') !== false) {
|
||||||
|
$errorMessage = strip_tags($output) ?: 'Send returned false';
|
||||||
error_log("Petition thank you email send failed: " . strip_tags($output));
|
error_log("Petition thank you email send failed: " . strip_tags($output));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
$errorMessage = $e->getMessage();
|
||||||
error_log("Petition thank you email exception: " . $e->getMessage());
|
error_log("Petition thank you email exception: " . $e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -1096,7 +1156,7 @@ function petitionGetPageData(?Context $ctx): ?array {
|
||||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||||
$host = $_SERVER['HTTP_HOST'];
|
$host = $_SERVER['HTTP_HOST'];
|
||||||
$deleteUrl = "{$protocol}://{$host}{$langPrefix}/{$currentPath}/?delete={$token}#sign-now";
|
$deleteUrl = "{$protocol}://{$host}{$langPrefix}/{$currentPath}/?delete={$token}#sign-now";
|
||||||
petitionSendThankYouEmail($token, $deleteUrl, $petitionTitle, $csvPath, $ctx);
|
petitionSendThankYouEmail($token, $deleteUrl, $petitionTitle, $petitionId, $csvPath, $ctx);
|
||||||
break;
|
break;
|
||||||
case 'already':
|
case 'already':
|
||||||
$confirmMessage = ['type' => 'info', 'text' => petitionT($ctx, 'petition', 'confirm_already')];
|
$confirmMessage = ['type' => 'info', 'text' => petitionT($ctx, 'petition', 'confirm_already')];
|
||||||
|
|
@ -1245,7 +1305,7 @@ function petitionGetPageData(?Context $ctx): ?array {
|
||||||
$confirmUrl = "{$protocol}://{$host}{$langPrefix}/{$currentPath}/?confirm={$token}#sign-now";
|
$confirmUrl = "{$protocol}://{$host}{$langPrefix}/{$currentPath}/?confirm={$token}#sign-now";
|
||||||
|
|
||||||
// Send confirmation email
|
// Send confirmation email
|
||||||
if (petitionSendConfirmationEmail($signatureData, $confirmUrl, $petitionTitle, $ctx)) {
|
if (petitionSendConfirmationEmail($signatureData, $confirmUrl, $petitionTitle, $petitionId, $ctx)) {
|
||||||
// Subscribe to newsletter if opted in (fire and forget - don't block petition)
|
// Subscribe to newsletter if opted in (fire and forget - don't block petition)
|
||||||
$newsletterOptIn = isset($_POST['newsletter']) && $_POST['newsletter'] === 'on';
|
$newsletterOptIn = isset($_POST['newsletter']) && $_POST['newsletter'] === 'on';
|
||||||
error_log("Newsletter checkbox: " . ($newsletterOptIn ? 'checked' : 'not checked'));
|
error_log("Newsletter checkbox: " . ($newsletterOptIn ? 'checked' : 'not checked'));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue