send html emails using a view as the content - php

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.

Related

Getting Undefined index error and Method name must be a string in ..error in PHP

While upgrading to PHP7, I encountered these problem.
The issue is am trying to create codes that I can reuse like a mini-framework and the RUN function where the problem is used to load the relevant template and supplying the variables. It complains about
undefined index
of these 2 variables
$controller = $routes[$this->route][$this->method]['controller'];
$action = $routes[$this->route][$this->method]['action'];
and it also complained about this line
$page = $controller->$action();
which displayed
Fatal error: Uncaught Error: Method name must be a string in...
public function run() {
$routes = $this->routes->getRoutes();
$authentication = $this->routes->getAuthentication();
if (isset($routes[$this->route]['login']) && !$authentication->isLoggedIn()) {
header('location: /login/error');
}
else if (isset($routes[$this->route]['permissions']) && !$this->routes->checkPermission($routes[$this->route]['permissions'])) {
header('location: /login/permissionserror');
}
else {
$controller = $routes[$this->route][$this->method]['controller'];
$action = $routes[$this->route][$this->method]['action'];
$page = $controller->$action();
$title = $page['title'];
if (isset($page['variables'])) {
$output = $this->loadTemplate($page['template'], $page['variables']);
}
else {
$output = $this->loadTemplate($page['template']);
}
echo $this->loadTemplate('layout.html.php', ['loggedIn' => $authentication->isLoggedIn(),
'output' => $output,
'title' => $title
]);
}
This is the index.php
try {
include __DIR__ . '/../includes/autoload.php';
$route = ltrim(strtok($_SERVER['REQUEST_URI'], '?'), '/');
$entryPoint = new \Ninja\EntryPoint($route, $_SERVER['REQUEST_METHOD'], new \Ijdb\IjdbRoutes());
$entryPoint->run();
}
catch (PDOException $e) {
$title = 'An error has occurred';
$output = 'Database error: ' . $e->getMessage() . ' in ' .
$e->getFile() . ':' . $e->getLine();
include __DIR__ . '/../templates/layout.html.php';
}
The code is much, so, I can't display the whole code here since am using MVC pattern, but if there is anything you still want to know, I will gladly post it here
This code is runnable in php 7.2.7 (MAMP and LAMP), your way of dynamic function calling is invalid and your two variables are empty. This is not exact as yours but you can take logic form this demo.
Ok i am just providing a very simple example of reflection with mapping url to class along with functions. I make folder structure like below-
Here .htaccess is used to redirect all the url to index.php (if no file exists).
index.php include all code that could initialize code(for now only three files were there - uri.php, urlMapping.php and actions.php)
URI.php - have function that provide values like basepath, baseurl, uri
urlMappig.php - that allows you to provide which url hit which class along with method
actions.php will call dynamic class and function (reflection)
now look into code of index.php
<?php
include_once('URI.php');
include_once('urlMapping.php');
include_once('actions.php');
?>
aNow code insise uri.php file -
<?php
// all function should be accessible to all file loaded now
// return full url
function full_url (){
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
}
// returns current directory
function directory() {
$parts = explode("/", __DIR__);
return $parts[count($parts)-1];
}
// return base url
function base_url(){
$dir = directory();
return substr(full_url(), 0, strpos(full_url(), $dir)+strlen($dir));
}
// return uri
function uri() {
return substr(full_url(), strlen(base_url())+1);
}
?>
Now code in urlMapping.php
Note - file name and name of the class must be same as you map url in
this file so that you can make call to dynamic classes and function on actions.php. If don't this will not work
<?php
// this $urlMap will be accessed in actions.php
$urlMap = [
// here we use only uri so .. https://example.com/login hit LoginController class along with login function, this is just only the mapping
'login' => ['class'=>'LoginController',
'function'=>'login'],
];
?>
Now actions.php code
<?php
// if call is not like example.com/ means no uri is there
if(uri()!='')
{
// if your uri exists in route collection or urlmapping collection then make call to your dynamic class and methods
if(array_key_exists(uri(), $urlMap))
{
// include the class file dynamically from controllers folder
include_once('controllers/'.$urlMap[uri()]['class'].'.php');
// making references for dynamic class
$controlerObject = new $urlMap[uri()]['class']();
// call function dynaically from the referece
$controlerObject->{$urlMap[uri()]['function']}();
}
else
{
// you can make 404 page not
echo 'No routing found';
}
}
// call for home page
else
{
include_once('index.html.php');
}
?>
Now controllres/LoginController.php class,
Note - As mentioned above file name and name of the class as in urlMapping.php
<?php
class LoginController{
public function login()
{
// .... something your code goes here
echo 'hello from the login function of login controller';
}
//...... other function you can define
}
?>
Before calling $page = $controller->$action(); check if controller and action are exists:
if (isset($routes[$this->route][$this->method]['controller'])
&& isset($routes[$this->route][$this->method]['action'])) {
$controller = $routes[$this->route][$this->method]['controller'];
$action = $routes[$this->route][$this->method]['action'];
$page = $controller->$action();
// ...
} else {
// return 404 Page not found
}

unrequire a php file

Im working on a website - just learning to improve my coding.
I have a routing system which works like this:
/config/routes.php:
$route->add('/' , function() {
require_once("/application/pages/index.php");
});
$route->add('/register', function() {
require_once("/application/pages/register.php");
});
$route->add('/login', function() {
require_once("/application/pages/login.php");
});
$route->add('/logout', function() {
require_once("/application/pages/logout.php");
});
$route->add('/panel', function() {
require_once('/application/pages/panel/index.php');
});
And in my index.php:
require_once('application/pages/header.php');
include('config/routes.php');
require_once('application/pages/footer.php');
Everything works fine but I need a different header.php and footer.php for when the user goes into the panel. file: /application/pages/panel/index.php
When I require_once a new header file in the panel/index.php then both the new and old header file is loaded. How can I unrequire the header and footer files in the /panel/index.php so I can require different ones? Any suggestions?
Note: Routing comes from an MVC design pattern, you should keep your controllers separate from your views.
Templates and Views could be kept separate, also. This meaning our directory set-up can look something like this:
- Templates
- header_one.php
- footer_one.php
- header_two.php
- footer_two.php
- Views
- index.php
- someOtherBody.php
Here is a simple, but unfinished (that is your challenge) example of an Object that could do what I am explaining:
class Page {
private $template_path = dirname(dirname(__FILE__)) . '/templates/';
private $view_path = dirname(dirname(__FILE__)) . '/views/';
protected $header;
protected $footer;
protected $body;
public function setHeader($file_name)
{
if(is_readable($this->template_path . $file_name))
{
$this->header = $this->template_path . $file_name;
return $this;
}
// add an exception
}
/* TODO: */
public function setFooter() {}
public function setBody() {}
/* Render page */
public function render()
{
$page = [$this->header,$this->body,$this->footer];
foreach($page as $file)
{
require_once($file);
}
}
}
The idea here is that we can set our page layout, using the above object, within the route method closure, then render / require all the files after the logic.
$route->add('/', function() {
$page = new Page();
$page->setHeader('header_one.php')
->setBody('index.php')
->setFooter('footer_one.php');
/* todo: add your logic here */
$page->render();
});
Each route can now have its own header, footer and body.
Hope this helped.
At your place, I will do something like that :
Use out buffer and check if the file is already required. I give you an quick example but adapt the code for you.
And check the function : http://php.net/manual/en/function.get-included-files.php
$route->add('/panel', function() {
include_once('YOUR_SPECIFIC_PATH/header.php');
require_once('/application/pages/panel/index.php');
include_once('YOUR_SPECIFIC_PATH_header/footer.php');
});
And :
ob_start();
include_once('config/routes.php');
$mainContent = ob_get_contents();
ob_end_clean();
include_once('application/pages/header.php');
echo $mainContent;
include_once('application/pages/footer.php');
I've not the time for help more sorry but I can explain later if you need
This solution requires you to have a header.php and footer.php in each folder where your sub-controllers (application/<module name>/index.php) are.
index.php only call your sub-controllers via routing:
// require not include, because "no routing" = "no web site" ;)
require_once('config/routes.php');
application/pages/index.php include appropriate header/footer w/ relative path
require_once('header.php');
// page code
require_once('footer.php');
application/register/index.php include appropriate header/footer w/ relative path
require_once('header.php');
// page code
require_once('footer.php');
etc
#KDOT , thanks you for your help but using your code I was getting an error that I could not fix:
Call to a member function setBody() on null
but thanks to your code, I managed to rewrite the class my way and now it works ;)
Thanks again #KDOT !
If anyone needs it:
class Page {
private $TEMPLATE_PATH = '/application/templates/';
private $VIEW_PATH = '/application/views/';
protected $header;
protected $footer;
protected $body;
public function __construct($header_file, $body_file, $footer_file) {
$this->header = $this->TEMPLATE_PATH . $header_file;
$this->body = $this->VIEW_PATH . $body_file;
$this->footer = $this->TEMPLATE_PATH . $footer_file;
}
public function render(){
$page = [$this->header, $this->body, $this->footer];
foreach($page as $file) {
require_once($file);
}
}
}
and:
$route->add('/', function() {
$page = new Page('header.php', 'home.php', 'footer.php');
$page->render();
});

Passing callback function created in my custom controller

I have the follow code snippet and works well:
<?php
require_once(FOLDER_ROOT . "/lib/transaction/RetornoBanco.php");
require_once(FOLDER_ROOT . "/lib/transaction/RetornoFactory.php");
$fileName = 'test.txt';
function rowProcess($self, $numLn, $vrow) {
print ($numLn . ' - ' . $vrow);
}
$test = RetornoFactory::getRetorno($fileName, 'rowProcess');
$retorno = new RetornoBanco($test);
$retorno->process();
The getRetorno function uses 'rowProcess' as a handler function.
But now I'm trying do it in my custom controller (on my own Magento Extension, looks like Zend Controllers).
For each line of my file (test.txt), rowProcess runs.
But now, in my controller, rowProcess has a class.
My controller:
class MY_CLASS_HERE extends Mage_Adminhtml_Controller_Action
{
public function uploadAction()
{
//just to simplify, this uploaded file exists
$fileName = '/home/user/public_html/app/files/file.RET';
$test = RetornoFactory::getRetorno($fileName, "rowProcess");
$retorno = new RetornoBanco($test);
$retorno->process();
echo 'My Controller Here';
}
public function rowProcess($self, $numLn, $vrow)
{
print ($numLn . ' - ' . $vrow);
//I'm creating log to prevent it is not a problem for standard output
Mage::log($numLn . ' - ' . $vrow);
//This function log is default in Magento and works without problems.
}
}
My controller works well but now the handler doesn't print nothing!
I think it's wrong because now my function handler inside a class and getRetorno function can not use it. What could I do to fix this?
How about using like this? Can you try it?
$test = RetornoFactory::getRetorno($fileName, $this->rowProcess);

Unable to use variable in view

I'm building my own MVC framework and have encountered a problem when sending variables into views. The loadView() looks like this:
function loadView($view, $variables = array())
{
$file_path = APPLICATION_PATH . 'views/' . $view;
if (file_exists($file_path))
{
if (is_readable($file_path))
{
if (! empty($variables)) extract($variables);
include($file_path);
}
else
{
throw new Exception('Could not read view from ' . $file_path);
}
}
else
{
throw new Exception('Could not load view from ' . $file_path);
}
}
It works just as expected. However, when I'm setting up a template view like this things get weird:
loadView('layout/header.php');
loadView($view);
loadView('layout/footer.php');
It's called like this ($user is an object):
$data['view'] = 'login/showUser.php';
$data['user'] = $user;
loadView('layout/template.php', $data);
The $view variable gets set in the template file and loads the correct view. However, the $user variable is unable to travel into the dynamically loaded view which only contains this code:
<p>User ID: <?php echo $user->id; ?></p>
I can do this in CodeIgniter and I find it a little weird since when the $view and $user variables are extracted in the first loadView() call to the template, they should be available to the next view which is simply included into the scope.
What did I overlook?
Each loadView() call has its own local scope, which are not shared between different invocations of loadView(). In CodeIgniter, this likely works because its view renderer stores the variables in some static storage. You need to pass all variables you need in each view explicitly, or you need to add static storage to loadView(), like this:
function loadView($view, $variables = array())
{
static $static_vars = array();
$static_vars = array_merge($static_vars, $variables);
...
extract($static_vars); // instead of extract($variables);
...

Sending an email using a template file

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.

Categories