Prejsť na obsah

DACore · coming December 2026

How DACore built-in events work

DACore fires a handful of PHP events on login, permission refresh, admin page render, and AI chat. Other modules subscribe with Events::on in module.listeners.php — the same bus as the framework. That is how you debug “did login run?”, “did withMenu finish?”, “did rights refresh?” without patching DACore. Framework names (dotapp.databaser.execute, router, module lifecycle) are listed in built-in events and database triggers. This article is only what DACore itself triggers.

Common mistakes

Wrong Right
Edit app/modules/DACore/ to add a var_dump Subscribe in your module. The next DACore update wipes patches
Expect the listener return to change the HTML from Page@withMenu trigger() ignores returns. Log, or enqueue work in your module
Log the password from dacore.login.before First extra argument is the plaintext password. Log the email only
Events::hasListener('DACore.login.before') Check the lowercase key: dacore.login.before. hasListener does not fold case
Treat Notifications@push as an event name That is an API call. The bus names are the six rows below

The six names

DACore calls DotApp::DotApp()->trigger(…). After lowercase, subscribe to the keys in the first column. Mixed case in the source still lands in the same bucket.

Subscribe as Fired from Listener arguments
dacore.login.before Login CSRF form, after lockout check, before Auth::login $email, $password, $rememberMe
dacore.login.after Immediately after Auth::login (and the legacy-password retry) $login — the Auth envelope, or false
dacore.permissions.refresh AuthTest middleware, when the permissions TTL expires None
dacore:page@withmenu.rendering Page@withMenu!, after CSS/JS tags are built, before the shell renders $title, $body, $headerCode, $cssCode, $jsCode, $menuId
dacore:page@withmenu.rendered After the page HTML is assembled (navbar, menu, AI slot) $viewcode, then the same six render arguments
dacore.ai.chat.active A chat session was created or reused None

There are no other DACore trigger() calls in the module. Email senders, notifications, menu register, and rights helpers do not publish events — they are DotApp::call APIs.

Debug from your module

File: app/modules/Shop/module.listeners.php. Do not put this in DACore.


use Dotsystems\App\Parts\Events;
use Dotsystems\App\Parts\Logger;

public function register($dotApp)
{
    Events::on('dacore.login.before', function ($email) {
        Logger::use()->warning('DACore login attempt', ['email' => $email]);
        // do not log the password (second argument)
    });

    Events::on('dacore.login.after', function ($login) {
        Logger::use()->warning('DACore login result', [
            'logged' => is_array($login) ? ($login['logged'] ?? false) : false,
            'error' => is_array($login) ? ($login['error'] ?? null) : null,
        ]);
    });

    Events::on('dacore.permissions.refresh', function () {
        Logger::use()->warning('DACore permissions refreshed');
        // rebuild a cached AI context here if you keep one
    });

    Events::on('dacore:page@withmenu.rendering', function ($title, $body, $header, $css, $js, $menuId) {
        Logger::use()->warning('DACore page', [
            'title' => $title,
            'menuId' => $menuId,
            'body_len' => is_string($body) ? strlen($body) : 0,
        ]);
    });

    Events::on('dacore.ai.chat.active', function () {
        Logger::use()->warning('DACore AI chat became active');
    });
}
    

login.after runs even when credentials fail or the account is later found inactive. Read $login['logged'] / $login['error']. Shape: Auth and 2FA.

permissions.refresh fires only when Config::module('DACore', 'permissionsAutorefresh') is on and the DSM timer elapsed. DACore then calls Auth::permissionRefresh(), merges role permissions, and reloads the profile — then the event. If your module caches “what this operator can see”, drop that cache here.

Page@withMenu hooks

The source names are Dacore:Page@withMenu.rendering and .rendered (note the spelling Dacore). After lowercase they become dacore:page@withmenu.rendering / .rendered. Subscribe to the lowercase form so hasListener checks you write yourself stay honest.

The comment in DACore says other modules can hook “custom renderers”. You still cannot rewrite $title or $viewcode through a listener return. Use rendering to notice which desk page is about to paint; use rendered to measure the finished HTML. To change chrome, pass CSS/JS into Page@withMenu! from your controller, or add assets in your module. Admin template and menu.

AI chat

dacore.ai.chat.active fires when a session is created or when an existing chat id is reused. No payload. If you keep extra system context per operator, refresh it here with DACore:AI@addSystemContext. Tool calls are not events — they are registered tools.

Framework 404 that DACore may own

DACore does not invent a new 404 event. In module.init.php it does:

if (Events::hasListener('dotapp.router.resolve.404') === false) {
    Events::on('dotapp.router.resolve.404', function () {
        // DACore 404 page
    });
}

If your module registers that name first, DACore leaves it alone and your listener wins. If you only want to log 404s, do not steal the event — log from a second listener if DACore already registered, or log inside your own 404 body. Kernel 404 behaviour: built-in framework events.

Not events

  • DACore:Email@send! / listSenders!email senders
  • DACore:Notifications@push — inbox API, not a bus name
  • DACore:Rights@* / Menu@register / AITools@register
  • SQL around those tables — use dotapp.databaser.execute from the framework catalog if you need a query log

FAQ

Why does DACore fire events at all?

So a plugin can observe login, rights, and the shell without forking DACore. That is the debug story and the integration story.

Can I inject a script tag from withmenu.rendering?

Not by returning HTML. The string arguments are copies. Pass $js / $css into withMenu from the controller that owns the page, or load your module assets there.

Is login.before a place to implement SSO?

You can notice the attempt. You cannot replace Auth::login from the listener return. SSO belongs in your own login route or a supported Auth hook — do not swallow the password into logs.

Why did permissions.refresh not fire?

Autorefresh is off, the operator is not in the TTL window, or you are looking at the first request after login (the timer is set then, the event fires on a later request).

See also