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:
-
copy
/administrator/components/com_media/views/images
to/templates/protostar/html/com_media/images
-
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
- tmpl
- view.html.php
- imageslist
- tmpl
- default_folder.php
- default_image.php
- default.php
- tmpl
- view.html.php
- images
- com_media
- html
- protostar
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.
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.
- Node.js which includes NPM.
- Browserify
- Gulp
- Command shell such as terminal, Cygwin or Windows Command prompt.
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.