Commit Graph
15 Commits
Author SHA1 Message Date
Divarion_D a453cd9620 docs: fix stale references to the removed prelude files
The prelude shims (Paths/AppConfig/Binaries/ErrorCodes.php) were deleted and the
$rErrorCodes global is gone. Point the developer guides at the new homes:
constants → ConstantsInitializer (paths()/appConfig()/binaries() maps), error
catalogue → ErrorResponder::codes(). Also refresh the now-outdated "refactored
later" note in build/rector.php's skip list.

Only docs/en is edited (docs/ru is regenerated from it before a release);
make docs-build passes.
2026-09-16 18:52:01 +03:00
Divarion_D 29d98939ef build(cs): temporarily mute error-level sniffs for a green baseline
`make cs` fired 2321 errors across legacy code, so it could not act as a
gate. Set the ~20 error-level sniffs to severity 0 in a clearly-marked
TEMPORARY block (and ParameterTypeHint in its own block), and add
ignore_warnings_on_exit so advisory warnings (line length, cyclomatic
"too high", silenced errors) are still reported but do not fail the run.
`make cs` now exits 0.

These mutes are technical debt to unwind ONE sniff at a time (drop the
severity line, run make cs, fix, commit); no new violations should be
added under a muted rule. Muting ParameterTypeHint also stops cs-fix from
re-adding the null-crash param types (see TYPE_AUDIT.md). Biggest buckets:
ParameterTypeHint 1323, GlobalKeyword 452, camelCaps naming 231,
CyclomaticComplexity.MaxExceeded 174.
2026-09-13 22:28:10 +03:00
Divarion_D c291aa5266 fix(admin): revert Rector's __DIR__ include rewrite that broke table loads
Rector's FollowRequireByDirRector (applied in the stage-2 pass, 9665a5a2)
rewrote the admin/reseller table bootstrap include:

    include "functions.php";   ->   include __DIR__ . "/functions.php";

The bare include resolved the legacy admin bootstrap via include_path /
CWD at runtime; that bootstrap lives under Public/Views/admin, NOT next
to the controller. Prepending __DIR__ pinned the path to
Public/Controllers/Admin/functions.php, which does not exist, so every
session-authenticated table request (the `isset($_SESSION['hash'])` /
`isset($_SESSION['reseller'])` branch — i.e. the normal browser-panel
path) hit "include(...functions.php): Failed to open stream" and lost the
$db / $rUserInfo / $rPermissions / $rServers globals the rest of index()
needs.

Restore the exact pre-Rector includes and document the rule as unsafe in
build/rector.php so it stays disabled if a future Rector version
reintroduces it. 721 tests green.
2026-09-13 20:29:33 +03:00
Divarion_D b0c77d8eb8 style(phpcs): allow is_null(), keep extract() banned with documented exceptions
Reviewed the Generic.PHP.ForbiddenFunctions list against real usage: the
whole 20-function ban produced only two kinds of violations across src —
is_null (84) and extract (2); the other 18 entries have zero call sites
and stay as free guardrails.

- is_null: dropped from the ban. It is equivalent to `=== null` and the
  prohibition was purely cosmetic; keeping it avoids churning 41 files
  for no functional gain.
- extract: kept banned (it injects variables from array keys — a real
  footgun), but the two legitimate uses in the view-render layer
  (BaseAdminController, BasePlayerController) expose the payload to legacy
  PHP templates and cannot be removed without rewriting every view, so
  they are annotated with `// phpcs:ignore` and a rationale.

phpcs ForbiddenFunctions now reports 0 findings. 721 tests green.
2026-09-13 20:20:25 +03:00
Divarion_D e8483a7345 fix(security): restore access-control guards mangled by Rector's De Morgan
The stage-2 pass ran SimplifyDeMorganBinaryRector, which — negating a condition
whose operand is an assignment — dropped the grouping parens:

    // was (correct):
    if (!(($rLine = UserRepository::getLineById($rID)) && Authorization::check('line', $rID)))
    // Rector produced (broken):
    if (!$rLine = UserRepository::getLineById($rID) || !Authorization::check('line', $rID))

By PHP precedence the second form does NOT mean `!((r=...) && check())`: it
returns "not failed" when the entity is missing OR the caller is unauthorised,
and leaves $rLine holding a bool. That is a broken-access-control bypass — a
background security review flagged it in ResellerAPIWrapper.

Scope: 49 conditions were mangled — 5 in ResellerAPIWrapper, 44 in
AdminAPIWrapper — most guarding Authorization::check / isset on get* lookups.
All were correct (parenthesised) on main; only the unpushed refactor/rector
branch was affected. Restored each to `!($x = f()) || !cond` (De Morgan of the
original). The `strtotime`/StreamView `!$x = f()` sites (no trailing `||`) parse
correctly and were left as-is.

Also disable SimplifyDeMorganBinaryRector in build/rector.php (its parens-drop
on assignment-in-condition outweighs the cosmetic wins) and document both
parens-bug variants + review greps in the config header.

Verified: both bug-pattern greps return nothing; PHPStan level 5 clean; suite
721 tests / 0 errors; Rector at fixpoint.
2026-09-13 19:37:08 +03:00
Divarion_D 9665a5a2ab refactor: expand Rector to Streaming / Public\Controllers / Ministra (stage 2)
Widen the Rector config to the remaining class-based trees (Streaming,
Public\Controllers, Ministra), keeping the templates, procedural
front-controllers (Public/stream, Public/admin, Ministra/portal.php) and the
streaming hot-path bootstraps out. Also document the recurring dropped-parens
bug + the review grep in the config header.

The resulting 107-file pass (94 Public\Controllers, 11 Streaming, 2 Ministra)
was reviewed against the suite + PHPStan + the usual scans:
- No dropped-parens bug this time (the assignment-in-condition pattern didn't
  occur in these files).
- 6 locally-called private static helpers became instance methods
  (behaviour-preserving; all called via $this->, no static call sites); one
  unused private param dropped (FanoutConfig::desired $rSnapshot, call site
  updated).
- De Morgan / ternary / dead-code simplifications; two `$x = getById()`
  truthy-assignments folded into the condition (no comparison, safe).

PHPStan level 5 clean; suite 721 tests / 0 errors.
2026-09-13 19:21:44 +03:00
Divarion_D 98aea670c3 build(rector): add Rector scaffolding (stage 1 — config + make targets + docs)
Adopt Rector as a require-dev tool alongside PHPStan/phpcs for safe, mechanical
refactoring:

- src/composer.json: add rector/rector ^2.0 (require-dev) + refactor/refactor:dry
  composer scripts.
- build/rector.php: narrowly-scoped config over the PSR-4 class trees
  (Core/Domain/Cli/Infrastructure). Skips the \TMDB lib, the streaming hot-path,
  vendor, tmp/backups. Import-adding stays OFF (the check-procedural-use gate
  relies on positional use imports). Behaviour-changing rules are disabled
  (SafeDeclareStrictTypes, UseIdenticalOverEqualWithSameType); only the safe
  deadCode + codeQuality prepared sets run (incl. the empty-if/else collapse).
- Makefile: `make rector` (dry-run, non-zero on pending changes) and
  `make rector-fix` (apply). Both require dev-tools.
- docs/en/guides/refactoring.md + dev-workflow.md + mkdocs.yml nav: the
  detect -> diff -> verify -> apply workflow.

No source code is changed by this commit — scaffolding only. `make rector-fix`
output will be reviewed separately.
2026-09-13 16:48:17 +03:00
Divarion_D 5843f18bb7 build(cs): adopt K&R + tab house style, require param type hints
Rework build/phpcs.xml.dist from strict PSR-12 to the project's actual
house style so 'make cs-fix' is idempotent against the codebase:

- K&R (one-true-brace) instead of Allman; enforce via
  Generic.Classes.OpeningBraceSameLine +
  Generic.Functions.OpeningFunctionBraceKernighanRitchie, and exclude the
  PSR12/PEAR/Squiz messages that push the opening brace to a new line.
- Tab indentation instead of 4 spaces: DisallowSpaceIndent + ScopeIndent
  (tabIndent), and disable the tab-incompatible alignment sniffs
  (MultiLineCondition, FunctionCallSignature, ControlStructureSpacing) plus
  ConcatenationSpacing newlines so phpcbf converges (0 FAILED TO FIX).
- Restrict to PHP only (extensions=php) so .js/.css are never touched.
- Drop Generic.Formatting.SpaceAfterNot and Generic.PHP.RequireStrictTypes
  (the codebase does not use declare(strict_types)).
- Add SlevomatCodingStandard.TypeHints.ParameterTypeHint to catch
  untyped parameters.

Update CONTRIBUTING.md to run 'make cs-fix' first and describe the style.
2026-09-13 14:14:13 +03:00
Divarion_D 1aede3bf4c refactor(i18n): co-locate language files with the Translator
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.
2026-08-31 21:28:16 +03:00
Divarion_D 512a7a2985 fix: resolve all PHPStan errors and remove the frozen baseline
Drop build/phpstan-baseline.neon (122 frozen pre-existing errors) and fix
the underlying issues in source instead of suppressing them. A fresh
level-5 run without the baseline surfaced 158 errors across 56 files; all
are resolved. `make phpstan`, `make cs`, and `make gates` are green.

Fix categories:
- variable.undefined — initialize vars to a correct default before the
  branch/loop that conditionally set them, so every path defines them.
- return.type — align declared return types with reality (widen to
  array|false / ?array / \Movie|null etc. where callers handle the
  sentinel, or return the declared type consistently).
- argument.type — cast at call sites (curl_setopt/stream_set_blocking bool
  flags, str_replace/mktime/uniqid operands, \CurlHandle phpdoc).
- redundant/dead conditions — simplify always-true guards and drop
  unreachable else branches, preserving behavior.

Real latent bugs caught along the way:
- Core/Http/CurlClient — retry loop never incremented on failure and never
  broke on success (could spin); now retries as documented.
- Core/Util/StreamUtils — explode('=', $x, 1) meant path/domain query
  params were never parsed (limit 1 -> 2).
- Cli/CronJobs/RootSignalsCronJob — set_governor emitted an undefined PHP
  $i into the cpufreq-set bash command (now a literal shell $i).

Root-cause analyzer fixes (not suppressions):
- Database::ping()/num_rows() marked @phpstan-impure so repeated calls are
  not treated as constant (clears the ResellerApiDispatcher false cluster).
- StreamService::getArchive() $rReturn given an explicit @var for the
  loop-accumulated shape PHPStan cannot infer from array().

Two residual entries are documented false positives (analyzer limitations,
not bugs) as path-scoped ignoreErrors in phpstan.dist.neon:
- CacheHandlerCommand:66 — settings force-reloaded from DB mid-run.
- EpgCronJob:314 — reconnect-verify ping() after db_connect().

Also drops an unused `use ...Epg\EPG;` import in admin/api.php.
2026-08-27 15:24:41 +03:00
Divarion_D b1e78aa35f refactor(cli): use safe settings accessors, hoist cron reads, clear baseline debt
Migrate SettingsManager::getAll()['key'] to SettingsManager::get('key') across
CLI commands and cron jobs. get() is used here (rather than the typed
accessors) because it returns the raw value unchanged, preserving the diverse
call sites verbatim: `=== null` checks, `?? ''`/`?? false` fallbacks, string
concatenation, numeric comparisons and ternaries. It still adds a default,
silencing PHP 8 "Undefined array key" warnings. Whole-array `$rSettings =
getAll()` grabs are left as-is.

Hoist the request-invariant redis_handler flag to method entry in
UsersCronJob/StreamsCronJob (loadCron/processDeletions), where it was read deep
inside nested per-connection loops.

Also fix two long-standing PHPStan baseline entries by initialising
$rRedisDelete and $rLiveKeys unconditionally in UsersCronJob (they were only
assigned inside `if ($rRedis)` but read under correlated guards) and drop the
now-stale baseline entries.
2026-08-24 21:52:56 +03:00
Divarion_D 9dd2ac2a48 chore(cs): replace PHP-CS-Fixer with phpcs + Slevomat Coding Standard
PHP-CS-Fixer's `no_unused_imports` is conservative — it treats a class name that
merely appears in a PHPDoc *description* as "used", so genuinely-dead imports
(e.g. `use ...Request;` next to a "Request IP" doc description) were never
flagged. Slevomat's UnusedUses is precise: it parses annotation *types*
(@param/@return/@var), so it keeps docblock-typed imports but removes truly
unused ones — matching what Intelephense (P1003) reports.

- Swap require-dev: friendsofphp/php-cs-fixer -> squizlabs/php_codesniffer +
  slevomat/coding-standard (+ phpcodesniffer-composer-installer, allow-listed).
- New narrow ruleset build/phpcs.xml.dist (import/namespace hygiene only, NOT
  full PSR-12): UnusedUses (searchAnnotations=true), UseFromSameNamespace,
  UseDoesNotStartWithBackslash, AlphabeticallySortedUses, UseSpacing,
  NamespaceSpacing. View templates stay excluded.
- Makefile: `make cs` -> phpcs, `make cs-fix` -> phpcbf (same target names).
- CI code-style job, CLAUDE.md, CONTRIBUTING.md, docs, .gitignore updated;
  build/.php-cs-fixer.dist.php removed. Committed vendor stays production-only.
2026-08-21 17:55:24 +03:00
Divarion_D 9ffbe1a34b build: add on-demand phpstan-deadcode audit (unused-public)
Adds a `make phpstan-deadcode` target and build/phpstan-deadcode.neon that
layer tomasvotruba/unused-public on top of the main PHPStan setup to report
unused PUBLIC methods/properties/constants. It is an on-demand audit, NOT a
CI gate (expect false positives for dynamically-invoked code — routes,
#[ListensTo] subscribers, CLI handlers, view templates).

unused-public is a require-dev package (installed by `make dev-tools`); the
committed vendor/ stays production-only.
2026-08-11 21:45:32 +03:00
Divarion_D fda7f41258 refactor(ministra): move Stalker portal from module into core (src/Ministra)
Ministra stops being a module — the whole Stalker portal (portal.php,
MinistraBootstrap, PortalHandler/PortalHelpers and the STB front-end) now
lives in src/Ministra/ under the XcVm\Ministra namespace, served at
/home/xc_vm/Ministra via the nginx alias.

- src/ministra/* and Modules/ministra_85a7d/{PortalHandler,PortalHelpers}
  → src/Ministra/; MinistraModule.php + module.json removed. Ministra was
  the only committed module, so src/Modules/ keeps a .gitkeep.
- portal.php resolves PortalHandler as a sibling and derives MAIN_HOME from
  its new location (glob crutch gone).
- nginx alias + AuthRepository $rAlias switched to /home/xc_vm/Ministra
  (PascalCase); ministra entry dropped from bundled_modules.php.
- Makefile: Modules/ removed from LB_DIRS — all modules are MAIN-only, so
  the ~50 MB of portal assets no longer ship to LB nodes.
- ArchitectureTest: zero committed modules is now a valid state.
- PHPStan: analyse src/Ministra, exclude the procedural portal.php entry,
  repath the ministra baseline entries.
- Docs (architecture, ministra-browser-emulation, extraction plan) updated
  to the new layout; the "extract to a separate repo" plan is cancelled.

Verified: php -l, make gates, make phpstan (No errors), full unit suite
(432 tests). On-server smoke: handshake + get_profile work end-to-end with
a registered MAC after deploy.
2026-08-11 21:41:11 +03:00
Divarion_D 7f3812e165 chore(build): move phpstan / php-cs-fixer config out of the repo root
Relocate the dev-tooling config into build/ so the project root only holds
source and first-class project files:
- phpstan.dist.neon        -> build/phpstan.dist.neon
- phpstan-baseline.neon    -> build/phpstan-baseline.neon
- .php-cs-fixer.dist.php    -> build/.php-cs-fixer.dist.php
- .php-cs-fixer.cache       -> build/ (regenerated there; gitignored)

Because neon/CS-Fixer resolve relative paths against the config file's own
directory, the internal references are re-anchored one level up (src/ ->
../src/, tools/ -> ../tools/, Finder in(__DIR__.'/../src'); the baseline's
path: entries likewise). The Makefile now points PHPStan at the config with
-c build/phpstan.dist.neon (it previously relied on root auto-discovery),
generates the baseline into build/, and passes --config=build/... to CS-Fixer;
the CS-Fixer cache is pinned to build/ via setCacheFile and re-gitignored.

No behaviour change. Verified: make phpstan (No errors), make cs (0 fixable),
make gates. CI runs through these make targets, so it is covered.
2026-08-09 19:37:11 +03:00