Richard Butterworth Digital Media Design

The media form field in Joomla 2.5

 
 
 
 
 
 
 
 
 
 
 

Guaranteed to generate a bit of online ire on StackExchange is the Joomla media form field. It doesn't do what it says on the tin; it only allows you to select images from the media manager, not pdfs, audio or video files. That said it does say very clearly on the Joomla docs that it only allows the user to select images, not arbitrary media files. Maybe it should be called images form field or similar. It is however a perfectly reasonable to expect that there will be situations where the user will want to select any media file.

Anyway, you would think that it would be easy to define a custom form field to allow the user to select any media file by extending existing code. Unfortunately, it doesn't seem to be easy at all. In fact on a couple of past projects (mentioning no names) I've committed the cardinal Joomla sin of hacking the core files to stop com_media filtering out files that aren't image files. The list of acceptable file extensions is hard coded into one of the com_media model files. Well, if the Joomla developers are going to do that, I'm going to hack their core.

However, lately I find myself working on a project where the client has set up a Joomla installation on a cloud hosted server where all the installation is done for you, and you can't get at the files on the server other than to upload extensions. So, no core hacks.

So what follows is my solution to getting round this problem with plug-ins only. Firstly caveats; this solution overrides the behaviour of the images view of com_media. It doesn't create a new custom form field leaving the core media form field to behave as described in the Joomla docs. This means that all admin-side uses of the media form field will allow all media files (e.g. in the Images and Links box of the Article editor). If you trust your users not to select videos when they should be selecting images, then this solution suffices. If you want to create a custom field to select all media files and leave the core media form field as is, then that adds another whole layer of complexity, and -- in the words of all the best text-books written by lazy authors -- is left as an exercise for the reader. Second caveat; I make no claims on general applicability. It works for the project I'm currently working on (which is Joomla 2.5). The code is on github if you want to work on it yourself, but I would treat this as a starting point for your project, not a polished solution. Thirdly, I'm first to admit that this solution is a mess, but if anyone can think of something cleaner...

What's the problem?

The core media form field opens a modal dialogue box and iframes the 'images' view of com_media into the dialogue box. You can create your own custom form field which puts different views into the iframe, but the other views defined for com_media don't allow you to select a file. There is no way of parameterising the images view to tell it which file extensions to include in the view; that is hardcoded into a com_media model file. See line 120 of...

administrator/components/com_media/models/list.php

So, possible solutions include creating your own version of com_media (e.g. com_mymedia) where you copy all of the core com_media files into a new component, change its model file and then point your custom form field at a view from that custom component. This is a horrendous amount of work. It is also just about possible to override the images view of com_media using a standard template override. However this does serious damage to the MVC concept because it requires putting a load of code in the layout file that really should be in view.html.php or even in the model file. If, like me, you simply use the default bluestork template for admin, then you'd have to create you own admin template (by copying all of bluestork into your custom template) and then add a template override. Again a solution bordering on insanity.

The solution is...

Lately there has been work showing how you can override core components, so this is the approach I will take. I will override the view.html.php file of the images view of com_media and I also need to override some of the layout files. The changes that actually need making to the view.html.php and layout files are actually quite minor.

All of this takes place in a system plug-in that uses the onAfterRoute event to load in a new version of com_media's images view class that overrides the core one, as follows...

class plgSystemComMediaOverride extends JPlugin {
      public function __construct(&$subject, $config = array()) {
         parent::__construct($subject, $config);
     }
 
     public function onAfterRoute() {
         $app = JFactory::getApplication();
         if('com_media' == JRequest::getCMD('option') &&
            $app->isAdmin()) {
             require_once(dirname(__FILE__) .
                        '/code/com_media/views/imageslist/view.html.php');
         }
     } 
}

This checks to see if we're loading the com_media component and we're in the admin site. If we are, then we require our new view.html.php file.

Note that I have stuck with the new convention of putting these sort of overrides in a directory hierarchy which starts in a directory called code and matches the existing hierarchy of the components and their views. You don't have to, its purely conventional. You could put the view.html.php file anywhere as long as the the require_once command points to it.

Now in the tradition of creating template overrides, you copy the following file...

/administrator/components/com_media/views/imageslist/view.html.php

...to...

WHEREEVER_YOUR_PLUGIN_IS/code/com_media/views/imageslist/view.html.php

...and you can happily edit this copy without damaging the core code.

Getting all the files, not just images

It turns out that the model code used by the imageslist view already gets all the files for you, its just that the imageslist view doesn't use the. The model offers a method getList which returns an array of folders, images and docs, where folders and images are self explanatory, and docs is everything else. The model's getImages method calls getList and then only returns the images part as follows...

  function getImages()
  {
    $list = $this->getList();
 
    return $list['images'];
  }
 

(/administrator/components/com_media/models/list.php line 38.)

view.html.php then calls getImages on the model, assigns the images to $images and then calls the layout files which display all the images inĀ $images. So all we have to do is add all the docs to $images and it will then display all the files in the media manager, not just the images. So we replace...

$images = $this->get('images');
 

...to...

$list = $this->get('list');
$images = array_merge($list['images'], $list['docs']);
 

...and job done, all files are displayed.

Job not done

Except of course its not quite that simple. The non-image files do not have thumbnail images for them, and the existing layout files attempt to create a thumbnail using the relative path of the file, which the layout files are expecting to be images and now aren't necessarily. So we finish up with output HTML looking like this...

<img src="path/mysong.mp3" alt="..." />
 

...and depending on your browser you get bad images with question marks, error messages and such like. So we need to assign a thumbnail image to the non-image files. Unfortunately the layout files use the property path_relative to create both the thumbnail and to determine the file that is selected. So if we set path_relative to be the address of some generic thumbnail file, then it would display the thumbnail correctly, but the user would then select the thumbnail image, not the file itself.

Instead of just merging the images and docs together we need to go through them and add a new thumb property which the layout files can use. Now fortunately the model file already assigns icon_32 and icon_16 properties to the docs files based on their extensions. Unfortunately though these properties don't actually point to the images in media/media/images. They nearly do, but not quite. I don't understand this at all, but if you chop off some of address and add some other bits you get the appropriate thumbnail url. Well I did promise this was a bit of a mess, didn't I? Messy code as follows...

    $list = $this->get('list');
    $images = array();
    foreach($list['docs'] as $d) {
        $d->thumb = JURI::root() .
                'media/media/images' .
                substr($d->icon_32, strpos($d->icon_32, '/'));
        $images[] = $d;
    }
    foreach($list['images'] as $i) {
        $i->thumb = COM_MEDIA_BASEURL . '/' . $i->path_relative;
        $images[] = $i;
    }
 

Finally we need to override the layout files so that they display the thumb property as the thumbnail instead of path_relative. Copy the file at...

/administrator/components/com_media/views/imageslist/tmpl/default_image.php

...to...

WHEREEVER_YOUR_PLUGIN_IS/code/com_media/views/imageslist/tmpl/default_image.php

Then change the line...

<?php echo JHtml::_('image', $this->baseURL.'/'.$this->_tmp_img->path_relative, JText::sprintf('COM_MEDIA_IMAGE_TITLE...

...to...

<?php echo JHtml::_('image', $this->_tmp_img->thumb, JText::sprintf('COM_MEDIA_IMAGE_TITLE...

Now we need to tell the view where to find this override. I'm not sure whether there's a more elegant way of doing this, but the _path['template'] property of the view contains an array of paths to possible layout files, usually containing the template override and the layout files in the component. Presumably the first existing file in the array gets used. So we unshift our override into this array before calling the parent's display method.

    array_unshift($this->_path['template'], dirname(__FILE__) . '/tmpl');
    parent::display($tpl);
 

So that's all! Working that out is about five hours I won't be getting back again. I hope it helps someone.

Comments

#2 Raj Thakkar 2015-09-22 09:03
Fantastic Post! Thank you so much for sharing this one really well defined all peaceful info,I Really like it,Love it- hire joomla developer
Quote
#1 Mark 2015-03-20 16:46
Richard, thanks very much for sharing this - was scratching my head for too long over this one! Much appreciated.
Quote

Add comment


Security code
Refresh