`requires_core` was only read and shown in the Modules table. A module
release that needs a newer core could be installed or updated anyway and
take the panel down, the update check offered such releases, and modules
left behind by a core rollback kept loading.
ModuleLoader::coreRequirementError() checks a manifest's constraint
(one or more `>= > <= < = == !=` conditions, a bare version meaning >=;
empty = any core; anything else unreadable and refused) against
XC_VM_VERSION, a nightly `X.Y.Z-dev.N` counting as X.Y.Z. It is applied:
- on load: the module is skipped with a log line, its dependents with it;
- on install (placeModuleFiles: zip, LB, standard set, platform) and on a
git/url update: refused before the installed copy is touched; a refused
copy the platform extracted into the modules directory is removed;
- in the update check: git offers the newest release whose module.json
(repository root, read from raw.githubusercontent.com, up to 10 tags)
this core can run, url honours requires_core in version.json;
- in the Modules table: the reason on the existing warning badge, now
labelled "Issue(s)".
The checks sit in small helpers, so discoverModules() and
updateModuleFromSource() come out simpler than before.
ModuleUpdateChecker resolves the latest available version per module by source:
git (via GitHubReleases), url (version.json), platform (best-effort), bundled
(no-op). ModuleUpdatesCronJob (cron:module_updates) records available_version
into config/modules.php; migration 008 registers it to run weekly. Network-free
unit tests cover the routing.