From 71c5f8d7738602adcf93ac6d6b5ab8bb5d12e205 Mon Sep 17 00:00:00 2001
From: Philip Rogge
Date: Fri, 5 Nov 2021 08:52:24 +0100
Subject: [PATCH 1/4] Added possibility for CC and BCC
---
README.md | 2 +
...20211105000000_AddCCAndBccToEmailQueue.php | 43 +++++++++++++++++++
src/Database/Type/JsonType.php | 35 ++++++++++-----
src/Database/Type/SerializeType.php | 7 ++-
src/EmailQueue.php | 3 +-
src/Model/Table/EmailQueueTable.php | 6 +++
src/Shell/PreviewShell.php | 10 +++++
src/Shell/SenderShell.php | 10 +++++
tests/Fixture/EmailQueueFixture.php | 2 +
tests/TestCase/Model/Table/EmailQueueTest.php | 26 +++++++++++
10 files changed, 132 insertions(+), 12 deletions(-)
create mode 100644 config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php
diff --git a/README.md b/README.md
index 33552d2..b262fa3 100755
--- a/README.md
+++ b/README.md
@@ -62,6 +62,8 @@ and queue a new one by storing the correct data:
email template
- Third arguments is an array of options, possible options are
* `subject`: Email's subject
+ * `cc`: Email's carbon copy (string with email, Array with email as key, name as value or email as value (without name))
+ * `bcc`: Email's blind carbon copy (string with email, Array with email as key, name as value or email as value (without name))
* `send_at`: date time sting representing the time this email should be sent at (in UTC)
* `template`: the name of the element to use as template for the email message. (maximum supported length is 100 chars)
* `layout`: the name of the layout to be used to wrap email message
diff --git a/config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php b/config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php
new file mode 100644
index 0000000..f0c7827
--- /dev/null
+++ b/config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php
@@ -0,0 +1,43 @@
+table('email_queue');
+ $table->addColumn('cc', 'string', [
+ 'default' => null,
+ 'limit' => 129,
+ 'null' => true,
+ 'after' => 'email'
+ ]);
+ $table->addColumn('bcc', 'string', [
+ 'default' => null,
+ 'limit' => 129,
+ 'null' => true,
+ 'after' => 'cc'
+ ]);
+ $table->addIndex([
+ 'cc',
+ ], [
+ 'name' => 'BY_CC',
+ 'unique' => false,
+ ]);
+ $table->addIndex([
+ 'bcc',
+ ], [
+ 'name' => 'BY_BCC',
+ 'unique' => false,
+ ]);
+ $table->update();
+ }
+}
diff --git a/src/Database/Type/JsonType.php b/src/Database/Type/JsonType.php
index 09ccc4b..e783401 100755
--- a/src/Database/Type/JsonType.php
+++ b/src/Database/Type/JsonType.php
@@ -18,11 +18,7 @@ class JsonType extends BaseType implements OptionalConvertInterface
*/
public function toPHP($value, DriverInterface $driver)
{
- if ($value === null) {
- return;
- }
-
- return json_decode($value, true);
+ return $this->decodeJson($value);
}
/**
@@ -33,11 +29,7 @@ public function toPHP($value, DriverInterface $driver)
*/
public function marshal($value)
{
- if (is_array($value) || $value === null) {
- return $value;
- }
-
- return json_decode($value, true);
+ return $this->decodeJson($value);
}
/**
@@ -61,4 +53,27 @@ public function requiresToPhpCast(): bool
{
return true;
}
+
+ /**
+ * Returns the given value as an array (if it is json or already an array)
+ * or as a string (if it is already a string)
+ *
+ * @param array|string|null $value json string, array or string to decode
+ * @return array|string|null depending on the input, see description
+ */
+ private function decodeJson($value)
+ {
+ if (is_array($value) || $value === null) {
+ return $value;
+ }
+
+ $jsonDecode = json_decode($value, true);
+
+ // check, if the value is null after json_decode to handle plain strings
+ if ($jsonDecode === null) {
+ return $value;
+ }
+
+ return $jsonDecode;
+ }
}
diff --git a/src/Database/Type/SerializeType.php b/src/Database/Type/SerializeType.php
index 773d785..8a7d5af 100755
--- a/src/Database/Type/SerializeType.php
+++ b/src/Database/Type/SerializeType.php
@@ -22,7 +22,12 @@ public function toPHP($value, DriverInterface $driver)
return null;
}
- return unserialize($value);
+ try {
+ return unserialize($value);
+ } catch (\Exception $e) {
+ // return value, if the value isn't serialized
+ return $value;
+ }
}
/**
diff --git a/src/EmailQueue.php b/src/EmailQueue.php
index 232c97a..b3cd616 100755
--- a/src/EmailQueue.php
+++ b/src/EmailQueue.php
@@ -15,6 +15,8 @@ class EmailQueue
* @param array $options list of options for email sending. Possible keys:
*
* - subject : Email's subject
+ * - cc: array of carbon copy
+ * - bcc: array of blind carbon copy
* - send_at : date time sting representing the time this email should be sent at (in UTC)
* - template : the name of the element to use as template for the email message
* - layout : the name of the layout to be used to wrap email message
@@ -22,7 +24,6 @@ class EmailQueue
* - headers: Key => Value list of extra headers for the email
* - theme: The View Theme to find the email templates
* - config : the name of the email config to be used for sending
- *
* @return bool
*/
public static function enqueue($to, array $data, array $options = [])
diff --git a/src/Model/Table/EmailQueueTable.php b/src/Model/Table/EmailQueueTable.php
index bb408f7..cb58b68 100755
--- a/src/Model/Table/EmailQueueTable.php
+++ b/src/Model/Table/EmailQueueTable.php
@@ -48,6 +48,8 @@ public function initialize(array $config = []): void
* @param array $options list of options for email sending. Possible keys:
*
* - subject : Email's subject
+ * - cc: array of carbon copy
+ * - bcc: array of blind carbon copy
* - send_at : date time sting representing the time this email should be sent at (in UTC)
* - template : the name of the element to use as template for the email message
* - layout : the name of the layout to be used to wrap email message
@@ -66,6 +68,8 @@ public function enqueue($to, array $data, array $options = []): bool
$defaults = [
'subject' => '',
+ 'cc' => '',
+ 'bcc' => '',
'send_at' => new FrozenTime('now'),
'template' => 'default',
'layout' => 'default',
@@ -199,6 +203,8 @@ protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaI
$schema->setColumnType('template_vars', $type);
$schema->setColumnType('headers', $type);
$schema->setColumnType('attachments', $type);
+ $schema->setColumnType('cc', $type);
+ $schema->setColumnType('bcc', $type);
return $schema;
}
diff --git a/src/Shell/PreviewShell.php b/src/Shell/PreviewShell.php
index 1ab9db5..e8e519e 100755
--- a/src/Shell/PreviewShell.php
+++ b/src/Shell/PreviewShell.php
@@ -61,6 +61,16 @@ public function preview($e)
$email = new Mailer($configName);
+ // set cc
+ if (!empty($e->cc)) {
+ $email->setCC($e->cc);
+ }
+
+ // set bcc
+ if (!empty($e->bcc)) {
+ $email->setBcc($e->bcc);
+ }
+
if (!empty($e['attachments'])) {
$email->setAttachments($e['attachments']);
}
diff --git a/src/Shell/SenderShell.php b/src/Shell/SenderShell.php
index b415938..5fc0af4 100755
--- a/src/Shell/SenderShell.php
+++ b/src/Shell/SenderShell.php
@@ -115,6 +115,16 @@ public function main(): void
$transport->setConfig(['additionalParameters' => "-f $from"]);
}
+ // set cc
+ if (!empty($e->cc)) {
+ $email->setCC($e->cc);
+ }
+
+ // set bcc
+ if (!empty($e->bcc)) {
+ $email->setBcc($e->bcc);
+ }
+
if (!empty($e->attachments)) {
$email->setAttachments($e->attachments);
}
diff --git a/tests/Fixture/EmailQueueFixture.php b/tests/Fixture/EmailQueueFixture.php
index 62d901c..cd1c11a 100755
--- a/tests/Fixture/EmailQueueFixture.php
+++ b/tests/Fixture/EmailQueueFixture.php
@@ -19,6 +19,8 @@ class EmailQueueFixture extends TestFixture
public $fields = [
'id' => ['type' => 'uuid', 'null' => false],
'email' => ['type' => 'string', 'null' => false, 'default' => null, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'],
+ 'cc' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 129, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'],
+ 'bcc' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 129, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'],
'from_name' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'],
'from_email' => ['type' => 'string', 'null' => true, 'default' => null, 'length' => 100, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'],
'subject' => ['type' => 'string', 'null' => false, 'default' => null, 'length' => 255, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'],
diff --git a/tests/TestCase/Model/Table/EmailQueueTest.php b/tests/TestCase/Model/Table/EmailQueueTest.php
index d85dd98..95cd941 100755
--- a/tests/TestCase/Model/Table/EmailQueueTest.php
+++ b/tests/TestCase/Model/Table/EmailQueueTest.php
@@ -73,6 +73,8 @@ public function testEnqueue()
'error' => null,
'from_name' => null,
'from_email' => null,
+ 'cc' => '',
+ 'bcc' => ''
];
$sendAt = new Time($result['send_at']);
unset($result['id'], $result['created'], $result['modified'], $result['send_at']);
@@ -197,4 +199,28 @@ public function testProxy()
$this->assertEquals('custom', $email['template']);
$this->assertEquals('email', $email['layout']);
}
+
+ /**
+ * Test cc and bcc with string and array
+ */
+ public function testCCAndBcc()
+ {
+ $cc = ['cc@example.com', 'cc2@example.com'];
+ $bcc = 'bcc@example.com';
+
+ $result = EmailQueue::enqueue(
+ 'd@example.com',
+ ['a' => 'c'],
+ ['subject' => 'Hey', 'cc' => $cc, 'bcc' => $bcc, 'template' => 'custom', 'layout' => 'email']
+ );
+ $this->assertTrue($result);
+ $email = $this->EmailQueue->find()
+ ->where(['email' => 'd@example.com'])
+ ->first()
+ ->toArray();
+
+ $this->assertEquals($cc[0], $email['cc'][0]);
+ $this->assertEquals($cc[1], $email['cc'][1]);
+ $this->assertEquals($bcc, $email['bcc']);
+ }
}
From 692c5dd70996c6d24a3a97cee64a5712c169b080 Mon Sep 17 00:00:00 2001
From: Philip Rogge
Date: Fri, 5 Nov 2021 08:53:00 +0100
Subject: [PATCH 2/4] Added possibility for CC and BCC
Fixed bug in PreviewShell
---
src/Shell/PreviewShell.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Shell/PreviewShell.php b/src/Shell/PreviewShell.php
index e8e519e..eb846c7 100755
--- a/src/Shell/PreviewShell.php
+++ b/src/Shell/PreviewShell.php
@@ -62,13 +62,13 @@ public function preview($e)
$email = new Mailer($configName);
// set cc
- if (!empty($e->cc)) {
- $email->setCC($e->cc);
+ if (!empty($e['cc'])) {
+ $email->setCC($e['cc']);
}
// set bcc
- if (!empty($e->bcc)) {
- $email->setBcc($e->bcc);
+ if (!empty($e['bcc'])) {
+ $email->setBcc($e['bcc']);
}
if (!empty($e['attachments'])) {
From b8db22534fcf6492251c739c7e6fa81208065ff9 Mon Sep 17 00:00:00 2001
From: Steve Kirsch
Date: Fri, 24 Mar 2023 00:25:27 +0100
Subject: [PATCH 3/4] implemented commands
---
src/Command/PreviewCommand.php | 131 ++++++
src/Command/SenderCommand.php | 218 ++++++++++
src/Database/Type/SerializeType.php | 111 ++---
src/Model/Table/EmailQueueTable.php | 383 +++++++++---------
tests/TestCase/Command/PreviewCommandTest.php | 50 +++
tests/TestCase/Command/SenderCommandTest.php | 50 +++
6 files changed, 698 insertions(+), 245 deletions(-)
create mode 100644 src/Command/PreviewCommand.php
create mode 100644 src/Command/SenderCommand.php
create mode 100644 tests/TestCase/Command/PreviewCommandTest.php
create mode 100644 tests/TestCase/Command/SenderCommandTest.php
diff --git a/src/Command/PreviewCommand.php b/src/Command/PreviewCommand.php
new file mode 100644
index 0000000..6bbac19
--- /dev/null
+++ b/src/Command/PreviewCommand.php
@@ -0,0 +1,131 @@
+io = &$io;
+ Configure::write('App.baseUrl', '/');
+
+ $conditions = [];
+ if ($args->getArgumentAt(0)) {
+ $conditions['id IN'] = $args->getArgumentAt(0);
+ }
+
+ $emailQueue = TableRegistry::getTableLocator()->get('EmailQueue', ['className' => EmailQueueTable::class]);
+ $emails = $emailQueue->find()->where($conditions)->all()->toList();
+
+ if (!$emails) {
+ $this->io->out('No emails found');
+
+ return;
+ }
+
+ // $this->io->clear();
+ foreach ($emails as $i => $email) {
+ if ($i) {
+ $this->io->ask('Hit a key to continue');
+ // $this->clear();
+ }
+ $this->io->out('Email :' . $email['id']);
+ $this->preview($email);
+ }
+ }
+
+ /**
+ * Preview email
+ *
+ * @param array $e email data
+ * @return void
+ */
+ public function preview($e)
+ {
+ $configName = $e['config'];
+ $template = $e['template'];
+ $layout = $e['layout'];
+ $headers = empty($e['headers']) ? [] : (array)$e['headers'];
+ $theme = empty($e['theme']) ? '' : (string)$e['theme'];
+
+ $email = new Mailer($configName);
+
+ // set cc
+ if (!empty($e['cc'])) {
+ $email->setCC($e['cc']);
+ }
+
+ // set bcc
+ if (!empty($e['bcc'])) {
+ $email->setBcc($e['bcc']);
+ }
+
+ if (!empty($e['attachments'])) {
+ $email->setAttachments(unserialize($e['attachments']));
+ }
+
+ $email->setTransport('Debug')
+ ->setTo($e['email'])
+ ->setSubject($e['subject'])
+ ->setEmailFormat($e['format'])
+ ->addHeaders($headers)
+ ->setMessageId(false)
+ ->setReturnPath($email->getFrom())
+ ->setViewVars(unserialize($e['template_vars']));
+
+ $email->viewBuilder()
+ ->setTheme($theme)
+ ->setTemplate($template)
+ ->setLayout($layout);
+
+ $return = $email->deliver();
+
+ $this->io->out('Content:');
+ $this->io->hr();
+ $this->io->out($return['message']);
+ $this->io->hr();
+ $this->io->out('Headers:');
+ $this->io->hr();
+ $this->io->out($return['headers']);
+ $this->io->hr();
+ $this->io->out('Data:');
+ $this->io->hr();
+ debug($e['template_vars']);
+ $this->io->hr();
+ $this->io->out('');
+ }
+}
diff --git a/src/Command/SenderCommand.php b/src/Command/SenderCommand.php
new file mode 100644
index 0000000..f6dd759
--- /dev/null
+++ b/src/Command/SenderCommand.php
@@ -0,0 +1,218 @@
+setDescription('Sends queued emails in a batch')
+ ->addOption(
+ 'limit',
+ [
+ 'short' => 'l',
+ 'help' => 'How many emails should be sent in this batch?',
+ 'default' => 50,
+ ]
+ )
+ ->addOption(
+ 'template',
+ [
+ 'short' => 't',
+ 'help' => 'Name of the template to be used to render email',
+ 'default' => 'default',
+ ]
+ )
+ ->addOption(
+ 'layout',
+ [
+ 'short' => 'w',
+ 'help' => 'Name of the layout to be used to wrap template',
+ 'default' => 'default',
+ ]
+ )
+ ->addOption(
+ 'stagger',
+ [
+ 'short' => 's',
+ 'help' => 'Seconds to maximum wait randomly before proceeding (useful for parallel executions)',
+ 'default' => false,
+ ]
+ )
+ ->addOption(
+ 'config',
+ [
+ 'short' => 'c',
+ 'help' => 'Name of email settings to use as defined in email.php',
+ 'default' => 'default',
+ ]
+ )
+ /* ->addSubCommand(
+ 'clearLocks',
+ [
+ 'help' => 'Clears all locked emails in the queue, useful for recovering from crashes',
+ ]
+ ) */;
+
+
+ // $this->params = [];
+
+ // $this->params['stagger'] = $parser->
+
+ return $parser;
+ }
+
+ protected function _fillParams(Arguments $args)
+ {
+ $this->params = [
+ 'stagger' => $args->getArgument('stagger'),
+ 'layout' => $args->getArgument('layout'),
+ 'config' => $args->getArgument('config'),
+ 'template' => $args->getArgument('template'),
+ 'limit' => $args->getArgument('limit'),
+ ];
+ }
+
+ /**
+ * Implement this method with your command's logic.
+ *
+ * @param \Cake\Console\Arguments $args The command arguments.
+ * @param \Cake\Console\ConsoleIo $io The console io
+ * @return null|void|int The exit code or null for success
+ */
+ public function execute(Arguments $args, ConsoleIo $io)
+ {
+ $this->_fillParams($args);
+ $this->io = &$io;
+ if ($this->params['stagger']) {
+ sleep(random_int(0, $this->params['stagger']));
+ }
+
+ Configure::write('App.baseUrl', '/');
+ // $emailQueue = TableRegistry::getTableLocator()->get('EmailQueue', ['className' => EmailQueueTable::class]);
+ $emailQueue = $this->fetchTable('EmailQueueTable', [
+ 'className' => EmailQueueTable::class
+ ]);
+ $emails = $emailQueue->getBatch($this->params['limit']);
+
+ $count = count($emails);
+ foreach ($emails as $e) {
+ $configName = $e->config === 'default' ? $this->params['config'] : $e->config;
+ $template = $e->template === 'default' ? $this->params['template'] : $e->template;
+ $layout = $e->layout === 'default' ? $this->params['layout'] : $e->layout;
+ $headers = empty($e->headers) ? [] : (array)$e->headers;
+ $theme = empty($e->theme) ? '' : (string)$e->theme;
+ $viewVars = empty($e->template_vars) ? [] : $e->template_vars;
+ $errorMessage = null;
+
+ try {
+ $email = $this->_newEmail($configName);
+
+ if (!empty($e->from_email) && !empty($e->from_name)) {
+ $email->setFrom($e->from_email, $e->from_name);
+ }
+
+ $transport = $email->getTransport();
+
+ if ($transport && $transport->getConfig('additionalParameters')) {
+ $from = key($email->getFrom());
+ $transport->setConfig(['additionalParameters' => "-f $from"]);
+ }
+
+ // set cc
+ if (!empty($e->cc)) {
+ $email->setCC($e->cc);
+ }
+
+ // set bcc
+ if (!empty($e->bcc)) {
+ $email->setBcc($e->bcc);
+ }
+
+ if (!empty($e->attachments)) {
+ $email->setAttachments($e->attachments);
+ }
+
+ $sent = $email
+ ->setTo($e->email)
+ ->setSubject($e->subject)
+ ->setEmailFormat($e->format)
+ ->addHeaders($headers)
+ ->setViewVars($viewVars)
+ ->setMessageId(false)
+ ->setReturnPath($email->getFrom());
+
+ $email->viewBuilder()
+ ->setLayout($layout)
+ ->setTheme($theme)
+ ->setTemplate($template);
+
+ $email->deliver();
+ } catch (SocketException $exception) {
+ $this->io->error($exception->getMessage());
+ $errorMessage = $exception->getMessage();
+ $sent = false;
+ }
+
+ if ($sent) {
+ $emailQueue->success($e->id);
+ $this->io->out('Email ' . $e->id . ' was sent');
+ } else {
+ $emailQueue->fail($e->id, $errorMessage);
+ $this->io->out('Email ' . $e->id . ' was not sent');
+ }
+ }
+ if ($count > 0) {
+ $locks = collection($emails)->extract('id')->toList();
+ $emailQueue->releaseLocks($locks);
+ }
+ }
+
+ /**
+ * Clears all locked emails in the queue, useful for recovering from crashes.
+ *
+ * @return void
+ */
+ public function clearLocks(): void
+ {
+ TableRegistry::getTableLocator()
+ ->get('EmailQueue', ['className' => EmailQueueTable::class])
+ ->clearLocks();
+ }
+
+ /**
+ * Returns a new instance of CakeEmail.
+ *
+ * @param array|string $config array of configs, or string to load configs from app.php
+ * @return \Cake\Mailer\Mailer
+ */
+ protected function _newEmail($config): Mailer
+ {
+ return new Mailer($config);
+ }
+}
diff --git a/src/Database/Type/SerializeType.php b/src/Database/Type/SerializeType.php
index 8a7d5af..81b409f 100755
--- a/src/Database/Type/SerializeType.php
+++ b/src/Database/Type/SerializeType.php
@@ -1,4 +1,5 @@
__toString();
- }
+ if (is_object($value) && method_exists($value, '__toString')) {
+ return $value->__toString();
+ }
- return serialize($value);
- }
+ return serialize($value);
+ }
- /**
- * Marshal - Return the value as is
- *
- * @param mixed $value php object
- * @return mixed|null|string
- */
- public function marshal($value)
- {
- return $value;
- }
+ /**
+ * Marshal - Return the value as is
+ *
+ * @param mixed $value php object
+ * @return mixed|null|string
+ */
+ public function marshal($value)
+ {
+ return $value;
+ }
- /**
- * Returns whether the cast to PHP is required to be invoked
- *
- * @return bool always true
- */
- public function requiresToPhpCast(): bool
- {
- return true;
- }
+ /**
+ * Returns whether the cast to PHP is required to be invoked
+ *
+ * @return bool always true
+ */
+ public function requiresToPhpCast(): bool
+ {
+ return true;
+ }
}
diff --git a/src/Model/Table/EmailQueueTable.php b/src/Model/Table/EmailQueueTable.php
index cb58b68..555c79f 100755
--- a/src/Model/Table/EmailQueueTable.php
+++ b/src/Model/Table/EmailQueueTable.php
@@ -1,4 +1,5 @@
addBehavior(
- 'Timestamp',
- [
- 'events' => [
- 'Model.beforeSave' => [
- 'created' => 'new',
- 'modified' => 'always',
- ],
- ],
- ]
- );
- }
-
- /**
- * Stores a new email message in the queue.
- *
- * @param mixed $to email or array of emails as recipients
- * @param array $data associative array of variables to be passed to the email template
- * @param array $options list of options for email sending. Possible keys:
- *
- * - subject : Email's subject
- * - cc: array of carbon copy
- * - bcc: array of blind carbon copy
- * - send_at : date time sting representing the time this email should be sent at (in UTC)
- * - template : the name of the element to use as template for the email message
- * - layout : the name of the layout to be used to wrap email message
- * - format: Type of template to use (html, text or both)
- * - config : the name of the email config to be used for sending
- *
- * @throws \Exception any exception raised in transactional callback
- * @throws \LengthException If `template` option length is greater than maximum allowed length
- * @return bool
- */
- public function enqueue($to, array $data, array $options = []): bool
- {
- if (array_key_exists('template', $options) && strlen($options['template']) > self::MAX_TEMPLATE_LENGTH) {
- throw new LengthException('`template` length must be less or equal to ' . self::MAX_TEMPLATE_LENGTH);
- }
-
- $defaults = [
- 'subject' => '',
- 'cc' => '',
- 'bcc' => '',
- 'send_at' => new FrozenTime('now'),
- 'template' => 'default',
- 'layout' => 'default',
- 'theme' => '',
- 'format' => 'both',
- 'headers' => [],
- 'template_vars' => $data,
- 'config' => 'default',
- 'attachments' => [],
- ];
-
- $email = $options + $defaults;
- if (!is_array($to)) {
- $to = [$to];
- }
-
- $emails = [];
- foreach ($to as $t) {
- $emails[] = ['email' => $t] + $email;
- }
-
- $emails = $this->newEntities($emails);
-
- return $this->getConnection()->transactional(function () use ($emails) {
- $failure = collection($emails)
- ->map(function ($email) {
- return $this->save($email);
- })
- ->contains(false);
-
- return !$failure;
- });
- }
-
- /**
- * Returns a list of queued emails that needs to be sent.
- *
- * @param int|string $size number of unset emails to return
- * @throws \Exception any exception raised in transactional callback
- * @return array list of unsent emails
- */
- public function getBatch($size = 10): array
- {
- return $this->getConnection()->transactional(function () use ($size) {
- $emails = $this->find()
- ->where([
- $this->aliasField('sent') => false,
- $this->aliasField('send_tries') . ' <=' => 3,
- $this->aliasField('send_at') . ' <=' => new FrozenTime('now'),
- $this->aliasField('locked') => false,
- ])
- ->limit($size)
- ->order([$this->aliasField('created') => 'ASC']);
-
- $emails
- ->extract('id')
- ->through(function (\Cake\Collection\CollectionInterface $ids) {
- if (!$ids->isEmpty()) {
- $this->updateAll(['locked' => true], ['id IN' => $ids->toList()]);
- }
-
- return $ids;
- });
-
- return $emails->toList();
- });
- }
-
- /**
- * Releases locks for all emails in $ids.
- *
- * @param array|\Traversable $ids The email ids to unlock
- *
- * @return void
- */
- public function releaseLocks($ids): void
- {
- $this->updateAll(['locked' => false], ['id IN' => $ids]);
- }
-
- /**
- * Releases locks for all emails in queue, useful for recovering from crashes.
- *
- * @return void
- */
- public function clearLocks(): void
- {
- $this->updateAll(['locked' => false], '1=1');
- }
-
- /**
- * Marks an email from the queue as sent.
- *
- * @param string $id queued email id
- * @return void
- */
- public function success($id): void
- {
- $this->updateAll(['sent' => true], ['id' => $id]);
- }
-
- /**
- * Marks an email from the queue as failed, and increments the number of tries.
- *
- * @param string $id queued email id
- * @param string $error message
- * @return void
- */
- public function fail($id, $error = null): void
- {
- $this->updateAll(
- [
- 'send_tries' => new QueryExpression('send_tries + 1'),
- 'error' => $error,
- ],
- [
- 'id' => $id,
- ]
- );
- }
-
- /**
- * Sets the column type for template_vars and headers to json.
- *
- * @param \Cake\Database\Schema\TableSchemaInterface $schema The table description
- * @return \Cake\Database\Schema\TableSchema
- */
- protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface
- {
- $type = Configure::read('EmailQueue.serialization_type') ?: 'email_queue.serialize';
- $schema->setColumnType('template_vars', $type);
- $schema->setColumnType('headers', $type);
- $schema->setColumnType('attachments', $type);
- $schema->setColumnType('cc', $type);
- $schema->setColumnType('bcc', $type);
-
- return $schema;
- }
+ public const MAX_TEMPLATE_LENGTH = 100;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array $config = []): void
+ {
+ TypeFactory::map('email_queue.json', JsonType::class);
+ TypeFactory::map('email_queue.serialize', SerializeType::class);
+ $this->addBehavior(
+ 'Timestamp',
+ [
+ 'events' => [
+ 'Model.beforeSave' => [
+ 'created' => 'new',
+ 'modified' => 'always',
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Stores a new email message in the queue.
+ *
+ * @param mixed $to email or array of emails as recipients
+ * @param array $data associative array of variables to be passed to the email template
+ * @param array $options list of options for email sending. Possible keys:
+ *
+ * - subject : Email's subject
+ * - cc: array of carbon copy
+ * - bcc: array of blind carbon copy
+ * - send_at : date time sting representing the time this email should be sent at (in UTC)
+ * - template : the name of the element to use as template for the email message
+ * - layout : the name of the layout to be used to wrap email message
+ * - format: Type of template to use (html, text or both)
+ * - config : the name of the email config to be used for sending
+ *
+ * @throws \Exception any exception raised in transactional callback
+ * @throws \LengthException If `template` option length is greater than maximum allowed length
+ * @return bool
+ */
+ public function enqueue($to, array $data, array $options = []): bool
+ {
+ if (array_key_exists('template', $options) && strlen($options['template']) > self::MAX_TEMPLATE_LENGTH) {
+ throw new LengthException('`template` length must be less or equal to ' . self::MAX_TEMPLATE_LENGTH);
+ }
+
+ $defaults = [
+ 'subject' => '',
+ 'cc' => '',
+ 'bcc' => '',
+ 'send_at' => new FrozenTime('now'),
+ 'template' => 'default',
+ 'layout' => 'default',
+ 'theme' => '',
+ 'format' => 'both',
+ 'headers' => [],
+ 'template_vars' => $data,
+ 'config' => 'default',
+ 'attachments' => [],
+ ];
+
+ $email = $options + $defaults;
+ if (!is_array($to)) {
+ $to = [$to];
+ }
+
+ $emails = [];
+ foreach ($to as $t) {
+ $emails[] = ['email' => $t] + $email;
+ }
+
+ $emails = $this->newEntities($emails);
+
+ return $this->getConnection()->transactional(function () use ($emails) {
+ $failure = collection($emails)
+ ->map(function ($email) {
+ return $this->save($email);
+ })
+ ->contains(false);
+
+ return !$failure;
+ });
+ }
+
+ /**
+ * Returns a list of queued emails that needs to be sent.
+ *
+ * @param int|string $size number of unset emails to return
+ * @throws \Exception any exception raised in transactional callback
+ * @return array list of unsent emails
+ */
+ public function getBatch($size = 10): array
+ {
+ return $this->getConnection()->transactional(function () use ($size) {
+ $emails = $this->find()
+ ->where([
+ $this->aliasField('sent') => false,
+ $this->aliasField('send_tries') . ' <=' => 3,
+ $this->aliasField('send_at') . ' <=' => new FrozenTime('now'),
+ $this->aliasField('locked') => false,
+ ])
+ ->limit($size)
+ ->order([$this->aliasField('created') => 'ASC'])
+ ->all();
+
+ $emails
+ ->extract('id')
+ ->through(function (\Cake\Collection\CollectionInterface $ids) {
+ if (!$ids->isEmpty()) {
+ $this->updateAll(['locked' => true], ['id IN' => $ids->toList()]);
+ }
+
+ return $ids;
+ });
+
+ return $emails->toList();
+ });
+ }
+
+ /**
+ * Releases locks for all emails in $ids.
+ *
+ * @param array|\Traversable $ids The email ids to unlock
+ *
+ * @return void
+ */
+ public function releaseLocks($ids): void
+ {
+ $this->updateAll(['locked' => false], ['id IN' => $ids]);
+ }
+
+ /**
+ * Releases locks for all emails in queue, useful for recovering from crashes.
+ *
+ * @return void
+ */
+ public function clearLocks(): void
+ {
+ $this->updateAll(['locked' => false], '1=1');
+ }
+
+ /**
+ * Marks an email from the queue as sent.
+ *
+ * @param string $id queued email id
+ * @return void
+ */
+ public function success($id): void
+ {
+ $this->updateAll(['sent' => true], ['id' => $id]);
+ }
+
+ /**
+ * Marks an email from the queue as failed, and increments the number of tries.
+ *
+ * @param string $id queued email id
+ * @param string $error message
+ * @return void
+ */
+ public function fail($id, $error = null): void
+ {
+ $this->updateAll(
+ [
+ 'send_tries' => new QueryExpression('send_tries + 1'),
+ 'error' => $error,
+ ],
+ [
+ 'id' => $id,
+ ]
+ );
+ }
+
+ /**
+ * Sets the column type for template_vars and headers to json.
+ *
+ * @param \Cake\Database\Schema\TableSchemaInterface $schema The table description
+ * @return \Cake\Database\Schema\TableSchema
+ */
+ protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface
+ {
+ $type = Configure::read('EmailQueue.serialization_type') ?: 'email_queue.serialize';
+ $schema->setColumnType('template_vars', $type);
+ $schema->setColumnType('headers', $type);
+ $schema->setColumnType('attachments', $type);
+ $schema->setColumnType('cc', $type);
+ $schema->setColumnType('bcc', $type);
+
+ return $schema;
+ }
}
diff --git a/tests/TestCase/Command/PreviewCommandTest.php b/tests/TestCase/Command/PreviewCommandTest.php
new file mode 100644
index 0000000..aa6c0d0
--- /dev/null
+++ b/tests/TestCase/Command/PreviewCommandTest.php
@@ -0,0 +1,50 @@
+useCommandRunner();
+ }
+ /**
+ * Test buildOptionParser method
+ *
+ * @return void
+ * @uses \EmailQueue\Command\PreviewCommand::buildOptionParser()
+ */
+ public function testBuildOptionParser(): void
+ {
+ $this->markTestIncomplete('Not implemented yet.');
+ }
+
+ /**
+ * Test execute method
+ *
+ * @return void
+ * @uses \EmailQueue\Command\PreviewCommand::execute()
+ */
+ public function testExecute(): void
+ {
+ $this->markTestIncomplete('Not implemented yet.');
+ }
+}
diff --git a/tests/TestCase/Command/SenderCommandTest.php b/tests/TestCase/Command/SenderCommandTest.php
new file mode 100644
index 0000000..242d891
--- /dev/null
+++ b/tests/TestCase/Command/SenderCommandTest.php
@@ -0,0 +1,50 @@
+useCommandRunner();
+ }
+ /**
+ * Test buildOptionParser method
+ *
+ * @return void
+ * @uses \EmailQueue\Command\SenderCommand::buildOptionParser()
+ */
+ public function testBuildOptionParser(): void
+ {
+ $this->markTestIncomplete('Not implemented yet.');
+ }
+
+ /**
+ * Test execute method
+ *
+ * @return void
+ * @uses \EmailQueue\Command\SenderCommand::execute()
+ */
+ public function testExecute(): void
+ {
+ $this->markTestIncomplete('Not implemented yet.');
+ }
+}
From 15d8c68179821a05f469955131b4a5f98c68ef33 Mon Sep 17 00:00:00 2001
From: Steve Kirsch
Date: Fri, 24 Mar 2023 00:25:53 +0100
Subject: [PATCH 4/4] changed file perms
---
LICENSE | 0
config/Migrations/20181120010607_add_error_message.php | 0
.../Migrations/20190610024410_AlterTemplateVarsToEmailQueue.php | 0
config/Migrations/20190814000000_AlterTemplateToEmailQueue.php | 0
config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php | 0
src/Command/PreviewCommand.php | 0
src/Command/SenderCommand.php | 0
tests/TestCase/Command/PreviewCommandTest.php | 0
tests/TestCase/Command/SenderCommandTest.php | 0
tests/test_app/src/Mailer/TestMailer.php | 0
10 files changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 LICENSE
mode change 100644 => 100755 config/Migrations/20181120010607_add_error_message.php
mode change 100644 => 100755 config/Migrations/20190610024410_AlterTemplateVarsToEmailQueue.php
mode change 100644 => 100755 config/Migrations/20190814000000_AlterTemplateToEmailQueue.php
mode change 100644 => 100755 config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php
mode change 100644 => 100755 src/Command/PreviewCommand.php
mode change 100644 => 100755 src/Command/SenderCommand.php
mode change 100644 => 100755 tests/TestCase/Command/PreviewCommandTest.php
mode change 100644 => 100755 tests/TestCase/Command/SenderCommandTest.php
mode change 100644 => 100755 tests/test_app/src/Mailer/TestMailer.php
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/config/Migrations/20181120010607_add_error_message.php b/config/Migrations/20181120010607_add_error_message.php
old mode 100644
new mode 100755
diff --git a/config/Migrations/20190610024410_AlterTemplateVarsToEmailQueue.php b/config/Migrations/20190610024410_AlterTemplateVarsToEmailQueue.php
old mode 100644
new mode 100755
diff --git a/config/Migrations/20190814000000_AlterTemplateToEmailQueue.php b/config/Migrations/20190814000000_AlterTemplateToEmailQueue.php
old mode 100644
new mode 100755
diff --git a/config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php b/config/Migrations/20211105000000_AddCCAndBccToEmailQueue.php
old mode 100644
new mode 100755
diff --git a/src/Command/PreviewCommand.php b/src/Command/PreviewCommand.php
old mode 100644
new mode 100755
diff --git a/src/Command/SenderCommand.php b/src/Command/SenderCommand.php
old mode 100644
new mode 100755
diff --git a/tests/TestCase/Command/PreviewCommandTest.php b/tests/TestCase/Command/PreviewCommandTest.php
old mode 100644
new mode 100755
diff --git a/tests/TestCase/Command/SenderCommandTest.php b/tests/TestCase/Command/SenderCommandTest.php
old mode 100644
new mode 100755
diff --git a/tests/test_app/src/Mailer/TestMailer.php b/tests/test_app/src/Mailer/TestMailer.php
old mode 100644
new mode 100755