failed attempt to avoid wordpress critical error sending test mailshot
This commit is contained in:
parent
ce50edc04a
commit
10395f0480
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: FECA Mailshots
|
||||
* Plugin URI: https://fenedge.co.uk/
|
||||
* Description: FECA mailshots plugin.
|
||||
* Version: 1.0.31
|
||||
* Version: 1.0.33
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 7.4
|
||||
* Author: FECA
|
||||
|
|
|
|||
|
|
@ -231,10 +231,7 @@ final class RunMailshotAdminPage
|
|||
if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
|
||||
return;
|
||||
}
|
||||
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
|
||||
$result = $this->runService()->runMailshot($mailshotId);
|
||||
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
|
||||
$this->redirect($mailshotId);
|
||||
$this->handleRunAction(static fn(self $page, int $mailshotId): array => $page->runService()->runMailshot($mailshotId));
|
||||
}
|
||||
|
||||
public function handleRunSelectedUi(): void
|
||||
|
|
@ -242,11 +239,9 @@ final class RunMailshotAdminPage
|
|||
if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
|
||||
return;
|
||||
}
|
||||
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
|
||||
$selectedRecipientKeys = $this->selectedRecipientKeysFromRequest();
|
||||
$result = $this->runService()->runMailshotSelected($mailshotId, $selectedRecipientKeys);
|
||||
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
|
||||
$this->redirect($mailshotId);
|
||||
$this->handleRunAction(static function (self $page, int $mailshotId): array {
|
||||
return $page->runService()->runMailshotSelected($mailshotId, $page->selectedRecipientKeysFromRequest());
|
||||
});
|
||||
}
|
||||
|
||||
public function handleRetryFailedUi(): void
|
||||
|
|
@ -254,10 +249,7 @@ final class RunMailshotAdminPage
|
|||
if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
|
||||
return;
|
||||
}
|
||||
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
|
||||
$result = $this->runService()->retryFailed($mailshotId);
|
||||
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
|
||||
$this->redirect($mailshotId);
|
||||
$this->handleRunAction(static fn(self $page, int $mailshotId): array => $page->runService()->retryFailed($mailshotId));
|
||||
}
|
||||
|
||||
public function handleRetryRecipientUi(): void
|
||||
|
|
@ -265,13 +257,52 @@ final class RunMailshotAdminPage
|
|||
if (!$this->enforceMutationGuardOrJson(self::CAPABILITY, self::NONCE_ACTION)) {
|
||||
return;
|
||||
}
|
||||
$this->handleRunAction(static function (self $page, int $mailshotId): array {
|
||||
$recipientKey = (string) ($page->wp->requestParam('recipient_key', '') ?? '');
|
||||
return $page->runService()->retryRecipient($mailshotId, $recipientKey);
|
||||
});
|
||||
}
|
||||
|
||||
/** @param callable(self,int):array<string,mixed> $action */
|
||||
private function handleRunAction(callable $action): void
|
||||
{
|
||||
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
|
||||
$recipientKey = (string) ($this->wp->requestParam('recipient_key', '') ?? '');
|
||||
$result = $this->runService()->retryRecipient($mailshotId, $recipientKey);
|
||||
$completed = false;
|
||||
$fatalHandled = false;
|
||||
|
||||
register_shutdown_function(function () use (&$completed, &$fatalHandled, $mailshotId): void {
|
||||
if ($completed || $fatalHandled) {
|
||||
return;
|
||||
}
|
||||
$error = error_get_last();
|
||||
if (!is_array($error) || !in_array((int) ($error['type'] ?? 0), [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
|
||||
return;
|
||||
}
|
||||
$fatalHandled = true;
|
||||
$this->storeRunFailure('Run action failed: ' . (string) ($error['message'] ?? 'Unknown fatal error.'));
|
||||
$this->redirect($mailshotId);
|
||||
});
|
||||
|
||||
try {
|
||||
$result = $action($this, $mailshotId);
|
||||
} catch (\Throwable $e) {
|
||||
$result = ['ok' => false, 'errors' => ['Run action failed: ' . $e->getMessage()]];
|
||||
}
|
||||
|
||||
$completed = true;
|
||||
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
|
||||
$this->redirect($mailshotId);
|
||||
}
|
||||
|
||||
private function storeRunFailure(string $message): void
|
||||
{
|
||||
try {
|
||||
$this->wp->updateOption(self::RESULT_OPTION_KEY, ['ok' => false, 'errors' => [$message]]);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('Mailshots run failure could not be stored: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function redirect(int $mailshotId): void
|
||||
{
|
||||
$url = $this->wp->adminUrl('admin.php?page=feca-mailshots-run&mailshot_id=' . $mailshotId);
|
||||
|
|
|
|||
|
|
@ -264,6 +264,16 @@ final class MailshotRunService
|
|||
|
||||
/** @return array<string, mixed> */
|
||||
public function runMailshot(int $mailshotId): array
|
||||
{
|
||||
try {
|
||||
return $this->runMailshotUnsafe($mailshotId);
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'errors' => ['Run mailshot failed: ' . $e->getMessage()]];
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function runMailshotUnsafe(int $mailshotId): array
|
||||
{
|
||||
$mailshot = $this->mailshots->find($mailshotId);
|
||||
if ($mailshot === null) {
|
||||
|
|
@ -286,6 +296,16 @@ final class MailshotRunService
|
|||
|
||||
/** @param list<string> $selectedRecipientKeys @return array<string, mixed> */
|
||||
public function runMailshotSelected(int $mailshotId, array $selectedRecipientKeys): array
|
||||
{
|
||||
try {
|
||||
return $this->runMailshotSelectedUnsafe($mailshotId, $selectedRecipientKeys);
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'errors' => ['Run selected mailshot failed: ' . $e->getMessage()]];
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<string> $selectedRecipientKeys @return array<string, mixed> */
|
||||
private function runMailshotSelectedUnsafe(int $mailshotId, array $selectedRecipientKeys): array
|
||||
{
|
||||
$selectedRecipientKeys = array_values(array_filter(array_map(static fn($v): string => trim((string) $v), $selectedRecipientKeys), static fn(string $v): bool => $v !== ''));
|
||||
if ($selectedRecipientKeys === []) {
|
||||
|
|
@ -323,6 +343,16 @@ final class MailshotRunService
|
|||
|
||||
/** @return array<string, mixed> */
|
||||
public function retryFailed(int $mailshotId): array
|
||||
{
|
||||
try {
|
||||
return $this->retryFailedUnsafe($mailshotId);
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'errors' => ['Retry failed sends failed: ' . $e->getMessage()]];
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function retryFailedUnsafe(int $mailshotId): array
|
||||
{
|
||||
$creds = $this->credentials->credentials();
|
||||
if ($creds === null) {
|
||||
|
|
@ -351,6 +381,16 @@ final class MailshotRunService
|
|||
|
||||
/** @return array<string, mixed> */
|
||||
public function retryRecipient(int $mailshotId, string $recipientKey): array
|
||||
{
|
||||
try {
|
||||
return $this->retryRecipientUnsafe($mailshotId, $recipientKey);
|
||||
} catch (\Throwable $e) {
|
||||
return ['ok' => false, 'errors' => ['Retry recipient failed: ' . $e->getMessage()]];
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function retryRecipientUnsafe(int $mailshotId, string $recipientKey): array
|
||||
{
|
||||
$recipientKey = trim($recipientKey);
|
||||
if ($recipientKey === '') {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ final class BasicSmtpSender implements SmtpSender
|
|||
private function buildMime(string $from, string $fromName, array $to, array $cc, array $bcc, string $subject, string $htmlBody, ?string $replyTo, array $attachments): string
|
||||
{
|
||||
$headers = [];
|
||||
$fromHeader = $fromName !== '' ? sprintf('%s <%s>', $fromName, $from) : $from;
|
||||
$fromHeader = $fromName !== '' ? sprintf('%s <%s>', $this->encodeHeaderValue($fromName), $from) : $from;
|
||||
$headers[] = 'From: ' . $fromHeader;
|
||||
$headers[] = 'To: ' . implode(', ', $to);
|
||||
if ($cc !== []) {
|
||||
|
|
@ -81,7 +81,7 @@ final class BasicSmtpSender implements SmtpSender
|
|||
if ($replyTo !== null && trim($replyTo) !== '') {
|
||||
$headers[] = 'Reply-To: ' . trim($replyTo);
|
||||
}
|
||||
$headers[] = 'Subject: ' . $subject;
|
||||
$headers[] = 'Subject: ' . $this->encodeHeaderValue($subject);
|
||||
$headers[] = 'MIME-Version: 1.0';
|
||||
if ($attachments === []) {
|
||||
$headers[] = 'Content-Type: text/html; charset=UTF-8';
|
||||
|
|
@ -115,6 +115,39 @@ final class BasicSmtpSender implements SmtpSender
|
|||
return $mime;
|
||||
}
|
||||
|
||||
private function encodeHeaderValue(string $value): string
|
||||
{
|
||||
$value = preg_replace('/[\r\n]+/', ' ', $value);
|
||||
$value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', is_string($value) ? $value : '');
|
||||
$value = trim(preg_replace('/[ \t]+/', ' ', is_string($value) ? $value : '') ?? '');
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$chars = [];
|
||||
if (preg_match_all('/./us', $value, $matches) === 1) {
|
||||
$chars = $matches[0];
|
||||
} else {
|
||||
$chars = str_split($value);
|
||||
}
|
||||
|
||||
$chunks = [];
|
||||
$chunk = '';
|
||||
foreach ($chars as $char) {
|
||||
if ($chunk !== '' && strlen($chunk . $char) > 45) {
|
||||
$chunks[] = $chunk;
|
||||
$chunk = '';
|
||||
}
|
||||
$chunk .= $char;
|
||||
}
|
||||
if ($chunk !== '') {
|
||||
$chunks[] = $chunk;
|
||||
}
|
||||
|
||||
$encoded = array_map(static fn(string $chunk): string => '=?UTF-8?B?' . base64_encode($chunk) . '?=', $chunks);
|
||||
return implode("\r\n ", $encoded);
|
||||
}
|
||||
|
||||
private function encodeBase64Chunked(string $bytes): string
|
||||
{
|
||||
$stream = fopen('php://temp', 'w+b');
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@
|
|||
* [X] "contacts and accounts and issues where fen1-contact and issue(125) and member-or-affiliate-or-parish-council" gives too many (103) - need is_deleted filter generates error: "SQLSTATE[HY000]: General error: 1267 Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation '='"
|
||||
* [X] "contacts and accounts and issues where fen1-contact and issue(125) and member-or-affiliate-or-parish-council and account-has-article-in-issue(125)" validates OK, but preview gives: General error: 1267 Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation '='
|
||||
* [X] issues.CopyDate and issue.PublicationDate render as e.g. "2026-02-23 00:00:00", would prefer to render dates as e.g. "Friday 23 February 2026"
|
||||
* [ ] I made an edit to a mailshot (), with a 9kb message. Save did not close the dialog - but did not show any errors. There was a long pause (long enough to start typing this report) before it appeared to have had an effect (mailshot saved message). But Edit button on Mailshot doesn't appear to do anything now browser shows page still loading. https://fenedge.co.uk is not responding.
|
||||
* [ ] Edit Data Source save DSL="ads and advertisers where issue(125)" takes a very long long time to repond.
|
||||
* [X] I made an edit to a mailshot (), with a 9kb message. Save did not close the dialog - but did not show any errors. There was a long pause (long enough to start typing this report) before it appeared to have had an effect (mailshot saved message). But Edit button on Mailshot doesn't appear to do anything now browser shows page still loading. https://fenedge.co.uk is not responding.
|
||||
* [X] Edit Data Source save DSL="ads and advertisers where issue(125)" takes a very long long time to repond.
|
||||
* [X] Downloaded merged pdf or .zip should be named after maiilshot purpose
|
||||
* [X] Mailshot data sources. Add "Duplicate" button per row.
|
||||
* [X] Select a single row in "review recipients". "Run Mailshot to selected rows" says "Run mailshot to 1 selected rows?". Yes -> error "None of the selected recipient rows exist in the current query result."
|
||||
* [X] Error report from wordpress. - packaging was missing a file
|
||||
* [ ] Subject to "Memory box café generates an error from receiveing yahoo email "subject contains an invalid character". Need to check rules for character set in subject and modify special characters accordingly.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ $plain = $buildMime->invoke(
|
|||
['to@example.org'],
|
||||
[],
|
||||
[],
|
||||
'Subject',
|
||||
"All Saint's café",
|
||||
'<p>Hello</p>',
|
||||
null,
|
||||
[]
|
||||
|
|
@ -28,6 +28,18 @@ if (!is_string($plain) || strpos($plain, 'Content-Type: text/html; charset=UTF-8
|
|||
fwrite(STDERR, "Plain MIME rendering missing HTML content-type header\n");
|
||||
exit(1);
|
||||
}
|
||||
if (strpos($plain, "Subject: All Saint's café") !== false) {
|
||||
fwrite(STDERR, "Subject header should not contain raw UTF-8/punctuation text\n");
|
||||
exit(1);
|
||||
}
|
||||
if (strpos($plain, 'Subject: =?UTF-8?B?QWxsIFNhaW50J3MgY2Fmw6k=?=') === false) {
|
||||
fwrite(STDERR, "Subject header missing expected RFC 2047 encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
if (strpos($plain, 'From: =?UTF-8?B?RnJvbSBOYW1l?= <from@example.org>') === false) {
|
||||
fwrite(STDERR, "From display name missing expected RFC 2047 encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$bytes = random_bytes(256 * 1024);
|
||||
$mime = $buildMime->invoke(
|
||||
|
|
@ -61,4 +73,3 @@ if (strpos($mime, base64_encode(substr($bytes, 0, 24))) === false) {
|
|||
}
|
||||
|
||||
echo "BasicSmtpSender MIME regression test passed\n";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue