JOOMLA

Customizing Media Manager

These examples show how to load copies of com_media views and templates instead of their core counterparts for customization without hacking any core Joomla files.

Here is the secret sauce to override com_media views and templates. A system plugin with an onAfterInitialise() event handler to capture request data and evaluate parameters using JInput. For example,

/plugins/system/mcm/mcm.php
// no direct access
defined ( '_JEXEC' ) or die ( 'Restricted access' );

jimport('joomla.plugin.plugin');

class plgSystemMcm extends JPlugin { 

	public function onAfterInitialise() {

		$input = JFactory::getApplication()->input;

		if('com_media' == $input->getCMD('option')) {

			$this->loadView($input->getCMD('view'));

		}

		return true;
	}

	protected function loadView($view)
	{
		if (('images' == $view) || ('imagesList' == $view)) {

			$overridePath = FOFPlatform::getInstance()->getTemplateOverridePath('com_media', true) . '/' . $view;

			require_once $overridePath . '/view.html.php';
		}
	}
}

Application Specific onAfterInitialise()

This version of the onAfterInitialise() function shows how to apply the override for just the frontend (site) views and templates.

public function onAfterInitialise() {

	$app = JFactory::getApplication();

	if ($app->isSite()) {

		if('com_media' == $app->input->getCMD('option')) {

			$this->loadView($app->input->getCMD('view'));

		}

		return true;
	}

	return false
}

Create the new custom templates and views by copying the folders from the com_media component into your template folders. For example, if you want to customize media manager for your protostar template, copy the com_media folders into the protostar template as follows:

  1. copy
    /administrator/components/com_media/views/images
    to
    /templates/protostar/html/com_media/images

  2. copy
    /administrator/components/com_media/views/imageslist
    to
    /templates/protostar/html/com_media/imageslist

Directory structure of com_media protostar template overrides.

  • templates
    • protostar
      • html
        • com_media
          • images
            • tmpl
              • default.php
          • view.html.php
          • imageslist
            • tmpl
              • default_folder.php
              • default_image.php
              • default.php
          • view.html.php

Then modify both copies of view.html.php to include their respective default.php template copies.

At the end of the display function, comment out or replace,
parent::display($tpl);

with an include directive to the template copy,
include( dirname(__FILE__) . '/tmpl/default.php');

for example, in both the images and imageslist folders:

/templates/protostar/html/com_media/images/view.html.php

/templates/protostar/html/com_media/imageslist/view.html.php

//parent::display($tpl);
include( dirname(__FILE__) . '/tmpl/default.php');

Also comment or replace both loadTemplate('folder') and loadTemplate('image') function calls in the imageslist default.php template and include their file and folder default.php template copies.

for example,

/templates/protostar/html/com_media/imageslist/tmpl/default.php

<?php for ($i = 0, $n = count($this->folders); $i < $n; $i++) :
	$this->setFolder($i);
	//echo $this->loadTemplate('folder');
	include( dirname(__FILE__) . '/default_folder.php');
endfor; ?>

<?php for ($i = 0, $n = count($this->images); $i < $n; $i++) :
	$this->setImage($i);
	//echo $this->loadTemplate('image');
	include( dirname(__FILE__) . '/default_image.php');
endfor; ?>

The com_media views and templates will now be loaded instead of their core counterparts and can be customized without hacking any core Joomla files. The plugin code above can be found here on GitHub. The next section covers extending the com_media controller to handle base64 image uploads.

Source Code

Extending The Media Component

This section documents extending the com_media component with a custom component to handle base64 image uploads. When an image is loaded on the client for upload, it is resized and converted to base64 with JavaScript to reduce the size of the image upload before it is sent to the server.

Component Entry Point

The site templates we will modify later contain the user interface for uploading image files. Our component entry point is therefore going to be in the site components folder.

/components/com_media_mcm/media_mcm.php

<?php
defined('_JEXEC') or die;

// Load the com_media_mcm language files, default to the admin file and fall back to site if one isn't found
$lang = JFactory::getLanguage();
$lang->load('com_media_mcm', JPATH_ADMINISTRATOR, null, false, true)
||	$lang->load('com_media_mcm', JPATH_SITE, null, false, true);

// Hand processing over to the admin base file
require_once JPATH_COMPONENT_ADMINISTRATOR . '/media_mcm.php';

The media component is an administrator component, therefore the new component that is extending com_media will also reside in the administrator components folder.

/administrator/components/com_media_mcm/media_mcm.php

<?php
defined('_JEXEC') or die;

$input  = JFactory::getApplication()->input;
$user   = JFactory::getUser();
$asset  = $input->get('asset');
$author = $input->get('author');

// Access check.
if (!$user->authorise('core.manage', 'com_media') && (!$asset or (!$user->authorise('core.edit', $asset)
	&& !$user->authorise('core.create', $asset)
	&& count($user->getAuthorisedCategories($asset, 'core.create')) == 0)
	&& !($user->id == $author && $user->authorise('core.edit.own', $asset))))
{
	return JError::raiseWarning(403, JText::_('JERROR_ALERTNOAUTHOR'));
}

$params = JComponentHelper::getParams('com_media');

// Set the path definitions
$popup_upload = $input->get('pop_up', null);
$path         = 'file_path';
$view         = $input->get('view');

if (substr(strtolower($view), 0, 6) == 'images' || $popup_upload == 1)
{
	$path = 'image_path';
}

define('COM_MEDIA_MCM_BASE', JPATH_ROOT . '/' . $params->get($path, 'images'));

$controller = JControllerLegacy::getInstance('MediaMcm', array('base_path' => JPATH_COMPONENT_ADMINISTRATOR));
$controller->execute($input->get('task'));
$controller->redirect();

Controller

The MediaMcmController extends the com_media MediaController to expose the upload_base64 task method for handling the image upload request.

/administrator/components/com_media_mcm/controller.php

<?php
/**
 * MediaMcm Controller
 * Depends on MediaController
 *
 */

defined('_JEXEC') or die;

jimport('joomla.filesystem.file');

require_once JPATH_ADMINISTRATOR . '/components/com_media/controller.php';

class MediaMcmController extends MediaController
{
	public function upload_base64()
	{
		// Check for request forgeries
		JSession::checkToken('request') or jexit(JText::_('JINVALID_TOKEN'));
		$params = JComponentHelper::getParams('com_media');

		// Get data from the request
		$data         = $this->input->get('base64str', null, null);
		$name         = $this->input->get('base64name', null, 'STRING');
		$return       = JFactory::getSession()->get('com_media.return_url');
		$this->folder = $this->input->get('folder', '', 'path');

		// Don't redirect to an external URL.
		if (!JUri::isInternal($return))
		{
			$return = '';
		}

		// Set the redirect
		if ($return)
		{
			$this->setRedirect($return . '&folder=' . $this->folder);
		}
		else
		{
			$this->setRedirect('index.php?option=com_media&folder=' . $this->folder);
		}

		// Authorize the user
		if (!$this->authoriseUser('create'))
		{
			return false;
		}

		// Total length of post back data in bytes.
		$contentLength = (int) $_SERVER['CONTENT_LENGTH'];

		// Instantiate the media helper
		$mediaHelper = new JHelperMedia;

		// Maximum allowed size of post back data in MB.
		$postMaxSize = $mediaHelper->toBytes(ini_get('post_max_size'));

		// Maximum allowed size of script execution in MB.
		$memoryLimit = $mediaHelper->toBytes(ini_get('memory_limit'));

		// Check for the total size of post back data.
		if (($postMaxSize > 0 && $contentLength > $postMaxSize)
			|| ($memoryLimit != -1 && $contentLength > $memoryLimit))
		{
			JError::raiseWarning(100, JText::_('COM_MEDIA_MCM_ERROR_WARNUPLOADTOOLARGE'));

			return false;
		}

		$file = [];
		$file['content'] = $this->decode_base64($data);

		// validate the decoded base64 string
		if (!$this->validate_base64($file['content'], 'image/jpeg'))
		{
			// invalid base64 'image/jpeg'
			JError::raiseWarning(100, JText::_('COM_MEDIA_MCM_INVALID_REQUEST'));

			return false;
		}

		// Perform basic checks on file info before attempting anything
		$file['name']     = JFile::makeSafe($name);
		$file['filepath'] = JPath::clean(implode(DIRECTORY_SEPARATOR, array(COM_MEDIA_MCM_BASE, $this->folder, $file['name'])));
		$file['size']     = strlen($file['content']);

		$uploadMaxSize = $params->get('upload_maxsize', 0) * 1024 * 1024;
		$uploadMaxFileSize = $mediaHelper->toBytes(ini_get('upload_max_filesize'));

		if (($uploadMaxSize > 0 && $file['size'] > $uploadMaxSize)
			|| ($uploadMaxFileSize > 0 && $file['size'] > $uploadMaxFileSize))
		{
			// File size exceed either 'upload_max_filesize' or 'upload_maxsize'.
			JError::raiseWarning(100, JText::_('COM_MEDIA_ERROR_WARNFILETOOLARGE'));

			return false;
		}

		if (JFile::exists($file['filepath']))
		{
			// A file with this name already exists
			JError::raiseWarning(100, JText::_('COM_MEDIA_MCM_ERROR_FILE_EXISTS'));

			return false;
		}

		if (!isset($file['name']))
		{
			// No filename (after the name was cleaned by JFile::makeSafe)
			$this->setRedirect('index.php', JText::_('COM_MEDIA_MCM_INVALID_REQUEST'), 'error');

			return false;
		}

		$this->uploadFile($file);

		return true;
	}

	/**
	 * Check that the user is authorized to perform this action
	 *
	 * @param   string  $action  - the action to be peformed (create or delete)
		*
		* @return  boolean
		*
		* @since   1.6
		*/
	protected function authoriseUser($action)
	{
		if (!JFactory::getUser()->authorise('core.' . strtolower($action), 'com_media'))
		{
			// User is not authorised
			JError::raiseWarning(403, JText::_('JLIB_APPLICATION_ERROR_' . strtoupper($action) . '_NOT_PERMITTED'));

			return false;
		}

		return true;
	}

	/**
	 * Task method
	 *
	 * @param   string  $data 	jinput parameter
		*
		*/
	protected function decode_base64($data) {

		$data = explode(',', $data, 2);

		$decoded = base64_decode($data[1]);

		return $decoded;
	}

	/**
	 * Task method
	 *
	 * @param   string  $data 	decoded(base64)
		*
		*/
	protected function validate_base64($data, $mimetype) {

		if ('image/jpeg' == $mimetype)
		{
			$img = imagecreatefromstring($data);
			if (!$img) {
				return false;
			}

			imagejpeg($img, 'tmp.jpg');
			$info = getimagesize('tmp.jpg');

			unlink('tmp.jpg');

			if ($info[0] > 0 && $info[1] > 0 && $info['mime']) {
				return true;
			}

			return false;
		}
	}

	/**
	 * Task method
	 *
	 * @param   array  $file
		*
		*/
	protected function uploadFile($file) {

		$dispatcher = JEventDispatcher::getInstance();

		$object_file = new JObject($file);
		$result = $dispatcher->trigger('onContentBeforeSave', array('COM_MEDIA_MCM.file', &$object_file, true));

		$success = file_put_contents($object_file->filepath, $object_file->content);

		if (!$success)
		{
			// Error in upload
			JError::raiseWarning(100, JText::_('COM_MEDIA_MCM_ERROR_UNABLE_TO_UPLOAD_FILE'));
			return false;
		
		// Trigger the onContentAfterSave event.
		$dispatcher->trigger('onContentAfterSave', array('com_media.file', &$object_file, true));
		$this->setMessage(JText::sprintf('COM_MEDIA_MCM_UPLOAD_COMPLETE', substr($object_file->filepath, strlen(COM_MEDIA_MCM_BASE))));
	}

}

The code files above are the main ingredients of the com_media_mcm component. The entire component can be found here on GitHub. The next section covers using these examples with a ready to test image modal user interface update.

Source Code

Image Resizer and Uploader

This section documents using the plg_system_mcm plugin, protostar com_media template overrides discussed in and the com_media_mcm component to customize the image modal user interface for front end article editors.

Image Manager – com_media_mcm
updated image modal – com_media_mcm

The articles image modal included in this code allows for drag and drop of the source image, resizes the image client side, uploads the resized image to the controller and has a checkbox to set the uploaded image as an intro image for the article.

Protostar Template Overrides

Download the protostar/html/com_media template overrides that have been modified to include all the necessary code changes for the new image modal user interface.

  • Instead of individually downloading the various files from the Joomla GitHub repository, Download or clone the entire Joomla repository in one step.

Copy the template overrides into the Protostar Template at /templates/protostar/html/com_media.

Custom Media Manager System Plugin

Download and install the plg_system_mcm plugin that contains the code from page 1 to use the template overrides. Additionally, this plugin contains configuration settings for the image resizer. After installation, navigate to the Custom Media Manager plugin in the Extension Manager and save the settings.

MCM Media Component

Download and install the com_media_mcm component.

Browserify is used to build and bundle the modular JavaScript, node modules will also be used to process the SASS files into CSS. These are the modern day tools for UI development. Download and install these requirements in order to build the user interface assets.

Install Browserify

After installing Node.js, using a Command shell, install Browserify globally. *

# global browserify install
npm install -g browserify

If you have an older version of global Browserify, run npm rm --global browserify to remove the old version before installing the newer one.

Install Gulp

Install gulp globally with the npm install -g command as follows: *

# global gulp install
npm install -g gulp

If you have an older version of global Gulp, run npm rm –global gulp to remove the old version before installing the newer one.

Install Node Modules

Using a command shell, navigate to the components media folder, /media/com_media_mcm/. Then enter the npm install command which will read the enclosed package.json folder and install all of the dependencies from NPM.

npm install

Run the these two gulp commands to browserify the javascript modules into the /media/com_media_mcm/js folder.

# browserify javascript

gulp js --src=./src/js/images.js --dest=./js

gulp js --src=./src/js/imageslist.js --dest=./js

Run the this gulp command to process the .scss file(s) into the /media/com_media_mcm/css folder.

# build sass

gulp css

Now that the JavaScript modules are converted and bundled with Browserify, the updated image modal user interface is ready for testing.


comments powered by Disqus