Calling your plugin from the browser
The pages of this guide:
- Extending Admin Screens: start here
- Admin screen events
- A complete feature plugin
- Calling your plugin from the browser (this page)
Sometimes a script on an admin screen needs a small answer from your plugin without loading a page: is this row
kept, what does this code look up to. Joomla has an entry point for that, the component com_ajax. It is part of
Joomla, not of Alfa Commerce, and Alfa Commerce adds no entry point of its own for feature plugins. With it your
plugin answers a request without a controller, a view or a route of its own.
How the request reaches your plugin
The browser calls this address in the administrator:
administrator/index.php?option=com_ajax&group=alfa&plugin=keeplist&format=json
| Part | Meaning |
|---|---|
group | The plugin group Joomla loads for the request. Without it Joomla loads the group ajax, and your plugin is never asked. |
plugin | The second half of the event's name. Joomla announces onAjax followed by this value with a capital first letter: onAjaxKeeplist. |
format | json wraps the answer as shown below. raw prints it as plain text. Leave it out and Joomla draws a whole admin page around the answer. |
Joomla then loads every enabled plugin of the group and announces the event. Your plugin hears it only if the event
is in its getSubscribedEvents(). A method with the right name that is not listed there is never called.
What Joomla checks, and what it leaves to you
Joomla checks one thing. In the administrator, a person who is not logged in is refused before your handler runs.
Everything else is yours to check:
- The side of the site. The same address works on the storefront (
index.php?option=com_ajax...), for any visitor. If the answer is for staff, refuse when the request is not in the administrator. - The form token.
com_ajaxchecks none. Check it yourself withSession::checkToken('get')for a request that carries it in the address, orSession::checkToken()for a posted form. Do this for every request that changes something, and for every answer that is not public. - The permission. Being logged in to the administrator is not a permission. Ask for the one your answer needs,
for example
core.manageoncom_alfa, or a permission of your own (see Permissions).
Answering
The handler receives Joomla's AjaxEvent. Give your answer with addResult(). What the method returns is ignored.
Add this to the plugin of A complete plugin. Two more use lines at the top of the class
file:
use Joomla\CMS\Event\Plugin\AjaxEvent;
use Joomla\CMS\Session\Session;
One more line in getSubscribedEvents():
'onAjaxKeeplist' => 'onAjaxKeeplist',
And the handler:
/** Answers the browser: which manufacturers are kept. Reached through com_ajax. */
public function onAjaxKeeplist(AjaxEvent $event): void
{
$app = $this->getApplication();
// The same address exists on the storefront. This answer is for staff only.
if (!$app->isClient('administrator')) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
// com_ajax checks no form token. The page's script sends it in the address.
if (!Session::checkToken('get')) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN_NOTICE'), 403);
}
// com_ajax checks no permission either.
if (!$app->getIdentity()->authorise('core.manage', 'com_alfa')) {
throw new \RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
$event->addResult(['kept' => $this->keptIds()]);
}
The script that asks. Build the address from the page's own options, not from a relative path, and send the form token with it:
const paths = Joomla.getOptions('system.paths');
const token = Joomla.getOptions('csrf.token');
const url = paths.baseFull + 'index.php?option=com_ajax&group=alfa&plugin=keeplist&format=json&' + token + '=1';
const response = await fetch(url, {headers: {'X-Requested-With': 'XMLHttpRequest'}});
const answer = await response.json();
if (!response.ok || !answer.success) {
throw new Error(answer.message || 'The request failed.');
}
const kept = answer.data[0].kept;
csrf.token is in the page's options on the Manufacturers list of 2.2.0, where this was tried. If a screen you
work on does not carry it, HTMLHelper::_('form.csrf') adds it.
What comes back
With format=json the answer is always this envelope:
{"success":true,"message":null,"messages":null,"data":[{"kept":[1]}]}
data is a list with one entry for each addResult() call, so your answer is data[0].
When the handler throws, or Joomla refuses the request, the envelope carries the error's message:
{"success":false,"message":"The security token did not match. The request was cancelled to prevent any security breach. Please try again.","messages":null,"data":null}
Three things to know about errors:
- With
format=jsonthe HTTP status stays 200, whatever code the error carries. Readsuccess, not the status. Withformat=rawthe status is the error's code (403 in the handler above) and the body is the error's class and message as text. - A request nobody answered looks like a success. If the event is not in
getSubscribedEvents(), the plugin is disabled, or thepluginvalue is misspelled, the answer is{"success":true,"message":null,"messages":null,"data":[]}. An emptydatalist means your handler did not run. - While the shop is paused, Alfa Commerce answers for you. During Tools, Reset and clear, "Clear
everything", a
com_ajaxrequest whosegroupstarts withalfais refused with status 503 before it reaches any plugin.
The handler and the script above were run on a fresh Alfa Commerce 2.2.0 (Joomla 6.1.4, PHP 8.3): staff with the token got the kept ids, staff without the token got the token error, and a visitor who was not logged in and a request on the storefront were both refused.