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

.
refactor(forum): pass the request as an action argument
Controllers are container singletons, so a Request injected into the constructor outlives the request it belongs to. Every forum controller now takes it as the first action argument; private helpers receive it as a parameter.

ForumUtils::topicLink() no longer resolves the request from the container: it needs the host only, which the caller now passes as a string.
.
refactor(http): make the session facade per-request
The shared Session facade now implements ResetInterface, so the kernel drops the bags
of the request it has served before serving the next one. An open session belongs to
the request being served (the FPM boot opens it before the kernel runs) and is left
alone.

Session::save() moves into the finally block of Kernel::handle(): a cycle that throws
no longer leaves its session open for the next one, which is what lets an open session
be read as the current request's.

Also: the console application implements ResetInterface, so the instanceof rule tagged
it as resettable and every HTTP request built the whole CLI application just to reset
it. Its definition now sits before that rule, guarded by a container test.
.
refactor: pass visitor facts into the application layer as a DTO
Introduce Johncms\Security\ClientInfoDTO and Environment::getClientInfo(): controllers
take the visitor address and user agent and hand them down, so use cases and forms no
longer depend on the HTTP-layer Environment service.

- forum: CreateTopic/PostMessage/ReplyMessage use cases take ClientInfoDTO
- registration: RegisterUserUseCase takes ClientInfoDTO
- admin: PrepareIpBanUseCase takes ClientInfoDTO; the admin's own addresses now go
through IpRangeParser, so an unparsable address takes part in no comparison
- contacts: ContactForm::getValidationRules() takes ClientInfoDTO
- fix stale ForumMessage $ip / $ip_via_proxy annotations (int -> string)
.
refactor(security): extract the request-rate log from Environment
The short-term log of who has been hitting the site is a storage, not a fact of the
request: it now lives behind RequestRateLogInterface with a flat-file implementation
(the record format is unchanged). The kernel records the visit, online/GetIpActivityUseCase
reads it, and Environment keeps only the address and the user agent.

Addresses cross the interface as strings — the unsigned-int form stays an implementation
detail, which is what IPv6 support will replace. The online module also stops depending
on Environment altogether: the current address is handed to the use case by its controller.
.
docs: record stages 5a-3 and 5a-4 in the migration plan
.
refactor(i18n): resolve the locale per request
The locale belongs to the request, the translator is shared. LocaleResolver holds the
choice (?setlng, then the session, the profile, the system default) and the kernel
applies it once the current user is known.

Setting the locale is no longer enough on its own: addTranslationDomain() loads
<locale>.lng.php at registration time, and domains are registered from the constructors
of the shared controllers — once per process. So setLocale() now reloads the dictionary
of every registered domain and restores the default one; the same locale stays a no-op.
.
refactor(users): authenticate the current user per request
Both current-user services are shared and taken in the constructor of hundreds of
controllers and services, so they cannot be rebuilt per request — their state is
replaced instead. The Eloquent user is rehydrated (raw attributes, exists, relations
dropped), the legacy one through the new setProperties(), which restores the declared
properties to their default and removes the dynamic ones left by the previous visitor.

CurrentUserAuthenticator drives it and is idempotent per request object: under FPM the
boot and the kernel serve the same Request, so the second call issues no extra queries.
The boot calls it before resolving the translator, which reads the locale of the profile.
.
refactor(homepage,ads): replace the _IS_HOMEPAGE constant with a request attribute
The constant belongs to the process: Ads::checkAccess() and Counters::counters() read
it with defined(), so under a long-running runtime the first home page served would have
made every later page look like the home page. HomepageController now marks the request
itself and both readers take the flag from the RequestStack.
.
refactor(http): introduce RequestStack and per-request service reset
Shared services that need the current request captured it at construction, which makes
them answer with a stale request for every request but the first in a process. They now
read it off Symfony's RequestStack: the bootstrap pushes the request it builds from the
globals, the kernel pushes the request of each cycle and pops it in a finally block.

- Environment no longer resolves the request through di() nor writes the request-rate log
from its constructor: recording a visit was a side effect of resolving the service.
The write is now logRequestRate(), called once per cycle by the kernel. Its unit test
no longer needs newInstanceWithoutConstructor().
- Services caching something request-scoped implement ResetInterface and are collected by
the johncms.resettable tag; the kernel clears them before serving. Environment and
NavChain implement it — three requests in one process now yield two breadcrumbs each
instead of two, three and four.
- $page and $start are gone from the bootstrap: every caller overwrote them. Resolving the
current user stays, it authenticates the visitor and runs the ban check.
- Render is deliberately not reset: its factory adds template globals once at build time,
so clearing the data alone breaks every template. It is fixed together with the rewrite
of that class, which also blocks the worker runtime. Recorded in the plan.
.
refactor(comments,downloads): pass the request globals explicitly
Johncms\Comments took its sub-action and page offset through $mod and $start globals,
which the four calling controllers set right before constructing it. Both are now keys of
the parameter array Comments already accepts, and the class casts the offset itself — it is
interpolated into a LIMIT clause, so that safety used to rest on all four callers
remembering to cast.

- The same controllers read $_REQUEST['page'] and $_GET['start'] directly; they now read
the request. A page number arriving in the body no longer counts, only the query string.
- $GLOBALS['old'], the "new file" threshold of the downloads module, is gone: FilePresenter
derives it from FilePresenter::NEW_FILE_PERIOD. This fixes the mark on eight of the ten
pages using the presenter — only two set the global, everywhere else the fallback of 0
made every file new.
- CommentsPageTest covers the four pages as far as the harness allows: they sit behind
preconditions a guest cannot satisfy, so it asserts they never answer 5xx and checks the
full page only when the stand serves one.