Working with Images in Joomla Component

In the back-end, administrators can select an image to be associated with each item. You can also display details of the image on the administrator's list of items. In the front-end, display the image associated with the item and also allow a user to upload an image when creating a new item.

To implement this functionality, you need to extend the database record to include the filename of the image stored within the images folder. You will store three things that are used in an HTML img tag - the image link, caption and alt text. So, create one database field to store all three items in JSON-encoded format. This means that you need conversion between the database format (JSON-encoded) and how the data is presented in a form (via an array).

In the back, change the edit form, both its xml definition and the layout file, to include capturing the image details. Then, the image information is extracted in the model, in order to prefill the data in the edit form. On the front end, you have to ensure that the layout file includes the image as well. So, you need to get the additional information from the model.

Step 1: Update the Database

Add a field called 'image' to the database record, which will store the filename of the image, its caption and its alt text all together in a JSON-encoded format.

`image` VARCHAR(1024),

In the update mysql file,

ALTER TABLE `#__helloworld` ADD `image` VARCHAR(1024);

Step 2: Edit Admin XML Form

Next, you need to add image fields into the form definition, and put them all inside a fieldset to enable them to be output easily within the layout file.

<fields name="imageinfo">
<fieldset
name="image-info"
label="COM_HELLOWORLD_IMAGE_FIELDS"
>
<field
name="image"
type="media"
preview="tooltip"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC" />
<field name="alt"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC"
size="30"/>
<field name="caption"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC"
size="30"/>
</fieldset>
</fields>

Step 3: Layout File

Now, in the layout file, you can output that fieldset into a new tab on the form.

<?php echo JHtml::_('bootstrap.addTab', 'myTab', 'image', JText::_('COM_HELLOWORLD_TAB_IMAGE')); ?>
<fieldset class="adminform">
<legend><?php echo JText::_('COM_HELLOWORLD_LEGEND_IMAGE') ?></legend>
<div class="row-fluid">
<div class="span6">
<?php echo $this->form->renderFieldset('image-info'); ?>
</div>
</div>
</fieldset>
<?php echo JHtml::_('bootstrap.endTab'); ?>

Step 4: Model File (JSON to Array)

You also need to get the image information within the data which is used to prefill the edit form. For this, the code calls getItem() within the model (specifically within the JModelAdmin class), and this gets the image field as well, but the data is in JSON-encoded format. So, you can do the following:

  1. Override the JModelAdmin getItem() with a getItem() in your own model.
  2. Inside the getItem(), call the parent JModelAdmin getItem() version to get the data from the database.
  3. Then, convert the image info into individual array fields.

You can use the Joomla Registry class to handle the conversion.

public function getItem($pk = null)
{
$item = parent::getItem($pk);
if ($item AND property_exists($item, 'image'))
{
$registry = new JRegistry($item->image);
$item->imageinfo = $registry->toArray();
}
return $item;
}

Step 5: Table File (Array to JSON)

Finally, you have to consider handling the HTTP POST when the administrator submits the edit form. In particular, you have the image information in array format, so you have to JSON-encode this before you put it into the database field. Now, convert the data within the bind() override of our JTable class.

if (isset($array['imageinfo']) && is_array($array['imageinfo']))
{
// Convert the imageinfo array to a string.
$parameter = new JRegistry;
$parameter->loadArray($array['imageinfo']);
$array['image'] = (string)$parameter;
}

Step 6: Admin List Model File

Within your list view model, you need to include image information within the SQL query. However, because this image information is JSON-encoded it's not going to be possible to sort via a SQL ORDER BY clause, so you will not provide functionality to sort by the equivalent image column in the display. So, there is no need to add any fields to the filter-fields array.

// Create the base select statement.
$query->select('a.id as id, a.greeting as greeting, a.published as published, a.created as created,
a.image as imageInfo')
->from($db->quoteName('#__helloworld', 'a'));

Step 7: Layout File

Now, you need to convert the JSON-encode image information into an array format. You can do this in the layout file, as it already has a for loop which cycles through the records. So, the view file won't require any changes.

In the layout file, there will be a column for the image caption text, and display the image as a tooltip, with help from some of the Bootstrap functionality.

$row->image = new Registry;
$row->image->loadString($row->imageInfo);

In the table data:

<td align="center">
<?php
$caption = $row->image->get('caption') ? : '' ;
$src = JURI::root() . ($row->image->get('image') ? : '' );
$html = '<p class="hasTooltip" style="display: inline-block" data-html="true" data-toggle="tooltip" data-placement="right" title="<img width=\'100px\' height=\'100px\' src=\'%s\'>">%s</p>';
echo sprintf($html, $src, $caption); ?>
</td>

Step 8: Site Model File

It includes the image info within the data you read from the database, and convert it into an array format.

In the query:

$query->select('h.greeting, h.params, h.image as image, c.title as category')

Convert the JSON-encoded image info into an array:

$image = new JRegistry;
$image->loadString($this->item->image, 'JSON');
$this->item->imageDetails = $image;

Step 9: Site Layout File

In the layout file, you need to display the image on the page.

<?php
$src = $this->item->imageDetails['image'];
if ($src)
{
$html = '<figure>
<img src="/%s" alt="%s" >
<figcaption>%s</figcaption>
</figure>';
$alt = $this->item->imageDetails['alt'];
$caption = $this->item->imageDetails['caption'];
echo sprintf($html, $src, $alt, $caption);
} ?>

Step 10: Site XML Form

To enhance this form to allow the user to upload an image, together with the associated caption and alt text, add these fields in the XML form definition.

<fields name="imageinfo" label="COM_HELLOWORLD_HELLOWORLD_IMAGE_LABEL">
<field
name="image"
type="file"
label="COM_HELLOWORLD_HELLOWORLD_PICTURE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_PICTURE_DESC"
accept="image/*"
>
</field>
<field
name="caption"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_CAPTION_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_CAPTION_DESC"
size="40"
class="inputbox"
>
</field>
<field
name="alt"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_ALTTEXT_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_ALTTEXT_DESC"
size="40"
class="inputbox"
>
</field>
</fields>

In the direction database to form, in the front-end you are not going to be editing an existing record, you are just allowing the user to enter a new record, so you don't need to worry about conversion in this direction.

In the direction form to database, the model save() method is using JTable functionality to write to the database, so the bind() method override will apply on the site side as well. So, you just need to ensure that the fields in the xml form has name attributes which are the same as those in the admin form.

Step 11: Site Editing Layout

For displaying the front-end form, the controller, form view and form model will not require any changes. The layout file will require the attribute enctype="multipart/form-data" added to the <form> tag to enable a file upload.

<form action="<?php echo JRoute::_('index.php?option=com_helloworld&view=form&layout=edit'); ?>"
method="post" name="adminForm" id="adminForm" class="form-validate" enctype="multipart/form-data">

Step 12: Site Controller File

There are significant changes in the controller which handles the HTTP POST from the form.

// save the form data and set up the redirect back to the same form, 
// to avoid repeating them under every error condition
$app->setUserState($context . '.data', $data);
$this->setRedirect($currentUri);

// Handle the uploaded file - get it from the PHP $_FILES structure

$fileinfo = $this->input->files->get('jform', array(), 'array');
$file = $fileinfo['imageinfo']['image'];
/* The $file variable above should contain an array of 5 elements as follows:
* name: the name of the file (on the system from which it was uploaded), without directory info
* type: should be something like image/jpeg
* tmp_name: pathname of the file where PHP has stored the uploaded data
* error: 0 if no error
* size: size of the file in bytes
*/

// Check if any files have been uploaded
if ($file['error'] == 4) // no file uploaded (see PHP file upload error conditions)
{
$validData['imageinfo'] = null;
}
else
{
if ($file['error'] > 0)
{
$app->enqueueMessage(JText::sprintf('COM_HELLOWORLD_ERROR_FILEUPLOAD', $file['error']), 'warning');
return false;
}

// make sure filename is clean
jimport('joomla.filesystem.file');
$file['name'] = JFile::makeSafe($file['name']);
if (!isset($file['name']))
{
// No filename (after the name was cleaned by JFile::makeSafe)
$app->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_BADFILENAME'), 'warning');
return false;
}

// files from Microsoft Windows can have spaces in the filenames
$file['name'] = str_replace(' ', '-', $file['name']);

// do checks against Media configuration parameters
$mediaHelper = new JHelperMedia;
if (!$mediaHelper->canUpload($file))
{
// The file can't be uploaded - the helper class will have enqueued the error message
return false;
}

// prepare the uploaded file's destination pathnames
$mediaparams = JComponentHelper::getParams('com_media');
$relativePathname = JPath::clean($mediaparams->get($path, 'images') . '/' . $file['name']);
$absolutePathname = JPATH_ROOT . '/' . $relativePathname;
if (JFile::exists($absolutePathname))
{
// A file with this name already exists
$app->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_FILE_EXISTS'), 'warning');
return false;
}

// check file contents are clean, and copy it to destination pathname
if (!JFile::upload($file['tmp_name'], $absolutePathname))
{
// Error in upload
$app->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_UNABLE_TO_UPLOAD_FILE'));
return false;
}

// Upload succeeded, so update the relative filename for storing in database
$validData['imageinfo']['image'] = $relativePathname;
}