Obix Class Extender System Plugin
This system plugin allows the adaptation and extension of Joomla! or 3rd party core classes.
Rationale
When building websites, many times there is the need for adaptation or extension of core functionality. For instance, when a Joomla! list model does not support filtering on a specific user id. An example:
- You are building a website where registered users can manage their own articles, keeping in mind that, since the introduction of custom fields, articles can be almost anything you can imagine.
- In the frontend you provide a fancy article manager.
- Obviously you don't want users to modify eachother's articles, so you configure the ACL properly.
- You also don't want your users to see eachother's articles
in the article manager. That presents you with a problem:
the Category List menu item type does not allow you to
filter on an article's
created_by
field. - The conventional approach is to create a template override, in which you run the article list query for a second time, but this time filtering on the id of the logged in user.
With this plugin you can prevent that second, redundant query.
Joomla! core's ContentModelArticles
already has the logic
in place to filter on com_content's created_by
. So the only
thing we have to do, is create an override for the getItems()
method of the ContentModelCategory
class, to which we add
filtering on the id of the logged in user.
Installation and configuration
Minimal requirements
Joomla! 3.9.x or 4.x PHP 7.4
Installation
Installation of the plugin is the same as for any other Joomla! extension. If it is installed for the first time, as opposed to ugraded, it should be activated automatically. Doesn't do any harm to check though :)
After the installation, it is important to check the order of all system plugins. This plugin relies on being the first to load the classes that are being extended. It must therefore be executed before the original classes are loaded by other parts of the Joomla! system. Making this plugin the first in the execution order, ensures that happens.
Configuration
Class extender folder path
A path, relative to the website root, of the folder where the plugin expects to find the configuration file and extended classes. Leading and trailing slashes are ignored.
Create if non existent
Whether or not to create the extender folder if it doesn't exist yet. If a non existing folder was created, a basic JSON configuration file (see JSON specifications below) with empty fields is created as well.
What's next
Create an extended class file
- Create a folder for your extended class like so:
- Assuming the base path of the extender folder is:
[web root]/<path_to_extender_folder>
. - Assuming the path of the folder that contains
the original class you want to extend is:
[web root]/<path_to_original_class_folder>
. - Create the following folder:
[web root]/<path_to_extender_folder>/<path_to_original_class_folder>
.
- Assuming the base path of the extender folder is:
- Create a file for your extended class like so:
- Assuming the name of the class you are extending is:
SomeClass
. - Create the following file:
[web root]/<path_to_extender_folder>/<path_to_original_class_folder>/SomeClass.php
- Assuming the name of the class you are extending is:
- The code in the extended class file must contain a class
definition
- with a name identical to the name of the class you are extending and
- extending a class with that same class name but with
ExtensionBase
appended to it, - like so:
class SomeClass extends SomeClassExtensionBase
.
- Add the JSON encoded specifics of the extended class to
file
[web root]/<path_to_extender_folder>/class_extensions.json
(see JSON specifications below).
Example
Class extensions are expected in a folder named
class_extensions
. By default that folder is a subfolder of
the default template, but its location can be changed in the plugin settings.
For the example we will assume that protostar is the default
template and the class_extensions
folder is in the default location.
To create an override of the core Joomla! content category model, do the following:
-
Check if folder
[web root]/templates/protostar/class_extensions/components/com_content/models
exists and create it if it doesn't. -
In the
.../models
folder, create a file for the extended class, namedContentModelCategory.php
. -
In the extended class file create the following class definition:
class ContentModelCategory extends ContentModelCategoryExtensionBase { ... ... }
-
Assuming the file does not yet exist, create file
[web root]/templates/protostar/class_extensions/class_extensions.json
. -
Add the following to the the JSON file:
[ { "class": "ContentModelCategory", "file": "components/com_content/models/category.php" } ]
How it works
- The
onAfterInitialise
handler of the system plugin processes the specifications in the JSON file that don't have specific routes. TheonAfterRoute
handler processes the specifications that do have specifc routes. - For each extended class file found, a copy of the original
class file is created. The path of that file is composed as
follows:
- The directory for the copied file is the base path of the
original file, relative to the website root, and
prefixed with
[web root]/templates/protostar/class_extensions
- If a route specification exists, the name of that route is appended to the new directory (see JSON specifications below).
- The filename of the copy is that of the original class
file, but with
ExtensionBase
appended to it. - So for the example above, this will result in the file
[web root]/templates/protostar/class_extensions/components/com_content/models/ContentModelCategoryExtensionBase
.
- The directory for the copied file is the base path of the
original file, relative to the website root, and
prefixed with
- If a copy already exists and the original class file is newer than the existing copy, the old copy is overwritten with the newer version.
- The name of the class in the copied file gets
ExtensionBase
appended to it. So for the example above, this will result inclass ContentModelCategoryExtensionBase
. - Using
include_once
, the copied class, with the new name, is loaded first, followed by the extended class, having the same name as the original class. - Because the system plugin is the first to load the class, later references to the same class will use the already loaded, extended class definition.
What doesn't work
Due to the way Joomla! handles legacy, non namespaced classes,
a whole bunch of core classes can not be extended using this
plugin. Those classes can be found in [web root]/libraries/classmap.php
. This file is included during
the bootstrap phase, before any plugin events are triggered.
JSON specification
File [web root]/<path_to_extender_folder>/class_extensions.json
describes the
(core) classes to be extended. It is a JSON file, containing extension descriptions
as an array of objects. Each object describes a single class to be extended. At
the moment of this writing, a minimal extension description must contain at least
the following attributes:
{
"class": ...,
"file": ...
}
A complete extension description may contain the following attributes:
{
"class": "...",
"file": "...",
"client": "...",
"dependencies": [
"...",
"...",
...
],
"route": {
"name": "...",
"option": "...",
"view": "...",
"layout": "...",
"task": "...",
"format": "...",
"Itemid": "..."
}
}
If a new folder was created while configuring the plugin ((see Configuration above), a basic configuration file with these fields is created automatically.
Extension description fields
class
(required): The name of the class to be extended.
file
(required): The path of the file containing the class to be
extended, relative to the website root.
client
(optional): The part of the website the extended class is to be effectuated
for. This attribute must contain a valid client identifier, which can be one site
or administrator
. Starting with Joomla! 4, it can also be one of api
or
installation
.
dependencies
(optional): An array containing paths of files, relative to
the website root, containing classes the extended class depends on. Those class
files will be loaded before loading the extended and extending class files.
route
(optional): A set of attributes, describing a route to
match for the extended class to be effectuated. If not
present, the extended class is always in effect.
route.name
(required): the name of the subdirectory to be added to the
default path, when looking for an extended class definition.
route.option
, route.view
, route.layout
, route.task
, route.format
, and route.Itemid
(at least one required): the
values to compare to the request parameters with the same names, when
determining if a route matches.
Credits
The idea for this plugin came from an earlier prototype by
Herman Peeren
Herman's GitHub.