История коммитов

.
refactor(auth): drop the columns the roles replaced
users.rights, users.failed_login, users.rest_code, users.rest_time and roles.legacy_rights are
gone from a fresh installation, and auth:drop-legacy-columns takes them off an existing one. It
is the only step of this update that deletes data, so it refuses to run before
auth:migrate-rights has turned the numbers into roles: dropping the column first would leave a
site with no staff and no way to work out who they had been.

RightsMirror goes with them. It existed to keep the number in step with the roles while three
hundred checks still compared against it; there are none left, and the role editor stops
writing a column nobody reads.

The other three columns had already been replaced and were only still being written: the
failed sign-in attempts, which the throttle counts per address and per account in the cache
and forgets on its own, and the password recovery code with its deadline, which
password_reset_tokens replaced.

The two schema commands were broken from the command line — Capsule::schema() answers with a
connection of null until something builds the Eloquent capsule, and under HTTP a request always
had. The schema builder is a service now, built by a factory that asks for the connection
first; auth:upgrade-schema works from a terminal for the first time.

Update order for an existing site: auth:upgrade-schema, auth:migrate-rights,
auth:apply-default-permissions, auth:migrate-module-access, then auth:drop-legacy-columns.
.
refactor(users): the caption under a nickname and the staff smilies come from the roles
The last two things that read users.rights: what an account is called next to its nickname,
which was a table of six numbers copied into four classes, and whether the author of an old
post gets the smilies kept for the staff, which was 'the number is above zero'.

Both are one question — the highest role granted to the account — and StaffTitles answers it.
An account holding nothing beyond the roles everybody carries has no caption and is not staff,
which is exactly what a zero used to mean. The caption of a built-in role is translated by its
slug, so it still speaks the visitor's language; a role the site added is shown under the name
the site gave it.

Listings ask about a whole page at once — the visitors of the forum, the files, the voters of
a poll — and the answers are kept for the length of the request, so a topic with ten posts by
three authors costs one query rather than ten.

The joins that dragged users.rights along for the ride are gone from five queries, and the
reply of the shared comments engine records whether its author was staff rather than their
number, the way the guestbook already does; replies written before that still answer with the
number they stored.

One accident fixed on the way: the mail lists passed a row of the users table where the
nickname formatter expected the id under another key, so those rows had no caption and no link
to the profile at all.
.
refactor(auth): one current user instead of two
Every request identified its visitor twice: the authenticator chain decided who it was, and
then two shared objects were filled from the database with the same account —
Johncms\System\Users\User, a bag of public properties with the columns written out by hand,
and Johncms\Users\User, the Eloquent model. Two lookups, two IP-history writers racing each
other over the same row, and two answers to 'who is here' that could drift apart.

The old one is gone, with its factory and the property list it inherited. Everything that took
it — the counters, the comments engine, the ads, the locale resolver, the date cast, four use
cases of the mail module, the two authorized-user middlewares — takes the model instead; both
carried the same fields and the same isValid(), so the change is the type and nothing else.

Two things the old factory did on the side go with it: recording the IP history, which the
model's factory already does, and zeroing the rights of a banned visitor in memory, which
BanVoter has been doing properly since the roles arrived.

The columns and the singleton itself are the next steps of this stage; this one is only about
there being one of them.
.
refactor(admin): retire the module access screen, the settings that are left move to the system ones
The screen managed the mod_* numbers, and there is nothing left of them to manage: who may
open a module and write in it is a permission of the guest and the user roles, edited in
/admin/roles. ModulesAccessController, UpdateModulesAccessUseCase, ModulesAccessDTO, the
template, the two routes and the item in the sidebar all go. No redirect from the old address:
it is an internal link of the panel, nobody bookmarks it.

Three switches on that screen were never permissions and would have been left without a home:
whether the library and the downloads have comments at all, and whether a new account waits
for an administrator. They are a 'Modules' fieldset of /admin/settings now, with a line saying
where the access itself is set.

There is a cost, taken knowingly: /admin/modules-access was open to any administrator and
/admin/settings only to whoever may change the site as a whole, so those three switches move
one level up. A screen per module for three checkboxes would have been the alternative.

The mod_* keys stay in the default configuration, marked deprecated and read by nothing but
auth:migrate-module-access, which needs them to see what a site had. docs/ gets the section
for module authors: how to ask a permission, how to declare one, and what the old settings
became.

This finishes 5b of the auth plan: the second access system of the CMS is gone.
.
refactor(registration): split mod_reg into a permission and a workflow setting
The number said two things at once: 2 was 'open', 1 was 'open, and every new account waits
for an administrator', 0 was 'closed'. Whether an account may be registered is
registration.register, held by the guest role — the only role that can act on it. Whether the
account then waits is registration_moderation, a setting, because a workflow is not a right.

The new key starts as null, meaning the site has not been asked yet, and the old number
answers until it is: a site updated with mod_reg = 1 keeps moderating registrations without
anybody having to open the settings first. The bridge is one line in RegistrationSettings and
goes away with the key.

What is left of /admin/modules-access is now only switches that are not permissions of
anybody — the comments of the library and of the downloads, and the moderation of
registrations — which is what the rows of that screen were reduced to one module at a time.

After updating: auth:apply-default-permissions --force, then auth:migrate-module-access.
.
refactor(community): who may open the lists is a permission, not the active setting
community.view, held by the guest and the user roles. Six controllers opened with the same
ten lines — read the config, compare it against isValid(), render a result page — and the
gate is one line in routes.php now, on the group the six routes share. That is what the
route permission of the previous commit was for.

A guest is redirected to sign in instead of being shown a page that says 'for registered
users only' with a 200; the middleware answers the same way everywhere else.

After updating: auth:apply-default-permissions --force, then auth:migrate-module-access.
.
refactor(downloads): who may open them is a permission, not the mod_down setting
downloads.view, held by the guest and the user roles, replacing the two branches of the
access use case; a section closed to everybody still opens for the administrator, and the
migration carries that exception over.

No separate permission for taking a file: nothing ever gated the download itself by the
setting — reaching the page was the whole check — and the file of a folder awaiting
moderation already asks downloads.moderate.

The 'Downloads are closed' banner leaves the three templates that carried it, for the reason
it left the forum and the library: whoever sees the page may open the section.

After updating: auth:apply-default-permissions --force, then auth:migrate-module-access.
.
refactor(library): who may read it is a permission, not the mod_lib setting
library.view, held by the guest and the user roles; the middleware of the module asks it
instead of comparing the setting and the number in two branches. A library closed to
everybody was still open to the administrator, and the migration writes that exception out
the way it did for the forum.

No library.post to go with it: nothing in the module ever gated writing by the setting.
Submitting an article asks for a session and lands in the pre-moderation queue, and inventing
a permission that nothing checks is how a catalogue of rights starts lying.

The 'Library is closed' banner on the index goes: whoever sees the page may read it.

After updating: auth:apply-default-permissions --force, then auth:migrate-module-access.
.
refactor(guestbook): who may read and write is a permission, not the mod_guest setting
guestbook.view and guestbook.post, held by the guest and the user roles. The guestbook is the
module where the two questions were most obviously one number: 2 meant guests could write, 1
meant they could only read, 0 meant nobody could do either — and the code asked
'mod_guest === 2' in the middle of a condition about bans.

GuestbookAccess loses the configuration it was built with: canRead() and canWrite() ask the
two permissions, canClear() already asked one. The 'the guestbook is closed' banner goes the
way of the forum's: whoever sees the page may read it, and the closed guestbook was only ever
seen by the staff, for whom the role editor now says it.

A guestbook closed to everybody still let whoever may empty it in, which was another
comparison against the number; the migration writes it out as a grant to the administrator.

After updating: auth:apply-default-permissions --force, then auth:migrate-module-access.
.
refactor(forum): who may read and write is a permission, not the mod_forum setting
The second access system of the CMS starts moving into the roles. One number said whether the
forum was open to everybody, to registered visitors, to nobody, or to everybody for reading
only — four answers to two questions, which is exactly why it had to be a number. They are
forum.view and forum.post now, held by the guest and the user roles.

The read-only forum is the case worth spelling out: it meant 'everybody reads, only the staff
write', and one number could say that only because there was no way to say two things. It is
forum.view with the guest and the user, forum.post without the user — which is why the staff
roles carry forum.post of their own rather than inheriting it through 'user'.

auth:migrate-module-access reads the settings of a running site and writes what they meant
into the roles. It is the first migration here that also revokes: 'the forum is for registered
visitors' is the guest role *without* forum.view, and a migration that only added would hand
the forum back to the guests of every site that closed it. The exception a closed forum made
for the administrator was a comparison against the number rather than a setting, so it is
written out in the migration as a grant that is never taken back.

The banner above the listings said 'Forum is closed' or 'Read only' by reading the same
number. It says 'Read only' to whoever may not write, which is a statement about the visitor
rather than about the site — the 'closed' half was only ever seen by the staff of a closed
forum, and what a role may do is now in the role editor.

The row of the forum leaves /admin/modules-access, which is being emptied module by module
and disappears at the end of this series.

After updating: auth:apply-default-permissions --force, then auth:migrate-module-access.