Email, Languages, and JFile with Joomla!

Exclusive offer: get 50% off this eBook here
Learning Joomla! 1.5 Extension Development

Learning Joomla! 1.5 Extension Development — Save 50%

A practical tutorial for creating your first Joomla! 1.5 extensions with PHP, written and tested against the final release of Joomla! 1.5

$31.99    $16.00
by | December 2008 | Joomla! MySQL Content Management Open Source PHP

Before you begin with coding, there are a few files and folders that have to be created, as well as a query that has to be run. This will not only allow you to build Joomla! components, but will also help you try different features without extensive configuration. The component in this article will be called restaurants.

After installing your basic Joomla! component and making the website ready, there are a few additional features that you can include. One of them came up with the idea of allowing visitors to send reviews to their friends through email. Another could be adding audio reviews in addition to text. We can also begin to look into ways of expanding the market for the software. Internationalizing the component now will make it easy to translate the user interface later.

In the following article by Joseph L. LeBlanc, we will be taking a look at including the following additional features :

  • Sending emails
  • Managing languages
  • Creating translations
  • Handling file uploads

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

Learning Joomla! 1.5 Extension Development A practical tutorial for creating your first Joomla! 1.5 extensions with PHP, written and tested against the final release of Joomla! 1.5
Published: December 2008
eBook Price: $31.99
Book Price: $39.99
See more
Select your format and quantity:

Managing languages

Joomla! 1.5 has a centralized approach to internationalization. The default language for the project is English; other languages are distributed separately as extensions. Although these extensions do not translate user-entered content, they do provide phrases for the core Joomla! extensions in other languages. Both the frontend and the backend of Joomla!'s user interface are internationalized; language packs for each are distributed separately.

The following examples assume that the French language packs are installed. If you do not already have them installed, they can be downloaded from Joomlacode at http://joomlacode.org/gf/project/french/frs. Be sure to download both the frontend and backend language packs. To install them, go to Extensions | Install/Uninstall in the backend, then use the Upload Package File section to install both packages.

Learning Joomla! 1.5 Extension Development

Once both language packs are installed, individual users can choose French, or French can be set as the default for the entire site. To set French as the default language for the frontend, go to Extensions | Language Manager, select the radio button next to French, then click the Default button at the top of the screen. Click on the Administrator link (next to Site) and repeat the process to make French the default language for the backend.

Learning Joomla! 1.5 Extension Development

You should now notice that the backend interface is now in French. Go to Composants | Restaurant Reviews to see how our reviews component looks compared to the rest of the interface. The portions we wrote will still be in English, with the exception of the toolbar. Because we used standard Joomla! toolbar buttons here, these will be automatically translated, with no further effort required on our part.

Learning Joomla! 1.5 Extension Development

Creating translations

When you installed the French language packs, you may have wondered exactly what happened. Joomla! language packs have one file for each core extension. There are two folders named language where these files are placed—one at Joomla!'s root and one inside the administrator folder. The language folders contain one folder for each language pack installed.

These folders are named for the language's ISO language code. For instance, the British English pack is in en-GB and French French is in fr-FR. The language files for the individual extensions are located within these folders. There is one additional file in each language folder that defines phrases that are not exclusively used by any single extension.

To begin translating the interface for the "Restaurant Reviews" component into French, open the file /administrator/components/com_restaurants/views/all/tmpl/default.php. Make the following highlighted change to the header row of the second table:

<tr>
<th width="20">
<input type="checkbox" name="toggle" value=
"" onclick="checkAll(<?php echo count( $this->rows ); ?>);" />
</th>
<th class="title"><?php echo JText::_('Name') ?></th>
<th width="15%">Address</th>
<th width="10%">Reservations</th>
<th width="10%">Cuisine</th>
<th width="10%">Credit Cards</th>
<th width="5%" nowrap="nowrap">Published</th>
</tr>

The _() function of JText is used throughout Joomla! for translating strings in the user interface. If Joomla! cannot find a translation for the string you pass in, it simply outputs the base string. For now, we have passed Name into _(), but we have not created a translation for it in the French language file yet. Save the file and refresh the main screen of Restaurant Reviews. The header row of the table should look like this:

Learning Joomla! 1.5 Extension Development

You may be wondering how Name has suddenly become Nom. Even though we did not create a translation for Name, Joomla! found one in the file /administrator/language/fr-FR/fr-FR.ini. This file is always loaded for translations regardless of which extensions are in use.

Now pass the rest of the table headers through JText::_(). The code changes to do this are highlighted below:

<tr>
<th width="20">
<input type="checkbox" name="toggle"
value="" onclick="checkAll(<?php echo count( $this->rows );
?>);" />
</th>
<th class="title"><?php echo JText::_('Name') ?></th>
<th width="15%"><?php echo JText::_('Address') ?></th>
<th width="10%"><?php echo JText::_('Reservations') ?></th>
<th width="10%"><?php echo JText::_('Cuisine') ?></th>
<th width="10%"><?php echo JText::_('Credit Cards') ?></th>
<th width="5%" nowrap="nowrap"><?php echo JText::_('Published') ? ></th>

</tr>

After saving the file and refreshing the page, the remainder of the headers should look like this:

Learning Joomla! 1.5 Extension Development

Notice that Published is now translated as Publié, but the four remaining headers are still in English. We need to define translations for these strings in a file specific to the Restaurant Reviews component. Create a file named /administrator/languages/fr-FR/fr-FR.com_restaurants.ini and add the following text:

ADDRESS=Adresse
RESERVATIONS=Réservations
CUISINE=Cuisine
CREDIT CARDS=Cartes de credit

Notice that although the strings passed into JText::_() are mixed case, the definitions in fr-FR.com_restaurants.ini are uppercase. If you attempt to use a mixed case string for your definitions, JText will not read them during translation. Punctuation marks in the definitions are acceptable.

Save your language file, and then reload the screen. You should now see all of the table headers translated:

Learning Joomla! 1.5 Extension Development

When we originally created the toolbar for this component, the title of the screen was passed through JText::_(). This string can now be translated with the following highlighted addition to fr-FR.com_restaurants.ini:

ADDRESS=Adresse
RESERVATIONS=Réservations
CUISINE=Cuisine
CREDIT CARDS=Cartes de crédit
RESTAURANT REVIEWS=Revues des restaurants

After saving the file, refresh the page. The title at the top of the screen should now appear as translated:

Learning Joomla! 1.5 Extension Development

Adding comments to language files
If you want to add a comment in your language file, simply add a # at the beginning of the line you wish to comment. This is particularly helpful when you have a component with several views. Simply add a comment before adding a set of definitions for a specific view, to identify the view to which the definitions belong.

Debugging languages

One thing you may notice in our translations is that 'cuisine' is a French word spelled the same way in English. Although this cuts down on the amount of work for the translators, it can be rather confusing if you are trying to determine what is being passed through JText::_(). Even more importantly, how can you tell when a language definition is missing from your language file?

Joomla!'s Debug Language feature solves this. Each string that goes through JText::_() is wrapped in either bullets or question marks. Bullets represent a translated string and question marks represent missing language definitions.

To turn Debug Language on, go to Site | Global Configuration (Configuration globale), then click on the System (Système) link beneath the tile. On the right, there will be a box labeled Debug Settings (Paramètres de débogage), with Debug Language (Débogage de la langue) as an option. Set this option to Yes (Oui), then click Save. Finally, go back to the Restaurant Reviews component. You should now see bullets surrounding much of the text in the user interface:

Learning Joomla! 1.5 Extension Development

To see what the output looks like when a language definition is missing, temporarily remove the line ADDRESS=Adresse from the /administrator/languages/fr-FR/fr-FR.com_restaurants.ini file. After saving the file, reload the component. The Address column should now look like this:

Learning Joomla! 1.5 Extension Development

The debug language feature will work regardless of which language you have loaded as your default. This makes it possible for you to debug each language individually. When beginning to internationalize your component, leave Debug Language on and start with your preferred language. This will help you to build a complete set of language definitions that you can send to a translator.

Learning Joomla! 1.5 Extension Development A practical tutorial for creating your first Joomla! 1.5 extensions with PHP, written and tested against the final release of Joomla! 1.5
Published: December 2008
eBook Price: $31.99
Book Price: $39.99
See more
Select your format and quantity:

Translating the frontend

The frontend uses a set of language packs that is separate from the backend. However, the translation process is the same—pass the string to be translated through JText::_() and add a definition to the language file. Language debugging also works in the frontend.

To translate the header Restaurants we have reviewed in the list view, open /components/com_restaurants/views/list/tmpl/default.php and make the change highlighted below:

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); ?>
<div class="componentheading"><?php echo JText::_('Restaurants we
have reviewed') ?></div>
<ul>

If you now follow a link to the Restaurant Reviews component in the frontend (or go to index.php?option=com_restaurants&view=list) and have Language Debugging turned on, your screen should look now similar to the following:

Learning Joomla! 1.5 Extension Development

To translate the header and get rid of the question marks, create the file /languages/fr-FR/fr-FR.com_restaurants.ini, and add the following definition to it:

RESTAURANTS WE HAVE REVIEWED=Restaurants que nous avons passés en revue

After saving the file, reload the frontend. Your screen should now have the updated header, wrapped in bullets:

Learning Joomla! 1.5 Extension Development

Once the interface has been translated to your satisfaction, turn off Debug Language and set the default language to your preferred one.

Handling file uploads

Although most needs for data storage can be handled through the database, sometimes you will want to allow users to upload files to the server. Joomla! provides a library that makes it easy to safely handle file uploads.

For the Restaurant Reviews component, we want to make it possible for the critics to upload MP3 fi les to go with the reviews. To handle this, we need to adjust the form on which reviews are entered so that it accepts files. Also, the save() function in our controller for the backend needs to be adjusted to look for the fi le and copy it to the correct place.

First, open the file /administrator/components/com_restaurants/views/single/tmpl/default.php and make the following highlighted adjustments:

<form action="index.php" method="post" name="adminForm" id="adminForm" 
enctype="multipart/form-data">
<fieldset class="adminform">
<legend>Details</legend>
<table class="admintable">
<tr>
<td width="100" align="right" class="key">
Name:
</td>
<td>
<input class="text_area" type="text" name="name" id="name"
size="50" maxlength="250" value="<?php echo
$this->row->name;?>" />
</td>
</tr>

……

<tr>
<td width="100" align="right" class="key">
Published:
</td>
<td>
<?php echo $this->published; ?>
</td>
</tr>
<tr>
<td width="100" align="right" class="key">
Audio File:
</td>
<td>
<input type="file" name="audiofile" value="" />
</td>
</tr>
</table>
</fieldset>

Before we can accept files, we need to set the form so that it will accept more than just text strings. Setting enctype to multipart/form-data in the <form> tag allows us to send both text strings and entire files from our inputs. Once this is in place, any <input> elements of type file will function as intended. The added code created another row in the table with a file input through which MP3s can be uploaded.

Load the Restaurant Reviews component in the backend and click on New. There should now be an input field, at the bottom of the page, for uploading files:

Learning Joomla! 1.5 Extension Development

If you tried to upload an MP3 file now, it would not be saved as we have not yet modified the save() function in our controller to handle the file as well. To do this, open the file /administrator/components/com_restaurants/controllers/reviews.php and add the highlighted code:

if(!$row->review_date)
{
$row->review_date = date( 'Y-m-d H:i:s' );
}

if(!$row->store())
{
JError::raiseError(500, $row->getError() );
}

$file = JRequest::getVar( 'audiofile', '', 'files', 'array' );

if(isset($file['name']) && $file['name'] != '')
{
jimport('joomla.filesystem.file');

$ext = JFile::getExt($file['name']);

$filename = JPATH_SITE . DS . 'components' . DS . $option . DS .
'audio' . DS . $row->id . '.' . $ext;

if (!JFile::upload($file['tmp_name'], $filename))
{
$this->setRedirect("index.php?option=$option", "Upload
failed, check to make sure that /components/$option/audio
exists and is writable.");
return;
}

}

if ($this->getTask() == 'apply')
{
$this->setRedirect('index.php?option=' . $option .
'&task=edit&cid[]=' . $row->id, 'Changes Applied');
}
else
{
$this->setRedirect('index.php?option=' . $option,
'Review Saved');
}

First, the file information is extracted from the HTTP request using JRequest:: getVar(). The first parameter is the name of the file that was entered, while the second is a default. The third tells getVar() to extract audiofile from PHP's $_FILES superglobal. The fourth parameter ensures that the file information is in an array.

Before attempting to process the file, we make sure that the name element of the $file array exists and is not empty. If so, the code continues by loading in the file system library that provides the file handling functions through the JFile class. The extension of the uploaded file is extracted using the getExt() member function of JFile and stored in $ext

Next, the complete path and name of the final destination for the file is constructed. The constant JPATH_SITE points to the base of the Joomla! installation, and DS is set to the operating system's appropriate directory separator. The rest of the constructed path points to /components/com_restaurants/audio. After the path is complete, the current record's id is used from $row->id, followed by a period and ending with the file extension. This will ensure that only one file is uploaded for the restaurant review and that it does not clash with the name for any other review.

Finally, the file is copied into the correct place using JFile::upload(). The first parameter is the location of the temporary file; temporary files are created by PHP automatically on file upload. The location is found in the tmp_name element of the $file array. The second parameter is the full path and filename where the file is to be copied.

Why is the function named JFile::upload()?
It may seem a little odd that we are using a function named upload() instead of copy(). This is because Joomla! uses a built-in FTP layer to handle file management. This FTP layer exists to get around a common issue where shared hosts assign the 'nobody' or 'apache' username to all PHP processes. If this username does not have privileges to write files to the destination directories, the copy will fail. Joomla! solves this problem by opening an FTP connection to the local server and uploading the file during the PHP process. You do not need to have an FTP client installed on your computer because it all happens on the server. The FTP layer is optional; if it is turned off, Joomla! will fall back on using standard PHP file management functions.

If the upload fails, the setRedirect() member function of the controller is called to take the user back to the Restaurant Reviews management screen and display a message. To halt further execution of the save() function, return is called straight away.

With this code in place, files can be successfully uploaded to the server. You may have noticed that the audio folder under /components/com_restaurants is referenced but we never explicitly created it. When a path that does not exist is passed into JFile::upload(), Joomla! attempts to create the folders necessary to complete it. If you have problems uploading files to the server, create the audio folder yourself, and try again. Also, make sure that your file is not too large. PHP file and POST size limits that are frequently set as low as two megabytes. These can be adjusted in your php.ini file.

Although you may now have some files uploaded to the server in the audio folder, there is currently no way for visitors to download them. To fix this, we need to make slight adjustments to the single view in the frontend. First, a link to the file must be generated when one is available. Open /components/com_restaurants/views/single/view.html.php and make the highlighted adjustments to the display() function:

$user =& JFactory::getUser();

$comments =& $this->get('Comments');

jimport('joomla.filesystem.file');

$download = '';

if (JFile::exists(JPATH_COMPONENT . DS . 'audio' . DS . $id .
'.mp3'))
{
$download = JURI::base() . 'components/com_restaurants/audio/'
. $id . '.mp3';
}

$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);
$this->assign('download', $download);

parent::display($tpl);

This code first imports the JFile libraries using jimport('joomla.filesystem.file'). Then, $download is set to a null string as a fallback value. Next, the function JFile::exists() is called to test whether a file for this specific review exists. The filename is assembled using the path to the component frontend (stored in JPATH_COMPONENT), along with the current review's id and the extension .mp3. If the file does exist, $download is set to the URL from where the file can be downloaded, using JURI::base() to get the Joomla! root URL. The $download variable is then assigned to the view.

Although this generates the link, it must still be output. Open /components/com_restaurants/views/single/tmpl/default.php and add this highlighted code:

<p><?php echo htmlspecialchars($this->review->review); ?></p>
<p><em>Notes:</em> <?php echo htmlspecialchars($this->review->notes);
?></p>
<?php if ($this->download != ''): ?>
<p><a href="<?php echo htmlspecialchars($this->download)
?>">Listen to the review</a></p>
<?php endif ?>
<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>

This code checks to make sure that the download variable is not set to a null string. If this is the case, it continues by outputting an anchor tag. The URL is passed through PHP's htmlspecialchars() function to escape common HTML entities.

Once all of the adjustments have been made, save all of the files. Then go to the frontend and load one of the Restaurant Reviews for which you also uploaded an MP3 file. You should now see a link beneath the review where the file can be downloaded:

Learning Joomla! 1.5 Extension Development

Using the original filename

In this upload file example, the MP3 files were simply renamed to reflect the ID of the associated database record. In many cases, you will actually want to use the original name of the file when it is written to the server. The user's browser will send the local filename in the HTTP request for you.

However, as with all incoming data, you cannot trust that the given filename is valid. If you were to trust this data, a malicious hacker could forge the name as something like ../../../configuration.php or any other path on your server. If you accept this name as the filename and the file permissions on configuration.php are not secure, a hacker can overwrite it with anything they like and take over the site.

To avoid such a scenario, clean the incoming filename using JFile::makeSafe(). When you pass in the filename from the HTTP request as the parameter, this function will return it, after stripping out all of the slashes and invalid characters. To implement this in our example of the audio file, $filename would need to be set with this line of code:

$filename = JPATH_SITE . DS . 'components' . DS . $option . DS 
. 'audio' . DS . JFile::makeSafe($file['name']);

A major drawback of this method is that you have to decide how to manage filename conflicts. For example, if someone uploads the file interview.mp3 for one review and then someone else also uploads interview.mp3 for a different review, the second one will overwrite the first. One way around this would be to create a separate folder for each review, numbering them according to the ID of the review. When doing this, you will still need to scan the record's folder each time someone loads the review to determine whether or not a given audio file is available for download.

About the Author :


Books From Packt

Mastering Joomla! 1.5 Extension and Framework Development
Mastering Joomla! 1.5 Extension and Framework Development

Drupal for Education and E-Learning
Drupal for Education and E-earning

OpenCms 7 Development
OpenCms 7 Development

Drupal: Creating Blogs, Forums, Portals, and Community Websites
Drupal: Creating Blogs, Forums, Portals, and Community Websites

AJAX and PHP: Building Responsive Web Applications
AJAX and PHP: Building Responsive Web Applications

WordPress Theme Design
WordPress Theme Design

Moodle 1.9 E-Learning Course Development
Moodle 1.9 E-Learning Course development

Joomla! Template Design: Create your own professional-quality templates with this fast, friendly guide
Joomla! Template Design: Create your own professional-quality templates with this fast, friendly guide

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software