XC_Bootstrap is now a thin facade, so the old header — a full description of every
context plus four usage examples — described the pre-refactor monolith. Replace it
with a short statement of what the file is (entry point: MAIN_HOME + Composer
autoloader + the BC facade) and a pointer to where the logic lives (BootKernel and
its stages; the context is a BootContext).
Replace the fully-static XC_Bootstrap god-class with a stage pipeline so the boot
logic becomes unit-testable and the per-context sequences are explicit.
- BootState replaces the 8 static readiness flags with a value object threaded
through the pipeline; stages read/write it instead of static state.
- BootStageInterface + BootPipeline run an ordered stage list and abort loudly
on a throwing stage.
- 16 stages under Core/Bootstrap/Stage/ hold one subsystem each, extracted
verbatim from the old private methods (constants, config, flood, host, session,
database, legacy core, redis, process title, admin API, translator, admin
shutdown, status constants, admin globals, container populate, health check).
- StageProfiles builds the ordered list per context, mirroring the exact previous
sequence; BootKernel resolves options, sets up the container and runs it.
- XC_Bootstrap is now a thin BC facade delegating to BootKernel; its getters read
the returned BootState. reset() also clears EventDispatcher and the new
DatabaseFactory::reset() (a side-effect-free registry clear for test isolation).
The DB-touching contexts (Cli/Stream/Admin) still require a live MySQL and the
xcvm_core extension, so they are verified on a canary rather than in CI; the
Minimal context and the pipeline/profile composition are covered by new tests.
Replace the ~49 inline define() calls in XC_Bootstrap::defineStatusConstants()
with a single delegation to ConstantsInitializer::initStatus(). New code and
tests can now read the values via StatusRegistry without the one-shot define()
blocking per-test variation; the legacy STATUS_* reads are unchanged.
Collapse the double blank line between the opening <?php and the namespace
declaration to a single one across 17 Core files (Auth, Enum, Events,
Reference, bootstrap). Whitespace only — no code changes.
Mechanical, behaviour-preserving reformat produced by 'make cs-fix' under
the new build/phpcs.xml.dist ruleset: K&R braces, tab indentation, and the
other whitespace normalisations. No logic changes.
Replace fully-qualified \XcVm\... class references (in code and in
docblocks/@see/@param/@return/@throws) with short names backed by
top-of-file use imports, project-wide. Same-namespace references drop
the prefix with no import; view templates gain the top-level imports the
check-procedural-use gate expects. Purely mechanical, no behavior change.
A successful admin or reseller login wrote the signed-in user into whatever
session the visitor arrived with; nothing in the panel ever called
session_regenerate_id. With session.use_strict_mode off, PHP adopts any id a
client presents, so an id planted in an admin's browser beforehand (a cookie
set from a sibling subdomain, a shared machine) became a signed-in admin
session the moment they logged in — session fixation.
Login (admin and reseller) and the first-run setup page now move the session
onto a fresh id and discard the old one. The admin session also starts with
use_strict_mode on, so ids this server never issued are refused, and with the
cookie HttpOnly: no panel script reads it, and an XSS should not be able to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuZvjFSdodqgpyXtaoH1Xt
Move the .ini language files from src/resources/langs/ to
src/Core/Localization/lang/, next to the Translator subsystem that owns
them — which already defaulted its $langsDir to __DIR__ . '/lang/'. This
dissolves the now-vestigial src/resources/ bucket (its data/ tree went
with admin_constants, libs/ was empty).
- bootstrap.php calls Translator::init() with no argument, relying on the
class's own __DIR__-relative default instead of MAIN_HOME . 'resources/langs/'.
- Makefile LB removal list points at Core/Localization/lang (and drops the
gone resources/langs, resources/libs); LB still ships no UI translations.
- Drop the stale src/resources entries from phpstan scanDirectories and the
phpunit coverage excludes.
- Update the English docs (translations guide, build-system table); the ru
tree is regenerated before release.
$language was a bootstrap-set global (literally Translator::class) that
reached views through four separate bridges: the BaseAdminController
extract whitelist, admin/functions.php, and the renderUnifiedLayoutHeader/
Footer $GLOBALS pulls. Removing the global and sourcing $language where
each page is actually assembled makes the dependency explicit.
- BaseAdminController::render() sets $data['language'] = Translator::class,
covering every admin/reseller page it renders.
- renderUnifiedLayoutHeader()/Footer() and admin/functions.php set a local
$language for the legacy header/footer and the standalone login/setup/post
pages; the reseller login/post controllers set their own local.
- Non-view code (TableController, SearchAjaxController, Admin/Reseller
ScopeBootstrap) calls Translator::get()/setLanguage() directly instead of
through `global $language`.
- bootstrap.php no longer creates the $language global; initTranslator()
and $allowedLangs call Translator:: directly.
Views keep their `$language::get(...)` syntax unchanged; $language is now a
local in every scope that renders one.
Move the 17 global lookup arrays from resources/data/admin_constants.php
into PSR-4 classes and enums, eliminating the last big pile of `global`
reference data.
New XcVm\Core\Reference\* (const + static accessors, FfmpegBinaries-style):
GeoReference, LocaleReference, DeviceReference, UiReference,
PermissionReference, StatusBadge. New XcVm\Core\Enum\* (backed enums with
label()/options(), ModuleState-style): Theme (int), ResellerAction and
ClientFilter (string).
All consumers migrated off `global $rX` / `$GLOBALS['rX']` and the
BaseAdminController extract bridge: TableController, DashboardController,
SearchAjaxController, SettingsController, ResellerAPI and
ResellerLoginController call the classes directly, and 21 view templates
import them via `use`. The extract whitelist entries, the bootstrap
require and admin_constants.php itself are removed.
Fixes a latent bug: TableController read $rClientFilters without a
`global`, so the client-request log showed raw status codes instead of
labels; ClientFilter::labelFor() now resolves them.
The reference classes and the three new enums are excluded from the
load-balancer build (all consumers are already MAIN-only).
Adds unit tests for the enums, StatusBadge and the reference data.
Audited every require/include in src/. Removed 45 manual require_once/require
statements that load a XcVm\* class in a context where the Composer autoloader
is already registered — the class resolves on first use, so the require is dead:
- CronTrait ×26 (Cli/CronJobs/*), DaemonTrait ×7 (Cli/Commands/*)
- DropboxClient ×5 (Core/Backup/BackupService)
- XmlStringStreamer (EpgCronJob; was a bare `require`, a redeclare risk)
- Thread + Multithread (CacheEngineCronJob)
- Logger, DatabaseHandler, LegacyInitializer, Translator (bootstrap.php — the
autoloader is registered earlier at line 89; these are also `use`-imported)
Deliberately KEPT (not redundant):
- The lightweight WebApiBootstrap / StreamingRequestBootstrap / StreamingBootstrap
have NO Composer autoloader (intentional, for high-traffic endpoints) — their
manual requires are load-bearing.
- ErrorHandler.php (global namespace, not PSR-4), and the define()-only config
files AppConfig.php / Paths.php / Binaries.php (autoload never loads constants).
- All view/template/procedural/dynamic includes.
Every removed target verified to resolve via the production autoloader
(class_exists / trait_exists). make phpstan / cs / gates all green.
Final call site: migrate the enable_cache check in the streaming cache path to
SettingsManager::get('enable_cache'), completing removal of the
getAll()['key'] pattern across src/.
Apply the new phpcs + Slevomat ruleset across src/ (phpcbf): 85 files. The bulk
are unused `use` imports that PHP-CS-Fixer's no_unused_imports missed (class
name only present in a PHPDoc description), plus blank-line normalization around
the use/namespace blocks. Verified safe: `make phpstan` stays green (0 errors) —
no import used in code or a real docblock type was removed.
The absolute-path symlink src/Public/assets/reseller -> /home/xc_vm/public/assets/admin
broke `make main` (cp: cannot stat). Remove it from git and let
XC_Bootstrap::ensureResellerAssetsSymlink() create a relative reseller -> admin
link on admin boot (idempotent, repairs a stale/broken link).
Replace the bundled src/Core/Util/MobileDetect.php (Mobile_Detect v2.8.45, copied
into XcVm\Core\Util) with the maintained Composer package, like M3uParser/PhpM3u8.
- composer require mobiledetect/mobiledetectlib (^4.9; 4.11 needs PHP 8.2, deploy
is 8.1.33 → Composer selected 4.9.0). Adds psr/simple-cache as a prod dependency.
- bootstrap.php: new \XcVm\Core\Util\Mobile_Detect() -> new \Detection\MobileDetect();
drop the manual require_once (Composer autoloads it). isMobile() unchanged.
- Remove the bundled MobileDetect.php and its phpstan.dist.neon analyse-exclude.
- .gitignore: whitelist the new prod packages under the prod-only vendor —
!src/vendor/mobiledetect/ and, since psr/ is mixed, un-ignore psr/ then
re-ignore psr/* and whitelist only psr/simple-cache (psr/container,
event-dispatcher, log stay dev-ignored).
- Commit the prod-only vendor + updated composer.lock.
The XC_Autoloader fallback was already retired (no-op stub); this deletes it for
good. Resolution is now 100% Composer PSR-4 (+ ModuleLoader for modules), no
legacy scanner, no class-map cache.
- Move `define('MAIN_HOME', ...)` into bootstrap.php (autoload.php used to define
it); bootstrap.php now requires only vendor/autoload.php.
- Drop `\XC_Autoloader::clearCache()/warmCache()` from StartupCommand.
- tests/bootstrap.php: locate-guard + require switched to vendor/autoload.php.
- Entry points that required autoload.php directly — Public/index.php,
Public/admin/index.php, Public/stream/index.php, Public/progress/index.php,
ministra/portal.php and Admin/Reseller TableController — switched to
vendor/autoload.php (defining MAIN_HOME where they did not already). These were
not in the plan's checklist; found via grep during execution.
- phpstan.dist.neon: drop src/autoload.php from scanFiles.
- Makefile: drop autoload.php from LB_ROOT_FILES.
- deleted_files.txt: add autoload.php (client cleanup on update).
- AutoloadOrderTest: now asserts the XC_Autoloader class and file are gone.
- git rm src/autoload.php.
- PSR4_MIGRATION_PLAN.md: mark final-phase step 2 done.
Verified: grep XC_Autoloader:: = 0; php -l clean; PHPStan no errors; PHPUnit
303/303; make gates pass; bootstrap smoke — MAIN_HOME + XC_Bootstrap present,
XC_Autoloader gone, only the Composer autoloader registered.
Apply 'make cs-fix' — 493 files. Mechanical, import-block only:
- sort use statements alphabetically (class/function/const grouped);
- drop imports left unused by the PSR-4 migration (e.g. classes referenced by
leading-backslash FQCN whose redundant 'use' the automated insertion had added);
- one blank line after namespace and after the import block; collapse stray
blank lines around use.
No logic changes. Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; and a
temporary PHPStan pass over src/Public/Controllers confirms no still-referenced
import was removed (0 unresolved classes). 'use' after inline HTML in view
templates is valid and aliases correctly (verified) — those imports are sorted too.
Move Core/Events into XcVm\Core\Events and its sub-trees: \Contract
(StoppableEventInterface), \Auth, \Module, \Settings, \Stream (the event
classes), plus root EventDispatcher, ListenerProvider, ListensTo, AbstractEvent.
- Namespace 14 files across 6 namespaces; cross-namespace refs imported via use
(AbstractEvent/EventDispatcher → Contract\StoppableEventInterface; Stream events
→ root AbstractEvent). Built-in \Attribute (ListensTo) and ioncube \XC_VM
(PackageInstalledEvent) qualified.
- Rewrite 'use ListensTo;' → FQCN; add use to referrers across src/ and tests/;
fix leading-backslash refs (\EventDispatcher, \ListensTo, \PackageInstalledEvent
from the Module commit) to their FQCNs.
- Tests: add real top-level use imports to ModuleLoaderBootTest (EventDispatcher)
and ListensToAttributeTest (ListensTo) — the use-inserter skipped/mis-placed them
due to a namespace() method and a heredoc fixture already containing the FQCN.
- phpstan-baseline.neon regenerated (292→292).
Completes Core/Container + Core/Events. Verified: php -l clean; PHPStan no errors;
PHPUnit 295/295; EventDispatcher resolves and AbstractEvent implements
XcVm\Core\Events\Contract\StoppableEventInterface.
Move Core/Database into XcVm\Core\Database (DatabaseHandler, Database,
MigrationRunner, QueryHelper). First of the Core hub sub-layers.
- Namespace the 4 classes; DatabaseHandler extends Database (same namespace);
built-ins/ioncube qualified (\PDO, \PDOException, \Exception, \Throwable,
\XC_VM); Database keeps its 'use XcVm\Core\Logging\FileLogger;'.
- Add 'use XcVm\Core\Database\...;' to referencing files (DatabaseHandler 51,
Database 64, QueryHelper 29, MigrationRunner 3) + 2 test files (PHPStan does not
analyse tests/, so PHPUnit is the gate there).
- Rewrite pre-existing leading-backslash global refs (\DatabaseHandler etc., e.g.
in @param docblocks of ResellerApiDispatcher/ResellerTableRenderer) to the full
FQCN \XcVm\Core\Database\... — a 'use' import does not cover a leading-\
reference. Done with a lookbehind so FQCN continuations and use-lines are intact.
- phpstan-baseline.neon regenerated (292→292; pre-existing Database/migration_logic
findings re-anchored after class names in messages gained the namespace).
Verified: php -l clean; PHPStan no errors; PHPUnit 295/295.
Move the last two Core/Config classes into XcVm\Core\Config. SettingsManager
has the largest fan-out of the whole migration (referenced by ~224 files).
- Namespace SettingsManager (self-contained singleton, no class deps) and
SettingsRepository (\FileCache:: qualified).
- Add 'use XcVm\Core\Config\SettingsManager;' to 224 referencing files and
'use ...\SettingsRepository;' to 12 — call sites (SettingsManager::get(), etc.)
unchanged. Done with a token-based inserter (after namespace/declare/<?php,
idempotent, same-namespace files skipped).
- ToolsCommand::processRecaptcha: drop dead class_exists('SettingsManager') +
method_exists guard (always autoloadable now) → call SettingsManager::clearCache()
directly. This was the only string-literal class reference.
Core/Config is now fully namespaced (ConfigReader, DomainResolver, SettingsManager,
SettingsRepository); the procedural constant files (AppConfig/Binaries/Paths)
remain global by design.
Verified: php -l clean (226 files); PHPStan no errors (baseline unchanged — it is
line-independent so the added use-lines don't disturb it); PHPUnit 295/295; all
Config FQCNs resolve via Composer; sample Public referrers lint-clean.
Move ConfigReader and DomainResolver into XcVm\Core\Config (Composer PSR-4).
SettingsManager/SettingsRepository stay global for now (migrated together later
due to SettingsManager's large fan-out).
- Add namespace to both classes.
- Qualify still-global / ioncube refs: \XC_VM:: in ConfigReader; \CacheReader::,
\ConnectionTracker:: in DomainResolver.
- Add 'use XcVm\Core\Config\...;' to the 15 referencing files (bootstrap, CLI
commands/cron, LegacyInitializer, ModuleManager, stream + player controllers,
player views, PlaylistGenerator, ...) — call sites unchanged.
- ModuleManager::isLoadBalancer: string class_exists('ConfigReader') →
class_exists(ConfigReader::class) (the literal would now always be false).
Verified: php -l clean; PHPStan no errors; PHPUnit 295/295.
Introduce a committed Composer PSR-4 autoloader without changing class
resolution behavior, as the foundation for the incremental PSR-4 migration.
- src/composer.json: PSR-4 (XcVm\ -> ./, M3uParser\, Chrisyue\PhpM3u8\),
platform php 8.1.33 (deploy runtime), optimize-autoloader/classmap-authoritative
false (live path resolution, no class-map cache). autoload.files left empty:
global functions are still loaded by existing require glue; moving them is
deferred until that glue is removed.
- src/vendor/ + src/composer.lock: committed (deploy path has no Composer);
generated with 'composer update' from src/. Regenerate with dump-autoload.
- src/bootstrap.php, tests/bootstrap.php: require vendor/autoload.php first,
then the legacy autoload.php.
- src/autoload.php: drop the igbinary disk cache (enableFileCache/saveCache/
shutdown handler/root-chown + bottom call); register at the END of the SPL
queue (prepend=false) so Composer wins for XcVm\* and only still-global
classes fall through to the in-memory scanner.
- Makefile: add vendor to LB_DIRS so load-balancer archives ship the loader.
- phpstan.dist.neon: exclude src/vendor/* from analysis.
- .gitignore: document that src/vendor/ is intentionally tracked.
- ci.yml: add composer-audit job (no-op until real require deps exist).
Verified: php -l clean; Composer first / XC_Autoloader last in the SPL stack;
tmp/cache/autoload_map no longer written; PHPUnit 292/292; PHPStan no errors.
wireDomainDatabase() called after container population — injects the db
service into all 32 domain classes and 6 module cron classes via their
static setDb() method so no domain code needs global \$db at runtime.
assertContainerHealth() added as a fail-fast guard after populateContainer():
verifies 'events' is always present, 'db' when database is ready, 'redis'
when Redis is ready. Throws RuntimeException listing all missing services.
BootContext enum replaces the CONTEXT_* string constants; bootstrap now uses
BootContext::ADMIN, CLI, STREAM, MINIMAL throughout for type safety.
- Removed ConfigLoader.php and transitioned to using ConfigReader for configuration management.
- Updated DatabaseHandler instantiation to no longer rely on global $_INFO, instead using default parameters.
- Enhanced Database and MigrationRunner classes to improve error handling and connection management.
- Simplified RedisManager connection logic by utilizing XC_VM::redis_connect().
- Adjusted various controllers and services to align with the new configuration and database connection methods.
- Removed unnecessary global variables and improved code readability across multiple files.
- Updated comments and documentation to reflect changes in configuration handling and database connections.
- StreamingRequestBootstrap: unified entry for streaming endpoints
- WebApiBootstrap: unified entry for web API requests
- index.php: use new bootstrap classes instead of direct requires
- www/init.php, www/stream/init.php: mark as deprecated shims
- StatusCommand: use StreamingRequestBootstrap::init()
- autoload.php, bootstrap.php: register new classes
- Remove DEVELOPMENT constant from AppConfig.php; introduce DB_ACCESS_ENABLED
(controls phpMiniAdmin access in admin panel only, not core DB connections)
- Detach bootstrap.php from www/constants.php: load core/Config/* and
core/Logging/Logger directly; define PHP_ERRORS fallback
- Switch Logger::init() to PHP_ERRORS in bootstrap.php, stream/init.php,
RequestGuard.php; remove leftover TODO comment
- Logger: always set error_reporting(E_ALL); UI visibility controlled
separately via display_errors; rename $development -> $showErrors
- MigrationRunner: track applied/failed counts separately; failed migrations
are not marked as applied
- Replace DEVELOPMENT with DB_ACCESS_ENABLED in admin settings.php and
database.php (phpMiniAdmin gate)
- Replace DEVELOPMENT with PHP_ERRORS in CertbotCronJob
- Docs (en/ru): add DB_ACCESS_ENABLED flag description; update feature-flags,
updates_checklist, http-request-handling; remove DEVELOPMENT references
- MIGRATION.md: mark L-1 as done, remove from backlog and wave A;
unblock L-2; renumber steps
- Updated the XC_Bootstrap class to include the EventDispatcher class in the service container.
- Modified the ModuleLoader class to utilize the EventDispatcher for subscribing to events, enhancing the event handling mechanism.
During bootstrap layer migration, `defineStatusConstants()` was moved to `XC_Bootstrap` but its invocation was not added to `boot()`. This left `STATUS_SUCCESS`, `STATUS_FAILURE` and other status constants undefined, causing a fatal error on admin login.
Added `self::defineStatusConstants()` to the `CONTEXT_ADMIN` branch before `initAdminGlobals()`.