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

.
refactor(help,redirect,mail,login): return Response objects instead of writing output
Stage 2c of the HTTP kernel migration, applied to four small modules: 33
sites in 13 files, no exit/header()/http_response_code() left in any of them.

- help: legacy redirect handler keeps its 301; SetMySmiliesController's three
mutually exclusive header() branches sharing one exit became three returns
with the branch selection unchanged.
- redirect: the four redirects now return RedirectResponse with their targets
untouched. This module redirects to external addresses by design, so no
same-origin check was added.
- mail: DownloadFileController redirects to a static file URL, so it returns
RedirectResponse rather than BinaryFileResponse.
- login: both controllers return RedirectResponse; setcookie() is left as is.

Also recorded in the plan two debts found while reviewing this batch:

- The 13 setcookie() calls belong to stage 2c but are untouched by the
per-module sweep, so 2c must not be called done until they are gone.
- LogoutController puts a FILTER_SANITIZE_SPECIAL_CHARS-filtered Referer into
an href. That filter does not block javascript:, so the cancel button on
/logout can execute script. It predates this change and belongs to the
escaping task (1a-ter), so parity is kept here.

Refs: .claude/http-kernel-migration-plan.md stage 2c
.
refactor(news): return Response objects instead of writing output
Stage 2c of the HTTP kernel migration, applied to the news module: no
exit/header()/http_response_code() left in modules/news.

- Admin article and section deletion used exit($exception->getMessage()),
which printed the exception text to the visitor with status 200 and logged
nothing. Both now log the exception and return a real 500, with details
gated behind DebugDetailsPolicy like the rest of the error output.
- Comment and file-upload endpoints return JsonResponse; every non-200 status
(400, 403, 404, 422, 500) is preserved.
- Helpers::returnJson() echoed JSON and called exit from a utility class. It
had two callers, so it is removed and both build their response directly.
- Admin redirects return RedirectResponse; all were 302 already.

The upload error branches still put $e->getMessage() into the JSON body,
bypassing DebugDetailsPolicy. That predates this change and the frontend shows
the text, so parity is kept here and the fix is noted in the plan instead.

Refs: .claude/http-kernel-migration-plan.md stage 2c
.
feat(http): introduce HTTP kernel with Request/Response objects
Replaces the PSR-7 request with symfony/http-foundation and adds a real HTTP
kernel, so the application can run in a long-running runtime without leaking
state between requests. Covers stages 0-3a of the migration plan plus stage 2c
for the downloads and library modules.

Request (stage 1):
- Johncms\Http\Request extends HttpFoundation Request with nine explicit
accessors (body/bodyInt/bodyList/bodyInts/hasBody/queryParam/queryInt/
queryInts/isPost); filterVar() is gone.
- RequestFactory configures trusted proxies, headers and hosts. Default is to
trust no proxy: a non-empty default is both unsafe behind docker port
publishing and impossible to override, because configs merge recursively.
- UploadedFileDTO/Mapper remove the HTTP type from Application and Domain.
- TrimStringsMiddleware preserves the historical always-trim behaviour.
- Johncms\System\Http\* is removed; guzzlehttp/psr7 is dropped.

Response and control flow (stage 2):
- redirect(), pageNotFound() and checkRedirect() throw instead of calling exit;
ExceptionResponseFactory turns those into responses. MethodNotAllowedException
replaces a 405 body that used to be served with status 200.
- ResponseNormalizer accepts Response|string|null, so controllers migrate one at
a time. Render no longer swallows template errors into the page body.
- downloads and library no longer emit exit/header()/http_response_code():
DownloadArticleController streams with its headers preserved 1:1, and each
module error renderer moved to Response together with its controllers.

Kernel (stage 3a):
- Johncms\Http\Kernel implements HttpKernelInterface and maps redirect, 404,
405, 400 and 500 centrally. public/index.php is down to 38 lines.
- Request became synthetic so the kernel can publish the current one.
- New tests/Functional suite boots the app once and drives real requests.

Static analysis gate (stage 1.5):
- PHPStan level 5 replaces psalm, which excluded 20 of 21 modules, and runs in
verify.sh between cs-check and test.

Fixes found while doing the above:
- Files could not be downloaded at all and screenshot upload was dead: both
compared queryParam() to null, which is never true. Same defect hit album and
admin more quietly. PHPStan had reported all four as notIdentical.alwaysTrue,
but the stage 1.5 baseline had accepted them.
- Album validation errors fataled on a readonly property redeclaration, JSON
posts to news comments fataled on getContent(), and the 10.x installer could
not run at all because stage 1d never reached public/install/.
- Rating::addVote() redirected to an unvalidated Referer; it now keeps only the
path and query, and rejects paths starting with // or /\.

BREAKING: sessions are unaffected here, but Johncms\System\Http\* is gone and
installations behind a proxy must now set http.trusted_proxies explicitly, or
getIp() returns the proxy address. A cached container dump must be deleted.
.
docs(agents): make code reviewers opt-in instead of automatic
Reviewer subagents cost several times more tokens than the implementation
itself, so running them after every task is not affordable. After an
implementation an agent now runs only the deterministic gate
(.agents/scripts/verify.sh); the checklists in .agents/review/ and the
reviewer-* subagents are launched only when explicitly requested.
.
chore(agents): version .claude planning docs
Track planning and analysis docs under .claude via *-plan.md / *-analysis.md
gitignore exceptions, so they sync across machines. Ad-hoc scratch files in
.claude/ stay local by default.

Adds the existing module refactoring plans (admin, album, profile), the
completed collections and pagination plans, and the guestbook analysis.
.
docs: add HTTP kernel and request migration plan
Plan for introducing an HTTP kernel (Request/Response/Kernel) built on
symfony/http-foundation, targeting long-running runtimes (FrankenPHP worker
mode / RoadRunner).

Key decisions captured:
- Request: thin Johncms\Http\Request wrapper over HttpFoundation with body*/
query* accessors (no get* methods, no state); soft-mode int getters preserve
current lenient behavior.
- Replace guzzle PSR-7; drop the whole Johncms\System\Http\* namespace (major
version, no deprecation window).
- Response as return value, redirect()/pageNotFound() via exceptions, single
send point; session as a service; request-scoped container for worker mode.
.
refactor: remove legacy Tools class
.
chore(deps): sync package-lock.json
.
chore(agents): add self-review protocol with checklists, reviewers and verification gate
.
build(vite): silence Sass deprecation warnings from Bootstrap