12 min read

Sending emails

Joomla!’s core content management has a built-in feature where visitors are able to send articles to their friends through email. A similar feature can be added to the “Restaurant Reviews” component. The component can be modified to display a link to a form where the email address of a friend can be entered along with a short message. We will create a new view to handle this form. Go to the /components/com_restaurants/views folder of your Joomla! component and create a new folder named email. In this folder, create a file called view.html.php, and load it with the following code:

<?php
defined( '_JEXEC' ) or die( 'Restricted access' );
jimport( 'joomla.application.component.view');
JTable::addIncludePath(JPATH_COMPONENT_ADMINISTRATOR . DS . 'tables');
class RestaurantsViewEmail extends JView
{
function display($tpl = null)
{
$id = JRequest::getInt('id', 0);
$review =& JTable::getInstance('review', 'Table');
$review->load($id);
$this->assignRef('review', $review);
parent::display($tpl);
}
}

This code first checks to make sure that the file is not being called directly, loads in the framework code for views, and adds /administrator/components/com_restaurants/tables to the include path list of JTable. After declaring RestaurantsViewEmail as an extension of JView, the display() function pulls in the restaurant review ID from the request. The getInstance() member function of JTable is used to get a reference to a table object for reviews. The review matching the ID is then loaded and assigned to the template using the assignRef() member function. Finally, JView‘s original display() member function is called.

Although the code in view.html.php now loads the review that our visitors are trying to email, the form still needs to be added. Create a folder named tmpl in the existing /components/com_restaurants/views/email folder, and create a new file default.php inside it with the following code:

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); ?>
<form action="index.php" method="post">

<div class="contentheading">Email review</div>
<p>&nbsp;</p>
<p>Fill this form to send this review of <em>
<?php echo htmlspecialchars($this->review->name) ?>
</em> to someone you know who will find it useful:</p>

<div>
<strong>Your name:</strong>
</div>
<p>
<input type="text" name="sender_name" value="" />
</p>

<div>
<strong>Your email address:</strong>
</div>
<p>
<input type="text" name="sender_email" value="" />
</p>

<div><strong>Recipient's email address:</strong></div>
<p>
<input type="text" name="recipient" value="" />
</p>

<div><strong>Message:</strong></div>
<p>
<textarea name="message" rows="4" cols="40"></textarea>
</p>

<p>
<input type="submit" value="Send Review" class="button" />
</p>

<?php echo JHTML::_( 'form.token' ); ?>
<input type="hidden" name="id" value=
"<?php echo $this->review->id; ?>" />
<input type="hidden" name="task" value="sendemail" />
<input type="hidden" name="option" value=
"<?php echo $option; ?>" />
</form>

Before any output occurs, the code checks to make sure that the request is coming from within Joomla! and is not being called directly. The file then outputs a brief message identifying the review by name, so that the visitors are sure of what they are sending. The form then continues with fields for the visitor’s name and email address, the email address of their friend, and an optional message.

Just after the submit button, there is a series of hidden fields. First, JHTML::_(‘form.token’) is called to generate a token for the request. This is the same style of token as is used in the backend to thwart CSRF attacks, only here it is used to cut down on abuse. Next, the ID of the review being emailed is placed into the form. The task variable is set to sendemail, which is a function that we will add to the controller in a moment. Finally, option is set, so that Joomla! loads the com_restaurants component.

Linking the form

If you now load index.php?option=com_restaurants&view=email in your browser, you will see this screen:

 Learning Joomla! 1.5 Extension Development

The message at the top of the screen is incomplete as we simply loaded the view without a review id. Although we could add id as a parameter onto the end of the URL, our visitors will not be doing this. They will need a link to follow from the review itself. To add this link, we need to make some small adjustments to the single view. This view first needs to generate URLs to the email view with the ID already included. Do this by making the following highlighted adjustment to the display() function in /components/com_restaurants/views/single/view.html.php:

$date = JHTML::Date($review->review_date);

$backlink = JRoute::_('index.php?option=com_restaurants');
$emaillink = JRoute::_('index.php?option=com_restaurants&view=email&id=' . $id);

$user =& JFactory::getUser();
$comments =& $this->get('Comments');

$this->assign('display_comments', $params->get('display_comments',
'1'));
$this->assignRef('review', $review);
$this->assignRef('smoking', $smoking);
$this->assignRef('date', $date);
$this->assignRef('backlink', $backlink);
$this->assignRef('emaillink', $emaillink);
$this->assignRef('name', $user->name);
$this->assignRef('comments', $comments);

parent::display($tpl);

With a URL to the email view now being generated, we now need to display it. Open /components/com_restaurants/views/single/tmpl/default.php and add the following highlighted code:

<p><?php echo htmlspecialchars($this->review->review); ?></p>
<p><em>Notes:</em> <?php echo htmlspecialchars($this->review->notes);
?></p>
<p><a href="<?php echo htmlspecialchars($this->emaillink);
?>">Email this to a friend</a></p>

<a href="<?php echo htmlspecialchars($this->backlink);
?>">&lt; return to the reviews</a>

After saving the files, navigate to one of the restaurant reviews in the frontend. Your screen should now have an Email this to a friend link, like the following screenshot:

Learning Joomla! 1.5 Extension Development

When you click on the Email this to a friend link, you will get a screen that looks like the following:

Learning Joomla! 1.5 Extension Development

Sending email

With the form and the navigation in place, we can now focus on creating the function that creates the email and sends it to the correct place. Throughout the creation of this component, we have used member functions of JRequest to filter our input. We will do the same here, but go one step further by verifying that the mail addresses entered are valid.

This extra step is necessary as malicious users can otherwise add invalid newline characters to your email fi elds, taking control of the message sending process. Once a remote user has control, the message can be sent anywhere with any text. This is known as an “Email Header Injection attack”. If you fail to protect your website against this type of attack, your component could be hijacked and used to send thousands of spam messages without your knowledge.

With this caution in mind, we will write the sendemail() function to process the form and send the review. Open /components/com_restaurants/restaurants.php and add this function to the controller class:

function sendemail()
{
JRequest::checkToken() or jexit( 'Invalid Token' );

JTable::addIncludePath(JPATH_COMPONENT_ADMINISTRATOR . DS .
'tables');

$sender_email = JRequest::getString('sender_email', '');
$recipient = JRequest::getString('recipient', '');
$sender_name = JRequest::getString('sender_name', '');
$message = JRequest::getString('message', '');
$id = JRequest::getInt('id', 0);

jimport( 'joomla.mail.helper' );

if (!JMailHelper::isEmailAddress($sender_email) ||
!JMailHelper::isEmailAddress($recipient))
{
JError::raiseError(500, 'One of the emails you entered is
invalid. Please try again.');
}

$review =& JTable::getInstance('review', 'Table');
$review->load($id);

$link = JURI::base() . 'index.php?option=com_restaurants&view=
single&id=' . $id;

$subject = $sender_name . ' wants you to know
about ' . $review->name;

$body = "Here's a review of {$review->name}:nn";
$body .= "{$review->review}nn";

if ($message != '')
{
$body .= $sender_name . " also added this message:n";
$body .= '"' . $message . '"' . "nn";
}

$body .= "For all of the details, follow this link: {$link}";

$sender_name = JMailHelper::cleanAddress($sender_name);
$subject = JMailHelper::cleanSubject($subject);
$body = JMailHelper::cleanBody($body);

if (JUtility::sendMail($sender_email, $sender_name, $recipient,
$subject, $body) !== true)
{
JError::raiseNotice( 500, 'Email failed.' );
}

JRequest::setVar('view', 'email');
JRequest::setVar('layout', 'success');

$this->display();
}

Before even checking the variables, the checkToken() member function of JRequest is called to make sure that the user actually loaded the form. Although this will not prevent spammers from abusing your component, it will slow them down; they will need to load your form and extract the token for each message.

Next, the path /administrator/components/com_restaurants/tables is added to the list of paths JTable will use to find table classes. This is necessary because we will be loading the review in a moment, in order to extract the summary and title. The email address of the sender, the address of the recipient, the name of the sender,any added message, and the review’s ID are all extracted from the HTTP request.With the exception of the id field, all fields are filtered as strings. The id field is more stringently filtered to ensure that the value is also an integer.

Joomla! has a library for handling email data, which we pull in by calling jimport( ‘joomla.mail.helper’ );. This is used immediately to ensure that the entered email addresses are in a valid format. Both the sender’s address and the recipient’s address are tested. If either one is in an invalid format or contains newlines, the raiseError() member function of JError is used to stop the script and display a message.

The function continues by generating some review-specific data. The review is loaded from the database, and then a link back to the review is built using the review’s ID. A subject line is built with the sender’s name and the name of the restaurant. The body of the email starts with the name of the review, followed by the review itself. If the visitor added a personal message then this is added, along with their name. The link to the full review is added at the end.

With all of the content generated, there is one step left before sending the message. The formats of the email addresses have already been validated, but the sender’s name, subject, and body all contain user-supplied data. These must be filtered before they are sent off. The cleanAddress(), cleanSubject(), and cleanBody() member functions of JMailHelper strip out any attempts at email header injections.

Finally, the sendMail() member function of JUtility is called to send the email with the sender’s address, sender’s name, recipient’s email address, subject line, and body as the respective parameters. If this function fails for any reason, the raiseError() member function of JError is called and processing stops.

Adding a success message

When you perform an action that sends an email, most web applications will display an “email success” screen letting you know that the message went through. Our component will be no different. At the end of the sendemail() function, we set the view request variable to email, set the layout request variable to success, and then call the display() member function that defaults to JView::display().

Why aren’t we calling $this->setRedirect()?
Typically, $this->setRedirect() would be called to tell the controller to redirect the user to a specific page. This time, we have chosen to instead set the request variables and call the display() function directly. This prevents Joomla! from sending a redirect HTTP header to the browser, which ultimately saves another trip to the server. Because we want to display a message instead of going back to the review straight away, this makes sense. It may also be useful in cases where you have a client-side application that would otherwise be confused by a redirect.

Instead of creating an entirely separate view to handle the success screen, we have opted instead to set the layout request variable and point back to the email view. This helps us to cut down on the number of views required, and allows us to reuse some of the view code. To add the markup for the success screen, we need to create a new file called success.php to the tmpl folder of the email view. Enter the code below in success.php:

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); ?>
<div class="componentheading">Success!</div>
<p>The review for <?php echo htmlspecialchars($this->review->name)
?> has been successfully emailed.</p>
<p><a href="<?php echo htmlspecialchars($this->reviewlink) ?>">Return
to the review for <?php echo
htmlspecialchars($this->review->name) ?>.</a></p>

After checking to make sure that the request to success.php is coming from within Joomla!, a confirmation message, including the name, of the review is displayed. A link back to the review is also output. However, the URL for this link has not yet been generated. To do this, go to /components/com_restaurants/views/email/view.html.php and add the highlighted code to the display() function:

$review->load($id);

$reviewlink = JRoute::_('index.php?option=com_restaurants&view=
single&id=' . $id);

$this->assignRef('review', $review);
$this->assign('reviewlink', $reviewlink);

parent::display($tpl);

Save all of your code, then load one of the reviews and click on the Email this to a friend link. Fill the form and click the Send Review button. If the email goes through correctly, you should see a screen like the following:

Learning Joomla! 1.5 Extension Development

If you sent the review to yourself, the email should look similar to the following:

Here’s a review of The Daily Dish:

Chicken fried steak, meatloaf, potatoes, string beans and hot turkey sandwiches are all favorites from this venerable institution the locals swear by. Apple, pumpkin, and pecan pies round out the dessert selection.Dinner there won’t break the bank, either.

Ralph Elderman also added this message:
“You should really try this place sometime. I take the family there every week!”

For all of the details, follow this link: http://localhost/index.php?option=com_restaurants&view=single&id=2

LEAVE A REPLY

Please enter your comment!
Please enter your name here