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

.
refactor(admin): scan public directories in the file integrity scanner
The scanned folders were suffixes appended to ROOT_PATH, so assets and upload
would have silently dropped out of the snapshot once they move into public/.
They are now absolute roots, taken from the constants that follow them.

The implicit rule that only the repository root is scanned without recursion
became an explicit flag.

The installer directory is now covered as well: it is reachable by URL but was
never scanned. Existing snapshots keep working, since scan() only compares the
paths they already contain.
.
fix(admin): install theme assets of language packs into the public path
A language pack ships both system/locale/xx.ini and a theme flag under
themes/default/assets/. Extracting the archive straight into ROOT_PATH would
drop the flag outside of the document root once themes assets move into
public/, leaving a stray themes/default/assets/ directory in the root.

The archive is now extracted into a temporary directory and moved into place:
theme assets go under PUBLIC_THEMES_PATH, everything else into ROOT_PATH.
.
refactor(core): resolve front controller paths from __DIR__
Both paths were relative to the current working directory. They run before
vendor/autoload.php, so they use __DIR__ rather than the path constants.
.
refactor(install): make the installer independent of the current working directory
The installer resolved the autoloader, the config check and the step files
relative to the current working directory, and the translation domain relative
to ROOT_PATH. They now come from __DIR__ or from the path constants.

step_3 read the .env file through dirname(__DIR__, 2), a hardcoded depth that
would silently break and drop the database autofill under Docker. step_2 lists
the writable directories as absolute paths and labels them by subtracting
ROOT_PATH; PUBLIC_PATH joins the list, the sitemap and robots.txt are written
there.
.
refactor(assets): resolve paths in asset scripts from constants
Both scripts addressed the upload directory relative to the current working
directory. Once assets and upload move into public/ together, those relative
paths would keep working while ../../../vendor/autoload.php would resolve to
public/vendor and fail.

thumbinal.php did not load the autoloader at all, so it had no constants to
work with.

preview.php accepts the path with or without a leading slash, since one caller
passes each form, and now refuses to serve anything resolving outside of the
document root.
.
fix(docker): forbid PHP execution inside the upload directory
upload/.htaccess only silences PHP under Apache, nginx had no counterpart: a
PHP file placed in the upload directory was executed. The ^~ prefix stops the
regex locations from picking those requests up, while static files keep being
served. The rule matches on the URI, so it holds both before and after the
document root moves.

The dev template also gets the dotfile rule the production one already had.
.
refactor(sitemap): write sitemap and robots.txt into the public path
Both files are fetched over HTTP, so they belong in the document root.
atomicWrite() writes its .tmp file next to the target, which means the public
path has to be writable.
.
refactor(themes): address published theme assets via PUBLIC_THEMES_PATH
Published assets and language flags are served over HTTP, so they resolve from
PUBLIC_THEMES_PATH. The list of available themes keeps using THEMES_PATH: it is
built from the theme templates, which stay outside the document root.
.
refactor(core): resolve public URLs from PUBLIC_PATH
Assets and avatars are served from the document root, so their URLs are
relative to PUBLIC_PATH rather than to the repository root.
.
refactor(core): add a single filesystem path to URL resolver
Three independent implementations mapped a filesystem path to a URL. They now
delegate to PublicUrlResolver and keep their signatures.

This also fixes pathToUrl(): it diffed the path segments against the root ones,
so /upload/app/photo.jpg under the /var/www/app root silently lost the app
directory. Paths outside of the base path now resolve to an empty string
instead of a mangled URL.