Commit Graph
39 Commits
Author SHA1 Message Date
Divarion_D 2364aae6ee refactor(streaming): extract delay resume helpers from startStream
The delayed-HLS branch inline-computed two things: the resume segment number
parsed from the existing delay playlist (last-or-previous line's _<n>.ts index),
and the delay sleep seconds (delay_minutes*60 reduced ~10s per already-produced
segment, floored at 0). Pull both into pure resolveDelaySegmentStart() and
resolveDelaySleepTime() helpers with unit tests. The playlist merge and the
short-playlist abort stay inline. No behaviour change.

Verified: phpstan level 5 green, phpunit 391/391, make gates green.
2026-08-07 21:01:59 +03:00
Divarion_D 39090e37ba refactor(streaming): extract hasSkipFFProbe and skipFFProbeOutput from startStream
The ffprobe-skip branch of the source loop hand-rolled two things: a scan of the
stream arguments for skip_ffprobe == 1, and the literal assumed h264/aac mpegts
result used when the scan matched. Give both names via pure helpers and cover
them with unit tests. No behaviour change.

Verified: phpstan level 5 green, phpunit 385/385, make gates green.
2026-08-07 20:54:14 +03:00
Divarion_D 2d885af262 refactor(streaming): extract aacBitstreamFilter from startStream
The {AAC_FILTER} substitution carried the rule inline: emit -bsf:a
aac_adtstoasc only when a copied AAC audio stream is muxed into a non-FLV
container. Give that rule a name and a test via a pure aacBitstreamFilter()
helper. No behaviour change.

Add unit tests: applied for copied AAC in mpegts/empty container, skipped for
FLV, skipped when the codec is not AAC or the output is not a copy.

Verified: phpstan level 5 green, phpunit 382/382, make gates green.
2026-08-07 20:46:10 +03:00
Divarion_D f99048b79f refactor(streaming): extract resolveStreamCodecMeta from startStream
The codec-metadata derivation persisted for a started live stream (player
compatibility, audio/video codec names, resolution snapped to the nearest
standard height) was an inline block reading ffprobe output. Pull it into a
pure resolveStreamCodecMeta() helper taking the ffprobe array and the
player_allow_hevc flag and returning [compatible, audio, video, resolution].

The only external input, SettingsManager::getAll()['player_allow_hevc'], is now
passed in (getAll() just returns the cached settings array, so hoisting the read
out of the guard is free). Add unit tests: non-array/missing-codecs defaults,
compatible h264/aac, resolution snapping, HEVC gating by the allow flag, and
incompatible-but-reported codecs.

Verified: phpstan level 5 green, phpunit 379/379, make gates green.
2026-08-07 20:43:37 +03:00
Divarion_D 8973f5580f refactor(streaming): extract appendHeaderArgument from startStream
The X-XC_VM-Detect / X-XC_VM-Prebuffer header injection was duplicated three
times in startStream: each block looped the argument list to append the header
to an existing 'headers' entry and added a new one if absent. Collapse the three
copies into a single appendHeaderArgument() helper and drop the shared
$rProcessed flag they threaded through (it leaked state between the blocks).

Behaviour is unchanged: the helper uses a local applied flag, and the three call
sites produce the same argument lists as before. Add unit tests covering
append-to-existing, create-when-absent, append-to-every-headers-entry, and the
empty-list case.

Verified: phpstan level 5 green, phpunit 373/373, make gates green.
2026-08-07 20:32:51 +03:00
Divarion_D b88ae60dc1 refactor(streaming): extract startStream probe/failover helpers and default its command variables
Continue breaking down StreamProcess::startStream:
  - extract resolveProbeSettings() (ffprobe/analysis timing) and
    rotateSourcesPastCurrent() (source-failover ordering) as private static
    helpers, with unit tests locking in their behaviour;
  - simplify 15 inverted empty-if/else blocks across the class into a single
    positive condition (no behaviour change).

Also fix the latent undefined-variable bugs the refactor surfaced under PHPStan:
startStream assembles the ffmpeg command from state produced inside the source
failover loop (which closes before the command is built) and inside the
non-custom_ffmpeg branch. On an empty source list, or when a custom ffmpeg line
is used, several variables were read while possibly undefined -- masked at
runtime only by ternary guards and suppressed in the PHPStan baseline. Default
them at the two scopes that own them:
  - loop-scoped, read after the loop: $rSource, $rRealSource, $rStreamSource,
    $rProtocol, $rFFProbeOutput
  - non-custom_ffmpeg-branch-scoped, read in the {MAP}/{GEN_PTS}/{READ_NATIVE}
    substitution and delay sleep: $rMap, $rGenPTS, $rReadNative, $rSleepTime
and drop the 9 now-obsolete entries from phpstan-baseline.neon.

Behaviour is unchanged on the happy path (the loop/branch overwrite the
defaults) and the defaults match the existing runtime ternary-guard results.

Verified: phpstan level 5 green (whole project, entries un-suppressed),
phpunit 369/369, make gates green.
2026-08-07 20:20:09 +03:00
Divarion_D 0f773b9758 refactor(streaming): extract map/scodec/source/HLS/FLV helpers with tests
Continue breaking up createChannelItem, startMovie and startStream by pulling
five self-contained blocks into pure, tested helpers:

- resolveOutputMap()          — VOD -map selection (custom > strip-subs > copy-all)
- subtitleCodecForContainer() — mp4->mov_text, mkv->srt, else copy
- resolveChannelSource()      — channel source string -> [serverId, path], rewriting
                                s:<server>:<path> to the remote getFile API URL
- buildHlsMpegtsOutput()      — HLS/mpegts segmenter output args
- buildFlvOutput()            — shared FLV output wrapper (RTMP relay + external
                                push, previously two identical lines)

The startStream extractions pass the conditional $rOptions/$rFLVOptions in as
parameters, so the control flow is unchanged. Behaviour preserved.

StreamProcessMovieOutputTest covers map precedence, container->codec mapping,
every source-resolution branch, HLS output assembly, and FLV wrapping.

Verified: php -l clean, make gates green, phpunit 360/360 green.
2026-08-07 19:31:18 +03:00
Divarion_D 0bb87452ad fix(streaming): import and map all VOD subtitles, not just the first
buildSubtitleImport nested the metadata loop inside the import loop and reused
the outer counter $i. The inner loop advanced $i to count(files), so the outer
import loop exited after its first iteration: only the first subtitle was
imported (-sub_charenc -i), while -map/-metadata was still emitted for every
file — mapping ffmpeg inputs that were never added. Multi-subtitle movies got a
single imported track plus dangling -map references (broken output).

Split the nested loop into two siblings — import every file, then map every
file — and cache the count. All configured subtitles are now imported and
mapped to the correct input index.

Add tests/Unit/StreamProcessSubtitleTest.php covering empty input, a single
local subtitle, remote-server fetch, and the multi-subtitle case that
reproduces the defect (import count was 1, must equal N). Verified red→green
against the pre-fix code.

Verified: php -l clean, make gates green, phpunit 336/336 green.
2026-08-07 19:04:16 +03:00
Divarion-D f107e5f769 feat(navbar): make admin profile dropdown registry-driven
The top-right profile dropdown was a hardcoded HTML block in
  admin/header.php, so modules could not add or remove entries and the
  plex/watch "settings" links were baked into core.

  Move the menu into the NavbarRegistry: CoreNavbarProvider now registers
  the core items (edit profile, settings, backups, cache, modules, logout)
  under a reserved 'profile' parent, and header.php renders
  NavbarRegistry::getChildren('profile') adaptively. There is intentionally
  no top-level 'profile' node, so the items never leak into the main
  navigation. Order 100–980 is reserved for module-provided links.

  Extract NavbarRegistry::collapseDividers() to drop separators orphaned by
  permission filtering (leading, trailing, consecutive), and reuse the
  shared _xc_nav_visible()/_xc_nav_label() helpers by defining them once
  near the top of the header. Remove the hardcoded settings_plex /
  settings_watch entries — those now belong to their owning modules.

  Add NavbarProfileMenuTest covering collapseDividers, the core 'profile'
  contract (no top-level leak, edit first / logout last, permission gates),
  and the module-integration contract (reserved order slots, divider
  collapse when folder_watch_settings is absent).
2026-07-29 19:37:43 +03:00
Divarion-D 2036f9db20 refactor: optimize uptime formatting and update data provider methods to static 2026-07-10 17:40:37 +03:00
Divarion-D 0532b82c96 refactor(tests): remove deprecated global $db exemptions from ArchitectureTest 2026-07-07 21:51:51 +03:00
Divarion-D f8a37947b1 feat(tmdb)!: fold the tmdb module into core, replace stale standard-set copies
The panel is deeply coupled to TMDb (VOD import, player metadata, admin
search, two crons), so shipping it as an uninstallable module only added
failure modes: after the move to hash-suffixed dirs (tmdb_f4e6e) every
hardcoded `Modules/tmdb/lib/...` require broke, and 2.3.3 crons died with
"Failed opening required TmdbClient.php".

tmdb -> core:
- Vendored \TMDB client -> src/Infrastructure/Tmdb/lib/; the only loader
  is TmdbApiService::requireLibrary() (now public, also loads Release.php).
- TmdbApiService -> XcVm\Infrastructure\Tmdb — composer-autoloaded in every
  bootstrap context, no module boot required (player scope never booted
  modules, so module-namespace classes were unreachable there).
- TmdbCron / TmdbPopularCron -> XcVm\Domain\Vod; cron jobs -> Cli/CronJobs
  (picked up by the console.php scan; command names cron:tmdb and
  cron:tmdb_popular are unchanged).
- TmdbController -> Public/Controllers/Admin; tmdb_search / tmdb api
  actions registered in routes/admin.php (same dispatchApi fallback).
- Domain/Vod services and player_functions.php load the lib through
  TMDbService::requireLibrary() instead of hardcoded module paths.
- tmdb removed from config/bundled_modules.php. ModuleLoader gains
  CORE_PROVIDED_MODULES: released watch/plex archives still declare
  "dependencies": ["tmdb"] — such deps are stripped during manifest
  normalization and in ModuleManager::listModules().
- syncBundledModules() purges stale on-disk tmdb module dirs and their
  config/modules.php state on upgraded panels, so the old copy cannot boot
  alongside the core implementation and collide on command names.

Standard-set provisioning fix (root cause of the "Undefined variable $db"
errors from watch/plex settings views on 2.3.3):
- Production still ran watch_e6c86/plex_20cd9-less legacy copies migrated
  from 2.3.2 with generated hash_ids; provisionStandardSet() treated any
  same-name directory as "already on disk" and never fetched the pinned
  1.0.2/1.0.1 releases that contain the fix. A same-name directory whose
  identity does not match the pinned hash_id is now considered stale: it
  is deleted and the pinned release is installed in its place.
- installModuleFromSource(): when the module is already recorded as
  installed (files re-provisioned over a stale copy), run updateModule()
  (incremental from->to migrations) instead of re-running the initial
  install.
2026-07-05 20:17:31 +03:00
Divarion-D a8c1c0f2cc test(modules): dir convention, legacy migration, loader resilience, install
- ArchitectureTest/InterfaceContractTest resolve modules via module.json instead
  of the directory basename (works with {name}_{hash5}).
- ModuleLoaderTest: loads from a hash-suffixed dir; a broken module and its
  dependents are skipped without aborting the whole load.
- ModuleManagerMigrationsTest: legacy bare -> hashed migration (+ stale-dup drop),
  and install of a module living in a hash-suffixed directory.
2026-07-03 20:12:01 +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 2995d69b4d fix(modules): improve dependency handling and diagnostics for module loading 2026-06-30 22:41:46 +03:00
Divarion-D 5863d1bd5e chore: remove legacy XC_Autoloader and update autoloading documentation 2026-06-26 16:35:18 +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 43e2f275b1 chore(psr4): phase 3 — namespace module sub-classes
Namespace the modules' own classes into XcVm\Module\<Pascal> (Plex, Watch, Tmdb,
Ministra) — PlexController/PlexService/PlexCron/..., WatchController/WatchService/...,
TmdbController/TmdbCron/TmdbApiService/..., PortalHandler/PortalHelpers (~23 classes).
They now resolve through the Phase-2 ModuleLoader PSR-4 resolver.

- The bundled third-party tmdb/lib/* (TMDB client, Entities, roles, config) stays
  GLOBAL, like the other vendored libs; namespaced Tmdb classes reference it via
  \TMDB etc.
- Rewrite the *Module classes' (and any sibling) 'use ShortName;' imports → FQCN;
  add use to referrers; convert leading-backslash refs. Sub-classes keep the
  Core/Domain/Cli use imports added during those layers.
- Qualify built-ins (\DateTime, \Exception, \PDO, ...) and the global lib classes.
- phpstan-baseline.neon regenerated.

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; all 4 modules load and
their sub-classes (e.g. XcVm\Module\Plex\PlexService) resolve via the module
PSR-4 autoloader; re-sweep clean.
2026-06-25 19:12:48 +03:00
Divarion-D 3377236d77 chore(psr4): phase 3 (Cli) — namespace Cli + switch console.php to FQCN discovery
Namespace all 55 Cli classes into XcVm\Cli (CommandInterface, CommandRegistry,
CronTrait, DaemonTrait), XcVm\Cli\Commands (28) and XcVm\Cli\CronJobs (23);
migration_logic.php stays procedural/global.

- console.php: switch command discovery from basename==classname + manual require
  to FQCN resolution — each scan dir maps to its PSR-4 namespace, classes load via
  Composer, implementsInterface(CommandInterface::class). Drop the manual
  CommandInterface/CommandRegistry requires. This is the atomic switch the plan
  required to land with the Cli namespacing.
- Commands/CronJobs get 'use XcVm\Cli\CommandInterface;' (implements) and the
  trait imports 'use XcVm\Cli\CronTrait;'/'DaemonTrait;'; rewrite the modules'
  'use CommandRegistry;' → FQCN; qualify built-ins/global (\ReflectionClass,
  \PDO, \Exception, \RuntimeException, \XC_Autoloader, \XC_VM).
- Fixtures: 'use CommandRegistry;' → FQCN; InterfaceContractTest registerCommands
  param-type → FQCN. phpstan-baseline.neon regenerated.

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; FQCN discovery resolves
51 classes (49 implement CommandInterface); no mangled FQCNs; re-sweep clean.
2026-06-25 19:03:39 +03:00
Divarion-D 62d2d1d942 chore(psr4): phase 3 — namespace Streaming layer
Namespace all 17 Streaming classes into XcVm\Streaming\<Subdir> (Auth, Balancer,
Codec, Delivery, Health, Lifecycle, Protection + root AsyncFileOperations,
StreamingBootstrap, TS).

- Add namespace to every class; add use to referrers across src/ and tests/;
  convert leading-backslash refs from earlier commits (\FfmpegPaths,
  \FFprobeRunner, \ProcessChecker, \StreamingBootstrap) to FQCNs.
- Qualify built-ins/ioncube inside Streaming (\DateTime, \DateTimeZone,
  \Exception, \XC_VM).
- FfmpegPathsTest: new ReflectionClass('FfmpegPaths') string → ::class FQCN.
- phpstan-baseline.neon regenerated (291).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; no mangled FQCNs;
leading-backslash re-sweep clean.
2026-06-25 18:55:35 +03:00
Divarion-D 42d9ca8ea0 chore(psr4): phase 3 — namespace Domain layer
Namespace all of Domain into XcVm\Domain\<Subdir> (33 classes across Bouquet,
Device, Epg, Line, Security, Server, Stream, User, Vod).

- Add namespace to every Domain class; add use to referrers across src/ and
  tests/; convert the leading-backslash refs qualified in earlier Core commits
  (\UserRepository, \ServerRepository, \BouquetService, \CategoryService,
  \ConnectionTracker, \BlocklistService, ...) to their FQCNs.
- Qualify still-global / built-in deps inside Domain with leading backslash
  (\RedisManager, \TMDB, \WatchService, \FFprobeRunner, \ProcessChecker,
  \Exception, \DateTime, \PDO, ...) — Infrastructure/Streaming/module classes
  migrate later.
- BruteforceGuard: string guards class_exists('ServerRepository'/'BlocklistService')
  → ::class FQCN; drop the now-always-true method_exists check.
- phpstan-baseline.neon regenerated (292→291).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; leading-backslash
re-sweep across Domain classes is clean.
2026-06-25 18:39:28 +03:00
Divarion-D da291e20f8 chore(psr4): phase 3 (Core) — namespace remaining Core subdirs
Namespace the rest of Core, completing the Core layer:
- Backup, Boundary, Cache (CacheInterface/FileCache/RedisCache), Diagnostics,
  GeoIP (GeoIPService/MaxMindUpdater), Init (LegacyInitializer), Localization
  (Translator), Process (Thread/Multithread/ProcessManager), Updates
  (GitHubReleases), Util (NetworkUtils, AdminHelpers, Encryption, GeoIP,
  ImageUtils/ImageResizeService, Mobile_Detect, StreamUtils, SystemInfo,
  TimeUtils), Validation (InputValidator) → XcVm\Core\<Subdir>.
- Rename GithubReleases.php → GitHubReleases.php so the file matches its class
  name (PSR-4 is case-sensitive); fix the WebApiBootstrap require accordingly.
- Qualify built-ins (\Exception, \DateTime, \DateTimeZone, \Redis,
  \RuntimeException, \BadMethodCallException, ...) and still-global deps
  (\ServerRepository, \CacheReader, \DatabaseFactory, \BouquetService,
  \CategoryService, \FfmpegPaths, \StreamSorter, \XC_VM). LegacyInitializer's
  Domain calls stay \-qualified (the known Core→Domain exception).
- Add use to referrers; fix leading-backslash refs from earlier commits; fix
  string class refs class_exists('Translator'/'NetworkUtils'/...) → ::class.
  Remove the duplicate global 'use BoundaryInterface;' in MinistraModule.
- phpstan-baseline.neon regenerated.

Core is now fully namespaced (procedural constant/error/RequestGuard files stay
global by design; M3uParser/PhpM3u8 remain vendored). Verified: php -l clean;
PHPStan no errors; PHPUnit 295/295; leading-backslash re-sweep clean.
2026-06-24 22:35:33 +03:00
Divarion-D dc335a8334 chore(psr4): phase 3 (Core) — namespace Core/Enum + Core/Exception
- Core/Enum → XcVm\Core\Enum (BootContext, ModuleState, ServerEnvironment).
- Core/Exception → XcVm\Core\Exception (XcVmException) + \Container
  (ContainerException, CircularDependencyException, ServiceCreationException) +
  \Module (ModuleException + the 4 module exceptions). The hierarchy resolves:
  XcVmException extends \RuntimeException; the Container/Module exceptions extend
  XcVmException via use; ContainerException keeps its PSR ContainerExceptionInterface.
- Add use to referrers across src/ and tests/; fix the leading-backslash refs
  qualified in earlier hub commits (\ModuleState, \ServerEnvironment,
  \XcVmException, \ContainerException, \ModuleLoadException, ...) to their FQCNs.
- phpstan-baseline.neon regenerated (292→292).

Verified: php -l clean; PHPStan no errors (first pass); PHPUnit 295/295;
leading-backslash re-sweep clean.
2026-06-24 22:23:42 +03:00
Divarion-D 432c8a010a chore(psr4): phase 3 (Core) — namespace Core/Events
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.
2026-06-24 22:18:52 +03:00
Divarion-D e8b7ab8b2e chore(psr4): phase 3 (Core) — namespace Core/Container
Move Core/Container into XcVm\Core\Container (ServiceContainer) and
Core/Container/Psr into XcVm\Core\Container\Psr (ContainerInterface,
ContainerExceptionInterface, NotFoundExceptionInterface, NotFoundException).

- Namespace ServiceContainer + the 4 PSR-11 interfaces/classes.
- ServiceContainer: import the PSR siblings (use ...\Psr\ContainerInterface,
  NotFoundException); migrated deps (DatabaseHandler/ModuleInterface/Request/
  SettingsManager) keep their use; still-global exceptions/decorators qualified
  with leading backslash (\ContainerException, \XcVmException, \Exception, ...).
- Rewrite 'use ServiceContainer;' → FQCN (incl. module fixtures); add use to
  referrers across src/ and tests/; fix leading-backslash \ServiceContainer refs.
- Core/Exception/Container/ContainerException: import the moved PSR
  ContainerExceptionInterface.
- Router: class_exists('ServiceContainer') → ::class. InterfaceContractTest boot
  param-type → FQCN. Repaired use-inserter mis-placement in the two namespace()
  fixture tests.
- phpstan-baseline.neon regenerated (292→292).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; ServiceContainer
resolves and implements XcVm\Core\Container\Psr\ContainerInterface.
2026-06-24 22:13:35 +03:00
Divarion-D 881991e52b chore(psr4): phase 3 (Core hubs) — namespace Core/Module
Move Core/Module into XcVm\Core\Module (BaseModule, ModuleInterface,
MigratableInterface, ModuleLoader, ModuleManager, NavbarRegistry, NavbarItem,
CoreNavbarProvider) and Core/Module/Contract into XcVm\Core\Module\Contract
(the 6 provider interfaces). Completes the Core hub layer.

- Namespace 14 files (8 root + 6 Contract). Root files import the contracts via
  'use XcVm\Core\Module\Contract\...'; NavbarProviderInterface imports the
  root NavbarRegistry. Qualify still-global deps with leading backslash
  (\ServiceContainer, \CommandRegistry, \ModuleState, \ServerEnvironment,
  \EventDispatcher, \ListensTo, the Module exceptions, \ZipArchive,
  \InvalidArgumentException, \RuntimeException) — Container/Events/Enum/Exception
  migrate later. Migrated deps (Router, StreamPipeline, ...) keep their use.
- Rewrite the modules' 'use BaseModule/NavbarRegistry/NavbarItem;' → FQCN; add
  'use XcVm\Core\Module\...;' to other referrers across src/ and tests/.
- Fix pre-existing leading-backslash refs to the FQCN.
- Tests: qualify the module fixtures' generated 'use ModuleInterface;' /
  'use BaseModule;' / 'use NavbarRegistry;' and the registerRoutes type hint to
  FQCN; add real top-level use imports to the fixture-builder tests; update
  InterfaceContractTest registerNavbar param-type to the FQCN. Repaired
  use-inserter mis-placements in the two tests that declare a namespace() method.
- Public/index.php: class_exists('ModuleLoader') → class_exists(ModuleLoader::class).
- phpstan-baseline.neon regenerated (292→292; no new/unknown-class errors).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; the 4 real modules
load and resolve as XcVm\Core\Module\ModuleInterface; leading-backslash
re-sweep across all migrated classes is clean.
2026-06-24 22:05:10 +03:00
Divarion-D 14202afd64 chore(psr4): phase 3 (Core hubs) — namespace Core/Http
Move Core/Http into XcVm\Core\Http (ApiClient, CurlClient, RequestManager,
Request, Response, Router) and Core/Http/Pipeline into XcVm\Core\Http\Pipeline
(StreamContext, StreamMiddlewareInterface, StreamPipeline). RequestGuard.php is
procedural (global functions) and stays global.

- Namespace the 9 classes; qualify still-global deps with leading backslash
  (\ServerRepository in ApiClient; \Authorization, \ServiceContainer,
  \AdminHelpers in Router) and built-in \Throwable; same-namespace siblings
  unqualified.
- Add 'use XcVm\Core\Http\...;' to referencing files — RequestManager 128,
  Router 19, ApiClient 15, Request 11, Response 6, CurlClient 5, plus Pipeline —
  across src/ and tests/.
- Rewrite the modules' 'use Router;' → 'use XcVm\Core\Http\Router;' (plex,
  watch, tmdb).
- Fix pre-existing leading-backslash global refs (\Router etc.) to the FQCN.
- Tests: fully-qualify the Router type hint in generated module fixtures
  (registerRoutes(\XcVm\Core\Http\Router ...)) and update the
  InterfaceContractTest param-type expectation to the FQCN. Also repaired two
  fixtures where the use-inserter mis-placed a 'use' (files declare a method
  literally named namespace(), which the tokenizer reports as T_NAMESPACE).
- phpstan-baseline.neon regenerated (292→292; class names in frozen messages
  gained the namespace, no new/unknown-class errors).

Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; Http classes resolve
via Composer; leading-backslash re-sweep across migrated classes is clean.
2026-06-24 21:32:10 +03:00
Divarion-D 42db4be509 chore(psr4): phase 3 (Core hubs) — namespace Core/Database
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.
2026-06-24 21:15:06 +03:00
Divarion-D 1bd9438820 chore(psr4): phase 2 — real PSR-4 resolver in ModuleLoader
Rewrite ModuleLoader::registerModuleAutoloader() from a lossy short-name + glob
lookup into a true per-module PSR-4 resolver: the module's base namespace
(XcVm\Module\{Name}) maps onto its directory, and the namespace remainder
becomes the sub-path.

  XcVm\Module\Watch\WatchModule          → {modulePath}/WatchModule.php
  XcVm\Module\Watch\Service\WatchService → {modulePath}/Service/WatchService.php

- Only classes under the module's own namespace are claimed; everything else
  (global legacy classes, other modules, core) falls through untouched. This
  removes the short-name glob, so two modules — or two sub-namespaces in one
  module — can declare same-named classes without colliding.
- load() now derives the base namespace from the resolved FQCN and registers the
  autoloader after class resolution (reordered, still before the main require).
- Marketplace slug dirs unaffected (real $modulePath is used); encrypted-file
  handling preserved (require_once + zend_compile_file decrypt hook).
- resolveClassName()/load() already targeted XcVm\Module\{Pascal}\{Pascal}Module
  (done in earlier work) — left as is.

console.php FQCN discovery is intentionally NOT changed here: the Cli layer is
still global (CommandInterface, *Command, *CronJob), so switching discovery to
\XcVm\Cli\... would break it. Per the plan it switches atomically with the Cli
layer namespacing (phases 3..N).

New test tests/Unit/ModuleLoaderPsr4ResolverTest.php: sub-namespace resolution,
same-short-name no-collision, foreign-namespace fall-through.

Verified: php -l clean; PHPUnit 295/295 (+3); PHPStan no errors; all 4 real
modules (plex/watch/tmdb/ministra) load and their *Module FQCNs resolve.
2026-06-24 20:20:30 +03:00
Divarion-D 1a296d0985 chore(psr4): phase 1 — rename 7 top-level dirs to PascalCase
Atomic case-rename of the seven class-holding source directories to match the
PSR-4 namespace casing, plus every consequent path reference. No code logic
changes — pure structural rename. (config/content/resources/signals/migrations/
ministra/storage/tmp/vendor/www/bin stay lowercase.)

- git mv: core→Core, domain→Domain, infrastructure→Infrastructure,
  streaming→Streaming, modules→Modules, cli→Cli, public→Public (module subdirs
  like Modules/plex stay lowercase; loaded by ModuleLoader, not Composer).
- PHP filesystem paths updated across src/ + tests/: require/include, glob/scandir
  bases, view includes, asset/nginx-alias paths, autoload.php registerDirectories(),
  ModuleLoader/ModuleManager module roots, console.php discovery dirs.
- src/composer.json PSR-4 vendored-lib paths → Core/Parsing/...; vendor/ regenerated.
- nginx.conf: docroot + SCRIPT_FILENAME → Public/. SCRIPT_NAME and the public-facing
  /streaming/*.php compat routes are URLs, left unchanged.
- Build/CI: Makefile LB_DIRS / LB_DIRS_TO_REMOVE / LB_FILES_TO_REMOVE PascalCased
  (closes the LB-archive privileged-leak blocker); phpstan.dist.neon paths/
  scanDirectories/excludePaths; phpunit.xml.dist coverage; phpstan-baseline.neon
  regenerated (294→294 errors, no new resolution failures).
- migrations/deleted_files.txt: old lowercase trees listed for client cleanup
  (assumes case-sensitive FS — the supported Linux target).

Verified: grep-gate 0 live lowercase-dir require/include in src+tests; php -l clean;
PHPUnit 292/292; PHPStan no errors; Composer first / XC_Autoloader last in SPL stack;
global classes resolve from the renamed dirs.
2026-06-24 20:10:34 +03:00
Divarion-D db98c5a18b test(core): add in-memory SQLite DB harness + sample repository test
Adds tests/Support/TestDb.php — a Database-compatible wrapper over
sqlite::memory: injected via setDb() — and StreamConfigRepositoryTest as a
worked example (SELECT/list/keyed/DELETE-cascade/not-found). 286 -> 292 tests.
2026-06-22 19:29:31 +03:00
Divarion-D 5ce38ea61f test(core): unit tests for Util/Database/Enum pure logic (+44 tests)
Adds tests for Encryption, TimeUtils, QueryHelper, AdminHelpers, ImageUtils,
StreamUtils and BootContext/ServerEnvironment enums. 242 -> 286 tests, all green.
2026-06-22 18:47:50 +03:00
Divarion-D 0d8607b17b test: add comprehensive test suite covering all P2–P4 audit phases (242 tests)
New test files:
- ModuleLoaderBootTest — 11 tests: boot lifecycle, event subscribers, BaseModule compat
- ModuleManagerMigrationsTest — 8 tests: versioned migrations, semver ordering, install/uninstall state
- ModuleStateEnumTest — 12 tests: fromRaw(), isLoadable(), ModuleManager integration
- ComposerDiscoveryTest — 7 tests: Composer 1/2 format, xcvm-module filter, deduplication, module-path
- CronProviderTest — 8 tests: BaseModule default, collectCronEntries aggregation, format validation
- ExceptionHierarchyTest — hierarchy correctness, PSR-11 NotFoundException compliance
- InterfaceContractTest — 29 reflection tests verifying all documented interface signatures
- ListensToAttributeTest — 8 tests: attribute shape, ModuleLoader integration, IS_REPEATABLE, hybrid mode
- ArchitectureTest — enforces no global \$db in modules or domain, BaseModule contract

Updated ModuleLoaderTest and ModuleLoaderPriorityTest for namespaced class
generation and resolveClassName() removal.
2026-06-15 18:07:50 +03:00
Divarion-D 4a0501324c feat(modules): evolve module system to extensible platform architecture
TASK-000: Remove legacy core patching system
- Delete src/core/Module/CoreCodePatcher.php
- Delete src/core/Module/CoreCodePatchableModuleInterface.php

TASK-001: Split ModuleInterface into specialized provider interfaces
- Add ServiceProviderInterface (boot, getEventSubscribers)
- Add RouteProviderInterface (registerRoutes)
- Add CommandProviderInterface (registerCommands)
- Add NavbarProviderInterface (registerNavbar)
- Add StreamMiddlewareProviderInterface
- ModuleLoader::bootAll() detects supported providers via instanceof

TASK-002: Refactor EventDispatcher
- Add ListenerProvider with priority support
- Add AbstractEvent base class
- Add StoppableEventInterface support
- Preserve legacy subscribe/publish API compatibility

TASK-003: Extend ServiceContainer
- Add ServiceContainer::decorate()
- Support priority-based decorator chains
- Add protected service registry:
  - db
  - settings
  - config
  - auth

TASK-004: Add core event catalog
- ModuleLoadedEvent
- ModuleBootedEvent
- PackageInstalledEvent
- UserAuthenticatedEvent
- UserLoggedOutEvent
- StreamStartingEvent
- StreamStartedEvent
- StreamStoppedEvent
- SettingsChangedEvent

TASK-005: Formalize subsystem boundaries
- Add BoundaryInterface
- Add MinistraBootstrap implementation
- Define exported service contracts

TASK-006: Add stream middleware pipeline
- Add StreamContext
- Add StreamMiddlewareInterface
- Add StreamPipeline
- Support middleware priority ordering

TASK-007: Add module hot reload
- Dispatch PackageInstalledEvent after installation
- Add ModuleManager::hotReload()
- Load and boot newly installed modules without service restart

TASK-008: Standardize container contracts
- Add container interfaces:
  - ContainerInterface
  - ContainerExceptionInterface
  - NotFoundExceptionInterface
- Add NotFoundException implementation
- ServiceContainer::get() throws NotFoundException

TASK-009: Refactor navbar registration
- CoreNavbarProvider implements NavbarProviderInterface
- Add registerNavbar() instance method
- ModuleLoader uses provider-based registration

TASK-010: Add module priority support
- Add priority field to module.json
- Default value: 0
- Higher priority modules load earlier
- Resolve load order by:
  1. Priority DESC
  2. Module name ASC

TASK-011: Add optional dependencies
- Add optional_dependencies field to module.json
- Missing optional dependencies do not block module loading
- Process optional dependencies after required dependencies

Tests
- Add ServiceContainer tests
- Add EventDispatcher tests
- Add StreamPipeline tests
- Add ModuleLoader priority tests

Documentation
- Rewrite module architecture documentation
- Update examples and extension development guides
- Document lifecycle, events, services, middleware, and dependencies
2026-06-14 14:25:19 +03:00
Divarion-DandCopilot e629101d15 feat(modules): enhance module manifest structure and loader functionality
- Added new fields to module.json: environment, dependencies, has_navbar, and has_settings.
- Updated ModuleLoader to support environment filtering and topological sorting of modules based on dependencies.
- Implemented error handling for missing and cyclic dependencies during module loading.
- Enhanced documentation for module.json structure and ModuleLoader functionality.
- Introduced unit tests for ModuleLoader to validate loading behavior and dependency resolution.
- Updated existing modules' manifest files to include new fields.

Co-authored-by: Copilot <copilot@github.com>
2026-05-06 21:53:37 +03:00
Divarion-D d2b160b7b6 feat: Introduce PhpM3u8 Stream Classes and Interfaces
- Added `StreamInterface` to define the contract for stream handling.
- Implemented `FileStream` class for file-based streaming.
- Implemented `TextStream` class for in-memory text streaming.
- Removed legacy `M3UParser` class and related resources.
- Added new tests for `AttributeStringToArray`, `Iso8601Transformer`, `Resolution`, `Byterange`, `Inf`, `Line`, and `Lines` classes.
- Updated bootstrap file to include new PhpM3u8 components.
2026-04-21 19:00:17 +03:00
Divarion-D d8a48520fe feat(stream): migrate to vendored M3uParser and add parser/import tests
- Replace legacy in-project M3U parser with vendored Gemorroj/M3uParser
- Initialize parser via bootstrap with proper namespacing
- Remove obsolete src/domain/Stream/M3UEntry.php implementation

- Update stream import/review mapping:
  - use logo as fallback when tvg-logo is missing

- Add third-party compliance files for vendored parser:
  - ATTRIBUTION.md
  - LGPL LICENSE

- Add full M3uParser unit test suite:
  - tag parsing
  - parser behavior
  - fixtures

- Add StreamService integration tests for M3U parsing/import flow

- Update test bootstrap:
  - add igbinary_serialize / igbinary_unserialize polyfills
  - explicitly load M3uParser bootstrap and custom test tag class
2026-04-21 14:52:46 +03:00
Divarion-D e737025ff8 test: add targeted PHPUnit setup and docs, fix GitHubReleases cache switching
- add PHPUnit bootstrap and config under tests/
- add focused unit tests for GitHubReleases, InputValidator and FfmpegPaths
- fix GitHubReleases cache file handling when switching update channel
- document PHPUnit PHAR usage and debug run commands in RU/EN docs
- document SFTP sync for tests/ and contributor test workflow
- remove broad smoke coverage approach in favor of per-file tests
2026-04-17 22:55:54 +03:00