refactor(bootstrap): replace per-scope include pairs with ScopeBootstrap classes

The front controller selected a pair of procedural files per scope
(<scope>_session.php + <scope>_functions.php) from a hardcoded map and
require'd them at global scope. Consolidate into a typed class hierarchy:

- ScopeBootstrap (interface) + ScopeBootstrapFactory::create($scope)
- Admin/Reseller/PlayerScopeBootstrap, each porting its former session +
  functions logic 1:1 into boot()/bootSession()/bootFunctions().

index.php now calls ScopeBootstrapFactory::create($scope)->boot() instead
of building file paths and require'ing. Unknown scopes still fall back to
admin.

Behaviour is unchanged: the view-facing globals ($rUserInfo, $rPermissions,
$_STATUS, $customScript, $_PAGE, server/health flags) are still injected via
explicit `global` declarations in the boot methods, so the ~140 procedural
view templates read them from scope exactly as before. player_utility_-
functions.php is unchanged and still loaded by PlayerScopeBootstrap.

Also fix the PHPStan stub: SERVER_ID is an int (getMainID(): ?int, a
server_id PK), not a string. Moving its define() into a class method meant
PHPStan no longer inferred the type from a top-level define and fell back to
the stub, which wrongly typed it `(string) mt_rand()` — surfacing 20
false argument.type errors at int-param call sites. Stub now uses mt_rand().

Removes: admin_session_fc.php, admin_functions_fc.php, reseller_session.php,
reseller_functions.php, player_session.php, player_functions.php.

make phpstan / cs / gates all green.
This commit is contained in:
Divarion_D
2026-08-27 15:57:35 +03:00
parent 512a7a2985
commit 72eb0fbc58
14 changed files with 519 additions and 432 deletions
@@ -0,0 +1,186 @@
<?php
namespace XcVm\Infrastructure\Bootstrap;
use XcVm\Core\Auth\AuthRepository;
use XcVm\Core\Auth\SessionManager;
use XcVm\Core\Config\SettingsManager;
use XcVm\Core\Http\RequestManager;
use XcVm\Core\Util\AdminHelpers;
use XcVm\Core\Util\NetworkUtils;
use XcVm\Domain\User\UserRepository;
/**
* Admin scope bootstrap (Front Controller path).
*
* Ports the former admin_session_fc.php + admin_functions_fc.php includes:
* admin session lifecycle (timeout, login redirect, heartbeat) followed by
* the full framework boot, $rUserInfo / $rPermissions setup, session-integrity
* validation and user preferences.
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
final class AdminScopeBootstrap implements ScopeBootstrap {
public function boot(): void {
$this->bootSession();
$this->bootFunctions();
}
/**
* Admin session lifecycle: start, expire after timeout, redirect if
* unauthenticated (JSON for AJAX). Session keys: hash, ip, code, verify,
* last_activity.
*
* @return void
*/
private function bootSession(): void {
$rSessionTimeout = 60;
if (!defined('TMP_PATH')) {
define('TMP_PATH', '/home/xc_vm/tmp/');
}
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
// Expire session after timeout
if (
isset($_SESSION['hash'], $_SESSION['last_activity'])
&& ($rSessionTimeout * 60) < (time() - $_SESSION['last_activity'])
) {
foreach (['hash', 'ip', 'code', 'verify', 'last_activity'] as $rKey) {
unset($_SESSION[$rKey]);
}
}
// Not authenticated → redirect to login (or JSON response for AJAX)
if (!isset($_SESSION['hash'])) {
if (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
) {
header('Content-Type: application/json');
echo json_encode(['result' => false]);
exit;
}
$referrer = '';
if (defined('PAGE_NAME') && PAGE_NAME !== 'login') {
$referrer = '?referrer=' . urlencode(PAGE_NAME);
}
header('Location: ./login' . $referrer);
exit;
}
$_SESSION['last_activity'] = time();
session_write_close();
}
/**
* Framework boot + admin user context. Injects the legacy view-facing
* globals ($rUserInfo, $rPermissions, $rServerError, ...) — the procedural
* admin views read them from scope.
*
* @return void
*/
private function bootFunctions(): void {
global $db, $rSettings, $rMobile, $rServers, $rProxyServers, $rDetect,
$rTimeout, $rProtocol, $allServers, $rPermissions, $language, $allowedLangs,
$rServerError, $allServersHealthy, $updateRequired, $rUserInfo,
$_STATUS, $customScript;
if (!defined('MAIN_HOME')) {
define('MAIN_HOME', '/home/xc_vm/');
}
require_once MAIN_HOME . 'bootstrap.php';
\XC_Bootstrap::boot(\XC_Bootstrap::CONTEXT_ADMIN);
if ($rMobile) {
$rSettings['js_navigate'] = 0;
}
if (isset($_SESSION['hash'])) {
$rUserInfo = UserRepository::getRegisteredUserById($_SESSION['hash']);
$__tz = trim($rUserInfo['timezone'] ?? '', '" ');
if ($__tz !== '' && in_array($__tz, timezone_identifiers_list())) {
date_default_timezone_set($__tz);
}
if (!empty($rUserInfo['hue']) && (!isset($_COOKIE['hue']) || $_COOKIE['hue'] != $rUserInfo['hue'])) {
setcookie('hue', $rUserInfo['hue'], time() + 604800);
}
if (!isset($_COOKIE['theme']) || $_COOKIE['theme'] != $rUserInfo['theme']) {
setcookie('theme', $rUserInfo['theme'], time() + 604800);
}
if (!isset($_COOKIE['lang']) || $_COOKIE['lang'] != $rUserInfo['lang']) {
$language::setLanguage($rUserInfo['lang']);
}
$rPermissions = AuthRepository::getPermissions($rUserInfo['member_group_id']);
$rPermissions['advanced'] = json_decode($rPermissions['allowed_pages'], true);
$rIP = NetworkUtils::getUserIP();
$rIPMatch = ($rSettings['ip_subnet_match'] ? implode('.', array_slice(explode('.', $_SESSION['ip']), 0, -1)) == implode('.', array_slice(explode('.', $rIP), 0, -1)) : $_SESSION['ip'] == $rIP);
if (!$rUserInfo || !$rPermissions || !$rPermissions['is_admin'] || !$rIPMatch && $rSettings['ip_logout'] || $_SESSION['verify'] != md5($rUserInfo['username'] . '||' . $rUserInfo['password'])) {
unset($rUserInfo, $rPermissions);
SessionManager::clearContext('admin');
header('Location: index');
exit();
}
if ($_SESSION['ip'] == $rIP || $rSettings['ip_logout']) {
} else {
$_SESSION['ip'] = $rIP;
}
$rServerError = false;
foreach ($rServers as $rServer) {
if (!$rServer['server_online'] && $rServer['enabled'] && $rServer['status'] != 3 && $rServer['status'] != 5) {
$rServerError = true;
}
}
$allServersHealthy = false;
foreach ($rProxyServers as $rServer) {
if (!$rServer['server_online'] && $rServer['enabled'] && $rServer['status'] != 3 && $rServer['status'] != 5) {
$allServersHealthy = true;
}
}
$updateRequired = false;
if (isset($rServers[SERVER_ID]) && !version_compare($rServers[SERVER_ID]['xc_vm_version'], SettingsManager::getString('update_version'), '>=')) {
$updateRequired = true;
}
}
if (RequestManager::has('status')) {
$_STATUS = intval(RequestManager::get('status'));
$rArgs = RequestManager::getAll();
unset($rArgs['status']);
$customScript = AdminHelpers::setArgs($rArgs);
}
if (AdminHelpers::getPageName() != 'setup') {
$db->query('SELECT COUNT(`id`) AS `count` FROM `users` LEFT JOIN `users_groups` ON `users_groups`.`group_id` = `users`.`member_group_id` WHERE `users_groups`.`is_admin` = 1;');
if ($db->get_row()['count'] == 0) {
header('Location: ./setup');
exit();
}
}
}
}
@@ -0,0 +1,118 @@
<?php
namespace XcVm\Infrastructure\Bootstrap;
use XcVm\Core\Auth\SessionManager;
use XcVm\Core\Config\SettingsManager;
use XcVm\Domain\Server\ServerRepository;
use XcVm\Domain\Stream\ConnectionTracker;
use XcVm\Domain\User\UserRepository;
use XcVm\Domain\Vod\TMDbService;
/**
* Player scope bootstrap (Front Controller path).
*
* Ports the former player_session.php + player_functions.php includes: player
* session lifecycle, framework boot, HTTPS/login redirects, player-session
* integrity validation, and loading of the player utility functions
* (getStream, getUserStreams, ...).
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
final class PlayerScopeBootstrap implements ScopeBootstrap {
public function boot(): void {
$this->bootSession();
$this->bootFunctions();
}
/**
* Player session lifecycle. Session keys: phash, pverify.
*
* @return void
*/
private function bootSession(): void {
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
// Not logged in → redirect to login
if (!isset($_SESSION['phash'])) {
$referrer = defined('PAGE_NAME') ? PAGE_NAME : '';
$code = $_SERVER['XC_CODE'] ?? '';
$loginUrl = $code ? '/' . $code . '/login' : 'login';
header('Location: ' . $loginUrl . ($referrer ? '?referrer=' . urlencode($referrer) : ''));
exit();
}
}
/**
* Framework boot + player user context, then load the player utility
* functions. Injects the legacy view-facing globals ($rServers, $rUserInfo,
* $_PAGE) read by the player views/header/footer.
*
* @return void
*/
private function bootFunctions(): void {
global $rServers, $rUserInfo, $_PAGE;
// Mark bootstrap as done to prevent double-init when legacy files
// include player/functions.php via require_once
define('PLAYER_BOOTSTRAP_DONE', true);
if (!defined('MAIN_HOME')) {
define('MAIN_HOME', dirname(__DIR__, 2) . '/');
}
require_once MAIN_HOME . 'bootstrap.php';
\XC_Bootstrap::boot(\XC_Bootstrap::CONTEXT_ADMIN);
TMDbService::requireLibrary();
if (!defined('SERVER_ID')) {
define('SERVER_ID', ConnectionTracker::getMainID());
}
// $_PAGE is used by header.php and footer.php for active nav highlighting
$_PAGE = defined('PAGE_NAME') ? PAGE_NAME : 'index';
$rServers = ServerRepository::getAll();
SettingsManager::update('live_streaming_pass', md5(sha1($rServers[SERVER_ID]['server_name'] . $rServers[SERVER_ID]['server_ip']) . '5f13a731fb85944e5c69ce863b0c990d'));
// HTTPS check: redirect to HTTP if HTTPS is on but not enabled in panel settings
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && !$rServers[SERVER_ID]['enable_https']) {
header('Location: ' . $rServers[SERVER_ID]['http_url'] . ltrim($_SERVER['REQUEST_URI'], '/'));
exit();
}
// Player auth verification
if (isset($_SESSION['phash'])) {
$rUserInfo = UserRepository::getUserInfo($_SESSION['phash'], null, null, true);
if (
!$rUserInfo
|| $_SESSION['pverify'] != md5($rUserInfo['username'] . '||' . $rUserInfo['password'])
|| (!is_null($rUserInfo['exp_date']) && $rUserInfo['exp_date'] <= time())
|| $rUserInfo['admin_enabled'] == 0
|| $rUserInfo['enabled'] == 0
) {
SessionManager::clearContext('player');
$code = $_SERVER['XC_CODE'] ?? '';
header('Location: ' . ($code ? '/' . $code . '/login' : 'login'));
exit();
}
sort($rUserInfo['bouquet']);
} else {
$code = $_SERVER['XC_CODE'] ?? '';
header('Location: ' . ($code ? '/' . $code . '/login' : 'login'));
exit();
}
// Load player utility functions (getStream, getUserStreams, etc.)
require_once MAIN_HOME . 'Infrastructure/Bootstrap/player_utility_functions.php';
}
}
@@ -0,0 +1,140 @@
<?php
namespace XcVm\Infrastructure\Bootstrap;
use XcVm\Core\Auth\AuthRepository;
use XcVm\Core\Auth\SessionManager;
use XcVm\Core\Http\RequestManager;
use XcVm\Core\Util\AdminHelpers;
use XcVm\Core\Util\NetworkUtils;
use XcVm\Domain\User\UserRepository;
/**
* Reseller scope bootstrap (Front Controller path).
*
* Ports the former reseller_session.php + reseller_functions.php includes:
* reseller session lifecycle (timeout, login redirect, heartbeat) followed by
* the framework boot, $rUserInfo / $rPermissions setup and session-integrity
* validation.
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
final class ResellerScopeBootstrap implements ScopeBootstrap {
public function boot(): void {
$this->bootSession();
$this->bootFunctions();
}
/**
* Reseller session lifecycle. Session keys: reseller, rip, rcode, rverify,
* rlast_activity.
*
* @return void
*/
private function bootSession(): void {
$rSessionTimeout = 60;
if (!defined('TMP_PATH')) {
define('TMP_PATH', '/home/xc_vm/tmp/');
}
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
// Expire session after timeout
if (
isset($_SESSION['reseller'], $_SESSION['rlast_activity'])
&& ($rSessionTimeout * 60) < (time() - $_SESSION['rlast_activity'])
) {
foreach (['reseller', 'rip', 'rcode', 'rverify', 'rlast_activity'] as $rKey) {
unset($_SESSION[$rKey]);
}
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
}
// Not logged in → redirect to login (unless this is a direct session check via FC)
if (!isset($_SESSION['reseller'])) {
// FC handles login/index pages via noBootstrapPages — this code runs only
// for authenticated pages. Redirect to login with referrer.
$referrer = defined('PAGE_NAME') ? PAGE_NAME : basename($_SERVER['REQUEST_URI'] ?? '', '.php');
header('Location: login?referrer=' . urlencode($referrer));
exit();
}
$_SESSION['rlast_activity'] = time();
session_write_close();
}
/**
* Framework boot + reseller user context. Injects the legacy view-facing
* globals ($rUserInfo, $rPermissions, ...) read by the reseller views.
*
* @return void
*/
private function bootFunctions(): void {
global $db, $rSettings, $rMobile, $language, $rPermissions, $rUserInfo,
$_STATUS, $customScript;
if (!defined('MAIN_HOME')) {
define('MAIN_HOME', '/home/xc_vm/');
}
require_once MAIN_HOME . 'bootstrap.php';
\XC_Bootstrap::boot(\XC_Bootstrap::CONTEXT_ADMIN);
if ($rMobile) {
$rSettings['js_navigate'] = 0;
}
if (isset($_SESSION['reseller'])) {
$rUserInfo = UserRepository::getRegisteredUserById($_SESSION['reseller']);
if (strlen($rUserInfo['timezone'] ?? '') > 0) {
date_default_timezone_set($rUserInfo['timezone']);
}
setcookie('hue', $rUserInfo['hue'] ?? '', time() + 604800);
setcookie('theme', $rUserInfo['theme'] ?? '', time() + 604800);
$language::setLanguage($rUserInfo['lang']);
$rPermissions = array_merge(AuthRepository::getPermissions($rUserInfo['member_group_id']), AuthRepository::getGroupPermissions($rUserInfo['id']));
$rPermissions['direct_reports'] = $rPermissions['direct_reports'] ?? [];
$rPermissions['all_reports'] = $rPermissions['all_reports'] ?? [];
$rPermissions['stream_ids'] = $rPermissions['stream_ids'] ?? [];
$rPermissions['category_ids'] = $rPermissions['category_ids'] ?? [];
$rPermissions['series_ids'] = $rPermissions['series_ids'] ?? [];
$rPermissions['subresellers'] = $rPermissions['subresellers'] ?? [];
$rUserInfo['reports'] = array_map('intval', array_merge(array($rUserInfo['id']), $rPermissions['all_reports']));
$rIP = NetworkUtils::getUserIP();
$rIPMatch = ($rSettings['ip_subnet_match'] ? implode('.', array_slice(explode('.', $_SESSION['rip']), 0, -1)) == implode('.', array_slice(explode('.', $rIP), 0, -1)) : $_SESSION['rip'] == $rIP);
if (!$rUserInfo || !$rPermissions['is_reseller'] || !$rIPMatch && $rSettings['ip_logout'] || $_SESSION['rverify'] != md5($rUserInfo['username'] . '||' . $rUserInfo['password'])) {
unset($rUserInfo, $rPermissions);
SessionManager::clearContext('reseller');
header('Location: ./index');
exit();
}
if ($_SESSION['rip'] != $rIP && !$rSettings['ip_logout']) {
$_SESSION['rip'] = $rIP;
}
}
if (RequestManager::has('status')) {
$_STATUS = intval(RequestManager::get('status'));
$rArgs = RequestManager::getAll();
unset($rArgs['status']);
$customScript = AdminHelpers::setArgs($rArgs);
}
}
}
@@ -0,0 +1,35 @@
<?php
namespace XcVm\Infrastructure\Bootstrap;
/**
* Per-scope request bootstrap contract.
*
* A scope bootstrap runs the session lifecycle (start/timeout/login redirect)
* and then the framework + user context init (XC_Bootstrap::boot, $rUserInfo /
* $rPermissions, session-integrity checks). It replaces the former per-scope
* pair of procedural includes (`<scope>_session.php` + `<scope>_functions.php`)
* that the front controller `require`d at global scope.
*
* Implementations still inject the legacy view-facing globals ($rUserInfo,
* $rPermissions, ...) via `global` declarations — the ~140 procedural view
* templates read them from scope — so behaviour is identical to the includes.
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
interface ScopeBootstrap {
/**
* Boot the request scope: session lifecycle, then framework + user context.
*
* May `header()`/`exit()` on an unauthenticated or invalidated session,
* exactly as the former include pair did.
*
* @return void
*/
public function boot(): void;
}
@@ -0,0 +1,33 @@
<?php
namespace XcVm\Infrastructure\Bootstrap;
/**
* Resolve the {@see ScopeBootstrap} implementation for a request scope.
*
* Unknown scopes (e.g. ministra) fall back to admin — matching the former
* `$rBootstrapFiles[$scope] ?? $rBootstrapFiles['admin']` behaviour.
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
final class ScopeBootstrapFactory {
/**
* @param string $rScope 'admin' | 'reseller' | 'player' (others → admin).
* @return ScopeBootstrap
*/
public static function create(string $rScope): ScopeBootstrap {
switch ($rScope) {
case 'reseller':
return new ResellerScopeBootstrap();
case 'player':
return new PlayerScopeBootstrap();
default:
return new AdminScopeBootstrap();
}
}
}
@@ -1,122 +0,0 @@
<?php
use XcVm\Core\Auth\AuthRepository;
use XcVm\Core\Auth\SessionManager;
use XcVm\Core\Config\SettingsManager;
use XcVm\Core\Http\RequestManager;
use XcVm\Core\Util\AdminHelpers;
use XcVm\Core\Util\NetworkUtils;
use XcVm\Domain\User\UserRepository;
/**
* Admin functions bootstrap (Front Controller path).
*
* Extracted from admin/functions.php for FC use.
* Loads includes/admin.php (full bootstrap), then sets up $rUserInfo,
* $rPermissions, validates session integrity, load user preferences.
*
* @see admin/functions.php — оригинал (для direct nginx access)
* @since Phase 10.5
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
if (!defined('MAIN_HOME')) {
define('MAIN_HOME', '/home/xc_vm/');
}
// Импорт глобальных переменных bootstrap'а.
// XC_Bootstrap::boot(CONTEXT_ADMIN) использует `global` для записи переменных.
// Когда этот файл включается из FC (4b bootstrap в scope index.php),
// переменные доступны без global. Но при включении из метода контроллера —
// нужен явный global.
global $db, $rSettings, $rMobile, $rServers, $rProxyServers, $rDetect,
$rTimeout, $rProtocol, $allServers, $rPermissions, $language, $allowedLangs,
$rServerError, $allServersHealthy, $updateRequired, $rUserInfo;
require_once MAIN_HOME . 'bootstrap.php';
XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_ADMIN);
if ($rMobile) {
$rSettings['js_navigate'] = 0;
}
if (isset($_SESSION['hash'])) {
$rUserInfo = UserRepository::getRegisteredUserById($_SESSION['hash']);
$__tz = trim($rUserInfo['timezone'] ?? '', '" ');
if ($__tz !== '' && in_array($__tz, timezone_identifiers_list())) {
date_default_timezone_set($__tz);
}
if (!empty($rUserInfo['hue']) && (!isset($_COOKIE['hue']) || $_COOKIE['hue'] != $rUserInfo['hue'])) {
setcookie('hue', $rUserInfo['hue'], time() + 604800);
}
if (!isset($_COOKIE['theme']) || $_COOKIE['theme'] != $rUserInfo['theme']) {
setcookie('theme', $rUserInfo['theme'], time() + 604800);
}
if (!isset($_COOKIE['lang']) || $_COOKIE['lang'] != $rUserInfo['lang']) {
$language::setLanguage($rUserInfo['lang']);
}
$rPermissions = AuthRepository::getPermissions($rUserInfo['member_group_id']);
$rPermissions['advanced'] = json_decode($rPermissions['allowed_pages'], true);
$rIP = NetworkUtils::getUserIP();
$rIPMatch = ($rSettings['ip_subnet_match'] ? implode('.', array_slice(explode('.', $_SESSION['ip']), 0, -1)) == implode('.', array_slice(explode('.', $rIP), 0, -1)) : $_SESSION['ip'] == $rIP);
if (!$rUserInfo || !$rPermissions || !$rPermissions['is_admin'] || !$rIPMatch && $rSettings['ip_logout'] || $_SESSION['verify'] != md5($rUserInfo['username'] . '||' . $rUserInfo['password'])) {
unset($rUserInfo, $rPermissions);
SessionManager::clearContext('admin');
header('Location: index');
exit();
}
if ($_SESSION['ip'] == $rIP || $rSettings['ip_logout']) {
} else {
$_SESSION['ip'] = $rIP;
}
$rServerError = false;
foreach ($rServers as $rServer) {
if (!$rServer['server_online'] && $rServer['enabled'] && $rServer['status'] != 3 && $rServer['status'] != 5) {
$rServerError = true;
}
}
$allServersHealthy = false;
foreach ($rProxyServers as $rServer) {
if (!$rServer['server_online'] && $rServer['enabled'] && $rServer['status'] != 3 && $rServer['status'] != 5) {
$allServersHealthy = true;
}
}
$updateRequired = false;
if (isset($rServers[SERVER_ID]) && !version_compare($rServers[SERVER_ID]['xc_vm_version'], SettingsManager::getString('update_version'), '>=')) {
$updateRequired = true;
}
}
if (RequestManager::has('status')) {
$_STATUS = intval(RequestManager::get('status'));
$rArgs = RequestManager::getAll();
unset($rArgs['status']);
$customScript = AdminHelpers::setArgs($rArgs);
}
if (AdminHelpers::getPageName() != 'setup') {
$db->query('SELECT COUNT(`id`) AS `count` FROM `users` LEFT JOIN `users_groups` ON `users_groups`.`group_id` = `users`.`member_group_id` WHERE `users_groups`.`is_admin` = 1;');
if ($db->get_row()['count'] == 0) {
header('Location: ./setup');
exit();
}
}
@@ -1,57 +0,0 @@
<?php
/**
* Admin session bootstrap (Front Controller path).
*
* Extracted from admin/session.php for FC use.
* Manages admin session lifecycle: timeout, login redirect, heartbeat.
*
* Session keys: 'hash' (user ID), 'ip', 'code', 'verify', 'last_activity'
*
* @see admin/session.php — оригинал (для direct nginx access)
* @since Phase 10.5
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
$rSessionTimeout = 60;
if (!defined('TMP_PATH')) {
define('TMP_PATH', '/home/xc_vm/tmp/');
}
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
// Expire session after timeout
if (isset($_SESSION['hash'], $_SESSION['last_activity'])
&& ($rSessionTimeout * 60) < (time() - $_SESSION['last_activity'])) {
foreach (['hash', 'ip', 'code', 'verify', 'last_activity'] as $rKey) {
unset($_SESSION[$rKey]);
}
}
// Not authenticated → redirect to login (or JSON response for AJAX)
if (!isset($_SESSION['hash'])) {
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
header('Content-Type: application/json');
echo json_encode(['result' => false]);
exit;
}
$referrer = '';
if (defined('PAGE_NAME') && PAGE_NAME !== 'login') {
$referrer = '?referrer=' . urlencode(PAGE_NAME);
}
header('Location: ./login' . $referrer);
exit;
}
$_SESSION['last_activity'] = time();
session_write_close();
@@ -1,78 +0,0 @@
<?php
use XcVm\Core\Auth\SessionManager;
use XcVm\Core\Config\SettingsManager;
use XcVm\Domain\Server\ServerRepository;
use XcVm\Domain\Stream\ConnectionTracker;
use XcVm\Domain\User\UserRepository;
use XcVm\Domain\Vod\TMDbService;
/**
* Player functions bootstrap.
*
* Loads core dependencies, verifies player session integrity,
* and makes player utility functions available.
*
* Equivalent to the bootstrap section of player/functions.php,
* but using the standard includes/admin.php chain.
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
// Mark bootstrap as done to prevent double-init when legacy files
// include player/functions.php via require_once
define('PLAYER_BOOTSTRAP_DONE', true);
if (!defined('MAIN_HOME')) {
define('MAIN_HOME', dirname(__DIR__, 2) . '/');
}
require_once MAIN_HOME . 'bootstrap.php';
XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_ADMIN);
TMDbService::requireLibrary();
if (!defined('SERVER_ID')) {
define('SERVER_ID', ConnectionTracker::getMainID());
}
// $_PAGE is used by header.php and footer.php for active nav highlighting
$_PAGE = defined('PAGE_NAME') ? PAGE_NAME : 'index';
$rServers = ServerRepository::getAll();
SettingsManager::update('live_streaming_pass', md5(sha1($rServers[SERVER_ID]['server_name'] . $rServers[SERVER_ID]['server_ip']) . '5f13a731fb85944e5c69ce863b0c990d'));
// HTTPS check: redirect to HTTP if HTTPS is on but not enabled in panel settings
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && !$rServers[SERVER_ID]['enable_https']) {
header('Location: ' . $rServers[SERVER_ID]['http_url'] . ltrim($_SERVER['REQUEST_URI'], '/'));
exit();
}
// Player auth verification
if (isset($_SESSION['phash'])) {
$rUserInfo = UserRepository::getUserInfo($_SESSION['phash'], null, null, true);
if (!$rUserInfo
|| $_SESSION['pverify'] != md5($rUserInfo['username'] . '||' . $rUserInfo['password'])
|| (!is_null($rUserInfo['exp_date']) && $rUserInfo['exp_date'] <= time())
|| $rUserInfo['admin_enabled'] == 0
|| $rUserInfo['enabled'] == 0
) {
SessionManager::clearContext('player');
$code = $_SERVER['XC_CODE'] ?? '';
header('Location: ' . ($code ? '/' . $code . '/login' : 'login'));
exit();
}
sort($rUserInfo['bouquet']);
} else {
$code = $_SERVER['XC_CODE'] ?? '';
header('Location: ' . ($code ? '/' . $code . '/login' : 'login'));
exit();
}
// Load player utility functions (getStream, getUserStreams, etc.)
require_once MAIN_HOME . 'Infrastructure/Bootstrap/player_utility_functions.php';
@@ -1,26 +0,0 @@
<?php
/**
* Player session bootstrap.
*
* Manages player session lifecycle: start session, redirect if not authenticated.
* Session keys: 'phash' (user ID), 'pverify' (md5 of username||password)
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
// Not logged in → redirect to login
if (!isset($_SESSION['phash'])) {
$referrer = defined('PAGE_NAME') ? PAGE_NAME : '';
$code = $_SERVER['XC_CODE'] ?? '';
$loginUrl = $code ? '/' . $code . '/login' : 'login';
header('Location: ' . $loginUrl . ($referrer ? '?referrer=' . urlencode($referrer) : ''));
exit();
}
@@ -1,80 +0,0 @@
<?php
use XcVm\Core\Auth\AuthRepository;
use XcVm\Core\Auth\SessionManager;
use XcVm\Core\Http\RequestManager;
use XcVm\Core\Util\AdminHelpers;
use XcVm\Core\Util\NetworkUtils;
use XcVm\Domain\User\UserRepository;
/**
* Reseller functions bootstrap.
*
* Extracted from reseller/functions.php for Front Controller use.
* Loads includes/admin.php, then sets up $rUserInfo, $rPermissions
* and validates reseller session integrity.
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
if (!defined('MAIN_HOME')) {
define('MAIN_HOME', '/home/xc_vm/');
}
// Импорт глобальных переменных bootstrap'а.
// XC_Bootstrap::boot(CONTEXT_ADMIN) записывает переменные через `global`.
// При включении из метода контроллера нужен явный global.
global $db, $rSettings, $rMobile, $language, $rPermissions, $rUserInfo;
require_once MAIN_HOME . 'bootstrap.php';
XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_ADMIN);
if ($rMobile) {
$rSettings['js_navigate'] = 0;
}
if (isset($_SESSION['reseller'])) {
$rUserInfo = UserRepository::getRegisteredUserById($_SESSION['reseller']);
if (strlen($rUserInfo['timezone'] ?? '') > 0) {
date_default_timezone_set($rUserInfo['timezone']);
}
setcookie('hue', $rUserInfo['hue'] ?? '', time() + 604800);
setcookie('theme', $rUserInfo['theme'] ?? '', time() + 604800);
$language::setLanguage($rUserInfo['lang']);
$rPermissions = array_merge(AuthRepository::getPermissions($rUserInfo['member_group_id']), AuthRepository::getGroupPermissions($rUserInfo['id']));
$rPermissions['direct_reports'] = $rPermissions['direct_reports'] ?? [];
$rPermissions['all_reports'] = $rPermissions['all_reports'] ?? [];
$rPermissions['stream_ids'] = $rPermissions['stream_ids'] ?? [];
$rPermissions['category_ids'] = $rPermissions['category_ids'] ?? [];
$rPermissions['series_ids'] = $rPermissions['series_ids'] ?? [];
$rPermissions['subresellers'] = $rPermissions['subresellers'] ?? [];
$rUserInfo['reports'] = array_map('intval', array_merge(array($rUserInfo['id']), $rPermissions['all_reports']));
$rIP = NetworkUtils::getUserIP();
$rIPMatch = ($rSettings['ip_subnet_match'] ? implode('.', array_slice(explode('.', $_SESSION['rip']), 0, -1)) == implode('.', array_slice(explode('.', $rIP), 0, -1)) : $_SESSION['rip'] == $rIP);
if (!$rUserInfo || !$rPermissions['is_reseller'] || !$rIPMatch && $rSettings['ip_logout'] || $_SESSION['rverify'] != md5($rUserInfo['username'] . '||' . $rUserInfo['password'])) {
unset($rUserInfo, $rPermissions);
SessionManager::clearContext('reseller');
header('Location: ./index');
exit();
}
if ($_SESSION['rip'] != $rIP && !$rSettings['ip_logout']) {
$_SESSION['rip'] = $rIP;
}
}
if (RequestManager::has('status')) {
$_STATUS = intval(RequestManager::get('status'));
$rArgs = RequestManager::getAll();
unset($rArgs['status']);
$customScript = AdminHelpers::setArgs($rArgs);
}
@@ -1,49 +0,0 @@
<?php
/**
* Reseller session bootstrap.
*
* Extracted from reseller/session.php for Front Controller use.
* Manages reseller session lifecycle: timeout, login redirect, heartbeat.
*
* Session keys: 'reseller' (user ID), 'rip', 'rcode', 'rverify', 'rlast_activity'
*
* @package XC_VM_Infrastructure_Bootstrap
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
$rSessionTimeout = 60;
if (!defined('TMP_PATH')) {
define('TMP_PATH', '/home/xc_vm/tmp/');
}
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
// Expire session after timeout
if (isset($_SESSION['reseller'], $_SESSION['rlast_activity'])
&& ($rSessionTimeout * 60) < (time() - $_SESSION['rlast_activity'])) {
foreach (['reseller', 'rip', 'rcode', 'rverify', 'rlast_activity'] as $rKey) {
unset($_SESSION[$rKey]);
}
if (session_status() === PHP_SESSION_NONE && !headers_sent()) {
session_start();
}
}
// Not logged in → redirect to login (unless this is a direct session check via FC)
if (!isset($_SESSION['reseller'])) {
// FC handles login/index pages via noBootstrapPages — this code runs only
// for authenticated pages. Redirect to login with referrer.
$referrer = defined('PAGE_NAME') ? PAGE_NAME : basename($_SERVER['REQUEST_URI'] ?? '', '.php');
header('Location: login?referrer=' . urlencode($referrer));
exit();
}
$_SESSION['rlast_activity'] = time();
session_write_close();
@@ -36,7 +36,7 @@ class BasePlayerController extends BaseAdminController
*/
protected function requirePermission()
{
// Player авторизация обрабатывается bootstrap (player_session.php).
// Player авторизация обрабатывается bootstrap (PlayerScopeBootstrap).
// Если пользователь дошёл до контроллера — он уже аутентифицирован.
}
+5 -18
View File
@@ -2,6 +2,7 @@
use XcVm\Core\Http\Router;
use XcVm\Core\Module\ModuleLoader;
use XcVm\Infrastructure\Bootstrap\ScopeBootstrapFactory;
use XcVm\Infrastructure\Bootstrap\StreamingRequestBootstrap;
use XcVm\Infrastructure\Bootstrap\WebApiBootstrap;
use XcVm\Public\Controllers\Api\AdminApiController;
@@ -198,16 +199,6 @@ if ($scope === 'ministra') {
$adminDir = ($scope === 'admin') ? MAIN_HOME . 'Public/Views/admin/' : MAIN_HOME . $scope . '/';
@chdir(is_dir($adminDir) ? $adminDir : MAIN_HOME);
// scope → [session file, functions file]; unknown scopes (e.g. ministra) use admin.
$rBootstrapFiles = [
'reseller' => ['reseller_session.php', 'reseller_functions.php'],
'player' => ['player_session.php', 'player_functions.php'],
'admin' => ['admin_session_fc.php', 'admin_functions_fc.php'],
];
[$rSessionName, $rFunctionsName] = $rBootstrapFiles[$scope] ?? $rBootstrapFiles['admin'];
$sessionFile = MAIN_HOME . 'Infrastructure/Bootstrap/' . $rSessionName;
$functionsFile = MAIN_HOME . 'Infrastructure/Bootstrap/' . $rFunctionsName;
if ($scope === 'player') {
$noBootstrapPages = ['login'];
} else {
@@ -229,14 +220,10 @@ if (in_array($pageName, $noBootstrapPages, true)) {
exit;
}
// 7b. Bootstrap (session → functions → includes/admin)
if (file_exists($sessionFile)) {
require $sessionFile;
}
if (file_exists($functionsFile)) {
require $functionsFile;
}
// 7b. Bootstrap the request scope: session lifecycle → framework + user context.
// Replaces the former per-scope <scope>_session.php + <scope>_functions.php
// includes; unknown scopes (e.g. ministra) fall back to admin.
ScopeBootstrapFactory::create($scope)->boot();
// 8. Загрузка маршрутов
$router = Router::getInstance();
+1 -1
View File
@@ -78,7 +78,7 @@ if (!defined('PLAYER_TMP_PATH')) define('PLAYER_TMP_PATH', (string) mt_rand());
if (!defined('PLAYLIST_PATH')) define('PLAYLIST_PATH', (string) mt_rand());
if (!defined('SEGMENT_DURATION')) define('SEGMENT_DURATION', mt_rand());
if (!defined('SERIES_TMP_PATH')) define('SERIES_TMP_PATH', (string) mt_rand());
if (!defined('SERVER_ID')) define('SERVER_ID', (string) mt_rand());
if (!defined('SERVER_ID')) define('SERVER_ID', mt_rand());
if (!defined('SIGNALS_PATH')) define('SIGNALS_PATH', (string) mt_rand());
if (!defined('SIGNALS_TMP_PATH')) define('SIGNALS_TMP_PATH', (string) mt_rand());
if (!defined('STATUS_CERTBOT')) define('STATUS_CERTBOT', mt_rand());