<?php
//
// Copyright (C) 2006-2025 Next Generation CMS (http://ngcms.org/)
// Name: cmodules.php
// Description: Common CORE modules
// Author: Vitaly Ponomarev
//
// Protect against hack attempts
if (!defined('NGCMS')) {
    throw new Exception('HAL');
}
function coreActivateUser()
{
    global $lang, $config, $SYSTEM_FLAGS, $mysql, $CurrentHandler;
    $lang = LoadLang('activation', 'site');
    $SYSTEM_FLAGS['info']['title']['group'] = $lang['loc_activation'];
    $userid = filter_var($_REQUEST['userid'] ?? ($CurrentHandler['params']['userid'] ?? null), FILTER_VALIDATE_INT);
    $code = preg_replace('/[^a-zA-Z0-9]/', '', $_REQUEST['code'] ?? ($CurrentHandler['params']['code'] ?? ''));
    // Validate input parameters
    if ($userid === false || $userid <= 0) {
        error404();
        return;
    }
    if (empty($code)) {
        error404();
        return;
    }
    // Check if user exists with ID = $userid
    $uRow = $mysql->record('select * from ' . prefix . '_users where id=' . db_squote($userid));
    if (!empty($uRow)) {
        // User found. Check activation.
        if ($uRow['activation']) {
            if ($uRow['activation'] == $code) {
                // Yeah, activate user!
                $mysql->query('update `' . uprefix . "_users` set activation = '' where id = " . db_squote($userid));
                msg(['text' => $lang['msgo_activated'], 'info' => sprintf($lang['msgi_activated'], admin_url)]);
                $SYSTEM_FLAGS['module.usermenu']['redirect'] = $config['home_url'] . '/';
            } else {
                // Incorrect activation code
                msg(['text' => $lang['msge_activate'], 'info' => sprintf($lang['msgi_activate'], admin_url)]);
            }
        } else {
            // Already activated
            msg(['text' => $lang['msge_activation'], 'info' => sprintf($lang['msgi_activation'], admin_url)]);
        }
    } else {
        // User not found
        error404();
    }
}
function coreRegisterUser()
{
    global $ip, $lang, $config, $AUTH_METHOD, $SYSTEM_FLAGS, $userROW, $PFILTERS, $mysql;
    $lang = LoadLang('registration', 'site');
    $SYSTEM_FLAGS['info']['title']['group'] = $lang['loc_registration'];
    // If logged in user comes to us - REDIRECT him to main page
    if (is_array($userROW)) {
        @header('Location: ' . $config['home_url']);
        return;
    }
    // Check for ban
    if ($ban_mode = checkBanned($ip, 'users', 'register', $userROW, $userROW['name'])) {
        msg(['type' => 'error', 'text' => ($ban_mode == 1) ? $lang['register.banned'] : $lang['msge_regforbid']]);
        return;
    }
    if (!$_REQUEST['type'] && $config['users_selfregister']) {
        // Receiving parameter list during registration
        $auth = $AUTH_METHOD[$config['auth_module']];
        $params = $auth->get_reg_params();
        generate_reg_page($params);
    } elseif ($_REQUEST['type'] == 'doregister' && $config['users_selfregister']) {
        // Receiving parameter list during registration
        $auth = $AUTH_METHOD[$config['auth_module']];
        $params = $auth->get_reg_params();
        $values = [];
        foreach ($params as $param) {
            $values[$param['name']] = filter_input(INPUT_POST, $param['name'], FILTER_SANITIZE_STRING) ?? null;
        }
        $msg = '';
        // Check captcha
        if ($config['use_captcha']) {
            // Read user input from POST
            $captcha = filter_input(INPUT_POST, 'vcode', FILTER_SANITIZE_STRING);
            // Captcha image is generated via captcha.php?id=registration, which stores code in $_SESSION['captcha.registration']
            $sessionCode = $_SESSION['captcha.registration'] ?? ($_SESSION['captcha'] ?? null);
            if (!$captcha || !$sessionCode || ($sessionCode != $captcha)) {
                $msg = $lang['msge_vcode'];
            }
        }
        // Execute filters - check if user is allowed to register
        if (!$msg && is_array($PFILTERS['core.registerUser'])) {
            foreach ($PFILTERS['core.registerUser'] as $k => $v) {
                if (!$v->registerUser($params, $msg)) {
                    break;
                }
            }
        }
        // Trying register
        if (!$msg && ($uid = $auth->register($params, $values, $msg))) {
            // OK, fetch user record
            if ($uid > 1) {
                // ** COMPAT: exec action only if $uid > 1
                $urec = $mysql->record('select * from ' . uprefix . '_users where id = ' . intval($uid));
                // LOG: Successfully registered
                ngSYSLOG(['plugin' => 'core', 'item' => 'register'], ['action' => 'register'], $urec, [1, '']);
                // Execute filters - add additional variables
                if (is_array($urec) && is_array($PFILTERS['core.registerUser'])) {
                    foreach ($PFILTERS['core.registerUser'] as $k => $v) {
                        $v->registerUserNotify($uid, $urec);
                    }
                }
                // Больше не редиректим: показываем уведомление на странице регистрации
                if (isset($_SESSION['flash_notify']) && is_array($_SESSION['flash_notify'])) {
                    msg($_SESSION['flash_notify']);
                    unset($_SESSION['flash_notify']);
                } else {
                    msg(['text' => $lang['msgo_registered'], 'info' => ($lang['register.ok#desc'] ?? 'Регистрация успешно выполнена')]);
                }
                // Оставляем пользователя на форме регистрации (пустой набор значений)
                generate_reg_page($params, [], '');
                return;
            }
        } else {
            // LOG: Registration failed
            ngSYSLOG(['plugin' => 'core', 'item' => 'register'], ['action' => 'register'], 0, [0, 'Registration failed']);
            // Fail
            generate_reg_page($params, $values, $msg);
        }
    } else {
        msg(['type' => 'error', 'text' => $lang['msge_regforbid']]);
    }
}
function generate_reg_page($params, $values = [], $msg = '')
{
    global $template, $PHP_SELF, $config, $PFILTERS, $twig, $twigLoader, $lang;
    $tVars = [
        'entries' => [],
        'flags'   => [],
    ];
    if ($msg) {
        msg(['text' => $msg]);
    }
    // Prepare variable list
    foreach ($params as $param) {
        $tRow = [
            'name'       => $param['name'],
            'title'      => $param['title'],
            'descr'      => $param['descr'],
            'error'      => '',
            'input'      => '',
            'flags'      => [],
            'type'       => $param['type'],
            'html_flags' => $param['html_flags'],
            'value'      => $param['value'],
            'values'     => $param['values'],
            'manual'     => $param['manual'],
        ];
        if (!empty($param['id'])) {
            $tRow['id'] = $param['id'];
        }
        if ($param['error']) {
            $tRow['flags']['isError'] = true;
            $tRow['error'] = str_replace('%error%', $param['error'], $lang['param_error']);
        }
        if (!empty($values[$param['name']])) {
            $tRow['value'] = $values[$param['name']];
            $param['value'] = $values[$param['name']];
        }
        $tVars['entries'][] = $tRow;
    }
    // Execute filters - add additional variables
    if (!empty($PFILTERS['core.registerUser'])) {
        foreach ($PFILTERS['core.registerUser'] as $k => $v) {
            $v->registerUserForm($tVars);
        }
    }
    // Generate captcha for registration if enabled (was missing, causing always invalid captcha)
    if ($config['use_captcha']) {
        $_SESSION['captcha'] = random_int(10000, 99999);
        $tVars['flags']['hasCaptcha'] = true;
    } else {
        $tVars['flags']['hasCaptcha'] = false;
    }
    // Generate inputs
    foreach ($tVars['entries'] as &$param) {
        $tInput = '';
        if ($param['type'] == 'text') {
            $tInput = '<textarea ' . (isset($param['id']) && $param['id'] ? 'id="' . $param['id'] . '" ' : '') . 'name="' . $param['name'] . '" title="' . $param['title'] . '" ' . $param['html_flags'] . '>' . secure_html($param['value']) . '</textarea>';
        } elseif ($param['type'] == 'input') {
            $tInput = '<input ' . (isset($param['id']) && $param['id'] ? 'id="' . $param['id'] . '" ' : '') . 'name="' . $param['name'] . '" type="text" title="' . $param['title'] . '" ' . $param['html_flags'] . ' value="' . secure_html($param['value']) . '"/>';
        } elseif ($param['type'] == 'password' || $param['type'] == 'hidden') {
            $tInput = '<input ' . (isset($param['id']) && $param['id'] ? 'id="' . $param['id'] . '" ' : '') . 'name="' . $param['name'] . '" type="' . $param['type'] . '" title="' . $param['title'] . '" ' . $param['html_flags'] . ' value="' . secure_html($param['value']) . '"/>';
        } elseif ($param['type'] == 'select') {
            $tInput = '<select ' . (isset($param['id']) && $param['id'] ? 'id="' . $param['id'] . '" ' : '') . 'name="' . $param['name'] . '" title="' . $param['title'] . '" ' . $param['html_flags'] . '>';
            foreach ($param['values'] as $oid => $oval) {
                $tInput .= '<option value="' . $oid . '"' . ($param['value'] == $oid ? ' selected' : '') . '>' . $oval . '</option>';
            }
            $tInput .= '</select>';
        } elseif ($param['type'] == 'manual') {
            $tInput = $param['manual'];
        }
        $param['input'] = $tInput;
    }
    // flag already set above
    $tVars['form_action'] = checkLinkAvailable('core', 'registration') ?
        generateLink('core', 'registration', []) :
        generateLink('core', 'plugin', ['plugin' => 'core', 'handler' => 'registration']);
    // Prepare REGEX conversion table
    $conversionConfigRegex = [
        "#\[captcha\](.*?)\[/captcha\]#si" => '{% if (flags.hasCaptcha) %}$1{% endif %}',
        '#{entries}#si' => '{% for entry in entries %}{% include "registration.entries.tpl" %}{% endfor %}',
    ];
    $conversionConfig = [
        '{form_action}' => '{{ form_action }}',
    ];
    $conversionConfigEntries = [
        '{name}' => '{{ entry.name }}',
        '{title}' => '{{ entry.title }}',
        '{descr}' => '{{ entry.descr }}',
        '{error}' => '{{ entry.error }}',
        '{input}' => '{{ entry.input }}',
    ];
    $twigLoader->setConversion('registration.tpl', $conversionConfig, $conversionConfigRegex);
    $twigLoader->setConversion('registration.entries.tpl', $conversionConfigEntries);
    // Вызываем фильтры для добавления виджетов от плагинов (например, ng-advanced-captcha)
    if (is_array($PFILTERS['core.registrationForm'])) {
        foreach ($PFILTERS['core.registrationForm'] as $k => $v) {
            $v->registerUserForm($tVars);
        }
    }
    $template['vars']['mainblock'] .= $twig->render('registration.tpl', $tVars);
}
function coreRestorePassword()
{
    global $lang, $userROW, $config, $AUTH_METHOD, $SYSTEM_FLAGS, $mysql, $CurrentHandler, $PFILTERS;
    $lang = LoadLang('lostpassword', 'site');
    $SYSTEM_FLAGS['info']['title']['group'] = $lang['loc_lostpass'];
    if (is_array($userROW)) {
        header('Location: ' . $config['home_url']);
        return;
    }
    $userid = ($CurrentHandler['pluginName'] == 'core' && $CurrentHandler['handlerName'] == 'lostpassword') ?
        ($CurrentHandler['params']['userid'] ?? $_REQUEST['userid']) : $_REQUEST['userid'];
    $code = ($CurrentHandler['pluginName'] == 'core' && $CurrentHandler['handlerName'] == 'lostpassword') ?
        ($CurrentHandler['params']['code'] ?? $_REQUEST['code']) : $_REQUEST['code'];
    // Confirmation
    if ($userid && $code) {
        $auth = $AUTH_METHOD[$config['auth_module']];
        $msg = '';
        if ($auth->confirm_restorepw($msg, $userid, $code)) {
            msg(['text' => $msg]); // OK
        } else {
            msg(['type' => 'error', 'text' => $msg]); // Fail
        }
    } elseif ($_REQUEST['type'] == 'send') {
        // PROCESSING REQUEST
        // Receiving parameter list during password recovery
        $auth = $AUTH_METHOD[$config['auth_module']];
        $params = $auth->get_restorepw_params();
        $values = [];
        foreach ($params as $param) {
            // Пропускаем элементы без поля name (текстовые сообщения)
            if (isset($param['name']) && $param['name']) {
                $values[$param['name']] = $_POST[$param['name']] ?? '';
            }
        }

        $msg = '';
        // Вызываем фильтры для проверки капчи от плагинов (например, ng-advanced-captcha)
        if (is_array($PFILTERS['core.lostpassword'])) {
            foreach ($PFILTERS['core.lostpassword'] as $k => $v) {
                if (!$v->lostpassword($msg, $params, $values)) {
                    // Фильтр вернул false - капча не прошла проверку
                    generate_restorepw_page($params, $values, $msg);
                    return;
                }
            }
        }
        // Check captcha (старая система, если плагин не активен)
        if ($msg === '' && $config['use_captcha']) {
            $captcha = $_REQUEST['vcode'];
            if (!$captcha || ($_SESSION['captcha'] != $captcha)) {
                $msg = $lang['msge_vcode']; // Fail
            }
        }
        // Trying password recovery
        if ($msg === '' && $auth->restorepw($params, $values, $msg)) {
            // OK
            // ...
        } else {
            generate_restorepw_page($params, $values, $msg); // Fail and reloading page
        }
    } else {
        // DEFAULT: SHOW RESTORE PW SCREEN
        $auth = $AUTH_METHOD[$config['auth_module']];
        $params = $auth->get_restorepw_params();
        if (!is_array($params)) {
            msg(['type' => 'error', 'text' => $lang['msge_lpforbid']]);
            return;
        }
        generate_restorepw_page($params);
    }
}
function generate_restorepw_page(array $params, array $values = [], string $msg = ''): void
{
    global $twig, $template, $config, $lang, $PFILTERS;
    if (!empty($msg)) {
        msg(['text' => $msg]);
    }

    $tVars = [
        'entries' => [],
        'flags'   => [],
    ];

    // Prepare variable list as array (like in registration)
    foreach ($params as $param) {
        // Skip text-only entries (messages)
        if ($param['text']) {
            // For text entries, we can add them as special type
            $tRow = [
                'type'  => 'message',
                'text'  => $param['text'],
                'name'  => '',
                'title' => '',
            ];
            $tVars['entries'][] = $tRow;
            continue;
        }

        $tRow = [
            'name'       => $param['name'],
            'title'      => $param['title'],
            'descr'      => $param['descr'],
            'error'      => '',
            'input'      => '',
            'flags'      => [],
            'type'       => $param['type'],
            'html_flags' => $param['html_flags'],
            'value'      => $param['value'],
            'values'     => $param['values'] ?? [],
            'manual'     => $param['manual'] ?? '',
        ];

        if (!empty($param['id'])) {
            $tRow['id'] = $param['id'];
        }

        if ($param['error']) {
            $tRow['flags']['isError'] = true;
            $tRow['error'] = str_replace('%error%', $param['error'], $lang['param_error']);
        }

        if (!empty($values[$param['name']])) {
            $tRow['value'] = $values[$param['name']];
            $param['value'] = $values[$param['name']];
        }

        $tVars['entries'][] = $tRow;
    }

    // Generate inputs for each entry
    foreach ($tVars['entries'] as &$entry) {
        if ($entry['type'] === 'message') {
            continue; // Skip message entries
        }

        $tInput = '';
        if ($entry['type'] == 'text') {
            $tInput = '<textarea ' . (isset($entry['id']) && $entry['id'] ? 'id="' . $entry['id'] . '" ' : '') . 'name="' . $entry['name'] . '" title="' . secure_html($entry['title']) . '" ' . $entry['html_flags'] . '>' . secure_html($entry['value']) . '</textarea>';
        } elseif ($entry['type'] == 'input') {
            $tInput = '<input ' . (isset($entry['id']) && $entry['id'] ? 'id="' . $entry['id'] . '" ' : '') . 'name="' . $entry['name'] . '" type="text" title="' . secure_html($entry['title']) . '" ' . $entry['html_flags'] . ' value="' . secure_html($entry['value']) . '"/>';
        } elseif ($entry['type'] == 'password' || $entry['type'] == 'hidden') {
            $tInput = '<input ' . (isset($entry['id']) && $entry['id'] ? 'id="' . $entry['id'] . '" ' : '') . 'name="' . $entry['name'] . '" type="' . $entry['type'] . '" title="' . secure_html($entry['title']) . '" ' . $entry['html_flags'] . ' value="' . secure_html($entry['value']) . '"/>';
        } elseif ($entry['type'] == 'select') {
            $tInput = '<select ' . (isset($entry['id']) && $entry['id'] ? 'id="' . $entry['id'] . '" ' : '') . 'name="' . $entry['name'] . '" title="' . secure_html($entry['title']) . '" ' . $entry['html_flags'] . '>';
            foreach ($entry['values'] as $oid => $oval) {
                $tInput .= '<option value="' . secure_html($oid) . '"' . ($entry['value'] == $oid ? ' selected' : '') . '>' . secure_html($oval) . '</option>';
            }
            $tInput .= '</select>';
        } elseif ($entry['type'] == 'manual') {
            $tInput = $entry['manual'];
        }
        $entry['input'] = $tInput;
    }

    if ($config['use_captcha']) {
        $_SESSION['captcha'] = random_int(10000, 99999);
        $tVars['flags']['hasCaptcha'] = true;
    } else {
        $tVars['flags']['hasCaptcha'] = false;
    }

    $formActionParams = checkLinkAvailable('core', 'lostpassword') ?
        ['core', 'lostpassword', []] :
        ['core', 'plugin', ['plugin' => 'core', 'handler' => 'lostpassword']];
    $tVars['form_action'] = generateLink(...$formActionParams);
    $tVars['captcha_source_url'] = admin_url . '/captcha.php';

    // Вызываем фильтры для добавления виджетов от плагинов (например, ng-advanced-captcha)
    if (is_array($PFILTERS['core.lostpasswordForm'])) {
        foreach ($PFILTERS['core.lostpasswordForm'] as $k => $v) {
            $v->lostpasswordForm($tVars);
        }
    }
    $template['vars']['mainblock'] .= $twig->render('lostpassword.tpl', $tVars);
}
//
// Execute an action for coreLogin() function
// This is a workaround for 2-stage AUTH functions (like openID)
// Parameter: user's record [row]
function coreLoginAction($row = null, $redirect = null)
{
    global $auth, $auth_db, $username, $userROW, $is_logged, $is_logged_cookie, $SYSTEM_FLAGS, $HTTP_REFERER;
    global $twig, $template, $config, $lang, $ip;
    $lang = LoadLang('login', 'site');
    $SYSTEM_FLAGS['info']['title']['group'] = $lang['loc_login'];
    // Try to auth && check for bans
    if (is_array($row) && !checkBanned($ip, 'users', 'auth', $row, $row['name'])) {
        $auth_db->save_auth($row);
        $username = $row['name'];
        $userROW = $row;
        $is_logged_cookie = true;
        $is_logged = true;
        // LOG: Successfully logged in
        ngSYSLOG(['plugin' => 'core', 'item' => 'login'], ['action' => 'login', 'list' => ['login' => $username]], null, [1, '']);
        // Redirect back
        @header('Location: ' . ($redirect ? $redirect : home));
    } else {
        // LOG: Login error
        ngSYSLOG(['plugin' => 'core', 'item' => 'login'], ['action' => 'login', 'list' => ['errorInfo' => $row]], null, [0, 'Login failed.']);
        $SYSTEM_FLAGS['auth_fail'] = 1;
        $is_logged_cookie = false;
        // Show login template
        $redirect = '';
        if (isset($_POST['redirect']) && $_POST['redirect']) {
            $redirect = filter_var($_POST['redirect'], FILTER_SANITIZE_URL);
        } elseif (isset($HTTP_REFERER)) {
            $redirect = filter_var($HTTP_REFERER, FILTER_SANITIZE_URL);
        }
        $tvars = [
            'form_action' => generateLink('core', 'login'),
            'redirect' => $redirect,
        ];
        if (preg_match('#^ERR:NEED.ACTIVATE#', $row, $null)) {
            $tvars['flags'] = [
                'need_activate' => true,
                'error' => false,
                'banned' => false,
            ];
        } elseif ($row == 'ERR:NOT_ENTERED') {
            $tvars['flags'] = [
                'need_activate' => false,
                'error' => false,
                'banned' => false,
            ];
        } else {
            $ban_mode = checkBanned($ip, 'users', 'auth', $row, $row['name'] ?? '') ? 1 : 0;
            $tvars['flags'] = [
                'need_activate' => false,
                'error' => ($ban_mode != 1),
                'banned' => ($ban_mode == 1),
            ];
        }
        // Execute filters - add additional variables (e.g., social auth links)
        global $PFILTERS;
        if (isset($PFILTERS['core.login']) && is_array($PFILTERS['core.login'])) {
            foreach ($PFILTERS['core.login'] as $k => $v) {
                $v->loginAction($tvars);
            }
        }
        $template['vars']['mainblock'] = $twig->render('login.tpl', $tvars);
    }
}
function coreLogin()
{
    global $auth, $auth_db, $username, $userROW, $is_logged, $is_logged_cookie, $SYSTEM_FLAGS, $HTTP_REFERER;
    global $template, $config, $lang, $ip;
    // If user ALREADY logged in - redirect to main page
    if (is_array($userROW)) {
        @header('Location: ' . $config['home_url']);
        return;
    }
    // Determine redirect point
    $redirect = '';
    if (isset($_POST['redirect']) && $_POST['redirect']) {
        $redirect = $_POST['redirect'];
    } elseif (isset($_REQUEST['redirect_home']) && $_REQUEST['redirect_home']) {
        $redirect = $config['home_url'];
    } elseif (preg_match('#^http\:\/\/#', $HTTP_REFERER, $tmp)) {
        $redirect = $HTTP_REFERER;
    } else {
        $redirect = $config['home_url'];
    }
    // Auth can work ONLY via POST method
    if ($_SERVER['REQUEST_METHOD'] != 'POST') {
        coreLoginAction('ERR:NOT_ENTERED');
        return;
    }
    // Try to auth
    $row = $auth->login();
    coreLoginAction($row, $redirect);
}
function coreLogout()
{
    global $auth_db, $userROW, $username, $is_logged, $HTTP_REFERER, $config;
    $auth_db->drop_auth();
    @header('Location: ' . (preg_match('#^http\:\/\/#', $HTTP_REFERER, $tmp) ? $HTTP_REFERER : $config['home_url']));
    unset($userROW);
    unset($username);
    $is_logged = false;
}
