Your own fields in the forms
The most common task right after installation. Let's work through it with a phone number in registration.
A field that is not in the rules gets dropped
This is not validator pedantry, it is protection: the form accepts only declared fields, otherwise anything at all could be slipped into it — sudo or active, for example. So every new field has to be declared, even if there is nothing to check in it ('nullable|string').
If the field has a database column
phone is a column of the modx_user_attributes table, MODX knows about it. Then two edits are enough, no PHP logic needed.
First edit — declare the field. core/App/config/pbauth.php:
<?php
return [
'rules' => [
'register' => [
'phone' => 'required|string|unique:user_attributes',
],
],
];Read it like this: the phone is required, it is a string, and no such phone exists yet in the user_attributes table.
Second edit — add the field to the form. In core/App/elements/auth/chunks/form.register.tpl, following the pattern of its neighbours:
<div class="form-group mb-3">
<label class="mb-2" for="phone">Phone</label>
<input type="text" name="phone" id="phone"
class="form-control{if $errors.phone} is-invalid{/if}"
value="{$old_input.phone}" required>
<span class="invalid-feedback" data-error="phone">{$errors.phone}</span>
</div>That's all. The phone saves itself.
The phone then works as a login too
Login looks a person up by login, by email and by phone. Phones are stored in the database as digits only, but people type them however they like — with a plus sign, spaces, brackets — so the comparison is normalised against normalised. Short input (fewer than seven digits) is not searched by phone at all: among the legacy rows there are fragments like 998, and they would have matched somebody else's profile.
If there is no database column
telegram, for example. MODX keeps such fields in the service field extended — you have to put them there yourself. The first two edits are the same, a third one is added.
Third edit — a small class that distributes the fields.
core/App/Events/Auth/StoreExtendedFields.php:
<?php
namespace PageBlocks\App\Events\Auth;
class StoreExtendedFields
{
protected const FIELDS = ['telegram', 'viber'];
public function handle(array $params, string $event): void
{
$profile = $params['profile'] ?? null;
$validated = $params['validated'] ?? [];
if (!$profile) {
return;
}
$extended = $profile->get('extended') ?: [];
foreach (static::FIELDS as $field) {
if (array_key_exists($field, $validated)) {
$extended[$field] = $validated[$field] ?? '';
}
}
$profile->set('extended', $extended);
}
}And tell the component when to call it — before the user is saved:
<?php
use Boshnik\PbAuth\Events\Dispatcher;
use PageBlocks\App\Events\Auth\StoreExtendedFields;
return [
'rules' => [
'register' => ['telegram' => 'nullable|string'],
'profile' => ['telegram' => 'nullable|string'],
],
'listeners' => [
Dispatcher::USER_SAVING => [StoreExtendedFields::class],
],
];There is no need to save the profile inside the class — the component saves it immediately afterwards.
USER_SAVING fires both on registration and on a profile edit
If the field is only needed in one of the two cases, look at $params['action'] — it holds either register or profile.
Removing a shipped field
Set it to null and take it out of the form chunk:
'rules' => [
'profile' => ['fullname' => null],
],null removes the field outright, it does not make it optional. It was done this way so that one extra field would not force you to rewrite the whole rule set.
Common validation rules
| Rule | What it means |
|---|---|
required | required |
nullable | may be left empty |
string, integer | value type |
email | looks like an email address |
min:8, max:30 | length |
confirmed | a field name_confirmation with the same value must be next to it |
unique:users | no such value exists yet in the modx_users table |
exists:user_attributes,email | such a value does exist in the table |
empty | the field must arrive empty (a honeypot) |
exclude | check it, but do not carry it through to saving |
file, image, mimes:jpg,png, max:2048 | an uploaded file |
Rules are written with |: 'required|string|min:3|max:30'.
string is mandatory for numeric logins
Without it min and max compare the magnitude, not the length, and the login 2385672156 passes max:30 as a number of two billion and change. That is why the shipped registration rule is required|string|alpha_dash:ascii|min:3|max:30|unique:users.
unique: and exists: take a table name, not a MODX class
unique:users, unique:user_attributes — yes. unique:modUser — no: the check silently finds nothing and lets the duplicate through.
What honeypot is for
The login, registration and password recovery forms have a honeypot field with the empty rule: a person does not see it and does not fill it in, a bot does. On every form except login it also has exclude — it needs checking, and after that it is not needed.
If you are building a form from scratch, keep this field in the markup. And do not rename it without fixing the rules.
Avatar
The newphoto field in the profile is already declared:
'newphoto' => 'nullable|file|image|mimes:jpg,jpeg,png|max:2048|exclude',Where to put the files is a setting, :user_id is substituted:
'avatar_path' => 'assets/images/avatars/:user_id',The path is stored in the profile's photo. To raise the size limit, edit max:2048 (kilobytes) — and remember that above it the ceiling is still set by upload_max_filesize and post_max_size in PHP.
If a field is not being saved
Two questions, in order:
- Is it declared in
rules? Everything that is not there is discarded before saving. - Does it have a database column? If not — you need a class on
USER_SAVING, see above. A field that is declared but "does not exist" passes validation and quietly disappears: MODX does not know where to write it.