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

.
fix(modules): protect the names the core holds
The alias of a module was checked against other modules and against nothing else, but an
alias is a key in several registries at once and not every holder is a module. The CMS
answers to "system" as a source of migrations and as the domain its strings are written
in; the theme engine answers to "theme" as a namespace of templates. Both were free to
take.

Taking "system" was the dangerous one. MigrationLocator collected sources without looking
for duplicates, so the module and the core ended up behind one name, locate() returned
both directories, and uninstalling that module with --purge would have rolled back the
schema of the site. Nothing stopped it but an accident: none of the three core migrations
has a down(), so the purge was refused before it began — and would stop being refused the
day one gets one.

The names are declared by whoever holds them — a constant of the migration source, of the
template registry, of the translator — and assembled by ModuleRegistryFactory, which is
where the registry is put together. So the list shrinks by itself as the core moves into
modules, instead of being a hand-kept blacklist that rots on the first move.

"admin" is deliberately not on it: @admin is the templates of the theme and of the admin
module merged on purpose, the name is held by that module, and the check between modules
already covers it.

The registry marks such a module broken and keeps the site running, the way it does for
an alias another module holds; installing refuses with the same sentence. And the reason
it mattered is closed on its own terms: two sources of migrations may no longer answer to
one name, whoever declared them.
.
fix(container): autoconfigure the remaining extension points
Ten extension points were applied by registerForAutoconfiguration(), and the docblock
there explains why: an instanceof rule of a services file reaches the services declared
in that same file, so a module would have to repeat it and one that forgot would compile
fine and never be asked anything. Five points were still registered the way the docblock
warns about.

They join the rest now: a console command, a sitemap provider, a Twig extension, a
validation rule factory and a service the kernel clears between requests. The last was
the one that mattered — a stateful service of a module was never reset, which an FPM
process hides and a worker runtime turns into one visitor's data shown to another.

Two services opt out. The console application implements ResetInterface, and tagging it
would build the whole CLI application on every HTTP request; it was kept out by standing
above the instanceof rule, which autoconfiguration makes meaningless. MailExtension
belongs to the mail environment alone, and every Twig extension is now tagged for the web
one.

The manual registrations they replace are gone: five of the six modules carrying the
instanceof rule for commands had no command at all, and the sixth would have lost its own
the day somebody tidied the line away.
.
chore(modules): drop the code nothing calls
Two pieces were written, documented and never wired to anything.

ModuleDependencyGraph::installationOrder() sorts modules topologically, but services,
routes and templates are loaded in the alphabetical order the registry returns. Giving
the loading an order that expresses dependencies is a decision of its own, with
consequences for route precedence; until it is taken, the method is untested against
real data and is better read out of the history.

ComposerHooks::note() wrote data/tmp/modules-pending.json, which nothing ever read. Its
docblock promised the admin panel told a module installed by Composer from one dropped in
by hand — the panel lists what is on disk and knows nothing of the file. The message in
the terminal, which is the useful half of the hook, stays.
.
refactor(modules): resolve module paths relative to the module
Every bundled module declared its service paths as MODULES_PATH . 'johncms/<name>/...',
which only ever resolves for a module lying in the modules directory. A module Composer
installed stays in vendor/, so the same file points at nothing there — and the reference
a module author copies from is the bundled module, not the documentation, which already
taught the relative form.

Paths are now dirname(__DIR__), the directory of the module itself, in all 20 modules and
in the scaffold guide. The migration table of the changelog recommended the absolute form
as well and now matches.
.
docs(changelog): describe the editor image limits
.
feat(images): limit and scale down the pictures uploaded into the editor
The four editors of the site stored an upload as it came: no size limit, no
bounds, and the type checked by the extension of the name. A ten-megabyte photo
landed on the disk at its full resolution.

- new settings screen /admin/settings/images writes johncms.editor_images: the
size limit, the bounds a picture is scaled down to, the quality and the format
it is stored in (original / jpeg / webp)
- StoreEditorImageUseCase reads the type off the contents, refuses what is too
heavy and scales down what exceeds the bounds; a picture already within them is
stored untouched, and an animated GIF is never re-encoded
- forum, guestbook, news comments and the news admin form upload through one
responder, which also names the failure when a body over post_max_size was
discarded before PHP ran
- FunctionalTestCase::handleRequest() accepts uploaded files
.
docs(agents): fix the middleware example in the module scaffold
The example implemented MiddlewareInterface::handle() with a return type of
mixed, which is not compatible with the Response the interface declares, and
injected the User model where the modules take CurrentUser. It also credited
index.php with normalising the URI, which RequestPathNormalizer does.
.
docs: bump the documentation submodule
Points at docs(modules): correct the module guides against the code.
.
fix(view): resolve module templates by alias and manifest path
The Twig namespace of a module was basename() of its key, and its templates
were looked for under MODULES_PATH . <key>. The alias therefore did nothing
for templates — it only looked as if it worked, because every module of the
release is called what its directory is called — and a module Composer left in
vendor/ got no namespace at all: its manifest, services, routes, translations
and migrations were read from vendor/, while its templates were expected in a
modules/ directory that does not exist. Nothing moves a Composer package into
modules/; only an archive is unpacked there.

Both now come from the manifest: the namespace is the alias, the directory is
the one the manifest was read from. A theme keeps overriding such a template
under themes/<theme>/templates/<alias>/, and a page the theme does not carry
still falls back to the package.
.
docs: bump the documentation submodule
Points at docs(upgrade): login and registration are one module in 10.0.