The Official Joomla! Book

JForm is one of the most powerful aspects of Joomla!1.6.  Last night I talked at JUG NYC about how to make a simple plugin to take advantage of JForm to add some fields to the basic contact form  (or really any form that is already present). That is to say, the form object already exists.

This is a very simple project; it just adds some additional information fields to the message that is sent via the form. The only fancy thing we did was have one field that showed just to guests, one just to registered users and one to everybody. This project is also really designed for a site designer or webmaster who wants something customized, not to make a plugin to be distributed on the JED. 

There is already a form plugin in the core distribution, the profile plugin. It's a great tool but quite complex because ... it's designed to be distributed to millions of people not specifically for your site. So this project is going bypass a lot of the things that the profile plugin does that make it both super flexible and a bit ovewhelming. 

Just a note, this example will only work on 1.6.2+.

First, we should ask, what is a plugin anyway?

We all know it's an extension, but what's so special? Plugins are observers that  "listen" for certain events to happen. When they do, the plugin springs into action to make things happen. In a lot of ways they are the most powerful extensions because they can change more or less anything in the core of Joomla!. The other nice thing is that a simple plugin is easy to code, even for a beginner with good copy and paste skills and a willingness to learn.

One last, thing, the events. In various places in the code you will see lines like this:

 

// Trigger the form preparation event
$dispatcher->trigger('onContentPrepareForm', array($form, $data));
 
Where you see a trigger, that's an event. So the event we're interested in is onContentPrepareForm. That's an event that happens when a form is being prepared for display on your site, which is when you would add new fields. So that's the event we want to listen for. Content used to mean specifically com_content but now it applies pretty much to ever component in the core that renders content on the site. You'll see many more  events throughout all of Joomla! 1.6 than in 1.5 which is one reason that from 1.6 on plugins are becoming more and more important in sites. 
 
Plugins usually have  two files, a php file and an xml file, both with the name of the plugin. In my case I'll call them contactform.php and contactform.xml. Because we are adding fields we also have a folder called forms that contains three xml files: form1.xml, form2.xml and form3.xml. You can have as many forms as you want, but in my example I have a form for guests (form1), a form for registered users (form2) and a form for everyone (form3). 
 
So in context the structure will be:
/plugins/content/contactorm/contactform.php
/plugins/content/contacform/contactform.xml
/plugins/content/contacform/forms/form1.xml
/plugins/content/contacform/forms/form2.xml
/plugins/content/contacform/forms/form3.xml
Of course we will put index.html files in all the folders and have two language files in a language folder
 
/plugins/content/contacform/language/en-GB.plg_contact_contactform.ini
/plugins/content/contacform/language/en-GB.plg_contact_contactform.sys.ini
 
Now, let's fill in some of the files. First the basics. The contactform.xml will contain almost nothing because we aren't using any parameters, but I'll leave space in case I want to add them later.
 
<?xml version="1.0" encoding="utf-8"?>
 
<extension version="1.6" type="plugin" group="content">
<name>plg_user_contactform</name>
<author>Joomla! Project</author>
<creationDate>January 2008</creationDate>
<copyright>(C) 2005 - 2011 Open Source Matters. All rights reserved.</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<authorEmail> This email address is being protected from spambots. You need JavaScript enabled to view it. </authorEmail>
<authorUrl>www.joomla.org</authorUrl>
<version>1.6.0</version>
<description>PLG_USER_CONTACTFORM_XML_DESCRIPTION</description>
 
<files>
     <filename plugin="contactform">contactform.php</filename>
     <filename>index.html</filename>
     <folder>forms</folder>
</files>
<languages>
     <language tag="en-GB">en-GB.plg_content_contactform.ini</language>
     <language tag="en-GB">en-GB.plg_conteng_contactform.sys.ini</language>
</languages>
<config>
<fields name="params">
 
<fieldset name="basic">
</fieldset>
 
</fields>
</config>
</extension>
 
All this is really doing is saying what the files are called and where they are located. 
 
Next let's look at contactform.php.
 
<?php
/**
 * @copyright Copyright (C) 2005 - 2011 Open Source Matters, Inc. All rights reserved.
 * @note Extended by Elin Waring
 * @license GNU General Public License version 2 or later; see LICENSE.txt
 */
 
defined('JPATH_BASE') or die;
 
/**
 * An example custom contact plugin.
 *
 * @package Joomla.Plugin
 * @subpackage Contact
 * @version 1.6
 */
class plgContentContactform extends JPlugin
{
     /**
     * @param JForm $form The form to be altered.
     * @param array $data The associated data for the form.
     *
     * @return boolean
     * @since 1.6
     */
     function onContentPrepareForm($form, $data)
     {
     // Load content_contactform plugin language
     $lang = JFactory::getLanguage();
     $lang->load('plg_content_contactform', JPATH_ADMINISTRATOR);
 
     if (!($form instanceof JForm))
     {
          $this->_subject->setError('JERROR_NOT_A_FORM');
          return false;
     }
 
     // Check we are manipulating a valid form.
     if (!in_array($form->getName(), array('com_contact.contact'))) {
          return true;
     }
 
     // Add the fields to the form.
     JForm::addFormPath(dirname(__FILE__).'/forms');
     $user = JFactory::getUser();
     // Fields just for guests
     if ($user->guest){
          $form->loadFile('form1', false);
     }
     // Fields just for people who are logged in
     else {
          $form->loadFile('form2', false);
     }
     // Fields for everyone
     $form->loadFile('form3', false);
     return true;
     }
}
A  few things to notice are:
  • The name of the class plgContentContactform which shows the group and the plugin name. 
  • The name of the function onContentPrepareForm($form, $data) which matches the event we want the plugin to watch for. 
  • Then we check to see if we are actually in a form, because you can't modify a form if there is no form to modify.
  • Then we check to see if we're actually in the specific form we are interested in. That is com_contact.contact. If you look in the frontend com_contact folder models/forms you will see contact.xml which is the form used for the contact form.
  • Next we say where to find the forms the plugin is using, which is in the forms folder, JForm::addFormPath(dirname(__FILE__).'/forms');
  • Finally we load each of our forms for the appropriate users. We know who is who from the user object.

Last but not least we have three three forms. To keep it simple I just put one field in each.

Form1, for guests.

 

<?xml version="1.0" encoding="UTF-8"?>
<form>
<fields name="guest">
<fieldset name="guests"
label="PLG_CONTENT_CONTACTFORM_FIELDSET1_LABEL"
>
<field
name="heard"
type="list"
label="PLG_CONTENT_CONTACTFORM_FIELD_HEARD_LABEL"
description="PLG_CONTENT_CONTACTFORM_FIELD_HEARD_DESC">
     <option value="1">JSELECT</option>
     <option value="1">HEARD_FRIEND</option>
     <option value="2">HEARD_SEARCH</option>
     <option value="3">HEARD_AD</option>
     <option value="4">HEARD_OTHER</option>
</field>
</fieldset>
</fields>
</form>
 
Form 2, for logged in users
 
<?xml version="1.0" encoding="UTF-8"?>
<form>
<fields name="loggedin">
<fieldset name="registered"
label="PLG_CONTENT_CONTACTFORM_FIELDSET2_LABEL"
>
<field
name="callback"
type="radio"
label="PLG_CONTENT_CONTACTFORM_FIELD_CALLBACK_LABEL"
description="PLG_CONTENT_CONTACTFORM_FIELD_CALLBACK_DESC">
     <option value="1">JYES</option>

 

     <option value="2">JNO</option>
</field>
</fieldset>
</fields>
</form>
And form3, for everyone
 
<?xml version="1.0" encoding="UTF-8"?>
<form>
<fields name="everyone">
<fieldset name="fieldset3"
label="PLG_CONTENT_CONTACTFORM_FIELDSET3_LABEL"
>
<field
name="department"
type="list"
label="PLG_CONTENT_CONTACTFORM_FIELD_DEPT_LABEL"
description="PLG_CONTENT_CONTACTFORM_FIELD_DEPT_DESC">
     <option value="1">JSELECT</option>
     <option value="1">DEPT_SALES</option>
     <option value="2">DEPT_SUPPORT</option>
     <option value="3">DEPT_BILLING</option>
     <option value="4">DEPT_OTHER</option>
</field>
</fieldset>
</fields>
</form>
 
That's it for the plugin. If I'm making one for myself, I just put it in the right folder and use the discover install method but you could zip it up and install it too. 
 
Now, we have to do one more thing to make the new fields render, which is to make a layout override for the default_form.php file that you can find in com_contact/views/contact/tmpl/.  In the html folder of your template make a com_contact folder with a contact folder inside it, and put a copy of the file there, so for beez2
/templates/beez_20/html/com_contact/contact/default_folder.php
 
Here's where copy and paste are your friends. You need to add this big block of code to the template . I put it befor the basic fields, but it could go anywhere:
<?php //Dynamically load any additional fields from plugins. ?>
     <?php foreach ($this->form->getFieldsets() as $fieldset): ?>
          <?php if ($fieldset->name != 'contact'):?>
               <?php $fields = $this->form->getFieldset($fieldset->name);?>
               <?php foreach($fields as $field): ?>
                    <?php if ($field->hidden): ?>
                         <?php echo $field->input;?>
                    <?php else:?>
                         <dt>
                            <?php echo $field->label; ?>
                            <?php if (!$field->required && (!$field->type == "spacer")): ?>
                               <span class="optional"><?php echo JText::_('COM_CONTACT_OPTIONAL');?></span>
                            <?php endif; ?>
                         </dt>
                         <dd><?php echo $field->input;?></dd>
                    <?php endif;?>
               <?php endforeach;?>
          <?php endif ?>
     <?php endforeach;?>
 
In essence this is saying, skip the core form (because we do that separately) but otherwise loop through all the forms and render the fields according to what the plugin says. You don't have to do it this way (it's on the lazy side), but it's quick and easy.
 
So the two versions are:
 
Form for guests
Form for logged in users
 
 
At the JUG NYC meeting where I presented this last night, people came up with all kinds of great ideas for how to use this to make better, more useful contact forms. And of course all we're doing here is renderiing the input fields and labels, but we could do other things too.   Mitch Pirtle and I got into a little debate about whether Joomla! design patterns are great because they let really experienced developers do anything or because they make it easy for pretty inexperienced developers to do cool things.  Showing my age, but as the old advertisement says, "Tastes great AND less filling."

CloudAccess.net Joomla Demo Free for 30 Days