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

.
refactor(profile,admin): the profile and the admin area ask CurrentUser for the visitor
.
refactor(forum): the forum asks CurrentUser for the visitor
.
refactor(modules): the content modules ask CurrentUser for the visitor
album, downloads, library, help and guestbook: the injected model becomes the service, the id comes from id() and the profile fields from user().
.
refactor(modules): the rest of the site modules ask CurrentUser for the visitor
news, notifications, registration, community, online, contacts and mail.
.
refactor(auth): the current user service carries the profile
CurrentUser answers who is visiting and, on demand, loads their row: id(), isGuest(), isValid() and a lazy user() behind UserRepositoryInterface::find(). A guest is an empty model, and both the identity and the profile are dropped between requests.

The core reads the visitor through it: the legacy classes, the casts, the validators and the Twig app variable. The shared User instance stays for now, mirrored from the same answer once per request.

Two side effects move out of the way: the ip history becomes IpHistoryRecorder, called by the kernel, and TranslatorServiceFactory stops resolving the visitor locale — at boot there is no database connection yet, and the kernel applies their language anyway.
.
docs: update the submodule pointer
The update path from 9.9 and the route cache.
.
feat(router): cache the route collection
With CACHE_ROUTES on, the routes are dumped once into data/cache/routes.php as a plain nested
array — no objects, so opcache holds it — and the collection is never built again: the twenty-odd
route files are not read, and no Route object exists for the length of the request.

This is what stage 5c bought. The collection could not be cached while it depended on the
visitor: a file that declared its staff routes inside 'if ($user->rights >= 9)' produces a
different set of routes for every visitor, and dumping one of them would have served it to
everybody. Now the route is always declared and its gate is a middleware.

The dump is written to a temporary file and moved into place, so a request arriving mid-write
cannot require half of it. A route whose handler or middleware is a closure cannot be written
out as data: the site is served either way, it just keeps building the collection and says why
in the log once.

Off by default, like the container cache, and cleared by cache:clear.
.
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.