<?php
//
// Copyright (C) 2006-2017 Next Generation CMS (http://ngcms.org)
// Name: core.php
// Description: core
// Author: NGCMS project team
//
// Configure error display mode
error_reporting(E_ALL ^ E_NOTICE);
// Define global directory constants
if (!defined('NGCoreDir')) {
    define('NGCoreDir', __DIR__ . '/'); // Location of Core directory
}
if (!defined('NGRootDir')) {
    define('NGRootDir', dirname(__DIR__) . '/'); // Location of SiteRoot
}
if (!defined('NGClassDir')) {
    define('NGClassDir', NGCoreDir . 'classes/'); // Location of AutoLoaded classes
}
if (!defined('NGVendorDir')) {
    define('NGVendorDir', NGRootDir . 'vendor/'); // Location of Vendor classes
}
$loader = require NGVendorDir . 'autoload.php';
// Autoloader for NEW STYLE Classes
spl_autoload_register(function ($className) {
    $fName = NGClassDir . $className . '.class.php';
    if (file_exists($fName)) {
        require_once $fName;
    }
});
// Magic function for immediate closure call
function NGRun($f)
{
    $f();
}
// ============================================================================
// MODULE DEPs check + basic setup
// ============================================================================
NGRun(function () {
    $depList = [
        'sql'      => ['pdo' => '', 'pdo_mysql' => ''],
        'zlib'     => 'ob_gzhandler',
        'iconv'    => 'iconv',
        'GD'       => 'imagecreatefromjpeg',
        'mbstring' => 'mb_internal_encoding',
    ];
    NGCoreFunctions::resolveDeps($depList);
    $sx = NGEngine::getInstance();
    $sx->set('events', new NGEvents());
    $sx->set('errorHandler', new NGErrorHandler());
});
// ============================================================================
// Global variables definition
// ============================================================================
global $PLUGINS, $EXTRA_HTML_VARS, $EXTRA_CSS;
global $AUTH_METHOD, $AUTH_CAPABILITIES, $PPAGES, $PFILTERS, $RPCFUNC, $TWIGFUNC;
global $RPCADMFUNC, $SUPRESS_TEMPLATE_SHOW, $SUPRESS_MAINBLOCK_SHOW, $SYSTEM_FLAGS;
global $DSlist, $PERM, $confPerm, $confPermUser, $systemAccessURL, $cron;
global $timer, $mysql, $ip, $parse, $tpl, $lang, $config;
global $TemplateCache, $siteDomainName;
global $currentHandler, $ngTrackID, $ngCookieDomain;
global $twigGlobal, $twig, $twigLoader, $twigStringLoader;
global $multiDomainName;
// Initialize global variables
$EXTRA_HTML_VARS = [];
$EXTRA_CSS = [];
$AUTH_METHOD = [];
$AUTH_CAPABILITIES = [];
$PPAGES = [];
$PFILTERS = [];
$RPCFUNC = [];
$TWIGFUNC = [];
$RPCADMFUNC = [];
$PERM = [];
$UGROUP = [];
$SUPRESS_TEMPLATE_SHOW = 0;
$SUPRESS_MAINBLOCK_SHOW = 0;
$CurrentHandler = [];
$TemplateCache = [];
$lang = [];
$SYSTEM_FLAGS = [
    'actions.disabled' => [],
    'http.headers'     => [
        'content-type'  => 'text/html; charset=utf-8',
        'cache-control' => 'private',
    ],
];
$twigGlobal = [
    'flags' => [
        'isLogged' => 0,
    ],
];
// List of DataSources
$DSlist = [
    'news'           => 1,
    'categories'     => 2,
    'comments'       => 3,
    'users'          => 4,
    'files'          => 10,
    'images'         => 11,
    '#xfields:tdata' => 51,
];
$PLUGINS = [
    'active'        => [],
    'active:loaded' => 0,
    'loaded'        => [],
    'loaded:files'  => [],
    'config'        => [],
    'config:loaded' => 0,
];
// Set internal encoding and HTTP output to UTF-8
mb_internal_encoding('UTF-8');
mb_http_output('UTF-8');
// Define global constants "root", "site_root"
if (!defined('root')) {
    define('root', __DIR__ . '/');
}
if (!defined('site_root')) {
    define('site_root', dirname(__DIR__) . '/');
}
// Define domain name for cookies
// Определяем cookie-домен в формате `.example.org` (для поддоменов)
$host = strtolower($_SERVER['HTTP_HOST']);
if (preg_match("#^(.+?):\d+$#", $host, $m)) {
    $host = $m[1]; // убираем порт
}
$hostParts = explode('.', $host);
// Список составных доменов верхнего уровня (compound TLDs)
$compoundTLDs = [
    // com.*
    'com.ua',
    'com.br',
    'com.au',
    'com.tr',
    'com.mx',
    'com.ar',
    'com.co',
    'com.pl',
    'com.sg',
    'com.my',
    'com.pk',
    'com.eg',
    'com.pe',
    'com.vn',
    'com.sa',
    'com.ph',
    'com.ng',
    'com.bd',
    // co.*
    'co.uk',
    'co.jp',
    'co.in',
    'co.za',
    'co.kr',
    'co.il',
    'co.nz',
    'co.th',
    'co.id',
    'co.ke',
    // org.*
    'org.uk',
    'org.au',
    'org.in',
    'org.br',
    'org.za',
    'org.ua',
    'org.nz',
    'org.il',
    'org.tr',
    // net.*
    'net.ua',
    'net.au',
    'net.br',
    'net.in',
    'net.nz',
    'net.za',
    'net.tr',
    // gov.*
    'gov.uk',
    'gov.au',
    'gov.br',
    'gov.in',
    'gov.za',
    'gov.tr',
    // edu.*
    'edu.au',
    'edu.br',
    'edu.in',
    'edu.tr',
    'edu.za',
    // ac.*
    'ac.uk',
    'ac.za',
    'ac.jp',
    'ac.in',
    'ac.th',
    'ac.nz',
    // Другие популярные
    'ne.jp',
    'or.jp',
    'go.jp',
    'gr.jp',
    'lg.jp',
];
if (count($hostParts) >= 2) {
    // Проверяем, является ли домен составным (например, .com.ua)
    $lastTwo = $hostParts[count($hostParts) - 2] . '.' . $hostParts[count($hostParts) - 1];
    if (count($hostParts) >= 3 && in_array($lastTwo, $compoundTLDs)) {
        // Для составных доменов типа .com.ua берем последние 3 части: .footballtransfer.com.ua
        $ngCookieDomain = '.' . $hostParts[count($hostParts) - 3] . '.' . $lastTwo;
    } else {
        // Для обычных доменов берем последние 2 части: .footballtransfer.ru
        $ngCookieDomain = '.' . $lastTwo;
    }
} else {
    // Для одночастных доменов (например, localhost) безопаснее не указывать домен вовсе
    $ngCookieDomain = '';
}
// Manage trackID cookie - can be used for plugins that don't require authentication,
// but need to track the user according to their ID
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
if (!isset($_COOKIE['ngTrackID'])) {
    $ngTrackID = md5(md5(uniqid(rand(), 1)));
    setcookie('ngTrackID', $ngTrackID, [
        'expires'  => time() + 86400 * 365,
        'path'     => '/',
        'domain'   => $ngCookieDomain ?: null,
        'secure'   => $isHttps,
        'httponly' => true,
        'samesite' => $isHttps ? 'None' : 'Lax',
    ]);
} else {
    $ngTrackID = $_COOKIE['ngTrackID'];
}
// Initialize last variables
$confArray = [
    // Pre-defined init values
    'predefined' => [
        'HTTP_REFERER' => $_SERVER['HTTP_REFERER'] ?? '',
        'PHP_SELF'     => $_SERVER['PHP_SELF'] ?? '',
        'REQUEST_URI'  => $_SERVER['REQUEST_URI'] ?? '',
        'config'       => [],
        'catz'         => [],
        'catmap'       => [],
        'is_logged'    => false,
    ],
];
// Load pre-defined variables
$predefinedUnsetArray = ['_GET', '_POST', '_SESSION', '_COOKIE', '_ENV'];
foreach ($confArray['predefined'] as $key => $value) {
    foreach ($predefinedUnsetArray as $arr) {
        if (isset($$arr[$key])) {
            unset($$arr[$key]);
        }
    }
    $$key = $value;
}
// Prepare variable with access URL
$systemAccessURL = $_SERVER['REQUEST_URI'];
if (($tmp_pos = strpos($systemAccessURL, '?')) !== false) {
    $systemAccessURL = mb_substr($systemAccessURL, 0, $tmp_pos);
}
// Initialize system libraries
// ** Time measurement functions
include_once root . 'includes/classes/timer.class.php';
$timer = new microTimer();
$timer->start();
// ** Multisite engine
include_once root . 'includes/inc/multimaster.php';
multi_multisites();
/**
 * @var $multiDomainName
 * @var $multimaster
 */
if (!defined('confroot')) {
    define('confroot', root . 'conf/' . ($multiDomainName && $multimaster && ($multiDomainName != $multimaster) ? 'multi/' . $multiDomainName . '/' : ''));
}
// Проверка наличия файла конфигурации
if (!file_exists(confroot . 'config.php') || filesize(confroot . 'config.php') < 10) {
    $scriptPath = $_SERVER['PHP_SELF'] ?? '';
    // Проверяем, соответствует ли путь ожидаемым шаблонам
    if (preg_match("#^(.*?)(/index\.php|/engine/admin\.php)$#", $scriptPath, $matches)) {
        $redirectUrl = $matches[1] . '/engine/install.php';
    } else {
        $redirectUrl = adminDirName . '/install.php';
    }
    // Выполняем переадресацию
    header('Location: ' . $redirectUrl);
    echo 'NGCMS: Engine is not installed yet. Please run installer from /engine/install.php';
    exit;
}
// ** Load system config
include_once confroot . 'config.php';
// [[FIX config variables]]
if (!isset($config['uprefix'])) {
    $config['uprefix'] = $config['prefix'];
}
// Make sure date_adjust is always treated as an integer offset (PHP 8 disallows string * int)
$config['date_adjust'] = (isset($config['date_adjust']) && is_numeric($config['date_adjust'])) ? intval($config['date_adjust']) : 0;
// Set up default timezone [ default: Europe/Moscow ]
date_default_timezone_set($config['timezone'] ?? 'Europe/Moscow');

// Call multisites processor again with loaded config (полноценный мультисайт)
multi_multisites();
// Call multidomains processor (базовая мультидоменность + замена переменных {domain})
multi_multidomains();
//print "siteDomainName [".$siteDomainName."]<br/>\n";

// Define template paths early so they're available for plugins
// ВАЖНО: делаем это ПОСЛЕ замены {domain}, чтобы в константах были правильные пути
if (!defined('tpl_site')) {
    define('tpl_site', site_root . 'templates/' . $config['theme'] . '/');
}
if (!defined('tpl_url')) {
    define('tpl_url', $config['home_url'] . '/templates/' . $config['theme']);
}
// [[MARKER]] Configuration file is loaded
$timer->registerEvent('Config file is loaded');
// Initiate session - корректные параметры cookie (учёт HTTP/HTTPS, домен, срок)
//print "<pre>".var_export($_SERVER, true).var_export($_COOKIE, true)."</pre>";
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
// Усиление настройки PHP-сессий на уровне приложения
@ini_set('session.use_strict_mode', '1');
@ini_set('session.cookie_httponly', '1');
if ($isHttps) {
    @ini_set('session.cookie_secure', '1');
}
$sessionLifetime = intval(ini_get('session.cookie_lifetime'));
if ($sessionLifetime <= 0) {
    // По умолчанию 24 часа, если в php.ini не задано положительное значение
    $sessionLifetime = 86400;
}
session_set_cookie_params([
    'lifetime' => $sessionLifetime,
    'path'     => '/',
    'domain'   => $ngCookieDomain ?: null,
    'secure'   => $isHttps,
    'httponly' => true,
    // Для HTTPS и междоменного шаринга — None, иначе Lax чтобы cookie не отбрасывался браузером
    'samesite' => $isHttps ? 'None' : 'Lax',
]);
session_start();
// Базовая привязка сессии к User-Agent (мягкая защита)
if (!isset($_SESSION['ua_fingerprint'])) {
    $_SESSION['ua_fingerprint'] = substr(hash('sha256', ($_SERVER['HTTP_USER_AGENT'] ?? 'na')), 0, 16);
} else {
    $currUA = substr(hash('sha256', ($_SERVER['HTTP_USER_AGENT'] ?? 'na')), 0, 16);
    if ($_SESSION['ua_fingerprint'] !== $currUA) {
        // При резкой смене UA сбрасываем авторизацию, сессию сохраняем
        unset($_SESSION['auth_user_id']);
    }
}
// Load system libraries
include_once root . 'includes/inc/consts.inc.php';
include_once root . 'includes/inc/functions.inc.php';
include_once root . 'includes/inc/extras.inc.php';

// Early load ng-helpers to make functions available for all plugins
$ngHelpersFile = root . 'engine/plugins/ng-helpers/src/helpers.php';
if (file_exists($ngHelpersFile)) {
    require_once $ngHelpersFile;
}

include_once 'includes/classes/templates.class.php';
include_once 'includes/classes/parse.class.php';
include_once 'includes/classes/uhandler.class.php';
// [[MARKER]] All system libraries are loaded
$timer->registerEvent('Core files are included');
// Activate URL processing library
$UHANDLER = new urlHandler();
$UHANDLER->loadConfig();
// Other libraries
$parse = new parse();
$tpl = new tpl();
$ip = checkIP();
// Load user groups
loadGroups();
// Init our own exception handler
set_exception_handler('ngExceptionHandler');
set_error_handler('ngErrorHandler');
register_shutdown_function('ngShutdownHandler');
// Initialize TWIG engine
$twigLoader = new NGTwigLoader(root);
// Configure cache path for multisite (separate cache for each domain)
$twigCachePath = root . 'cache/twig/' . ($multiDomainName ?: 'main') . '/';
// Create cache directory if not exists
if (!is_dir($twigCachePath)) {
    @mkdir($twigCachePath, 0755, true);
}
// Configure environment and general parameters
$twig = new NGTwigEnvironment($twigLoader, [
    'cache'       => $twigCachePath,
    'auto_reload' => true,
    'autoescape'  => false,
    'charset'     => 'UTF-8',
]);
// [[MARKER]] TWIG template engine is loaded
$timer->registerEvent('Template engine is activated');
// Add global variables to TWIG before runtime is initialized
$twig->addGlobal('tpl_url', tpl_url);
$twig->addGlobal('scriptLibrary', scriptLibrary);
// Add domain variables for multisite support
$twig->addGlobal('domain', $siteDomainName ?: $_SERVER['HTTP_HOST']);
$twig->addGlobal('domainid', $multiDomainName ?: '');
// Expose engine name/version to Twig templates for <meta name="generator">
if (!isset($template) || !is_array($template)) {
    $template = [];
}
if (!isset($template['vars']) || !is_array($template['vars'])) {
    $template['vars'] = [];
}
if (!isset($template['vars']['what']) || $template['vars']['what'] === '') {
    $template['vars']['what'] = defined('engineName') ? engineName : 'NGCMS';
}
if (!isset($template['vars']['version']) || $template['vars']['version'] === '') {
    $template['vars']['version'] = defined('engineVersion') ? engineVersion : '';
}
// Some scanners read only headers/HEAD, add X-Generator when enabled in config
if (!empty($config['x_ng_headers'])) {
    $gen = (defined('engineName') ? engineName : 'NGCMS');
    if (defined('engineVersion')) {
        $gen .= ' ' . engineVersion;
    }
    if (defined('engineVersionType') && defined('engineVersionBuild')) {
        $gen .= ' (' . engineVersionType . ' ' . engineVersionBuild . ')';
    }
    header('X-Generator: ' . $gen);
}
// Give domainName to URL handler engine for generating absolute links
$UHANDLER->setOptions(['domainPrefix' => $config['home_url']]);
// Check if engine is installed in subdirectory
if (preg_match('#^http\:\/\/([^\/])+(\/.+)#', $config['home_url'], $match)) {
    $UHANDLER->setOptions(['localPrefix' => $match[2]]);
}
// Load cache engine
include_once root . 'includes/classes/cache.class.php';
// NEW :: PDO driver with global classes handler
NGRun(function () use ($config) {
    global $mysql;
    $sx = NGEngine::getInstance();
    $sx->set('db', new NGPDO(['host' => $config['dbhost'], 'user' => $config['dbuser'], 'pass' => $config['dbpasswd'], 'db' => $config['dbname'], 'charset' => 'utf8']));
    $sx->set('config', $config);
    $sx->set('legacyDB', new NGLegacyDB(false));
    $sx->getLegacyDB()->connect('', '', '');
    $mysql = $sx->getLegacyDB();
    // Sync PHP <=> MySQL timezones
    $mysql->query('SET @@session.time_zone = "' . date('P') . '"');
});
// [[MARKER]] MySQL connection is established
$timer->registerEvent('DB connection established');
// Load categories from DB
ngLoadCategories();
// [[MARKER]] Categories are loaded
$timer->registerEvent('DB category list is loaded');
// Load compatibility engine [ rewrite old links ] if enabled
if ($config['libcompat']) {
    include_once root . 'includes/inc/libcompat.php';
    compatRedirector();
}
//
/// Special way to pass authentication cookie via POST params
if (!isset($_COOKIE['zz_auth']) && isset($_POST['ngAuthCookie'])) {
    $_COOKIE['zz_auth'] = $_POST['ngAuthCookie'];
}
// [[MARKER]] Ready to load auth plugins
$timer->registerEvent('Ready to load auth plugins');
loadActionHandlers('auth');
// Load user's permissions DB
loadPermissions();
// ============================================================================
// Initialize system libraries
// ============================================================================
// System protection
if (!$AUTH_CAPABILITIES[$config['auth_module']]['login']) {
    $config['auth_module'] = 'basic';
}
if (!$AUTH_CAPABILITIES[$config['auth_db']]['db']) {
    $config['auth_db'] = 'basic';
}
if (isset($AUTH_METHOD[$config['auth_module']]) && isset($AUTH_METHOD[$config['auth_db']])) {
    // Auth subsystem is activated
    // Choose default or user-defined auth module
    $auth = &$AUTH_METHOD[$config['auth_module']];
    $auth_db = &$AUTH_METHOD[$config['auth_db']];
    $xrow = $auth_db->check_auth();
    $CURRENT_USER = $xrow;
    if (is_array($xrow)) {
        NGEngine::getInstance()->set('currentUser', new NGUser($xrow));
    }
    if (isset($xrow['name']) && $xrow['name']) {
        $is_logged_cookie = true;
        $is_logged = true;
        $username = $xrow['name'];
        $userROW = $xrow;
        if ($config['x_ng_headers']) {
            header('X-NG-UserID: ' . (int) $userROW['id']);
            header('X-NG-Login: ' . htmlentities($username));
        }
        // Now every TWIG template will know if user is logged in
        $twigGlobal['flags']['isLogged'] = 1;
        $twigGlobal['user'] = $userROW;
        // Единоразовая ротация session_id после успешной авторизации
        if (empty($_SESSION['sid_rotated'])) {
            session_regenerate_id(true);
            $_SESSION['sid_rotated'] = 1;
        }
    }
} else {
    echo "Fatal error: No auth module is found.<br />Configuration is damaged, please restore from backup or perform manual fix.<br />\n";
}
// [[MARKER]] Authentication process is complete
$timer->registerEvent('Auth procedure is finished');
if ($is_logged) {
    define('name', $userROW['name']);
}
// Init internal cron module
$cron = new cronManager();
// Load action handlers for action 'all'
loadActionHandlers('all');
$timer->registerEvent('ALL core-related plugins are loaded');
// Execute 'core' action handler
executeActionHandler('core');
$timer->registerEvent('ALL core-related plugins are executed');
// After plugins (e.g. template_switch) $config['theme'] may have changed — recalculate paths
$activeTplSite = site_root . 'templates/' . $config['theme'] . '/';
$activeTplUrl  = $config['home_url'] . '/templates/' . $config['theme'];
// Update global variables in TWIG after plugins execution (if runtime was initialized by plugins)
try {
    $twig->addGlobal('tpl_url', $activeTplUrl);
    $twig->addGlobal('scriptLibrary', scriptLibrary);
} catch (LogicException $e) {
    // Runtime already initialized by plugins, use mergeGlobals instead
    $twig->env->mergeGlobals([
        'tpl_url' => $activeTplUrl,
        'scriptLibrary' => scriptLibrary,
    ]);
}
// Reconfigure allowed template paths in TWIG - use active theme (may differ from tpl_site constant)
// If theme was switched (e.g. by template_switch plugin), keep tpl_site as fallback so templates
// missing in the switched theme (slider.tpl, etc.) are found in the default theme.
if ($activeTplSite !== tpl_site) {
    $twigLoader->setPaths([$activeTplSite, tpl_site, root]);
} else {
    $twigLoader->setPaths([$activeTplSite, root]);
}
// Load lang files after executing core scripts. This is done for the switcher plugin.
$lang = LoadLang('common');
$lang = LoadLangTheme();
$langShortMonths = explode(',', $lang['short_months']);
$langMonths = explode(',', $lang['months']);
$timer->registerEvent('* CORE.PHP is complete');
