feat: extend module platform with UI slots, runtime components, CLI tooling and {{userId}} search support

Completes the generic module platform that enables modules to contribute
UI elements, runtime JS components, and search resources without any
core hardcoding.

New generic UI slot types:
- topbar.right_item: module-contributed topbar buttons
- layout.body_end_template: module-contributed dialog/overlay templates
- layout.head_style: module-contributed global CSS
- runtime.component: declarative JS component registration with phase ordering

New infrastructure:
- ModuleAutoloader: PSR-4 autoloading for module-local PHP classes
- ModuleRuntimePageBuilder: symlinks module pages into runtime directory
- ModuleRuntimeAssetPublisher: publishes module CSS/JS to web/modules/
- ModulePermissionSynchronizer: syncs module permissions to DB
- CLI scripts: module-runtime-sync, module-build, module-migrate,
  module-permissions-sync, module-assets-sync
- {{userId}} placeholder in SearchDataService for user-scoped search queries
- Component runtime with phased initialization (early/default/late)
- AppContainer.protectExistingBindings() to prevent module→core overwrites
- Architecture tests: ModuleStructureContractTest, CoreTemplateIsolationTest,
  FrontendComponentRuntimeContractTest, AppContainerIsolationContractTest

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 22:19:56 +01:00
parent c364e2b46d
commit c7b8fd516a
139 changed files with 7591 additions and 2535 deletions

View File

@@ -13,6 +13,8 @@ use MintyPHP\Http\RequestContext;
use MintyPHP\Http\Request;
use MintyPHP\I18n;
use MintyPHP\Router;
use MintyPHP\App\Module\ModuleRegistry;
use MintyPHP\App\Module\ModuleRuntimePageBuilder;
use MintyPHP\Service\Access\UiAccessService;
use MintyPHP\Service\Auth\AuthServicesFactory;
use MintyPHP\Session;
@@ -24,12 +26,27 @@ chdir(__DIR__ . '/..');
require 'vendor/autoload.php';
// Load the config parameters
require 'config/config.php';
// Load the routes
require 'config/router.php';
// Register shortcut functions
require 'lib/Support/helpers.php';
$container = require 'lib/App/registerContainer.php';
setAppContainer($container);
// Resolve runtime page root before routes are initialized.
$moduleRegistry = app(ModuleRegistry::class);
if (count($moduleRegistry->getModules()) > 0) {
$runtimePageRoot = __DIR__ . '/../storage/runtime/pages';
if (!is_dir($runtimePageRoot) && !is_link($runtimePageRoot)) {
$builder = new ModuleRuntimePageBuilder();
$builder->build($runtimePageRoot, __DIR__ . '/../pages', $moduleRegistry->getModules());
}
if (is_dir($runtimePageRoot) || is_link($runtimePageRoot)) {
Router::$pageRoot = 'storage/runtime/pages/';
}
}
// Load routes after container/module registry are available.
require 'config/router.php';
RequestContext::start();
header('X-Request-Id: ' . RequestContext::requestHeaderValue());
@@ -200,8 +217,8 @@ if ($requestedLocale === '' && !$isAssetRequest && !$isApiRequest) {
// Guard: restrict non-public pages to logged-in users
// Merge core public paths with module-contributed public paths.
$modulePublicPaths = [];
if (app(\MintyPHP\App\Module\ModuleRegistry::class) instanceof \MintyPHP\App\Module\ModuleRegistry) {
$modulePublicPaths = app(\MintyPHP\App\Module\ModuleRegistry::class)->getPublicPaths();
if (app(ModuleRegistry::class) instanceof ModuleRegistry) {
$modulePublicPaths = app(ModuleRegistry::class)->getPublicPaths();
}
$allPublicPaths = array_values(array_unique(array_merge(
defined('APP_PUBLIC_PATHS') ? APP_PUBLIC_PATHS : [],