Sending an email using a template file - php

I am trying to figure out the best way to send emails from an external template file, at the moment I have a template file that looks like this:
Thank you, your order has been received, someone will review it and process it. No money has been taken from your account.
<?php
echo date('Y-m-d H:i:s');
?>
<pre>
<?php print_r($this->data); ?>
</pre>
And then my send method looks like this:
public function notify($template) {
// get the template from email folder
$path = $_SERVER['DOCUMENT_ROOT'].'templates/email/'.$template.'.php';
if(file_exists($path)) {
ob_start();
require_once($path);
$body = ob_get_contents();
ob_end_clean();
$subject = 'email send';
foreach($this->emailTo as $email)
new Mail($email,$subject,$body);
}
}
This all works fine when I call it like this:
$notifications = new notifications();
$notifications->setData(array('order' => $order->order));
$notifications->addEmail($order->order->email);
$notifications->notify('orderReceived');
However, if I try to make two calls to the "notify" method then the second email is blank, I know this is because the object buffer, but I cannot think of any other way to do it.
Thanks,
Ian

You are using require_once, so the file will only load once. Try require.
Also consider loading a pure text template and use str_replace to replace the variables in the template like this:
$template = "<pre>%DATA%</pre>";
$text = str_replace('%DATA%', $this->data, $template);

I would do this:
Template file
Thank you, your order has been received, someone will review it and process it. No money has been taken from your account.
%s
<pre>
%s
</pre>
Notify function
public function notify($template) {
// get the template from email folder
$path = $_SERVER['DOCUMENT_ROOT'].'templates/email/'.$template.'.php';
if (!file_exists($path)) {
// Return false if the template is missing
return FALSE;
}
// Create the message body and subject
$body = sprintf(file_get_contents($path), date('Y-m-d H:i:s'), print_r($this->data, TRUE));
$subject = 'email send';
// Send the mail(s)
foreach($this->emailTo as $email) {
new Mail($email, $subject, $body);
}
// Return true for success
return TRUE;
}
This will solve the problem - which could be solved anyway by changing require_once to require.
Using require_once means the template file will only be loaded once (clue's in the function name), so the second call will result in a blank body.

Related

PHP: class function works in one file but not another

I am making a Telegram Bot using PHP. I have bot.php, filter.php and test.php.
I want my bot to send a message to the user that includes an ID.
I have a Filter class and I have a function in my filter.php with a regex pattern to detect this id and I'm using preg_match to obtain the match.
public function getID($string) {
$pattern = "/e0(\d){6}\b/i";
preg_match($pattern, $string, $matches);
return $matches[0];
}
In my test.php, I use that function and it was able to echo the match to me.
<?php
include __DIR__ . './filter.php';
$check = new Filter();
$pattern = "/e0(\d){6}\b/i";
$text = "hi e0000000";
echo "id: ".$check->getID($text);
?>
In my bot.php, I try to use the same function to send a message, but it doesn't work. (the sendMsg function is just a simple curl http request to the Telegram Bot API)
include __DIR__ . './filter.php';
$filter = new Filter();
function handleGoodMessage($chatId, $text) {
$report = "Message '".$text."' passed the filters.\nID: ".$filter->getID($text);
sendMsg($chatId, $report);
}
Instead, whenever the function is called the bot returns a 500 Internal Server Error.
Please help.
$filter is not accessible inside the function.
$filter = new Filter(); //<--- filter is here, in the outer scope
function handleGoodMessage($chatId, $text) {
$report = "Message '".$text."' passed the filters.\nID: ".$filter->getID($text);
//this scope is inside the function, $filter does not exist here
sendMsg($chatId, $report);
}
This works in test as you do not change the scope. You need to pass $filter in
------UPDATE----
personally I would always rely on injection rather than using globals so my preference would be to redefine the function like this:
function handleGoodMessage($chatId, $text, $filter) {
$report = "Message '".$text."' passed the filters.\nID: ".$filter->getID($text);
sendMsg($chatId, $report);
}
I would probably (at the risk of upsetting some people) have getID defined as a static function because it's not really interacting anything, not using any member variables and is just processing a string and returning it. So then instead of injecting it, or using global you could say
function handleGoodMessage($chatId, $text) {
$report = "Message '".$text."' passed the filters.\nID: ".Filter::getID($text);
sendMsg($chatId, $report);
}

I am trying to replace text using replacers in email template - PHP

Hello Community
I am trying to replace text in email templates using data from database.
This is my code where %<$receiver>% works and %<$sender>% doesnt.
If you have any knowledge what im doing wrong or hints what i should change i would appreciate it.
public static function getRowValue($type, $receiver, $sender, $job) {
$model = self::where('type', $type)->where('status', DEFAULT_TRUE)->first();
if ($model) {
$subject = $model->subject;
$page = "admin.templates.email.email";
$email = $receiver->email;
$model->content = html_entity_decode($model->content, ENT_COMPAT, 'UTF-8');
$sender_replaced = ($sender) ? str_replace('%<$sender>%',$sender->name, $model->content) : $model->content;
$receiver_replaced = ($receiver) ? str_replace('%<$receiver>%',$receiver->name,$sender_replaced) : $sender_replaced;
$job_replaced = ($job) ? str_replace('%<$job>%',$job->name,$receiver_replaced) : $receiver_replaced;
$message = $job_replaced;
$email_data = [];
$email_data['message'] = $message;
Helper::send_email($page,$subject,$email,$email_data);
return $model->message;
}
return "";
}
If you need additional informations please feel free to ask, hope my example will help someone else who is having hard time solving this issue.
<?= $email_data['message'] ?: "NO MESSAGE" ?></span></p>
Line how i call data in email template.
There was nothing wrong with this part of code, ive missed data flow logic in controllers where i was pulling out $sender and it hasnt been accepted by user.
I have to change data flow logic now to make it work.
Thanks all on help.

send html emails using a view as the content

In my base controller I have this code which checks if there is a view and then displays it. I want to modify this to use for sending an email. I basically want to create views which contain html content for sending the mails. But trying to access it normally as if I were displaying a view doesn't quite work. This is the code for displaying the views on the website.
public function view($view, $data = []) {
// check for view file
if(file_exists('../app/views/' . $view . '.php')) {
require_once '../app/views/' . $view . '.php';
} else {
die('View does not exist');
}
}
To display a view, I would just do this in my controller:
$this->view('pages/contact-us');
When I am trying to send email, I want to just send the view into the email body.
$html = $this->view('pages/email');
//other variables go here
$send = new Email();
$send->sendMail($html, $subject, $setFrom, $addReplyTo, $addAddress, $altBody);
Doing this fails. I think I need to just return the view but not sure how to do this.
If I try this:
$html = $this->view('pages/contact-email');
I get this error:
Fatal error: Uncaught Exception: <strong>Message body empty</strong><br />
UPDATE:
I added this to the controller class:
public function getView($view, $data = []) {
if(file_exists('../app/views/' . $view . '.php')) {
return file_get_contents('../app/views/' . $view . '.php');
} else {
die('View does not exist');
}
}
And then did this in the controller:
$html = $this->getView('pages/contact-email', $data);
The plain html content is displaying but not the php. If I try this in the view:
<?php echo $data['name']; ?>
nothing shows up...
Yes you just need to add return in your view function.
Because actually your $html is empty.
To return the content of your file, you need to read the file before, for exeample : file_get_contents(PATH_OF_YOUR_FILE), and after that return the content inside a variable.

Laravel mail: pass string instead of view

I want to send a confirmation e-mail using laravel.
The laravel Mail::send() function only seems to accept a path to a file on the system.
The problem is that my mailtemplates are stored in the database and not in a file on the system.
How can I pass plain content to the email?
Example:
$content = "Hi,welcome user!";
Mail::send($content,$data,function(){});
update on 7/20/2022: For more current versions of Laravel, the setBody() method in the Mail::send() example below has been replaced with the text() or html() methods.
update: In Laravel 5 you can use raw instead:
Mail::raw('Hi, welcome user!', function ($message) {
$message->to(..)
->subject(..);
});
This is how you do it:
Mail::send([], [], function ($message) {
$message->to(..)
->subject(..)
// here comes what you want
->setBody('Hi, welcome user!'); // assuming text/plain
// or:
->setBody('<h1>Hi, welcome user!</h1>', 'text/html'); // for HTML rich messages
});
For Html emails
Mail::send(array(), array(), function ($message) use ($html) {
$message->to(..)
->subject(..)
->from(..)
->setBody($html, 'text/html');
});
It is not directly related to the question, but for the ones that search for setting the plain text version of your email while keeping the custom HTML version, you can use this example :
Mail::raw([], function($message) {
$message->from('contact#company.com', 'Company name');
$message->to('johndoe#gmail.com');
$message->subject('5% off all our website');
$message->setBody( '<html><h1>5% off its awesome</h1><p>Go get it now !</p></html>', 'text/html' );
$message->addPart("5% off its awesome\n\nGo get it now!", 'text/plain');
});
If you would ask "but why not set first argument as plain text ?", I made a test and it only takes the html part, ignoring the raw part.
If you need to use additional variable, the anonymous function will need you to use use() statement as following :
Mail::raw([], function($message) use($html, $plain, $to, $subject, $formEmail, $formName){
$message->from($fromEmail, $fromName);
$message->to($to);
$message->subject($subject);
$message->setBody($html, 'text/html' ); // dont miss the '<html></html>' or your spam score will increase !
$message->addPart($plain, 'text/plain');
});
Hope it helps you folks.
The Mailer class passes a string to addContent which via various other methods calls views->make(). As a result passing a string of content directly won't work as it'll try and load a view by that name.
What you'll need to do is create a view which simply echos $content
// mail-template.php
<?php echo $content; ?>
And then insert your string into that view at runtime.
$content = "Hi,welcome user!";
$data = [
'content' => $content
];
Mail::send('mail-template', $data, function() { });
I had a similar issue where the HTML and/or plain text of my email were not built by a view and I didn't want to create a dummy view for them (as proposed by #Matthew Odedoyin).
As others have commented, you can use $this->html() to set the HTML content of the message, but what if you want your email to have both HTML and plain text content?
Unfortunately $this->text() only takes a view, but I got around this by using:
$this->text(new HtmlString('Here is the plain text content'));
Which renders the content of the HTMLString instead of the view.
try
public function build()
{
$message = 'Hi,welcome user!'
return $this->html($message)->subject($message);
}
as you know
Only mailables may be queued.
meaning, if you use ShouldQueue interface
1) first, you should always do
php artisan queue:restart
2) second, in your mailable you can use html method (tested in laravel 5.8)
public function build(): self
{
return $this
->html('
<html>
<body>
ForwardEmail
</body>
</html>
')
->subject(config('app.name') . ' ' . 'email forwarded')
->attachData($this->content, 'email.eml', [
'mime' => 'application/eml',
]);
}
If you were using mailables. You can do something like this in the build method :
public function build()
{
return $this->view('email')
->with(['html'=>'This is the message']);
}
And you just go ahead and create the blade view email.blade.php in your resource folder.
Then in the blade you can reference your string using laravel blade syntax
<html>
<body>
{{$html}}
</body>
</html>
or
<html>
<body>
{!!$html!!}
</body>
</html>
If your raw text contains HTML mark up
I hope this works for those who have templates stored in the database and wants to take advantage of the Mailables class in Laravel.
To send raw html, text etc using Laravel Mailables you can
override Mailable->send() in your Mailable and in there, use the method in previous responses:
send([], [], function($message){ $message->setBody() } )
No need to call $this->view() at your build function at all.
NOTE: Below answer is for those who are looking for a flexible approach. i,e (with or without laravel template)
With Template
$payload['message'] = View::make('emails.test-mail',$data)->render();
Without Template
$payload['message'] = "lorem ipsum";
Mail::raw([], function ($mail) use ($payload) {
$mail->from($payload['from_email'])
->to($payload['to'])
->setBody($payload['message'], 'text/html')
->cc($payload['cc'])
->bcc($payload['bcc'])
->subject($payload['subject']);
foreach ($payload['attachments'] as $file){
$mail->attach($file);
}
});
This can be accomplished within a Mailable implementation, with plain text and html content parts:
public function build() {
// Text and html content sections we wish to use in place of view output
$bodyHtml = ...
$bodyText = ...
// Internally, Mailer::renderView($view) interprets $view as the name of a blade template
// unless, instead of string, it is set to an object implementing Htmlable,
// in which case it returns the result $view->toHtml()
$htmlViewAlternative = new class($bodyHtml) implements Htmlable {
protected string $html;
public function __construct($html) {
$this->html = $html;
}
public function toHtml(): string {
return $this->html;
}
};
// We can now set both the html and text content sections without
// involving blade templates. One minor hitch is the Mailable::view($view)
// documents $view as being a string, which is incorrect if you follow
// the convoluted downstream logic.
/** #noinspection PhpParamsInspection */
return $this
->to(...)
->from(...)
->subject(...)
->view([
'html' => $htmlViewAlternative,
'raw' => $bodyText
]);
}
Laravel mailable now has an ->html() function to be used instead of ->view() and works both with o without ->text()
laravel 9 has built in function to send HTML without view. Here is the example:
\Illuminate\Support\Facades\Mail::html($content, function ($message) {
$message->to("email#example.com")
->subject("Test dev 4")
->from("email#example.com");
});
and also if we use accepted answer will return:
Symfony\Component\Mime\Message::setBody(): Argument #1 ($body) must be
of type ?Symfony\Component\Mime\Part\AbstractPart, string given,
called in
/Users/yaskur/Sites/laravel/mail-builder/vendor/laravel/framework/src/Illuminate/Support/Traits/ForwardsCalls.php
on line 23
It's happened because laravel use new library to send email. Previously, use Swiftmailer and now use Symfony Mailer. To send HTML email without view you can also use below code:
Mail::raw("", function ($message) use ($content) {
$body = new \Symfony\Component\Mime\Part\TextPart($content);
$message->to("dyas#example.com")
->subject("Test dev")
->from("no-reply#example.com")
->setBody($body);
});

Replace multiple placeholders with PHP?

I have a function that sends out site emails (using phpmailer), what I want to do is basically for php to replace all the placheholders in the email.tpl file with content that I feed it. The problem for me is I don't want to be repeating code hence why I created a function (below).
Without a php function I would do the following in a script
// email template file
$email_template = "email.tpl";
// Get contact form template from file
$message = file_get_contents($email_template);
// Replace place holders in email template
$message = str_replace("[{USERNAME}]", $username, $message);
$message = str_replace("[{EMAIL}]", $email, $message);
Now I know how to do the rest but I am stuck on the str_replace(), as shown above I have multiple str_replace() functions to replace the placeholders in the email template. What I would like is to add the str_replace() to my function (below) and get it to find all instances of [\] in the email template I give it and replace it with the placeholders values that I will give it like this: str_replace("[\]", 'replace_with', $email_body)
The problem is I don't know how I would pass multiple placeholders and their replacement values into my function and get the str_replace("[{\}]", 'replace_with', $email_body) to process all the placeholders I give it and replace with there corresponding values.
Because I want to use the function in multiple places and to avoid duplicating code, on some scripts I may pass the function 5 placeholders and there values and another script may need to pass 10 placeholders and there values to the function to use in email template.
I'm not sure if I will need to use an an array on the script(s) that will use the function and a for loop in the function perhaps to get my php function to take in xx placeholders and xx values from a script and to loop through the placeholders and replace them with there values.
Here's my function that I referred to above. I commented the script which may explain much easier.
// WILL NEED TO PASS PERHAPS AN ARRAY OF MY PLACEHOLDERS AND THERE VALUES FROM x SCRIPT
// INTO THE FUNCTION ?
function phpmailer($to_email, $email_subject, $email_body, $email_tpl) {
// include php mailer class
require_once("class.phpmailer.php");
// send to email (receipent)
global $to_email;
// add the body for mail
global $email_subject;
// email message body
global $email_body;
// email template
global $email_tpl;
// get email template
$message = file_get_contents($email_tpl);
// replace email template placeholders with content from x script
// FIND ALL INSTANCES OF [{}] IN EMAIL TEMPLATE THAT I FEED THE FUNCTION
// WITH AND REPLACE IT WITH THERE CORRESPOING VALUES.
// NOT SURE IF I NEED A FOR LOOP HERE PERHAPS TO LOOP THROUGH ALL
// PLACEHOLDERS I FEED THE FUNCTION WITH AND REPLACE WITH THERE CORRESPONDING VALUES
$email_body = str_replace("[{\}]", 'replace', $email_body);
// create object of PHPMailer
$mail = new PHPMailer();
// inform class to use smtp
$mail->IsSMTP();
// enable smtp authentication
$mail->SMTPAuth = SMTP_AUTH;
// host of the smtp server
$mail->Host = SMTP_HOST;
// port of the smtp server
$mail->Port = SMTP_PORT;
// smtp user name
$mail->Username = SMTP_USER;
// smtp user password
$mail->Password = SMTP_PASS;
// mail charset
$mail->CharSet = MAIL_CHARSET;
// set from email address
$mail->SetFrom(FROM_EMAIL);
// to address
$mail->AddAddress($to_email);
// email subject
$mail->Subject = $email_subject;
// html message body
$mail->MsgHTML($email_body);
// plain text message body (no html)
$mail->AltBody(strip_tags($email_body));
// finally send the mail
if(!$mail->Send()) {
echo "Mailer Error: " . $mail->ErrorInfo;
} else {
echo "Message sent Successfully!";
}
}
Simple, see strtrĀ­Docs:
$vars = array(
"[{USERNAME}]" => $username,
"[{EMAIL}]" => $email,
);
$message = strtr($message, $vars);
Add as many (or as less) replacement-pairs as you like. But I suggest, you process the template before you call the phpmailer function, so things are kept apart: templating and mail sending:
class MessageTemplateFile
{
/**
* #var string
*/
private $file;
/**
* #var string[] varname => string value
*/
private $vars;
public function __construct($file, array $vars = array())
{
$this->file = (string)$file;
$this->setVars($vars);
}
public function setVars(array $vars)
{
$this->vars = $vars;
}
public function getTemplateText()
{
return file_get_contents($this->file);
}
public function __toString()
{
return strtr($this->getTemplateText(), $this->getReplacementPairs());
}
private function getReplacementPairs()
{
$pairs = array();
foreach ($this->vars as $name => $value)
{
$key = sprintf('[{%s}]', strtoupper($name));
$pairs[$key] = (string)$value;
}
return $pairs;
}
}
Usage can be greatly simplified then, and you can pass the whole template to any function that needs string input.
$vars = compact('username', 'message');
$message = new MessageTemplateFile('email.tpl', $vars);
The PHP solutions can be:
usage of simple %placeholder% replacement mechanisms:
str_replace,
strtr,
preg_replace
use of pure PHP templating and conditional logic (short open tags in PHP and alternative syntax for control structures)
Please, find the wide answer at Programmers.StackExchange to find out other approaches on PHP email templating.
Why dont you just make the email template a php file aswell? Then you can do something like:
Hello <?=$name?>, my name is <?=$your_name?>, today is <?=$date?>
inside the email to generate your HTML, then send the result as an email.
Seems to me like your going about it the hard way?

Categories