From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh
---
inc/integrations/services/PostMark.php | 301 +++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 247 insertions(+), 54 deletions(-)
diff --git a/inc/integrations/services/PostMark.php b/inc/integrations/services/PostMark.php
index c8a4956..bba6656 100644
--- a/inc/integrations/services/PostMark.php
+++ b/inc/integrations/services/PostMark.php
@@ -2,9 +2,10 @@
namespace JVBase\integrations\services;
use JVBase\integrations\Admin;
-use JVBase\integrations\Instance;
+use JVBase\Instance;
use JVBase\integrations\Integrations;
use JVBase\integrations\Requests;
+use JVBase\meta\Meta;
use WP_Error;
if (!defined('ABSPATH')) {
@@ -217,64 +218,79 @@
return $this->response(false, 'Rate limit exceeded');
}
$endpoint = str_replace($this->apiBase, '', $endpoint);
- $error = $response['ErrorCode'] ?? false;
- if ($error) {
- switch ($error) {
- case '0':
- //Do nothing, no error found
- break;
- //Bad or missing API Token
- case '10':
- //Multiple errors
- case '11':
- //Resource Not found
- case '12':
- //Invalid Pagination Key
- case '13':
- //Maintenance; PostMark API down for a sec
- case '100':
- //Invalid Email Request
- case '300':
- //Sender Signature not found
- case '400':
- //Sender signature not confirmed
- case '401':
- //Invalid JSON
- case '402':
- //Invalid request field(s) - {FIELD_NAME}
- case '403':
- //Not allowed to send (no more credits in account)
- case '405':
- //Inactive recipient. Recipient hard bounces, or filed spam complaint
- case '406':
- //JSON required
- case '409':
- //Too many requests (500 max)
- case '410':
- //Forbidden Attachment type
- case '411':
- //Account is pending
- case '412':
- //Account may not exist
- case '413':
- //Invalid query string parameter
- case '500':
- default:
- $this->logError(
- 'handleResponse',
- 'Something went wrong: '.$response['Message']??'',
- $response
- );
- return $this->response(false, 'Something went wrong: '.$response['Message']??'');
- }
+ $checkErrors = $this->checkErrors($response);
+ if (!empty($checkErrors)) {
+ return $checkErrors;
}
+
return match ($endpoint) {
'/email' => $this->handleEmailResponse($response, $method),
'/email/bulk' => $this->handleBulkEmailResponse($response, $method),
+ '/email/batch' => $this->handleBatchEmailResponse($response, $method),
default => $this->response(false, 'Unhandled Response for '. $endpoint . ' endpoint, with method: '. $method),
};
}
+ protected function checkErrors(WP_Error|array $response):array|false
+ {
+ $error = $response['ErrorCode'] ?? false;
+ if ($error) {
+ switch ($error) {
+ case '0':
+ //Do nothing, no error found
+ return false;
+ //Bad or missing API Token
+ case '10':
+ //Multiple errors
+ case '11':
+ //Resource Not found
+ case '12':
+ //Invalid Pagination Key
+ case '13':
+ //Maintenance; PostMark API down for a sec
+ case '100':
+ //Maybe schedule a retry?
+ //Invalid Email Request
+ case '300':
+ //Sender Signature not found
+ case '400':
+ //Sender signature not confirmed
+ case '401':
+ //Invalid JSON
+ case '402':
+ //Invalid request field(s) - {FIELD_NAME}
+ case '403':
+ //Not allowed to send (no more credits in account)
+ case '405':
+ //Inactive recipient. Recipient hard bounces, or filed spam complaint
+ case '406':
+ //JSON required
+ case '409':
+ //Too many requests (500 max)
+ case '410':
+ //Maybe schedule a retry?
+ //Forbidden Attachment type
+ case '411':
+ //Account is pending
+ case '412':
+ //Account may not exist
+ case '413':
+ //Some errors in bulk request
+ case '422':
+ //Invalid query string parameter
+ case '500':
+ default:
+ $this->logError(
+ 'handleResponse',
+ 'Something went wrong: '.$response['Message']??'',
+ $response
+ );
+ return $this->response(false, 'Something went wrong: '.$response['Message']??'');
+ }
+ }
+ return false;
+ }
+
/**********************************************************
* UTILITY
**********************************************************/
@@ -379,16 +395,16 @@
$payload = $this->buildPayload($to, $subject, $message, $is_html, $headers, $attachments);
- $response = $this->postRequest(
+ return $this->postRequest(
sprintf('%s/email', $this->apiBase),
$payload
);
}
protected function handleEmailResponse(WP_Error|array $response, string $method):array
{
-
+ return $this->response(true, 'Successfully sent request to PostMark', $response);
}
- protected function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
+ public function buildPayload(string|array $to, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
{
$to = is_array($to) ? implode(',',$to) : $to;
@@ -426,6 +442,8 @@
if (!empty($headers['reply-to'])) {
$payload['ReplyTo'] = $headers['reply-to'];
+ } else {
+ $payload['ReplyTo'] = $this->getFromEmail();
}
if (!empty($attachments)) {
@@ -440,4 +458,179 @@
return $payload;
}
+
+ /**
+ * Sends the same message to multiple emails. Can use templates
+ * @param array<int> $userIDs
+ * @param string $subject
+ * @param string $message
+ * @param bool $is_html
+ * @param array $headers
+ * @param array $attachments
+ * @return array
+ */
+ public function sendMessageToMany(array $userIDs, string $subject, string $message, bool $is_html = true, array $headers = [], array $attachments = []):array
+ {
+ if (!$this->isSetUp()) {
+ return $this->response(false, 'Not set up');
+ }
+
+ $tags = [];
+ $pattern = '/\{\{(.*?)\}\}/';
+
+ if (str_contains($subject, '{{')){
+ preg_match_all($pattern, $subject, $tags);
+ }
+ if (str_contains($message, '{{')) {
+ preg_match_all($pattern, $message, $tags);
+ }
+
+ $tags = array_unique($tags);
+ foreach ($tags as $tag) {
+ $t = implode(
+ array_map(function($word) {
+ return ucfirst($word);
+ }, explode('_', $tag)
+ ));
+ str_replace('{{'.$tag.'}}', '{{'.$t.'}}', $subject);
+ str_replace('{{'.$tag.'}}', '{{'.$t.'}}', $message);
+ }
+
+
+ $to = array_map(function ($userID) use ($tags, $subject, $message) {
+ $meta = Meta::forUser($userID);
+ $email = $meta->get('user_email');
+ $out = [];
+ if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $out['To'] = $email;
+ }
+
+ if (empty($out)) {
+ $this->logError('sendMessageToMany', 'User does not have an email address',['user' => $userID]);
+ return [];
+ }
+
+ $tmp = [];
+ foreach ($tags as $tag) {
+ $v = $meta->get($tag);
+
+ if (!empty($v)) {
+ $tag = implode(
+ array_map(function($word) {
+ return ucfirst($word);
+ }, explode('_', $tag)
+ ));
+ $tmp[$tag] = $v;
+ }
+ }
+ if (!empty($tmp)) {
+ $out['TemplateModel'] = $tmp;
+ }
+ return $out;
+ }, $userIDs);
+ $to = array_filter($to, function($result) {
+ return array_key_exists('To', $result) && !empty($result['To']);
+ });
+
+ if (empty($to)) {
+ $this->logError('sendMessageToMany', 'No users set',['users' => $userIDs]);
+ }
+
+ //Onward!
+ $payload = [
+ 'From' => sprintf(
+ '%s <%s>',
+ $headers['from-name'] ?? $this->getFromName(),
+ $headers['from'] ?? $this->getFromEmail()
+ ),
+ 'Subject' => sanitize_text_field($subject),
+ 'TrackOpens' => $this->getTrackOpens() === 'true',
+ 'TrackLinks' => $this->getTrackLinks(),
+ 'MessageStream' => 'broadcast'
+ ];
+
+ if ($is_html) {
+ $payload['HtmlBody'] = $message;
+ $payload['TextBody'] = $this->textFromHtml($message);
+ } else {
+ $payload['TextBody'] = $message;
+ }
+
+ if (!empty($headers['reply-to'])) {
+ $payload['ReplyTo'] = $headers['reply-to'];
+ } else {
+ $payload['ReplyTo'] = $this->getFromEmail();
+ }
+ if (!empty($attachments)) {
+ $payload['Attachments'] = $this->processAttachments($attachments);
+ }
+ if (!empty($headers['x-headers'])) {
+ $payload['Headers'] = $headers['x-headers'];
+ }
+ $payload['Tag'] = $this->determineEmailTag($subject, $headers);
+
+ $payload['Messages'] = $to;
+
+ return $this->postRequest(
+ sprintf('%s/email/bulk', $this->apiBase),
+ $payload
+ );
+ }
+ protected function handleBulkEmailResponse(WP_Error|array $response, string $method):array
+ {
+ return $this->response($response['Status']??'' === 'Accepted', 'Successfully sent request to Postmark', $response);
+ }
+
+ /**
+ * @param array $emails An array of results from buildPayload, ready to batch send to Postmark. Limit of 500 at a time
+ * @return array
+ */
+ public function sendMany(array $emails):array
+ {
+ if (!$this->isSetUp()) {
+ return $this->response(false, 'Not set up');
+ }
+ $emails = array_filter($emails, function ($email) {
+ if (!array_key_exists('To', $email) || !filter_var($email['To'], FILTER_VALIDATE_EMAIL)) {
+ $this->logError('sendMany', 'Email does not have a To email address', $email);
+ return false;
+ }
+ //This shouldn't happen, but if it does, we'll just remove it for now.
+ if (!array_key_exists('From', $email)) {
+ $this->logError('sendMany', 'Email does not have a From email address', $email);
+ return false;
+ }
+
+ return true;
+ });
+
+ if (empty($emails)) {
+ $this->logError('sendMany', 'No emails have been sent', $emails);
+ return $this->response(false, 'No emails have been sent', $emails);
+ }
+
+ return $this->postRequest(
+ sprintf(
+ '%s/email/batch',
+ $this->apiBase
+ ),
+ $emails
+ );
+ }
+ protected function handleBatchEmailResponse(WP_Error|array $response, string $method):array
+ {
+ $main = $response[0];
+ $checkErrors = $this->checkErrors($main);
+ if (!empty($checkErrors)) {
+ return $checkErrors;
+ }
+ if (count($response) > 1) {
+ $errors = $response;
+ unset($errors[0]);
+ foreach ($errors as $error) {
+ $this->checkErrors($error);
+ }
+ }
+ return $this->response($main['Message']??'' === 'OK', 'Successfully sent request to Postmark', $response);
+ }
}
--
Gitblit v1.10.0