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.
Related
Coming from Yii2 at the end of each request when something is logged, Yii2 adds additional data to your log, for example the $_POST data so you know what paramaters caused the issue.
Is there a way to add these information in Monolog too?
I don't want to use a Processor as it includes all those parameters to each and every record. I would just like to add an additional string in case something is logged at the end of the log after all messages are included/send (for example via BufferHandler when sending Mails via SwiftMailerHandler)
I found a way..
I can extend the default HtmlFormatter or LineFormatter and include my custom information that way
namespace modules\myspa\monolog;
class HtmlFormatter extends \Monolog\Formatter\HtmlFormatter
{
public function formatBatch(array $records): string
{
$formatBatch = parent::formatBatch($records);
$formatBatch .= '<br><br>Foobar.. add some custom info';
return $formatBatch;
}
}
Then I can add this one as a formatter
$swiftMailHandler = new SwiftMailerHandler($swiftMailer, $message, \Monolog\Logger::ERROR);
// add my formatter
$swiftMailHandler->setFormatter(new HtmlFormatter());
$psrLogger->pushHandler(new BufferHandler($swiftMailHandler));
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();
}
}
Currently building a system that requires users to design a custom template for a PDF document generated by the application. To achieve this, I have saved a blade template in the database to be retrieved when the document is about to be converted into a PDF document.
I know Laravel's View component can pick up a blade template from the file system and render it as an HTML document. However, to achieve my objective, this component should be able to read a text field fetched from the database. This is not currently possible with Laravel and I suspect is largely due to security reasons.
The question now is, how else can this be achieved using Laravel or what may be the recommended way to get this done?
Based on your comments and re-reading your initial question, my best guess is that you need a macro replacement function. This is the method I use for email templates in my own applications, which are stored in the database. It sounds similar to what you are attempting to do.
Helpers.php
Some autoloaded helpers file.
if (!function_exists('macro_parse')) {
function macro_parse($body, $haystack, $pattern = '/{{([^}]+)}}/')
{
$replace = preg_replace_callback($pattern, function ($needle) use ($haystack) {
return $haystack[$needle[1]];
}, $body);
return $replace;
}
} else {
Log::error("macro_parse is already defined");
}
Usage
// a simple template example...
// you would retrieve this template from the database
$template = '<div>{{some_macro}}</div>';
// key/value pairs can be set from database values
$macros = [
'some_macro' => 'it works!'
];
// parse the template
$parsed = macro_parse($template,$macros);
dd($parsed); // <div>it works!</div>
You can then pass the filled template to the PDF generator.
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}}
if i want send email with SwiftMailer in symfony then i must in action:
$message = $this->getMailer()->compose(
array('user#gmail.com' => 'user'),
$affiliate->getEmail(),
'Jobeet affiliate token',
body
);
$this->getMailer()->send($message);
but this doesnt working in template and in model (i would like create function for this).
I wouldn't recommend sending mail using a template - put a link in the template and call the action to send the mail ... you can send from a model although again i wouldn't recommend it as using sfContext::getInstance() inside the model is a really bad practice as it makes the model class rely on the context. So, your model class can't be unit tested as it needs a context to work...
You need an instance of the current sfContext to do it ... i would suggest passing it as a parameter when you create the model