Commit Graph
695 Commits
Author SHA1 Message Date
Divarion-D b513281502 fix(redis): handle Redis connection errors and validate connection on creation 2026-07-03 20:34:44 +03:00
Divarion-D 938aede7e9 fix(admin): wire phpMiniAdmin access password to DB_ACCESS_PWD
database.php set $ACCESS_PWD to the boolean result of defined('DB_ACCESS_ENABLED'),
so the configured DB_ACCESS_PWD was never used as the phpMiniAdmin password. Source
it from the DB_ACCESS_PWD constant instead.

AppConfig ships safe defaults: DB_ACCESS_ENABLED=false and an empty DB_ACCESS_PWD
(a repo-committed password is public and protects nothing) — admins opt in and set
a strong password per install. Access stays behind the admin session + is_admin;
an empty password admits only local IPs.
2026-07-03 20:19:06 +03:00
Divarion-D 5aa3300246 fix(admin): load modules when post.php is served directly
nginx serves the existing post.php directly (SCRIPT_FILENAME=$request_filename),
bypassing the front controller, so module classes were never loaded and module
services (e.g. WatchService) resolved as not-found. Boot the modules in
standalone mode from post.php so module-provided services are available.
2026-07-03 20:11:48 +03:00
Divarion-D 9d64d12330 feat(admin): module update/delete actions + state-gated buttons
ModulesController handles the update action (updateModuleFromSource) and a delete
action. The modules view gates each button by state: Install only when not
installed, Update only when a newer available_version exists, Uninstall when
installed, Delete always.
2026-07-03 20:11:33 +03:00
Divarion-D a80506f030 feat(modules): hash_id dir resolution, runtime hash-gen, legacy migration, loader resilience
ModuleManager:
- modulePathFor() resolves {name}_{hash5} (glob) or a legacy bare {name}.
- ensureHashId() generates + persists a hash_id for uploads that lack one, so a
  module is never placed in a hash-less directory; moduleDirName() is now strict.
- placeModuleFiles() always lands modules in {name}_{hash5}.
- migrateLegacyModuleDirs() renames bare {name} dirs to {name}_{hash5} (dropping
  stale bare duplicates); invoked from syncBundledModules() on every status pass.
- listModules() keys by canonical manifest name, not directory basename.
- Fix "unpacks but does not install": loadModuleInstance() and readModuleManifest()
  now resolve via modulePathFor() instead of building a bare modulesPath/{name}
  path, which failed for a freshly-uploaded hash-suffixed module.
- update-source plumbing: normalizeUpdateBlock consumption, updateModuleFromSource,
  standard-set provisioning, findModuleByHashId.

ModuleLoader:
- loadAll() no longer aborts all module loading when one module fails: it logs,
  marks the module failed and skips it (and its dependents), keeping the panel
  and CLI up. Cyclic / unknown-in-graph errors still throw.
- normalizeUpdateBlock() + hash_id/update in the normalized manifest.
2026-07-03 20:11:22 +03:00
Divarion-D ff435f2c01 refactor(modules)!: move bundled modules to {name}_{hash5} directories
Rename watch->watch_2541a, plex->plex_20cd9, tmdb->tmdb_f4e6e,
ministra->ministra_85a7d, where the suffix is the first 5 chars of each
module's hash_id. The logical name (module.json "name", never containing "_")
stays canonical, so config keys, namespaces and dependencies are unchanged —
only the on-disk directory carries the hash. This lets same-named modules
coexist on disk. deleted_files.txt captures the old paths at release time
(make generate_deleted_files, --no-renames).
2026-07-03 20:11:07 +03:00
Divarion-D 39a53fb2cd feat(modules): standard-set registry + module delete (panel + LB)
config/bundled_modules.php is the standard-set registry keyed by hash_id (stable
across renames). ModuleDeleteCommand (module:delete) fully removes a module
(uninstall -> drop files -> config). RootSignalsCronJob gains a delete_module
signal case so a delete on MAIN propagates to LB nodes.
2026-07-03 20:10:52 +03:00
Divarion-D cb4dfe04b5 feat(modules): resolve module update sources + weekly update-check cron
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.
2026-07-03 20:10:41 +03:00
Divarion-D 0114a111f7 fix(cron/maxmind): framed output block and always save downloaded GeoIP
- Print a "GeoIP (MaxMind)" section header to match the Database/Migrations blocks.
- A successfully downloaded GeoLite2 DB is now always saved; a missing or
  mismatched checksum (hashes.md5 can time out over SSL) only downgrades the
  status to a warning instead of discarding the file. Only a real download
  failure is treated as an error.
2026-07-02 21:28:52 +03:00
Divarion-D 85433cbefd fix(assets): drop tracked reseller symlink; panel recreates it
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).
2026-07-02 21:28:51 +03:00
Divarion-D 58656fa396 refactor(modules): master/drop/migrations schema layout + harden install
Mirror core's DB layout at the module level: one master database.sql (full
current schema), one database_drop.sql (teardown), and forward-only
migrations/<semver>.sql deltas. ModuleMigrator gains install()/uninstall();
up() reads migrations/*.sql; down() removed.

- watch: database.sql (delete_missing folded into watch_folders),
  database_drop.sql, migrations/1.0.1.sql; drop 1.0.0.up/down.sql;
  getVersion() -> 1.0.1 (matches module.json).
- Move core migration 004_add_watch_delete_missing.sql into the watch module.
- installModule no longer wraps the DDL migration in a transaction: MySQL/MariaDB
  implicitly commit DDL, so the wrapping rollback() only masked the real SQL error.
- syncBundledModules retries a previously-failed install (skips only admin-disabled)
  so `console.php status` self-heals.
2026-07-02 21:28:33 +03:00
Divarion-D 2995d69b4d fix(modules): improve dependency handling and diagnostics for module loading 2026-06-30 22:41:46 +03:00
Divarion-D 8d6debee8b Prepare release 2.3.2 2026-06-30 21:46:45 +03:00
Divarion-D dfcad177bd fix(epg): only delete orphan EPG caches from prior runs
Cleanup removed files older than time()-10, but a full run takes far longer than 10s, so caches written early in the loop were deleted, leaving most channels without EPG. Stamp $runStart before generation and only remove files with mtime < $runStart.
2026-06-30 21:23:01 +03:00
Divarion-D 2b00393113 fix(modules): call listModules() instead of undefined list()
installedDependentsOf() and syncBundledModules() called $this->list(), which does not exist — the method is listModules(). Fixes PHPStan method.notFound.
2026-06-30 21:23:01 +03:00
Divarion-D 65f28911e6 fix(bootstrap): skip missing domain classes when wiring $db
The LB build strips MAIN-only domains (Domain/User, Domain/Device, Domain/Auth), so DomainDatabaseWiring::wire() fataled with 'class not found' on load-balancer nodes (EnigmaService, then EPG, ...). Iterate a flat list of domain services and guard each setDb() call with class_exists(), so classes absent from a given build are skipped instead of crashing boot.
2026-06-30 21:23:00 +03:00
Divarion-D aac1a51024 fix(updates): raise GitHub API cURL timeout and add connect timeout
The 5s total transfer timeout aborted the /releases list (tens of KB of changelogs) mid-download during LB installs, when the link is saturated by apt/wget. Bump the transfer timeout to 30s and add a 10s CURLOPT_CONNECTTIMEOUT so unreachable hosts still fail fast.
2026-06-30 21:22:45 +03:00
Divarion-D 413d486fa4 fix(service): restore config.enc ownership to xc_vm after root startup
console.php startup runs as root and is the first reader of an LB transport config.enc; the extension rewrites it to at-rest format owned by root, which blocks the xc_vm daemons (signals/watchdog/queue/cache_handler) from reading it. chown config.enc back to xc_vm before they start (idempotent on later boots).
2026-06-30 21:22:44 +03:00
Divarion-D 26d4ac65e2 fix(lb): own install_id and config.enc as xc_vm during provisioning
install_id() created config/install_id as root, so PHP-FPM (xc_vm) could not read it and config.enc decryption fell back to a default config (server_id=1, is_lb=0). Create config/ and run install_id() as xc_vm before packing, chown the uploaded config.enc to xc_vm, and reorder runStartup so the chown precedes any PHP run and status runs as xc_vm — root never touches install_id/config.enc.
2026-06-30 21:22:44 +03:00
Divarion-D e6976d18eb feat(ServerInstallCommand): implement secure provisioning of config.enc for LB nodes 2026-06-28 22:30:39 +03:00
Divarion-D 5df0f1f826 feat(modules): own DB schema via migrations + uninstall + event decoupling
Make the bundled watch/plex modules own their database schema and lifecycle
  instead of being hard-wired into the base schema, and decouple core code from
  module-owned tables.

  File-based module migrations:
  - ModuleMigrator runs Modules/<name>/migrations/<semver>.up.sql / .down.sql,
    ordered by semver; installed_version (config/modules.php) is the watermark.
  - Wired into ModuleManager: install → up(null→target), update → up(from→target),
    uninstall → down(installed). Install is transactional (a failing migration
    rolls back and marks the module Failed).
  - watch ships 1.0.0.up/down.sql (creates/drops watch_categories, watch_folders,
    watch_logs, watch_refresh + seeds the default TMDb genre categories with
    INSERT IGNORE). Removed those tables from bin/install/database.sql — the
    module is now the single source of that DDL.

  Module lifecycle:
  - syncBundledModules() auto-installs never-installed bundled modules in
    dependency order; called from StatusCommand after MigrationRunner::run so a
    fresh install/update provisions their tables. Idempotent.
  - uninstallModule() refuses to remove a module that installed dependents still
    require.
  - plex declares "dependencies": ["watch"], owns no tables, and on uninstall
    deletes only its own rows (watch_folders type='plex', categories 3/4).

  Decouple core from watch tables via events (core would otherwise fatal once the
  tables are droppable):
  - New StreamsDeletedEvent / BouquetDeletedEvent, dispatched from
    StreamRepository::deleteStream/deleteStreams, ToolsCommand::deleteStreams and
    BouquetService::deleteById in place of direct DELETE/UPDATE on watch tables.
  - WatchModule subscribes via #[ListensTo] and delegates to WatchService, which
    now holds the watch_refresh/watch_logs/watch_folders cleanup. Fixes a latent
    bug where bouquet deletion wiped fb_bouquets (column was read but not SELECTed).
  - Moved TMDbService::updateCategories() (writes watch_categories) into
    WatchService; it now builds the client via the PSR-4
    TmdbApiService::createClient() instead of a manual require of the legacy lib
    (createClient is now self-contained).
2026-06-28 21:51:36 +03:00
Divarion-D 57e4805769 refactor: remove unused Database imports across multiple services 2026-06-28 20:45:17 +03:00
Divarion-D e6166f56b5 Refactor code structure for improved readability and maintainability 2026-06-28 20:30:14 +03:00
Divarion-D 00f3b38496 fix(ServerInstallCommand): correct service description formatting in systemd service file 2026-06-28 20:01:22 +03:00
Divarion-D ac3d388fab feat(geoip): replace maxminddb PHP extension with pure-PHP Composer package
Switch GeoIP/ISP lookups from the native maxminddb C extension to the
pure-PHP maxmind-db/reader, pulled in via geoip2/geoip2. This removes a
compiled-extension runtime dependency — the panel now works on any stock
PHP 8.1+ without building or installing maxminddb.so.

Dependencies:
- Add geoip2/geoip2 ~3.0 (brings maxmind-db/reader, maxmind/web-service-common)
- Rebuild committed vendor/ with `composer install --no-dev` (production-only)

Code fixes:
- Resolve MaxMind\Db\Reader against the global namespace (leading backslash)
  in namespaced classes — `new MaxMind\Db\Reader(...)` previously resolved to
  a non-existent XcVm\...\MaxMind\Db\Reader and would fatal once the extension
  was gone. Fixed in GeoIPService, ResellerApiDispatcher, admin/api.php view.
- Update GeoIP util docblock to reflect the Composer-based source.

Installer / runtime cleanup:
- bin/php/lib/php.ini: comment out `extension=maxminddb.so`
- install (main installer) and LbInstallFlow: drop libmaxminddb0/-dev,
  libmaxminddb/-devel and the now-unused mmdb-bin from all distro package
  lists (Debian/Ubuntu/RHEL)

Behavior, the raw `->get()` array format and the on-disk GeoIP file cache
are unchanged; legacy libGeoIP packages are left untouched.
2026-06-28 15:12:22 +03:00
Divarion-D 241fb685dc fix(StreamProcess): adjust FFMPEG command options for improved performance 2026-06-28 14:26:14 +03:00
Divarion-D d2226d573c fix(BruteforceGuard): ensure flood seconds and limit are integers 2026-06-28 14:25:48 +03:00
Divarion-D 0202eddfdc feat(LbInstallFlow): reset ownership of extracted binaries before PHP-FPM starts 2026-06-28 14:25:37 +03:00
Divarion-D a9cf7a5266 refactor(database): centralize domain DB access in DatabaseAware trait
Replace the fragile per-class setDb()/db() pattern (which threw when a
  bootstrap path forgot to wire $db) with a shared trait that lazily resolves
  from DatabaseFactory. Fixes the streaming UserRepository fatal and the same
  latent issue in module crons. 42 classes converted.
2026-06-28 13:48:35 +03:00
Divarion-D 578268a4a2 feat(database): refactor domain database wiring into a dedicated class 2026-06-28 13:08:15 +03:00
Divarion-D 92c89b5f2a feat(settings): add binary version and OS information to settings view 2026-06-28 11:18:08 +03:00
Divarion-D 9d51092ace Prepare release 2.3.1 2026-06-26 21:22:27 +03:00
Divarion-D 2ddc516978 chore(deps): migrate Mobile_Detect to mobiledetect/mobiledetectlib (Composer)
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.
2026-06-26 20:29:36 +03:00
Divarion-D 666da2b059 fix: remove backslashes from TMDB references in comments and descriptions 2026-06-26 20:17:23 +03:00
Divarion-D f829bd5672 fix(nginx): add LB loopback admin routes and correct Public docroot case
LoadBalancer nginx had no ^/admin/ location, so when a server higher in
the tree pulled a stream whose source is an LB, the LB returned 404 for
/admin/{live,timeshift,thumb,vod} before the request ever reached PHP.
The MAIN→LB direction worked (MAIN has the route); LB→MAIN was silently
broken — stream shown down, nothing in the panel logs.

Add a reduced admin location exposing ONLY the loopback handlers
(live|timeshift|thumb|vod) — not index|api|proxy_api — so the admin
panel itself is never served from a LoadBalancer. Mirrors the MAIN
admin gateway block.

Also fix a PSR-4 rename miss: the directory rename public/ → Public/
(1a296d09) updated src/bin/nginx/conf/nginx.conf but left
lb_configs/nginx.conf pointing at /home/xc_vm/public/ everywhere
(root, stream, progress, api). On a case-sensitive fresh LB install the
on-disk path is Public/, so every SCRIPT_FILENAME 404'd — all LB
streaming broke, not just loopback. Repoint the 8 filesystem paths to
Public/; SCRIPT_NAME stays /public/ (logical CGI path, matches MAIN).

Note: bin/nginx is update-excluded, so existing LBs keep their old
config and must be patched manually or reinstalled; only fresh installs
pick up the corrected lb_configs/nginx.conf.
2026-06-26 20:02:28 +03:00
Divarion-D 7fa26585e0 fix(stream): add initial segment time for faster playback start 2026-06-26 19:52:54 +03:00
Divarion-D 7c6c6baeb7 fix(episode): remove unnecessary closing PHP tag and clean up conditional statement 2026-06-26 19:51:10 +03:00
Divarion-D c2343885dc chore(phpstan): exclude vendored libs + fix safe return.void/deadCode errors
- Exclude bundled third-party code from analysis (Modules/tmdb/lib/*,
    Core/Util/MobileDetect.php) — like the vendored Composer packages, these
    are not our code (php-tmdb client, Mobile-Detect). Removes 67 baselined
    errors incl. the only class.notFound and phpDoc.parseError (both vendored).
  - return.void (10): drop the discarded return value (return true/false/null →
    return;) in void methods — NetworkUtils, LineService, StreamProcess, TMDbService.
  - deadCode.unreachable (2): remove the dead `return;` after AdminHelpers::goHome()
    (a never-returning call) in Plex/WatchController.
  - Regenerate phpstan-baseline.neon: 440 -> 361 errors (291 -> 241 blocks).

  Verified: make phpstan green, PHPUnit 298/298.
2026-06-26 19:26:18 +03:00
Divarion-D 70024ad666 fix(modules): drop stray backslash in tmdb/lib/Release.php require paths
The PSR-4 migration's leading-backslash qualification leaked into string
  require paths, turning 'Modules/tmdb/lib/Release.php' into
  'Modules/tmdb/lib/\Release.php' → "Failed to open stream: No such file or
  directory" at runtime (TmdbCronJob, TmdbCron, watch/WatchItemCommand). Remove
  the stray backslash in all three. PHPStan can't catch this (constant + string
  literal concatenation in require), so it surfaced only at runtime.
2026-06-26 19:15:23 +03:00
Divarion-D ce48e67585 fix(nginx): PascalCase Public/ in access-code config templates
The per-access-code nginx configs are generated from
bin/nginx/conf/codes/template{,_ministra} by AuthRepository::updateCodes().
Both templates still hard-coded the pre-migration lowercase path
`fastcgi_param SCRIPT_FILENAME /home/xc_vm/public/index.php` and
`alias /home/xc_vm/public/assets/...`, while the directory was renamed to
Public/ during the PSR-4 migration. On a case-sensitive filesystem PHP-FPM
gets a non-existent SCRIPT_FILENAME and answers "File not found." for every
access-code URL.

Point the filesystem paths at /home/xc_vm/Public/ (SCRIPT_NAME, a logical URI,
is left as /public/ to match nginx.conf). The migration's path sweep only
touched PHP files under src/ and missed these nginx templates.
2026-06-26 18:03:25 +03:00
Divarion-D ef1835d8fb fix: resolve string class references missed by the PSR-4 migration
PHP string class names are always fully-qualified (they ignore the current
namespace and use-imports), so several short-name string references silently
broke when their classes were namespaced — failing only at runtime.

- StreamUtils::sortArguments(): the uasort() callback array('StreamUtils',
  'customOrder') referenced the now-namespaced class by its old global short
  name -> "class StreamUtils not found". Use array(self::class, 'customOrder').
- Router::checkPermission(): `class_exists('Authorization')` was always false
  (Authorization is now XcVm\Core\Auth\Authorization), so the ['type','key']
  permission branch never called check() and fell through to `return true` —
  granting access unconditionally. Call Authorization::check() directly.
- TmdbController::hasMediaPermission(): `class_exists('Authorization')` always
  false -> method always returned false (media permissions always denied). Drop
  the dead guard; the call already imports the FQCN.
- AdminLogoutController: `class_exists('SessionManager')` always false -> the
  admin session was never cleared on logout. Call SessionManager::clearContext()
  directly.
- BruteforceGuard::getDB(): `class_exists('DatabaseFactory', false)` always
  false -> qualify to \XcVm\Infrastructure\Database\DatabaseFactory::class.

Verified: php -l clean; StreamUtils callback is callable; make gates pass;
PHPUnit green (incl. StreamUtilsTest).
2026-06-26 17:43:31 +03:00
copilot-swe-agent[bot]andGitHub 64f25ff517 Merge remote-tracking branch 'origin/main' into chore/psr4-migration
# Conflicts:
#	src/migrations/deleted_files.txt
2026-06-26 13:45:45 +00:00
Divarion-D 5863d1bd5e chore: remove legacy XC_Autoloader and update autoloading documentation 2026-06-26 16:35:18 +03:00
Divarion-D baf6c8e231 build: ship a production-only vendor; install dev tools via Composer
Switch from "commit vendor with dev deps + strip at release" to the standard
application model: the committed src/vendor/ is PRODUCTION-ONLY, and dev tooling
(PHPStan, PHP-CS-Fixer + ~37 transitive deps) is installed on demand with
"composer install".

- Regenerate the committed src/vendor/ via "composer install --no-dev"
  (34 MB -> ~0.5 MB; only the Composer autoloader + gemorroj/m3u-parser +
  chrisyue/php-m3u8 remain). This also stops PHPStan\PharAutoloader registering
  in production.
- Commit src/composer.lock (un-ignored) — this is an application, so the lock is
  committed to make "composer install" reproducible across dev/CI.
- Revert the release-time strip step (Makefile hooks + tools/build/
  strip-dev-vendor.sh) — no longer needed; the archive ships the prod vendor as-is.
- CI: the phpstan and code-style jobs now run "composer install --working-dir=src"
  (with tools: composer) to obtain the dev tools before running.
- New gate tools/ci/check-vendor-prod-only.sh (+ make check-vendor-prod-only,
  wired into "make gates"): asserts no require-dev package from composer.lock is
  committed under src/vendor/ — guards against accidentally committing a
  dev-bloated vendor. Inspects git-tracked files, so it is correct even in a CI
  job that already ran "composer install".
- Fix verify-lb-archive.sh: LB legitimately ships most of Cli/Commands and
  Cli/CronJobs (edge commands + certbot/cache/cleanup crons), so flag only the
  genuinely privileged dirs + the specific install/root files, not the whole dirs.
- .gitignore / composer.json notes updated.

Verified before pruning: PHPStan no errors, PHPUnit 303, cs + gates green. After
pruning: PHPUnit 303 (prod-only vendor), all three gates green. Local dev tools
restored afterwards with "composer install" (not committed).
2026-06-25 21:51:13 +03:00
Divarion-D 2bed01e660 chore(psr4): final phase step 2 — remove the legacy autoload.php entirely
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.
2026-06-25 21:11:00 +03:00
Divarion-D fd35f00c4d fix(views): import migrated classes at top of 5 admin view templates
enigma, episode, episodes, process_monitor and stream_view referenced migrated
classes (UserRepository, BouquetService, StreamRepository, RequestManager,
SettingsManager, ...) by short name with either no `use` or a `use` placed
*below* the first usage. PHP imports outside the top scope are positional, so the
short name resolved to a now-nonexistent global class — a runtime fatal on those
admin pages (php -l passes; not covered by PHPStan/PHPUnit). The migration's
automated `use` insertion missed them due to the interleaved HTML / short-tag
(<? , <?=) structure, and the later php-cs-fixer pass stripped some as 'unused'
because it could not see usage inside short-tag blocks.

- Consolidate every needed `use XcVm\...;` into a single top-of-file PHP block.
- Exclude Public/Views and Modules/*/views from php-cs-fixer (no_unused_imports
  is unreliable on short-tag templates); their import correctness is enforced by
  the new check_procedural_use gate instead.

Verified: php -l (short_open_tag=1) clean; PHPStan no errors; PHPUnit green.
2026-06-25 20:57:37 +03:00
Divarion-D fc0fdb2f29 Fix documentation links in Reseller controllers and related classes to point to the correct GitHub repository URL. Remove unused directory registration code in autoload.php. 2026-06-25 20:32:18 +03:00
Divarion-D 9eec63937e style: import/namespace hygiene across src (PHP-CS-Fixer)
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.
2026-06-25 20:24:36 +03:00
Divarion-D 1a0ad5667a chore(cs): add PHP-CS-Fixer (import/namespace hygiene only)
Add friendsofphp/php-cs-fixer as a committed Composer dev dependency (src/vendor/,
same model as PHPStan — no composer install on deploy).

- .php-cs-fixer.dist.php: deliberately NARROW ruleset — no_unused_imports,
  ordered_imports, no_leading_import_slash, single_line_after_imports,
  blank_line_after_namespace, no_extra_blank_lines[use]. NO @PSR12 / indentation
  rules: the codebase is tab-indented legacy and a full reformat would be
  unreviewable. Indent forced to tabs, LF endings. Excludes vendor, the bundled
  Modules/tmdb/lib, tmp/, backups/.
- Makefile: 'make cs' (dry-run, fails on diff — CI) and 'make cs-fix' (apply).
- CI: new 'Code Style (PHP-CS-Fixer)' job running 'make cs' on PHP 8.3.
- .gitignore: ignore .php-cs-fixer.cache.
2026-06-25 20:24:34 +03:00
Divarion-D 50ae44a87c chore(psr4): final phase — retire the XC_Autoloader fallback scanner
The per-file PSR-4 migration is complete: every class resolves via the Composer
autoloader (src/vendor/, PSR-4 XcVm\* + M3uParser/Chrisyue) or an explicit require
(the bundled tmdb/lib + M3u libs, MinistraBootstrap, procedural entry points). The
ioncube XC_VM class stays guarded.

- XC_Autoloader::init() is now a no-op: it no longer registers the SPL directory
  scanner. The class is kept one release as a no-op stub (its clearCache()/
  warmCache() remain so StartupCommand keeps working); it will be deleted in a
  follow-up (then dropped from bootstrap require / PHPStan scanFiles /
  LB_ROOT_FILES / deleted_files.txt).
- bootstrap.php still requires autoload.php (it defines MAIN_HOME and the stub).

Verified: only the Composer autoloader is registered (XC_Autoloader gone from the
SPL stack); every layer + vendored lib resolves via Composer; the 4 modules load
via the ModuleLoader PSR-4 resolver; PHPStan no errors; PHPUnit 295/295 — and ~20x
faster (0.26s vs 5s) now that class misses no longer trigger a full directory rescan.
2026-06-25 20:06:32 +03:00