MarkRS/misc/override the core
From Joomla! Documentation
< User:MarkRS | misc
Override core functions, without hacking[edit]
This was gleaned from a nugget of information picked up in the Joomla IRC channel. A very useful place.
Thanks to Hackwar for the pointer.
Audience[edit]
If you need to change something that requires coding, but you're not a hotshot PHP programmer, I hope you'll find this useful.
You'll need to have an idea of how components run, since I have illustrated my point with a component override.
Why Change the Core[edit]
The Joomla core offers some fabulous functionality that can be used for lots of situations. Sometimes those situations don't exactly fit the way the core goes about things, but they're so close that it would be a cryin' shame, and a real waste of valuable time, to write the whole thing again, even if that's just copying what's already there. It would be oh so nice to be able to make slight changes that gave the website a really polished, made to measure, feel when in fact you're really using the features available out of the box, almost.
Say you wanted to use the contacts component supplied, just slightly differently. If you just want to make the output look a little different, then that's no problem. The output override system will let you do that easily. If you also want to add, say, the filtering functionality that's available in the back end for contacts, then you've got a different job. Yes, you can add those filter boxes to the output, but without changes to the model and the view they won't do anything.
General Overview[edit]
Change the model? Without hacking the core? Yes, don't hack the core code! It'll all end in tears.
There is a plugin that you can use to do this, MVC override. That's a nice piece of work, the problem is it's not being updated. It also creates some problems in some situations that are hard to get around.
Fortunately for us, the expert beginner, it's not hard to create our own plugin that makes it easy for us to do it ourselves.
Strategy[edit]
We use a strategy that has been built into the Joomla core. The core always checks for framework classes in memory before it loads them from code. So all we have to do is ensure that our own copy of specific classes are loaded before Joomla would load them itself!
Joomla's plugin system is perfect for what we want. You simply need to find a trigger point late enough in the execution chain to have the facilities you're modifying available, and not so late that the classes you want to modify have already been loaded in their original form.
Let's do it[edit]
Situation[edit]
Let's take the situation mentioned above. You want to use the contacts component to show details for people in a certain category. The existing "Contacts -> Show Category" view is what you want.
Furthermore, let's say that the list is going to be very large and you want to offer your user search and filter facilities, the good ones that you the administrator can see in the back end, not the woefully inadequate search box offered in the standard front end.
Requirement[edit]
You're going to need to override the output template and use a new (although easy to copy from the back end) filter form, which means you need to override the view class (since the current front end one doesn't use a form), and override the model, to make use of the nifty filters you're bringing in. Here we go!
Output Template[edit]
This is the bit you're most likely to be familiar with. The very fine page about this will, I trust, be enough for you if you need a hand. In addition, look in the corresponding administration template to see how to include the filter bar. It's very easy only taking a single line of code.
The entire search and filter bar can be included in the template with the line
echo JLayoutHelper::render('joomla.searchtools.default', array('view' => $this));
Include this in the existing search bar section and remove the lines that implement the normal search bar. To include the javascript "chosen" feature, which makes for nicer selection boxes, I've included the line
JHtml::_('formbehavior.chosen', 'select');
in the top of the template file.
Now to make it do something.
Getting your oar in first, the plugin =[edit]
If you need to see how simple it is to write a plugin, make sure to read that document mentioned in the "Strategy" section above.
How do plugins work? In brief, there are event triggers peppered throughout the core code. You may have seen something like
$this->triggerEvent('onAfterRoute');
there, where "onAfterRoute" might've been "onBeforeRender" or a whole host of other trigger points.
When such an event is triggered, Joomla looks for all plugins that are registered to react to that trigger and executes them one after another. I have chosen "onAfterRoute", which is a system generated trigger, I am using the category view of the contact component, and I have called my plugin "AskFred".
This all means that I have created a folder called "askfred" under the folder "/plugins/system/". In that directory I have an xml file called askfred.xml, which defines my plugin, and a code file called askfred.php, which has the code my plugin needs to run. The file askfred.php looks like this :
<?php
// no direct access
defined( '_JEXEC' ) or die;
class plgSystemAskfred extends JPlugin
{
function onAfterRoute()
{
$input = JFactory::getApplication()->input;
if($input->get('option') == 'com_contact' && $input->get('view') == 'category') {
require_once dir(__FILE__) . 'modelforaskfred.php';
require_once dir(__FILE__) . 'viewforaskfred.php';
}
return true;
}
}
Simple, eh?
Having just dropped these files into the correct place, I then need to run the "Discover Extension" utility in the back end so Joomla can incorporate my plugin into its workings, but that's about it!
The name of my plugin class is defined by the skeleton "plg<directory_path>", omitting the "/plugins/" part of the directory path. Methods in this plugin are named after the triggers that we want them to be called by. Now, my trigger will be called after the routing phase of loading web pages. Of course, that happens after every page is routed, so the first thing that I do is limit my code to running when the com_contact component is running the category view. I don't suppose it would cause a problem if I didn't check, but it would waste time and space, which is a very bad thing.
Having checked that the plugin is being run by the page load that I want, it then loads the two files which create my custom versions of the model and view.
View[edit]
As the supplied front end template doesn't use a form at all, the view doesn't load one. We have to change that.
Since we are using the category view of the contacts component, we need to cause a class called ContactViewCategory to be loaded before the framework tries to load one itself.
My file viewforaskfred.php is a copy of the file /components/com_contact/views/category/view.html.php, with my custom additions. I only need to add two lines,
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
which I placed after the code, "parent::commonCategoryDisplay();"
When the component controller deems it time to load the view, it finds my version already in memory, and is happy.
Filter Elements[edit]
Here I was very slightly naughty. Rather than add a path to an innocuous location for the form xml definition file, I put it in the place in the core that Joomla expects it.
I copied the file /administrator/components/com_contact/models/forms/fiter_category.xml to /components/com_contact/models/forms/filter_category.xml .
Model[edit]
The model is just as simple as the view, although it needs rather more modification. My file modelforaskfred.php is simply a copy of the file /components/con_contact/models/category.php and contains the class ContactModelCategory, so when the view (in fact my own copy) comes to load the class, it finds my version already loaded and is very happy.
More changes are need in the model than the view because I now have an extra few dropdowns that can be used to restrict the contacts displayed.
It is easy to find the sections of code that you want from the admin version of the model and transplant them into your model that will be used in the copy of the front end model.
There are three methods that need to be modified. To make sure your filter bar retains its contents across different page loadings, you need to recreate the getStoreId method from the backend that didn't previously exist in the front at all.
After that, add the filter values into the populateState method, and add selections for your filters into the getListQuery method, all lifted from the back end.
In fact, rather than pick up filter values one at a time, I used
$filters = $input->get('filter', '', 'array')
to get them all at once and then used them from the resulting array. The similar action for the 'list' values.