Laravel 5.6 - how to change theme of notification - php

I send out user emails and afterwards an admin report. I want to change the theme of the admin notification.
To do this I defined a custom css template in the vendor/mail/themes directory.
I tried to follow this example although it is for Mailables:
https://laravel-news.com/email-themes
class AdminReport extends Notification
{
use Queueable;
protected $theme = 'adminemail';
But this doesn't change anything the theme.
I also tried to change the theme before the notification is sent and it didn't work:
config([ "mail.markdown.theme" => "adminemail" ]);
Changing the theme does work though when I set the config before I send out the first user notification.
Does anyone know the right way to do this?

As of Laravel v5.3.7 Mailables can also be passed to Notifications. So create a Mailable for your email and then pass the mailable to the toMail() method:
class AdminReport extends Mailable
{
protected $theme = 'my-theme';
...
}
-
class AdminReport extends Notification
{
...
public function toMail($notifiable)
{
return (new App\Mailables\AdminReport)->to($notifiable->email);
}
}

Related

Laravel. Disable observer methods if the database is seeding

I have an observer for my User model. Inside my observer->created event i have some code.
public function created(User $user)
{
sendEmail();
}
So, the idea is, when a user is created, the system will send for the user email notification that the account was created.
Question: When the database is seeding, it also calls this method 'created' and sends users (that are in the seeds) email notification.
So, my question is, how can i check, probably inside this 'created' method if at the moment laravel is seeding data -> do not send email of do not run the 'created' observer method.
Tried to google, found something, but not working correct.
Something like YourModel::flushEventListeners();
You can use YourModel::unsetEventDispatcher(); to remove the event listeners for a model temporary.
If you need them after seeding in the same execution, you can read the dispatchers, unset them and then set them again.
$dispatcher = YourModel::getEventDispatcher();
// Remove Dispatcher
YourModel::unsetEventDispatcher();
// do stuff here
// Re-add Dispatcher
YourModel::setEventDispatcher($dispatcher);
namespace Database\Seeders;
use App\Models\Blog;
use Illuminate\Database\Seeder;
class BlogsTableSeeder extends Seeder
{
public function run()
{
Blog::withoutEvents(function () {
// normally
Blog::factory()
->times(10)
->hasUploads(1) //hasOne
->hasComments(2) //hasMany
->create();
});
}
}
You may mute event with WithoutModelEvents trait
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class SomeSeeder extends Seeder
{
use WithoutModelEvents;
public function run()
{
User::factory( 30 )->create();
}
}
or you may try createQuietly method of a factory, for example
class SomeSeeder extends Seeder
{
public function run()
{
User::factory( 30 )->createQuietly();
}
}
You could use the saveQuietly() function https://laravel.com/docs/8.x/eloquent#saving-a-single-model-without-events
This allows you to disable all events for a single model.
If you wanna disable a single event for a single model, read about it here: http://derekmd.com/2019/02/conditionally-suppressing-laravel-event-listeners/

Laravel Mailable Preview not taking account of the ->theme(...) method call on Mailable

I have two different types of theme/css for my emails that are sent from my app. A default one for system emails (reset password etc) and one for consumer emails (emails as a result of actions form users within the app).
In my consumer emails, in the toMail() method of the mailable/notification I execute the mailable like so:
public function toMail($notifiable)
{
return (new MailMessage)
->theme($this->theme)
->from($this->booking->school->school_email, $this->booking->school->name)
->subject($this->getSubject())
->attachData($this->booking->payments()->first()->toPdf()->output(), 'Invoice.pdf')
->markdown('mail.notifications.bookings.paid_booking', $this->toArray());
}
Notice that I call ->theme(...) on the mailable. This works perfectly fine and the correct theme is set in the template that is received in the mailbox.
When I try to use Laravel's Mailable Preview within a route:
Route::get('/mail/resetpass', function () {
return (new App\Notifications\ResetPasswordNotification('token'))->toMail(\App\User::find(2));
});
Route::get('/mail/reserved', function () {
$booking = \App\Domains\Customers\Models\Booking::find(1);
return (new \App\Domains\Customers\Notifications\ReservedBookingConfirmation($booking))->toMail($booking->customer);
});
The "default" theme, as defined in my config files is the one that is used, and my call to ->theme(...) is ignored.
Is there a solution for this? Changing the config value, isn't a feasible option as I actually want to use this functionality to allow my users to view their emails in the browser. I'm unsure what else to try.
The issue lies in the render() method of the Illuminate\Notifications\Messages\MailMessage class.
This is fixed in Laravel 8 and so this solution only applies if your project is using laravel 7 or below
it does not call the theme. I extended the class and have overridden the render method. Adding the call to ->theme(...) like so.
<?php
namespace App\Mail;
use Illuminate\Container\Container;
use Illuminate\Mail\Markdown;
use Illuminate\Notifications\Messages\MailMessage as Original;
class MailMessage extends Original
{
/**
* Render the mail notification message into an HTML string.
*
* #return string
*/
public function render()
{
if (isset($this->view)) {
return Container::getInstance()->make('mailer')->render(
$this->view, $this->data()
);
}
return Container::getInstance()
->make(Markdown::class)
->theme($this->theme ?? config('mail.markdown.theme')) //add this line here
->render($this->markdown, $this->data());
}
}

Override Laravel From Address on SendsPasswordResetEmails trait

How can I override the forgot password email from address field?
I'm using the SendsPasswordResetEmails trait.
It seems to be using the .env mail-from configuration
here is the trait vendor code SendsPasswordResetEmail Trait
sendResetLinkEmail method seems to be where the magic happens but i cannot determine how to override the mail send from the broker where is this function? sendResetLink
You can just copy the trait its code and past it in the PasswordBroker class to overwrite it if that's what you're asking.
I think you don't have to edit broker() just override sendResetLinkEmail() in your ForgetPasswordController. Then override $request->mail entry.
Anyway, the function you are looking for is at "\vendor\laravel\framework\src\Illuminate\Auth\Passwords\PasswordBroker.php" => sendResetLink()
I think what you are trying to do is set
MAIL_FROM_ADDRESS=sender#example.com
MAIL_FROM_NAME=Sender
in .env file and it should work fine, you do not need to override sendResetLinkEmail method.
There are a two methods I can think of that will achieve what you want.
1) In AppServiceProvider:
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
config()->set('mail.from.address', 'YOUR FROM ADDRESS HERE');
}
2) In Controller.php:
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function __construct()
{
config()->set('mail.from.address', 'YOUR FROM ADDRESS HERE');
}
}
Hope that helps!

How to modify password change mail made with Laravel auth?

In Laravel Framework 5.4.18 I just ran php artisan make:auth
When I request to reset my password, I get an email that says
(...)
You are receiving this email because we received a password reset
request for your account
(...)
Where is the file where it is specified to say that? I want to change it completely.
Notice that here is how to change (only) the general appearance of any notification, and that here is how to change (in addition) the body of the notification.
Thank you very much.
Your User model uses the Illuminate\Auth\Passwords\CanResetPassword trait. This trait has the following function:
public function sendPasswordResetNotification($token)
{
// use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification
$this->notify(new ResetPasswordNotification($token));
}
When a password reset is requested, this method is called and uses the ResetPassword notification to send out the email.
If you would like to modify your reset password email, you can create a new custom Notification and define the sendPasswordResetNotification method on your User model to send your custom Notification. Defining the method directly on the User will take precedence over the method included by the trait.
Create a notification that extends the built in one:
use Illuminate\Auth\Notifications\ResetPassword;
class YourCustomResetPasswordNotification extends ResetPassword
{
/**
* Build the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('This is your custom text above the action button.')
->action('Reset Password', route('password.reset', $this->token))
->line('This is your custom text below the action button.');
}
}
Define the method on your User to use your custom notification:
class User extends Authenticatable
{
public function sendPasswordResetNotification($token)
{
$this->notify(new YourCustomResetPasswordNotification($token));
}
}
First open a terminal and go to the root of the application and run the following command:
php artisan vendor:publish
You will see some files copied, you can find the email template files in
Root_Of_App/resources/views/vendor
You can edit the Email templates for notifications there.

Translate queued mails (localization)

I am looking for a working solution, to translate queued emails in laravel-5.
Unfortunately, all emails use the default locale (defined under app.locale).
Let's assume, we have two emails in the pipeline, one for an English en user and another for an Japanese jp user.
What data should I pass to the Mail facade to translate (localize) the queued emails?
// User model
$user = User:find(1)->first();
Mailer::queue($email, 'Party at Batman\'s cave (Batcave)', 'emails.party-invitation', [
...
'locale' => $user->getLocale(), // value: "jp", but does not work
'lang' => $user->getLocale(), // value: "jp", but does not work
'language' => $user->getLocale(), // value: "jp", but does not work
]);
I have been struggling to get this done in a more efficient way. Currently I have it set up like this. Hopefully this helps someone in the future with this issue:
// Fetch the locale of the receiver.
$user = Auth::user();
$locale = $user->locale;
Mail::queue('emails.welcome.template', ['user' => $user, 'locale' => $locale], function($mail) use ($user, $locale) {
$mail->to($user->email);
$mail->subject(
trans(
'mails.subject_welcome',
[], null, $locale
)
);
});
And use the following in your template:
{{ trans('mails.welcome', ['name' => ucfirst($user['first_name'])], null, $locale) }}
Note: do not forget to restart your queue
If your emails inherits the built-in Illuminate\Mail\Mailable class you can build your own Mailable class that will take care of translations.
Create your own SendQueuedMailable class that inherits from Illuminate\Mail\SendQueuedMailable.
Class constructor will take current app.location from config and remember it in a property (because laravel queues serializes it).
In queue worker it will take the property back from config and set the setting to the current environment.
<?php
namespace App\Mail;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
use Illuminate\Mail\SendQueuedMailable as IlluminateSendQueuedMailable;
class SendQueuedMailable extends IlluminateSendQueuedMailable
{
protected $locale;
public function __construct(MailableContract $mailable)
{
parent::__construct($mailable);
$this->locale = config('app.locale');
}
public function handle(MailerContract $mailer)
{
config(['app.locale' => $this->locale]);
app('translator')->setLocale($this->locale);
parent::handle($mailer);
}
}
Then, create your own Mail class that inherits from Illuminate\Mail\Mailable
<?php
namespace App\Mail;
use Illuminate\Contracts\Queue\Factory as Queue;
use Illuminate\Mail\Mailable as IlluminateMailable;
class Mailable extends IlluminateMailable
{
public function queue(Queue $queue)
{
$connection = property_exists($this, 'connection') ? $this->connection : null;
$queueName = property_exists($this, 'queue') ? $this->queue : null;
return $queue->connection($connection)->pushOn(
$queueName ?: null, new SendQueuedMailable($this)
);
}
}
And, voila, all your queued mailables inherited from App\Mailable class automatically will take care of current locale.
In Laravel 5.6 is a locale function added to Mailable:
$infoMail->locale('jp');
Mail::queue($infoMail);
Laravel 5.7.7 introduced the HasLocalePreference interface to solve this issue.
class User extends Model implements HasLocalePreference
{
public function preferredLocale() { return $this->locale; }
}
Now if you use Mail::to() function, Laravel will send with the correct locale.
Also introduced was the Mail chainable locale() function.
Mail::to($address)->locale($locale)->send(new Email());
Here's a solution that worked for me. In my example, I am processing the booking on a queue, using a dedicated Job class.
When you dispatch a job on a queue, pass the locale, you want your email in. For example:
ProcessBooking::dispatch($booking, \App::getLocale());
Then, in your job class (ProcessBooking in my example) store the locale in a property:
protected $booking;
protected $locale;
public function __construct(Booking $booking, $locale)
{
$this->booking = $booking;
$this->locale = $locale;
}
And in your handle method temporarily switch locales:
public function handle()
{
// store the locale the queue is currently running in
$previousLocale = \App::getLocale();
// change the locale to the one you need for job to run in
\App::setLocale($this->locale);
// Do the stuff you need, e.g. send an email
Mail::to($this->booking->customer->email)->send(new NewBooking($this->booking));
// go back to the original locale
\App::setLocale($previousLocale);
}
Why do we need to do this?
Queued emails get the default locale since queue workers are separate Laravel apps. If you ever ran into a problem, when you cleared your cache, but queued 'stuff' (e.g. emails) still showed the old content, it's because of that. Here's an explanation from Laravel docs:
Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.
using preferredLocale() is the "official" way to go.
you can implement HasLocalePreference and add method preferredLocale() to the notifiable model (as #Peter-M suggested):
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Model implements HasLocalePreference
{
/**
* Get the user's preferred locale.
*
* #return string
*/
public function preferredLocale()
{
return $this->locale;
}
}
and then you just send the notification to that model and locale will be automatically applied to your email template
$user->notify(new InvoicePaid($invoice));
This also works with queued mail notifications.
read more here:
https://floyk.com/en/post/how-to-change-language-for-laravel-email-notifications
I'm faced with the same problem
Without extending the queue class the quickest / dirtiest solution would be to create an email template for each locale.
Then when you create the queue, select the local template i.e.
Mail::queue('emails.'.App::getLocale().'notification', function($message)
{
$message->to($emails)->subject('hello');
});
If the local is set to IT (italian) This will load the view emails/itnotification.blade.php
As I said... Dirty!
Since this method is actually horrible I came across this answer
And its working for me, you actually send the entire translated html version of the email in a variable to the queue and have a blank blade file that echos out the variable when the queue runs.

Categories