I'm using Laravel 5.3 to build an application which allows admins to build template emails to be used within the system for email address verification, forgot password etc. The template emails are built using a form which creates a new record in the database. Once the templates have been created they can be used within the application using the following command
Mail::to($user)->queue(new MailEmailTemplate($emailtemplate, $user));
where $emailtemplate is a model with the following fillable fields (there is nothing else worth disclosing within the model)
$fillable = ['from_name', 'from_email','subject','body','email_type','status'];
In order to allow the emails to be personalised to the user which they are being sent to (i.e. 'Dear John' at the top of the email body) I need to allow admins to enter variables into the form.
An example of how the form body should be filled out by the system admins:
Dear {{$user->first_name}}
Image of the form that admins will use to create the email template
The problem I am having is when the emails are sent the variables are not injected into the HTML sent to the client. i.e in the example above the recipient receives the email without the variables inserted
Dear {{$user->first_name}} as opposed to Dear John.
I have added the following route in my app/routes/web.php file
Route::get('/email_templates/{emailtemplate}/send/{user}', 'emailTemplatesController#send')->name('email_templates.send');
my App/Http/Controllers/emailTemplatesController.php
...
use App\Mail\MailEmailTemplate;
class EmailTemplatesController extends Controller
{
...
public function send(EmailTemplate $emailtemplate, User $user)
{
Mail::to($user)->queue(new MailEmailTemplate($emailtemplate, $user));
return back();
}
}
my app/Mail/MailEmailTemplate.php Mailable
use App\Models\User;
use App\Models\EmailTemplate;
use Session;
class MailEmailTemplate extends Mailable
{
use Queueable, SerializesModels;
public $user;
public $emailtemplate;
public function __construct(EmailTemplate $emailtemplate, User $user)
{
$this->emailtemplate = $emailtemplate;
$this->user = $user;
}
public function build()
{
$data['user'] = $this->user;
$data['email_template'] = $this->emailtemplate;
return $this->subject('testing')
->view('emails.index')
->with($data);
}
}
and finally the resources/views/emails/index.blade.php file (which is used to dislpay the email to the recipient)
{!! $email_template->body !!}
I've used unescaped output here as the text that is stored in the body field is HTML (admins will be developing the email template as a laravel 'view' in a text editor (phpStorm) then copy the code from the view into the form field for the body to store it within the application).
How can I modify this to allow the variables from the $user object to be inserted in place of the variables specified in the $emailtemplate->body?
If I modify the resources/views/emails/index.blade.php to the following the correct output can be achieved
{!! str_replace('<<$user->first_name>>',$user->first_name, $email_template->body)!!}
however this method is inefficient as it would require a str_replace to be used for any variable to be used within the emails. Note I've had to use << >> tags in the $email_template->body to prevent a parse error.
Update:
Could variable variables be used in conjunction with a regex to find every instance of {{ }} tags inside the $email_template->body and treat the text contained inside as a variable variable?
I apologise for the long question - I am relatively new to Laravel and wanted to make sure you guys could scrutinise my code structure.
Appreciate your help.
Jordan
try using
{{$email_template->body}}
Related
I want to get the render of an email before to send it.
I created a TemplatedEmail with htmlTemplate and context, it works fine for sending but how get the generated template with context to save it in database ? (customer needs)
I tried the getBody() but seems to work only with text template as I get A message must have a text or an HTML part or attachments.
$email = new TemplatedEmail();
$email->htmlTemplate($htmlTemplate);
$email->from($from)->to(...$to)->subject($subject);
$email->context($context);
dd($email->getBody());
I thought to use the render method but I'm in a service and not sure if it's a good way to store in database.
Symfony only renders the message when actually sending it, via an Event Listener. The class responsible from doing the rendering is BodyRenderer, from the Twig Bridge.
But nothing stops you from rendering the message yourself.
You have the template and the context variables, so you could simply inject Twig wherever you are doing the sending, render the template to a string and do whatever you need with that.
You could also register your own MessageEvent::class listener, set it with lower priority than the one registered by the Twig Bundle (it uses the default priority) so it's executed after that one, and then you could access the message body since it would have been rendered already. This is (very) slightly more complex, but you'd gain some performance since you wouldn't be rendering the template twice.
Which approach to use would depend on your application, your constraints, etc. But the important bit is to realize on what part of the process you'll find the body actually rendered, or render it yourself if you want it before that.
For future information it's possible to render the template in a service using the documentation here https://symfony.com/doc/current/templates.html#rendering-a-template-in-services
Here is solution (for logging templated email to DB, but can be easily customized to anything else) using EventListener
use App\Entity\EmailLog;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Mailer\Event\MessageEvent;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
#[AsEventListener(event: MessageEvent::class, method: 'onEmailSent', priority: -1)]
class EmailLogListener
{
public function __construct(
private EntityManagerInterface $entityManager
) {}
public function onEmailSent(MessageEvent $event): void
{
$message = $event->getMessage();
if (!$message instanceof Email) {
return;
}
$log = new EmailLog();
$log
->setSentFrom(array_map(function (Address $address) {
return $address->getAddress();
}, $message->getFrom()))
->setSentTo(array_map(function (Address $address) {
return $address->getAddress();
}, $message->getTo()))
->setSubject($message->getSubject())
->setText($message->getHtmlBody());
$this->entityManager->persist($log);
$this->entityManager->flush();
}
}
I need to edit template which used for emails in admin panel. Any Ideas?
I think about several ways:
Save email template in DB in the text field, edit it in the admin panel and then display the text in the blade's view.
The problem of this way's realization is I have to display php variables in blade template and then use the final code as the html for email. I think, it's so difficult for Laravel.
And the additional problem is if I store {{ $var }} in template's text in DB - it will display as the text, blade compiler doesn't process it.
Store only the static text information from email in the DB and then display it in the template. PHP variables will transfer separately.
This way will solve the problem with the php var's display, but I still don't know how to use the final code in the Mail::send, because Laravel allows using the template's name only, not a HTML, as I know...
I think about the following way:
$view = view('template')->render();
mail(..., $view, ...);
But I don't want to use it because I want use Mail::queue() for querying emails and I don't know how to use it with PHP mail().
Thanks to everybody for replies.
You can create your own variable syntax and store email template as text in your DB. Foe example, you can store each variable as ${VARIABLE_KEY} string.
Then during email preparation you should resolve all such constructions into their real values. I don't know which variables are required, but during email preparation you should execute these steps:
Load email template from DB.
Replace all ${VARIABLE_KEY} with their real values.
You can use regular expressions for the searching and replacement, but also you can use functions such str_replace. For example, if you want to paste email of the current user into your email (and your table for model User has an email field), then you can create variable: ${user.name} and then replace this manually with simple str_replace function:
$variables['${user.name}'] = Auth::user()->email;
str_replace(array_keys($variables), array_values($variables), $yourEmailTemplateBody);
Also you can do replacements by the same method not only in the email template body, but in the email subject too.
Then you have to create your own class which extends Laravel Illuminate\Mail\Mailable class. In this class you should define build method, where you can use not only the name of the view, but also some additional parameters, like in the "regular" view, for example:
class SomeClassName extends Mailable
{
public function build()
{
$email = $this->view('mail.common', [
'mail_header' => 'some header',
'mail_footer' => 'some footer',
])->subject('Your subject');
...
return $email;
}
For example, in your view you can store layout for entire email with some extra parameters: footer and header as in my example.
Also you can create more complex syntax for ${VARIABLE_NAME} constructions, for example, VARIABLE_NAME can be a method definition in PHP or Laravel notation, i.e.: SomeClass::someStaticMethod. You can detect this case and resolve SomeClass via Laravel Service Container. Also it can be an object.field notation, for example, user.email, where user is the current Auth::user().
But be carefull in this cases: if you will grant ability to edit email templates with this variables for all users, you should filter fields or available methods and classes for calling to prevent executing any method of any available class in your email template or to prevent display private information.
You can read about writing mailables in Laravel documentation
I was doing this for a project yesterday and found a good post describing Alexander's answer in more detail. The core is creating an EmailTemplate model with this method:
public function parse($data)
{
$parsed = preg_replace_callback('/{{(.*?)}}/', function ($matches) use ($data) {
list($shortCode, $index) = $matches;
if( isset($data[$index]) ) {
return $data[$index];
} else {
throw new Exception("Shortcode {$shortCode} not found in template id {$this->id}", 1);
}
}, $this->content);
return $parsed;
}
Example usage:
$template = EmailTemplate::where('name', 'welcome-email')->first();
Mail::send([], [], function($message) use ($template, $user)
{
$data = [
'firstname' => $user->firstname
];
$message->to($user->email, $user->fullname)
->subject($template->subject)
->setBody($template->parse($data));
});
For all the details (db migration, unit test, etc), see the original post at http://tnt.studio/blog/email-templates-from-database
You can simply use this awesome laravel package:
https://github.com/Qoraiche/laravel-mail-editor
Features (from readme file):
Create mailables without using command line.
Preview/Edit all your mailables at a single place.
Templates (more than 20+ ready to use email templates).
WYSIWYG Email HTML/Markdown editor.
Im creating a survey that is sent by newsletter email, and basically the app records the user data based on the email of the user, if the email isnt present in the route isnt possible to fill in the survey, but im not quite sure if im doing it right way and also for security purpose maybe i should have some kind of validation regarding the email. Can someone suggest me what is the best practise or the way im doing is already alright?!
The url that the users enter is like:
http://domain.com/surveys/23/email#hotmail.com/show
Here is my code:
Route:
Route::get('surveys/{id}/{email}/show/', 'SurveyController#show');
Controller:
public function show($id,$email)
{
$survey = Survey::find($id);
$email = $email;
return view('admin.surveys.show', compact('survey','email'));
}
View:
Html
...
#if(!empty($email))
show the survey form
#else
A message saying is not possibile fill without a email
#endif
Note: The survey is completelly a part from the newsletter system, it cannot have any kind of integration between them.
Definitely you should validate if e-mail was provided. In controller you should do check like this:
$this->validate($request, [
'email' => 'required|email|exists:users,email',
]);
Example above will make sure that e-mail was provided (required), if is actually an email and if it exists in database (table users, column email).
You can read more about this in documentation. On this page you can also check all available rules if I missed any that could be used.
EDIT: Please also remember to add "Request $request" as method parameter, like so:
public function show(Request $request, $id,$email) {...}
Regarding Larans sugegstion to inject the Request as well.. Maybe you don't need it in the show method. If you feel it messes with your code, maybe inject it in the constructor...
private $request;
public function __construct(Request $request)
{
$this->request = $request;
}
And do the validation in your show method,
I've inherited a website built using Codeigniter (v2.1.4). The client has asked for a change, and I'm not sure of the best way to achieve it.
I have the following method in the Main controller that powers a new vans page.
public function new_vans($slug = null){
$this->load->view('inc/header_view');
if($slug === NULL){
//If no slug is provided, show all new vans
$this->load->view('new_vans_view');
}else{
//If there is a slug, just show the selected van, or redirect if nothing returned
$data['new_van'] = $this->Database->getSingle('new_vans', array('slug' => $slug));
if(!empty($data['new_van'])){
$this->load->view('new_van_details_view',$data);
}else{
redirect('/new-vans');
}
}
$this->load->view('inc/footer_view');
}
The client has asked for a contact form to be added to a couple of pages including this one, and my question is, should I create a new method that just handles the contact form submissions? If so, how would I handle sending validation errors back to the page? The contact forms will all have the same fields, so I would guess creating a new method is the way to go?
Partial Views(forms)
Partial views are good for forms, they can be re-used
like your client has requested.
Returning views as data
There is a third optional parameter lets you change the behavior
of the function so that it returns data as a
string rather than sending it to your browser.
This can be useful if you want to process the data in some way.
If you set the parameter to true (boolean) it will return data.
The default behavior is false, which sends it to your browser.
Remember to assign it to a variable if you want the data returned:
$string = $this->load->view('myfile', '', true);
Master layouts
To create a Master layout so you can wrap your views
create a new file inside your views directory
views/master/layout.php
<body>
<?php $this->load->view($view); ?>
</body>
Controller
class someController extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->template = 'master/layout';
}
public function index()
{
return $this->load->view($this->template, array(
'view' => 'somecontrollerview',
'contact_form' => $this->load->view('partials/forms/contact', array(), true)
));
}
}
somecontrollerview
Echo out the contact form(string)
<?php echo $contact_form; ?>
Contact Controller
Create a new Controller to handle your form validation
The client has asked for a contact form to be added to a couple of pages including this one, and my question is, should I create a new method that just handles the contact form submissions?
create a new controller and new methods
If so, how would I handle sending validation errors back to the page?
look through the codeigniter documentation for form validation. basically if they have an error you are going to show them a view with the form again. it does not matter which page they came "from".
The contact forms will all have the same fields, so I would guess creating a new method is the way to go?
you need to validate the form fields, hopefully capture the contact info to a database, send an email confirmation to the customer, and send an email to the sales person unless its being done directly from the database, and then show a view with a thank you.
each one of those steps is a separate method.
optionally you can show the email address on the thank you page saying 'we have sent you a copy to the email address: something#gmail.com -- that way if the customer messed up the email address they can go back and correct it.
It's a MVC question.
Here is the situation:
I am writing an application where I have "groups".
You can invite other persons to your groups by typing their email and clicking "invite".
There are two ways this functionality can be called: a) web interface and b) API
After the mail sending is over I want to report to the user which mails were sent successfully (i.e., if the SMTP send succeeded. Currently, I am not interested in reporting mail bounces).
So, I am thinking how should I design so that there is no code duplication. That is, API and web-interface should share the bulk of the code.
To do this, I can create the method "invite" inside the model "group". So, the API and and the Web-interface can just call:
group->invite($emailList);
This method can send the emails. But the, problem is, then I have to access the mail templates, create the views for the mails, and then send the mails. Which should actually be in the "View" part or at least in the "Controller" part.
What is the most elegant design in this situation?
Note: I am really thinking to write this in the Model. My only doubt is: previously I thought sending mails also as "presentation". Since it is may be considered as a different form of generating output.
Added after edit
I understand that View does not necessarily have to be output to the browser. And that is where my doubt is. Now the problem is, say I have a "task list" in my app. We can assign a task to some folks. Now the "assignTo" method can be called in two situations: 1) While creating the task 2) reassign a task to someone else.
In both cases the new assignee should get the email notification. So if the method "assignTo" is not sending the mail, we have to duplicate the mailing part in two places: "task create controller" and "task reassign controller".
I wanted to avoid this duplication.
The View doesn't necessarily have to be an output to the browser that the user gets to see. It can also be the template that's being emailed. The Controller receives input on what to do, requests the necessary information from the Model, and users the View to output that information. In this case, the View would be the template of the email, and the Controller has the task to email that output.
View: click "invite" (or for the API: send an invite command);
Controller: user clicked "invite", get data from model;
Model: Controller requests data (emails for specific folks or whatnot), return;
Controller: receives data from Model, setup data for the View (template) and email the "View".
After that, return something to the API, or tell the Controller to output a View for the web interface that tells the user that the invite has been processed.
I had same doubts with mailing system in Kohana myself, in the end figured out the best way to do it was like this;
Have one main 'mailer' class ( wrapper ) which will contain methods to send mails, extend it for each separately used mailer classes ( factories );
application/classes/mailer.php:
abstract class Mailer {
protected $from;
protected $to;
protected $cc;
protected $bcc;
protected $subject;
protected $body;
protected $reply_to;
protected $sent_on;
protected $content_type = 'text/html';
protected $headers;
protected $template;
public static function factory($name)
{
$class = 'Mailer_'.$name;
return new $class();
}
public function __construct()
{
return $this;
}
public function send($save = FALSE)
{
// send the email using swift mailer, zend_mail, phpmailer, whatever..
}
protected function save($to, $subject, $body, $headers)
{
}
}
application/classes/mailer/user.php
class Mailer_User extends Mailer {
// Who is sending the mail
protected $from = "users#domain.com";
// Content type of the email
protected $content_type = 'text/html';
public function email_activated($name, $email)
{
$this->to = $email;
$this->subject = 'Your email has been verified';
$this->body = View::factory('mailer/user/email_verified')
->set('name', $name)
->render();
return $this;
}
}
and later on in code use it like factory;
Mailer::factory('user')
->email_activated( $user->username, $user->email)
->send();
sending the email from wherever you want it to.
The API itself is group of all public methods in model - no additional wrapper is needed, because model does all the wrapping itself (and keeps other methods non-public). The application itself should not use another api than the official one is (so you prevent the duplication).
I'd do something like:
sendMail(array $recipients);
sendGroupMail($group_id) not{
sendMail(/*get recipients by group_id*/);
};