Skip to content

Events and your own code ​

Three levers, in increasing order of force: an event listener, your own route, your own controller. Take the weakest one that will do the job — the weaker the lever, the more of your code keeps being updated along with the component.

Do not edit files in core/components/pbauth/

They are overwritten on update, and your edit disappears silently. Everything below is a way of avoiding that.

Event listener ​

A class with a handle(array $params, string $event) method. Put it anywhere in core/App/, name it in listeners.

php
<?php

namespace PageBlocks\App\Events\Auth;

use Boshnik\PageBlocks\Support\Mail;

class SendWelcomeLetter
{
    public function handle(array $params, string $event): void
    {
        $user = $params['user'] ?? null;
        $validated = $params['validated'] ?? [];

        if (!$user) {
            return;
        }

        Mail::to($validated['email'])
            ->subject('Welcome!')
            ->view('file:auth/chunks/email.welcome', ['username' => $user->username])
            ->send();
    }
}
php
<?php

use Boshnik\PbAuth\Events\Dispatcher;
use PageBlocks\App\Events\Auth\SendWelcomeLetter;

return [
    'listeners' => [
        Dispatcher::AFTER_REGISTER => [SendWelcomeLetter::class],
    ],
];

You can hang as many classes on a single event as you like — they run one after another. Instead of a class name, any callable is accepted, including a closure.

To register a listener on the fly, without the config:

php
Dispatcher::listen(Dispatcher::AFTER_LOGIN, function (array $params, string $event) {
    // …
});

A failed listener does not break the action

An exception inside handle() is caught, written to the MODX log and interrupts neither registration nor login. That is by design: losing a registration because of an unsent email is not acceptable. It also follows that a listener cannot forbid an action — forbidding needs your own controller.

Two moments, and the difference between them matters ​

  • Before saving (USER_SAVING) — the user is not written to the database yet. This is where you change the object itself: add fields, substitute values. No need to save, the component will save right after.
  • After (AFTER_REGISTER, AFTER_LOGIN and the rest) — everything is already written. This is where you send emails, write to a journal, call an external service.

All events ​

WhenConstantWhat arrives in $params
before saving — both on registration and on profile editingUSER_SAVINGuser, profile, validated, action (register or profile)
user registeredAFTER_REGISTERuser, profile, validated
user logged inAFTER_LOGINuser
user logged outAFTER_LOGOUTuser
profile savedAFTER_PROFILE_UPDATEuser, profile, validated
email confirmed via the link from the letterAFTER_VERIFY_EMAILuser
password recovered via the linkAFTER_RESET_PASSWORDuser
password changed in the profileAFTER_CHANGE_PASSWORDuser
confirmation link sent againAFTER_RESEND_VERIFICATIONuser, email
user enabled the second factorTWO_FACTOR_ENABLEDuser
user disabled the second factorTWO_FACTOR_DISABLEDuser
social network linked to the accountSOCIAL_LINKEDuser, provider
social network unlinkedSOCIAL_UNLINKEDuser, provider
manager logged in under someone else's accountAFTER_IMPERSONATEuser, manager

The constants are in Boshnik\PbAuth\Events\Dispatcher. validated is an array of what came from the form and passed validation.

MODX plugins work too ​

A MODX system event of the same name is fired at the same time (pbAuthAfterRegister, pbAuthUserSaving and so on) — in case plugins are more familiar to you.

Class in the configPlugin on a system event
after a deployworks right awayhas to be re-attached in System → System Events
what it receivesobjects: user, profile, validatedscalars only: user_id, profile_id, email
can change the objectyes, on USER_SAVINGno

Objects are deliberately not passed to the plugin: a modUser in $scriptProperties is useless to a plugin and breaks event caching. That is why USER_SAVING via a plugin is pointless — there is nothing to change there.

Your own routes ​

The component's routes live in core/components/pbauth/routes/auth.php, and you should not edit them. Add your own pages in your own file in core/App/routes/:

php
<?php

use Boshnik\PageBlocks\Facades\Route;

Route::middleware('auth')->group(function () {
    Route::get('/profile/settings', 'ProfileSettingsController@show')->name('profileSettings');
});

middleware('auth') — for logged-in users only, middleware('guest') — for guests only. The controller goes into core/App/Http/Controllers/ — from there on it is ordinary PageBlocks development, pbAuth has nothing to do with it.

Declaration order does not matter: an exact route always beats a wildcard one. /profile/settings will open your page even if /profile/{alias} is declared somewhere.

Just do not name the file auth.php

As long as a file with exactly that name sits in core/App/routes/, pbAuth assumes the site runs on the old scheme and does not register its own routes at all — /login will stop opening. Any other name will do.

Your own controller ​

The last lever — for when settings and events were not enough. For example, you need to forbid registration from certain addresses: a listener cannot do that, it is unable to say "no".

core/App/Http/Controllers/Auth/RegisterController.php:

php
<?php

namespace PageBlocks\App\Http\Controllers\Auth;

use Boshnik\PageBlocks\Http\Request;

class RegisterController extends \Boshnik\PbAuth\Http\Controllers\Auth\RegisterController
{
    public function register(Request $request)
    {
        if ($this->isBlacklisted($request->ip())) {
            return response()->error('Registration from this address is closed');
        }

        return parent::register($request);   // then as usual
    }

    protected function isBlacklisted(string $ip): bool
    {
        return in_array($ip, ['203.0.113.7'], true);
    }
}
php
'controllers' => [
    'register' => \PageBlocks\App\Http\Controllers\Auth\RegisterController::class,
],

After that the component's routes lead into your class. Everything you have not overridden keeps working from the component and keeps being updated — so override only what you really need to.

The keys: auth (email confirmation), login, register, profile, forgot_password, reset_password, change_password, confirm_password, resend_verification, two_factor, social, impersonate.

The logic lives in the controllers, not in a service

The component currently has no separate $auth object with a public API: the login and registration scenarios are implemented right in the controllers. So "call registration from my own code" means calling a controller method — not $auth->register($data).

This is a recognised debt of the component, not an architectural feature. If you need registration from cron, from a console command or from your own API endpoint, inherit the controller and call its method; do not duplicate the logic on your side — it will diverge from the component on the very first update.

JSON instead of a redirect ​

The component has no separate API, but the form answers differently depending on the request: PageBlocks looks at expectsJson(), that is, at Accept and X-Requested-With.

POST /login
Accept: application/json
X-Requested-With: XMLHttpRequest
json
{
    "success": false,
    "message": "Check the fields",
    "errors": {
        "username": "User not found"
    },
    "redirect": ""
}

Success is the same thing, with success: true and an address in redirect; on validation errors the response code is 422. That is enough for your own frontend to draw the form: this is exactly how the stock PageBlocks script works when pageblocks_load_scripts is on.

This is a form response, not an API contract

The set of response fields is common to all PageBlocks forms, and you can rely on it. But /login and /register remain forms: they expect a CSRF token and live in the session. For a sessionless client (a mobile app) this is not enough — pbAuth does not issue tokens.

Both of the ordinary scenarios work without JS: with no script the form is submitted by an ordinary POST and answers with a redirect, with the errors in the session.

pbAuth — login, registration and profile for PageBlocks