diff --git a/app/Console/Commands/SendEmailsCommand.php b/app/Console/Commands/SendEmailsCommand.php
new file mode 100644
index 0000000..153a7eb
--- /dev/null
+++ b/app/Console/Commands/SendEmailsCommand.php
@@ -0,0 +1,141 @@
+output->error('E-mail queueing is not enabled. E-mails are being sent immediately.');
+ }
+
+ $this->output->writeln('E-mail queue runner started');
+
+ while (true)
+ {
+ $emailsToSend = EmailLog::where([
+ ['sent_at', null],
+ ['number_attempts', '<', self::MAX_NUMBER_ATTEMPTS]
+ ])->limit(self::MAX_EMAILS_PER_BATCH)->get();
+
+ $this->output->writeln(sprintf(
+ '%d e-mail%s to send',
+ $emailsToSend->count(),
+ $emailsToSend->count() == 1 ? '' : 's'
+ ));
+
+ /** @var EmailLog $emailToSend */
+ foreach ($emailsToSend as $emailToSend)
+ {
+ $this->sendEmail($emailToSend);
+ }
+
+ if (!$this->option('poll'))
+ {
+ exit();
+ }
+
+ sleep(self::SECONDS_TO_SLEEP);
+ }
+ }
+
+ private function sendEmail(EmailLog $emailLog)
+ {
+ $this->output->writeln(sprintf('Sending message with subject \'%s\'', $emailLog->subject));
+
+ try
+ {
+ app('mailer')->send(
+ [],
+ [],
+ function (Message $message) use ($emailLog)
+ {
+ $message->setFrom($emailLog->sender_address, $emailLog->sender_name);
+ $message->setSubject($emailLog->subject);
+
+ $this->addAddresses($emailLog->to_addresses, $message, 'To');
+ $this->addAddresses($emailLog->cc_addresses, $message, 'Cc');
+ $this->addAddresses($emailLog->bcc_addresses, $message, 'Bcc');
+
+ $message->addPart($emailLog->body_plain, 'text/plain');
+ $message->setBody($emailLog->body_html, 'text/html');
+ }
+ );
+
+ $emailLog->sent_at = new \DateTime();
+
+ $this->output->writeln('Send completed');
+ }
+ catch (\Exception $ex)
+ {
+ $this->output->error(sprintf('Send failed: %s', $ex->getMessage()));
+ }
+ finally
+ {
+ $emailLog->number_attempts++;
+ $emailLog->save();
+ }
+ }
+
+ private function addAddresses($dbFieldData, Message $message, $property)
+ {
+ $decoded = json_decode($dbFieldData);
+
+ if (is_array($decoded))
+ {
+ foreach ($decoded as $addressInfo)
+ {
+ $this->output->writeln(sprintf('Adding %s address: \'"%s" <%s>\'', $property, $addressInfo->name, $addressInfo->address));
+
+ $message->{"set{$property}"}($addressInfo->address, $addressInfo->name);
+ }
+ }
+ }
+}
diff --git a/app/EmailLog.php b/app/EmailLog.php
new file mode 100644
index 0000000..fb2c9c6
--- /dev/null
+++ b/app/EmailLog.php
@@ -0,0 +1,26 @@
+getSwiftMailer();
+
+ /** @var \Swift_SmtpTransport $transport */
+ $transport = $swiftMailer->getTransport();
+ $transport->setHost(UserConfig::get('smtp_server'));
+ $transport->setPort(intval(UserConfig::get('smtp_port')));
+
+ $username = UserConfig::get('smtp_username');
+ if (!is_null($username))
+ {
+ $transport->setUsername($username);
+ }
+
+ $password = UserConfig::get('smtp_password');
+ if (!is_null($password))
+ {
+ try
+ {
+ $transport->setPassword(decrypt($password));
+ }
+ catch (DecryptException $ex)
+ {
+ // Unable to decrypt the password - presumably the app's key has changed
+ }
+ }
+
+ if (UserConfig::get('smtp_encryption'))
+ {
+ $transport->setEncryption('tls');
+ }
+
+ $mailer->alwaysFrom(UserConfig::get('sender_address'), UserConfig::get('sender_name'));
+ }
+
/**
* Create a new middleware instance.
*
@@ -170,42 +208,4 @@ class GlobalConfiguration
View::share('app_version', $version);
View::share('app_version_url', $version);
}
-
- private function updateMailConfig()
- {
- /** @var Mailer $mailer */
- $mailer = $this->app->mailer;
- $swiftMailer = $mailer->getSwiftMailer();
-
- /** @var \Swift_SmtpTransport $transport */
- $transport = $swiftMailer->getTransport();
- $transport->setHost(UserConfig::get('smtp_server'));
- $transport->setPort(intval(UserConfig::get('smtp_port')));
-
- $username = UserConfig::get('smtp_username');
- if (!is_null($username))
- {
- $transport->setUsername($username);
- }
-
- $password = UserConfig::get('smtp_password');
- if (!is_null($password))
- {
- try
- {
- $transport->setPassword(decrypt($password));
- }
- catch (DecryptException $ex)
- {
- // Unable to decrypt the password - presumably the app's key has changed
- }
- }
-
- if (UserConfig::get('smtp_encryption'))
- {
- $transport->setEncryption('tls');
- }
-
- $mailer->alwaysFrom(UserConfig::get('sender_address'), UserConfig::get('sender_name'));
- }
}
\ No newline at end of file
diff --git a/app/Mail/MailableBase.php b/app/Mail/MailableBase.php
new file mode 100644
index 0000000..df85fb0
--- /dev/null
+++ b/app/Mail/MailableBase.php
@@ -0,0 +1,41 @@
+build();
+
+ // Get the current user for the ID
+ $currentUser = Auth::user();
+
+ // Build the body so we can use it as a string
+ $bodies = $this->buildView();
+
+ /** @var HtmlString $html */
+ $html = $bodies['html'];
+
+ /** @var HtmlString $text */
+ $text = $bodies['text'];
+
+ return new EmailLog([
+ 'sender_user_id' => !is_null($currentUser) ? $currentUser->id : null,
+ 'sender_name' => $this->from[0]['name'],
+ 'sender_address' => $this->from[0]['address'],
+ 'to_addresses' => json_encode($this->to),
+ 'cc_addresses' => json_encode($this->cc),
+ 'bcc_addresses' => json_encode($this->bcc),
+ 'subject' => $this->subject,
+ 'body_plain' => $text->toHtml(),
+ 'body_html' => $html->toHtml()
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/app/Mail/ResetMyPassword.php b/app/Mail/ResetMyPassword.php
new file mode 100644
index 0000000..2624434
--- /dev/null
+++ b/app/Mail/ResetMyPassword.php
@@ -0,0 +1,48 @@
+user = $user;
+ $this->token = $token;
+ }
+
+ /**
+ * Build the message.
+ *
+ * @return $this
+ */
+ public function build()
+ {
+ $subject = trans('email.reset_my_password_subject', ['app_name' => UserConfig::get('app_name')]);
+
+ return $this
+ ->subject($subject)
+ ->markdown(Theme::viewName('email.reset_my_password'))
+ ->with([
+ 'subject' => $subject,
+ 'token' => $this->token,
+ 'user' => $this->user
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/app/Notifications/EmailDatabaseWriterChannelBase.php b/app/Notifications/EmailDatabaseWriterChannelBase.php
new file mode 100644
index 0000000..1d9c8d5
--- /dev/null
+++ b/app/Notifications/EmailDatabaseWriterChannelBase.php
@@ -0,0 +1,22 @@
+queued_at = new \DateTime();
+ }
+ else
+ {
+ $logEntry->sent_at = new \DateTime();
+ }
+
+ $logEntry->save();
+ }
+}
\ No newline at end of file
diff --git a/app/Notifications/QueueEmailDatabaseChannel.php b/app/Notifications/QueueEmailDatabaseChannel.php
new file mode 100644
index 0000000..b4ae877
--- /dev/null
+++ b/app/Notifications/QueueEmailDatabaseChannel.php
@@ -0,0 +1,24 @@
+toQueueEmailDatabase($notifiable);
+
+ $this->writeToTable($logEntry, true);
+ }
+}
\ No newline at end of file
diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php
index 68f7ecf..8a4e100 100644
--- a/app/Notifications/ResetPassword.php
+++ b/app/Notifications/ResetPassword.php
@@ -2,7 +2,11 @@
namespace App\Notifications;
+use App\Facade\UserConfig;
+use App\Mail\MailableBase;
+use App\Mail\ResetMyPassword;
use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -37,33 +41,40 @@ class ResetPassword extends Notification
*/
public function via($notifiable)
{
- return ['mail'];
+ $drivers = [];
+
+ if (UserConfig::get('queue_emails'))
+ {
+ $drivers[] = QueueEmailDatabaseChannel::class;
+ }
+ else
+ {
+ $drivers[] = 'mail';
+ $drivers[] = SentEmailDatabaseChannel::class;
+ }
+
+ return $drivers;
+ }
+
+ public function toQueueEmailDatabase($notifiable)
+ {
+ return $this->toMail($notifiable)->buildEmailLog();
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
- * @return \Illuminate\Notifications\Messages\MailMessage
+ * @return \Illuminate\Notifications\Messages\MailMessage|MailableBase
*/
public function toMail($notifiable)
{
- return (new MailMessage)
- ->line('You are receiving this email because we received a password reset request for your account.')
- ->action('Reset Password', route('password.reset', $this->token, true))
- ->line('If you did not request a password reset, no further action is required.');
- }
+ $notification = new ResetMyPassword($notifiable, $this->token);
- /**
- * Get the array representation of the notification.
- *
- * @param mixed $notifiable
- * @return array
- */
- public function toArray($notifiable)
- {
- return [
- //
- ];
+ // Set to and from properties accordingly
+ $notification->from(UserConfig::get('sender_address'), UserConfig::get('sender_name'));
+ $notification->to($notifiable->email, $notifiable->name);
+
+ return $notification;
}
}
diff --git a/app/Notifications/SentEmailDatabaseChannel.php b/app/Notifications/SentEmailDatabaseChannel.php
new file mode 100644
index 0000000..851cb15
--- /dev/null
+++ b/app/Notifications/SentEmailDatabaseChannel.php
@@ -0,0 +1,24 @@
+toDatabaseWriter($notifiable);
+
+ $this->writeToTable($logEntry, false);
+ }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 1de1171..1255b7c 100644
--- a/composer.json
+++ b/composer.json
@@ -6,6 +6,7 @@
"type": "project",
"require": {
"php": ">=7.0.0",
+ "ext-json": "*",
"laravel/framework": "5.5.*",
"rackspace/php-opencloud": "^1.16",
"doctrine/dbal": "^2.5",
diff --git a/database/migrations/2019_07_13_203923_create_jobs_table.php b/database/migrations/2019_07_13_203923_create_jobs_table.php
deleted file mode 100644
index 8533de5..0000000
--- a/database/migrations/2019_07_13_203923_create_jobs_table.php
+++ /dev/null
@@ -1,36 +0,0 @@
-bigIncrements('id');
- $table->string('queue')->index();
- $table->longText('payload');
- $table->unsignedTinyInteger('attempts');
- $table->unsignedInteger('reserved_at')->nullable();
- $table->unsignedInteger('available_at');
- $table->unsignedInteger('created_at');
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('background_jobs');
- }
-}
diff --git a/database/migrations/2019_07_14_100811_create_email_logs_table.php b/database/migrations/2019_07_14_100811_create_email_logs_table.php
new file mode 100644
index 0000000..e02f5b1
--- /dev/null
+++ b/database/migrations/2019_07_14_100811_create_email_logs_table.php
@@ -0,0 +1,47 @@
+bigIncrements('id');
+ $table->unsignedInteger('sender_user_id')->nullable(true);
+ $table->dateTime('queued_at')->nullable(true);
+ $table->dateTime('sent_at')->nullable(true);
+ $table->string('sender_name');
+ $table->string('sender_address');
+ $table->text('to_addresses');
+ $table->text('cc_addresses')->nullable(true);
+ $table->text('bcc_addresses')->nullable(true);
+ $table->string('subject');
+ $table->longText('body_plain');
+ $table->longText('body_html');
+ $table->integer('number_attempts')->default(0);
+ $table->timestamps();
+
+ $table->foreign('sender_user_id')
+ ->references('id')->on('users')
+ ->onDelete('cascade');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('email_logs');
+ }
+}
diff --git a/resources/lang/en/email.php b/resources/lang/en/email.php
index 5842358..b5b87c0 100644
--- a/resources/lang/en/email.php
+++ b/resources/lang/en/email.php
@@ -30,6 +30,10 @@ return [
'photo_comment_replied_to_p1' => 'A reply to your comment has been posted in the :album_name album.',
'photo_comment_replied_to_p2' => 'Click the button below to view the photo\'s page to see the reply to your comment.',
'photo_comment_replied_to_subject' => 'A reply to your comment was posted in :album_name',
+ 'reset_my_password_action' => 'Reset Password',
+ 'reset_my_password_p1' => 'You are receiving this email because we received a password reset request for your account.',
+ 'reset_my_password_p2' => 'If you did not request a password reset, no further action is required.',
+ 'reset_my_password_subject' => 'Your password reset link for :app_name',
'user_self_activated_p1' => 'A new user has been created and activated at :app_name. The user\'s details are shown below.',
'user_self_activated_p2' => 'Name: :user_name',
'user_self_activated_p3' => 'E-mail address: :email_address',
diff --git a/resources/views/themes/base/email/reset_my_password.blade.php b/resources/views/themes/base/email/reset_my_password.blade.php
new file mode 100644
index 0000000..1b3f39b
--- /dev/null
+++ b/resources/views/themes/base/email/reset_my_password.blade.php
@@ -0,0 +1,19 @@
+@component('mail::message')
+@lang('email.generic_intro', ['user_name' => $user->name])
+
+
+@lang('email.reset_my_password_p1')
+
+
+@component('mail::button', ['url' => route('password.reset', $token, true), 'color' => 'blue'])
+ @lang('email.reset_my_password_action')
+@endcomponent
+
+
+@lang('email.reset_my_password_p2')
+
+
+@lang('email.generic_regards')
+{{ UserConfig::get('app_name') }}
+{{ route('home') }}
+@endcomponent
\ No newline at end of file