Notifly in Joomla
In Joomla everything is extended through plugins. A small system plugin
subscribes to events (onUserAfterSave, onContentAfterSave,
onContactAfterSubmit) and sends a push to Notifly. No third-party libraries are
needed — the built-in HTTP client of the Joomla framework is used.
We reuse the shared notifly_send() helper — the same one as for the other
CMSes. Inside the plugin it is easiest to wrap it in a private
method and keep the token and URL in the plugin parameters.
Plugin structure
Section titled “Plugin structure”plugins/system/notifly/├── notifly.xml└── src/Extension/Notifly.phpnotifly.xml — the manifest with url and token parameters:
<?xml version="1.0" encoding="utf-8"?><extension type="plugin" group="system" method="upgrade"> <name>System - Notifly</name> <author>Notifly</author> <version>1.0.0</version> <namespace path="src">Notifly\Plugin\System\Notifly</namespace> <files> <folder plugin="notifly">src</folder> </files> <config> <fields name="params"> <fieldset name="basic"> <field name="url" type="url" label="Notifly URL" default="https://your-notifly.example.com" /> <field name="token" type="text" label="App token" default="AGdjfk_L.dKe8q" /> </fieldset> </fields> </config></extension>Plugin class
Section titled “Plugin class”src/Extension/Notifly.php — this is where our notifly_send() lives (as a
private method) together with all the event subscriptions:
<?php
namespace Notifly\Plugin\System\Notifly\Extension;
\defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;use Joomla\CMS\Http\HttpFactory;use Joomla\CMS\Uri\Uri;
final class Notifly extends CMSPlugin{ /** Universal helper (same logic as in /cases/cms/). */ private function send(string $title, string $message, int $priority = 5): void { $url = $this->params->get('url'); $token = $this->params->get('token'); if (!$url || !$token) { return; }
try { HttpFactory::getHttp()->post( "$url/message?token=$token", json_encode([ 'title' => mb_substr($title, 0, 200), 'message' => mb_substr($message, 0, 1500), 'priority' => $priority, ], JSON_UNESCAPED_UNICODE), ['Content-Type' => 'application/json'], 5 ); } catch (\Throwable $e) { // Never break the site request because of a notification. } }
/** New registration or user change. */ public function onUserAfterSave($user, $isNew, $success, $msg): void { if (!$success || !$isNew) { return; } $this->send( "👤 Новый пользователь: {$user['username']}", "Email: {$user['email']}\n" . "ID: {$user['id']}\n" . "Профиль: " . Uri::root() . "administrator/index.php?option=com_users&task=user.edit&id={$user['id']}", 4 ); }
/** New or edited article / content item. */ public function onContentAfterSave($context, $article, $isNew): void { if ($context !== 'com_content.article') { return; } $action = $isNew ? 'создан' : 'обновлён'; $this->send( "📄 Материал $action: {$article->title}", "ID: {$article->id}\n" . "Категория: {$article->catid}\n" . "Открыть: " . Uri::root() . "administrator/index.php?option=com_content&task=article.edit&id={$article->id}", $isNew ? 5 : 4 ); }
/** Form submission via com_contact. */ public function onContactAfterSubmit($contact, $data): void { $this->send( "✉️ Обращение через контакт: {$contact->name}", "Тема: " . ($data['contact_subject'] ?? '—') . "\n" . "Сообщение:\n" . mb_substr($data['contact_message'] ?? '', 0, 1000), 7 ); }}PHP errors
Section titled “PHP errors”The system plugin is loaded very early, so it is also a convenient place to catch
fatal errors via register_shutdown_function — for example, in
onAfterInitialise:
public function onAfterInitialise(): void{ register_shutdown_function(function () { $err = error_get_last(); if ($err && \in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { $this->send( "🔥 Fatal: {$err['message']}", "Файл: {$err['file']}:{$err['line']}\n" . "URL: " . ($_SERVER['REQUEST_URI'] ?? '-'), 10 ); } });}Where to enable the plugin
Section titled “Where to enable the plugin”- Zip the
notiflyfolder and install it via System → Install → Extensions. - Open System → Plugins, find “System - Notifly”, set the URL and token, and enable the plugin.
System plugins fire on every frontend and admin request — that is enough to catch all of the events listed above.
Throttling
Section titled “Throttling”To keep the contact form or bot registrations from flooding the channel, cache
the sending via the built-in \Joomla\CMS\Cache\Cache:
$cache = \Joomla\CMS\Factory::getCache('notifly', '');$key = 'throttle_' . md5($title);if ($cache->get($key)) { return;}$cache->store(1, $key);Benefits
Section titled “Benefits”- Instant reaction to enquiries. A com_contact enquiry — a push before the email even reaches the mailbox.
- Content control. Every article edit is visible to the editor right away.
- A cheap error monitor without Sentry for a small Joomla site.
What to improve next
Section titled “What to improve next”- Separate Notifly channels for content, users and errors.
- Priorities by content category via plugin parameters.
- Passing the article/user
idinto extras for a deep link.