Thursday, March 26, 2009

Emailing from CakePHP 1.2.x

I've recently started working on a project in CakePHP and I wanted to share something that came out of it ...

Sending nice emails from any website is always fun, as we all know PHP has some very basic mail functionality that everyone has extended in their own way. I was trying to add email functionality to a site that I am developing and when I googled for anything to help I found this post from Alex McFayden that had a way to integrate PHPMailer into CakePHP as a component for any controller. Unfortunately it was written in 2006 for a previous version, obviously some changes were needed, so here are the new integration methods.

Get PHPMailer

  1. Get PHPMailer http://phpmailer.sourceforge.net/
  2. Unpack it into app/vendors/phpmailer/ , so you'll have /vendors/phpmailer/class.phpmailer.php etc.etc.

Create views and layouts

  1. Create two views, default_html.thtml and default_text.thtml and place them in app/views/your_controller/email/
  2. Create a layout for the HTML part of the email, call it app/views/layouts/email.ctp

Create component

  1. Create new component email. Paste the following code into app/controllers/components/email.php

<?php
/**
* This is a component to send email from CakePHP using PHPMailer
* @link http://bakery.cakephp.org/articles/view/94
* @see http://bakery.cakephp.org/articles/view/94
*/

class EmailComponent
{
/**
* Send email using SMTP Auth by default.
*/
var $from = 'some_email@goes.here.com';
var $fromName = "Displayed Name";
var $sitePrefix = '[MySite]';
var $useSMTPAuth = false;
var $smtpUserName = '';
var $smtpPassword = '';
var $smtpHostNames = "localhost:25";
var $text_body = null;
var $html_body = null;
var $to = null;
var $toName = null;
var $subject = null;
var $cc = null;
var $bcc = null;
var $template = 'email/default';
var $attachments = null;

var $controller;

function startup( & $controller)
{
$this->controller = & $controller;
}

/**
* Helper function to generate the appropriate template location
*
* @return string CakePHP location of the template file
* @param object $template_type
*/
function templateLocation($template_type)
{
return ('..'.DS.strtolower($this->controller->name).DS.$this->template.$template_type);
}

/**
* Renders the content for either html or text of the email
*
* @return string Rendered content from the associated template
* @param object $type_suffix
*/
function bodyContent($type_suffix)
{
$temp_layout = $this->controller->layout; // store the current controller layout

if ($type_suffix == 'html')
$this->controller->layout = '..'.DS.'email';
else
$this->controller->layout = '';

$mail = $this->controller->render($this->templateLocation('_'.strtolower($type_suffix)));
// render() automatically adds to the controller->output, we'll remove it
$this->controller->output = str_replace($mail, '', $this->controller->output);

$this->controller->layout = $temp_layout; // restore the controller layout
return $mail;
}

function attach($filename, $asfile = '')
{
if ( empty($this->attachments))
{
$this->attachments = array ();
$this->attachments[0]['filename'] = $filename;
$this->attachments[0]['asfile'] = $asfile;
} else
{
$count = count($this->attachments);
$this->attachments[$count+1]['filename'] = $filename;
$this->attachments[$count+1]['asfile'] = $asfile;
}
}

function send()
{
App::import('Vendor', 'PHPMailer', array ('file'=>'phpmailer'.DS.'class.phpmailer.php'));

$mail = new PHPMailer();

$mail->IsSMTP();
$mail->SMTPAuth = $this->useSMTPAuth;
$mail->Host = $this->smtpHostNames;
$mail->Username = $this->smtpUserName;
$mail->Password = $this->smtpPassword;

$mail->From = $this->from;
$mail->FromName = $this->fromName;
$mail->AddAddress($this->to, $this->toName);
$mail->AddReplyTo($this->from, $this->fromName);

$mail->CharSet = 'UTF-8';
$mail->WordWrap = 80; // set word wrap to 50 characters

if (! empty($this->attachments))
{
foreach ($this->attachments as $attachment)
{
if ( empty($attachment['asfile']))
{
$mail->AddAttachment($attachment['filename']);
} else
{
$mail->AddAttachment($attachment['filename'], $attachment['asfile']);
}
}
}

$mail->IsHTML(true); // set email format to HTML

$mail->Subject = $this->sitePrefix.' '.$this->subject;
$mail->Body = $this->bodyContent('html');
$mail->AltBody = $this->bodyContent('text');

$result = $mail->Send();

if ($result == false)
$result = $mail->ErrorInfo;

return $result;
}
}
?>


Then, to use this component you can do the following in your controller ...


var $components = array('Email');

function send_verification($id)
{
$this->Email->template = 'email/default';

$this->set('data', $this->data);
$toUser = $this->User->find(array('User.id'=>$id));
$this->Email->to = $toUser['User']['email'];
$this->Email->subject = 'Your new account';

//$this->Email->attach($fully_qualified_filename, optionally $new_name_when_attached);
// You can attach as many files as you like.

$result = $this->Email->send();
}


I've tested it and it works even when used from another function in the same controller being called as a JSON or XML action extension.

7 comments:

ManuM said...

Hi!

Thank you for the post. I have a question. Do I need a SMTP server installed to make the code work?

In that case, which one would you recommend?

Thank's. Regards.

Jaime said...

Hi SenorGeek,

Thanks for the tutorial, it works for me also.

Only changed this line " $this->controller->layout = '';" to be the default config in the component.

The only this is that the page of the action "/newsletter/send" doesn't show anything when I turn on the send of the mailer component in my controller. ($this->Email->send();).

Any advise? Or tutorials why this happens?

P.s. just working 2 week with Cake 1.2 ;)

Tressless said...

Fatal error: Call to undefined method stdClass::render() in email.php on line 63

Justin said...

If you want to make this work with Gmail, using their SMTP server a few changes are required:

1. Make sure you have the latest phpmailer (obviously).

2. Put these extra variables at the top of the email component:
var $smtpHostNames = "smtp.gmail.com";
var $smtpPort = "465";
var $smtpHost = 'mail.yourdomain.com'; // for peeps with google apps for domain
var $smtpDebug = 0; // enables SMTP debug information (for testing)
var $smtpSecure = "ssl"; // sets the prefix to the servier

3. Modify the send function in the email component to look something like:
$mail->IsSMTP();
$mail->SMTPAuth = $this->useSMTPAuth;
$mail->Host = $this->smtpHostNames;
$mail->Port = $this->smtpPort;
$mail->Username = $this->smtpUserName;
$mail->Password = $this->smtpPassword;
$mail->SMTPDebug = $this->smtpDebug;
$mail->SMTPSecure = $this->smtpSecure;

(note: some extra settings were added)

Good luck.

Unknown said...

How can u explain this $fully_qualified_filename?

Unknown said...

How can u explain this $fully_qualified_filename?

江仁趙雲虹昆 said...

正妹女警 正妹無名 校園正妹牆 日本正妹照片 杜蕾斯正妹牆 正妹實驗室 wretched無名正妹牆