Using Plugins to Override Core Classes
We can use the existing plugin framework to override most core classes in Joomla. Note: This is an advanced topic and not something that you want to do unless you really need to. However, it is useful to know about in case you need it.
How Plugins Are Imported
We have seen that, to trigger a plugin, you first use the JPluginHelper::importPlugin() method to import the plugin. This adds the class and its methods to working memory. If we take a closer look at how this method works, we see that the code that actually does the import is in the private import() method of the JPluginHelper class (libraries/joomla/plugin/helper.php), as follows:
if (!isset($paths[$path])) { require_once $path; } $paths[$path] = true;
The first line checks to see if this specific plugin has already been added. The $paths variable is an associative array containing all the plugins that have already been imported. The key to the array is the full path to each plugin file and the value of each element is the class name. We use the PHP function isset() to check if this element is in the array. If not, then the PHP command require_once includes this file.
Finally, the value for this element is set to the boolean true, which ensures that the next time through this element will be set in the array, so the require_once will not be called again for the same file.
There are two important points to understand about this process:
- As discussed earlier, normally plugin files declare classes, so no code is executed at that time. The only thing that happens is that the class and its methods are loaded into memory so that their methods can be called later in the cycle. In that case, no code is actually executed as a result of the JPluginHelper::importPlugin() method.
- Nothing in Joomla requires a plugin to be a class declaration. A plugin can be a simple PHP script—that is, one that executes as soon as it is included. If we make a plugin this way, it will execute immediately, as soon as the JPluginHelper::importPlugin method is executed (instead of when the event is triggered).
This provides a mechanism for loading any PHP script whenever we import plugins.
How Joomla Classes Are Loaded
Next, we need to understand an important point about how Joomla core classes are loaded into working memory. If we look at the jimport function that is typically used to load Joomla core classes, we see it is a function in the file libraries/loader.php. Note that this is a free-standing function, not a class method. That is why it is invoked just with the function name and no class name. The code for this function is shown here:
function jimport($path) { return JLoader::import($path); }
It simply invokes the JLoader::import() method. The first lines of code in the JLoader::import() method are the following:
// Only import the library if not already attempted. if (!isset(self::$imported[$key]))
This is checking to see whether we have already imported this class. The value self::$imported is a static associative array with a key (the variable $key) equal to the argument passed to JImport (for example, “joomla.plugin.plugin”) and a value of boolean true or false. When a class is imported, an element is added to this array, and the value is set to true if the import was successful and false if it was unsuccessful. So, once a class has been imported, Joomla won’t try to import it again.
The JLoader::load() and JLoader::_autoload() (the Platform “autoloader”) methods also check to see if a class has already been loaded before trying to load a class.
So the important point is this: if the class already exists—meaning it is already loaded into working memory—we skip loading this class. The method just returns a value of true and exits. None of the Joomla load methods will try to load a class a second time.
This means that we can use a plugin to load a class into working memory, as long as we do it before it gets loaded by the Joomla core programs. If we do this, the methods from our class will be used instead of the methods from the core class.
As it happens, system plugins are loaded into working memory very early in the Joomla execution cycle, before most (but not all) Joomla core classes.
Example: Override the JTableNested Class
Let’s do a quick example to illustrate this. We will override the core JTableNested class. This class is the parent class for all the nested table classes in Joomla (for example, JTableCategories for the #__categories table). In this example, we will demonstrate how to override this class but we will leave it to the reader to imagine what code and behavior you might want to change.
Here are the steps:
- Create a new folder called plugins/system/myclasses and copy the file libraries/joomla/database/tablenested.php to this new folder. This will give you a file called plugins/system/myclasses/tablenested.php. (Remember to add index.html files to all the new folders we create.)
- Edit the new file and replace the existing rebuild() method with the following code:
public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = '')
{
exit('From myclasses/tabelnested.php file');
}This code will simply prove that we are running our override class in place of the core class. When we press the Rebuild option (for example, in the Category Manager: Articles), if we are running our method, the program should exit with the message just shown.
- Now we need to add the plugin to load our class in place of the core class. We will call the plugin “myclasses.” To do this, create a new file called myclasses.php in the plugins/system/myclasses folder.
- In the new file (plugins/system/myclasses/myclasses.php), add the code shown in Listing 5.7.
Listing 5.7. myclasses.php File
<?php /** * Demonstration plugin to replace a core class. * This is fired on the first system import (before * the onBeforeInitialise event). */ // no direct access defined('_JEXEC') or die; // Replace core JTableNested with override version include_once JPATH_ROOT.'/plugins/system/myclasses/tablenested.php';
Notice that this code is not declaring a class. It is simply running a script. This means that it will be executed as soon as the system plugins are imported, before the first system event. This code just includes our new tablenested.php file.
- Create the XML file for this plugin (plugins/system/myclasses/myclasses.xml) with the code shown in Listing 5.8.
Listing 5.8. myclasses.xml File
<?xml version="1.0" encoding="utf-8"?> <extension version="2.5" type="plugin" group="system"> <name>plg_system_myclasses</name> <author>Mark Dexter and Louis Landry</author> <creationDate>January 2012</creationDate> <copyright>Copyright (C) 2012 Mark Dexter and Louis Landry.</copyright> <license>GPL2</license> <authorEmail>admin@joomla.org</authorEmail> <authorUrl>www.joomla.org</authorUrl> <version>1.6.0</version> <description>MyClasses plugin demonstration</description> <files> <filename plugin="myclasses">myclasses.php</filename> <filename>index.html</filename> </files> <config> </config> </extension>
- Navigate to the back end of Joomla and Discover and Install the plugin as we have done before in the previous examples. Remember also to enable the plugin in the Plugin Manager.
- Navigate to Content → Category Manager in the Joomla back end and click on the Rebuild icon. Joomla should halt and you should see the message “From myclasses/tabelnested.php file.” This indicates that we have successfully overridden this core class.
This technique can be used to override most Joomla core classes, except for those that are already loaded before the system plugins are imported.
If you override a core class in this manner, you don’t need to worry about your class getting overwritten during a Joomla upgrade. So this technique is much better than simply hacking core files. However, a word of caution is in order here. If there are bug fixes for any core classes you have overridden, you will need to check whether these fixes apply to your override classes. If so, you will need to apply the fixes yourself. This will be especially important if the bug fixes correct security issues.