<?php

// Функция обработки вывода вычислений
function ob_calc_catch($context)
{
    global $bm_errors, $config;
    if ($bm_errors->ob_calc_start == 1) // Обрабатываем только в случае ошибок
    {
        return $bm_errors->convert_fatal_error_text($context);
    } else {
        return $context;
    }
}

// процедурная оберка на класс bm_errors
function generate_error($str)
{
    global $bm_errors;
    $bm_errors->generate($str);
}

// Обработка некритичных ошибок
function bm_error_handler($level, $message, $file, $line)
{
    if (substr($message, 0, strlen('Illegal string offset')) == 'Illegal string offset') {
        $system_last_error = "";
        return true;
    }

    // Warnings перенаправляем в консоль браузера
    global $bm_errors, $config, $user;
    if (isset($config['disable_errors_catch']) && $config['disable_errors_catch']) {
        return false;
    }
    if ($bm_errors->display_warnings) {
        if (($user && $user['group_id'] == 1) || (isset($config['admin_mode']) && $config['admin_mode'] == $_SERVER['REMOTE_ADDR'])) {
            if (($level == E_USER_ERROR) || ($level == E_ERROR) || ($level == E_USER_WARNING) || ($level == E_WARNING)) {
                $warning_block = "";
                if (($level == E_USER_ERROR) || ($level == E_ERROR)) {
                    $warning_block .= "Error: ";
                }
                if (($level == E_USER_WARNING) || ($level == E_WARNING)) {
                    $warning_block .= "Warning: ";
                }
                $warning_block .= " $message in ";
                if ($bm_errors->ob_calc_start == 1) { // Ошибка в вычислении выводим корректную ошибку
                    global $calc_stack;
                    if (is_array($calc_stack)) {
                        $one_calc = $calc_stack[count($calc_stack) - 1];
                        if ($one_calc['report_id']) {
                            $warning_block .= '\"' . $one_calc['report_name'] . '\"';
                        }
                        if ($one_calc['table_id']) {
                            $warning_block .= '\"' . $one_calc['table_name'] . "." . $one_calc['name'] . '\"';
                        }
                    }
                } else {
                    $warning_block .= $file;
                }
                $warning_block .= " on line $line";
                //Отключаю сохранение некритичных ошибок, так как их слишком много на PHP8
                //$bm_errors->warnings[] = $warning_block;
            }
        }
    }
    return true;
}

set_error_handler('bm_error_handler');

class bm_errors
{
    var $ob_calc_start = 0; // Выпольняется пользовательское вычисление
    var $dbgs;            // Стек вызовов функций
    var $dbg_line;        // Номер строки с ошибкой
    var $dbg_error;       // Текст ошибки
    var $tags;            // Выводит ошибки с отображением тегов
    var $fatal_error_memory_reserve; // Резерв памяти для обработки ошибок
    var $self_link;       // Ссылка на самого себя, чтобы класс не разрушался при ошибке
    var $display_warnings;// Выводить в консоль уведомления об <Warnings>
    var $warnings;        // Блок warings, для вывода на экран

    function __construct()
    {
        global $script_name;
        $this->fatal_error_memory_reserve = '0'; // Резевр памяти на случай обработки ошибок в вычислениях 5 мегабайт
        $this->fatal_error_memory_reserve = str_repeat('xxxxx', 1024 * 1024);

        $this->tags = 1;
        if ($script_name == 'cron.php') {
            $this->tags = 0;
        } // ShortForm

        $this->display_warnings = 0;
        if ($script_name != 'select_value.php' && $script_name != 'update_value.php') {
            $this->display_warnings = 1;
        }
        $this->warnings = array();

        global $bm_errors;
        $self_link = &$bm_errors;
    }

    // Этот метод кладет фронт, надобности в этом методе нет
    // function __destruct() {}

    // Запуск пользовательского вычисления
    public function start_calc()
    {
        global $config, $compatible_sql_mode;
        $this->ob_calc_start = 1;
        if (!isset($config['disable_errors_catch']) || !$config['disable_errors_catch']) {
            ob_start('ob_calc_catch', 10048);
        }
        if ($config['compatible_sql_mode']) {
            $compatible_sql_mode = 1;
        }
    }

    // Запуск пользовательского вычисления
    public function stop_calc()
    {
        global $config, $compatible_sql_mode;
        $compatible_sql_mode = 0;
        if (isset($config['disable_errors_catch']) && $config['disable_errors_catch']) { // Не перехватывать вывод ошибок
            $this->ob_calc_start = 0;
        } else {  // Перехват вывода ошибок
            $e = error_get_last();
            if ($e['type'] == E_PARSE) { // Ошибка синтаксиса в eval, не вызывает ошибки программы
                ob_end_flush();
                exit;
            } else {
                $this->ob_calc_start = 0;
                // т.к. ob_end_flush() глючит на 5.3.18 и выводит буффер обмена даже если в буффере ничего нет, то делаем проверку
                if (ob_get_length()) {
                    ob_end_flush();
                } else {
                    ob_get_clean();
                }
            }
        }
    }

    // Функция генерации ошибки, ошибка выводиться на экран, а также пишеться в лог.
    public function generate($dbg_error)
    {
        global $config, $lang, $qst_id, $qst_rand, $event;
        if ($qst_id) { // для внешних форм
            sql_insert(QST_ANSWERS_TABLE, array(
                'qst_id' => $qst_id,
                'r' => $qst_rand,
                'answer' => $lang['Qst_error_message']
            )); // передаем сообщение об ошибке
            if ($event['is_new_line']) {
                data_delete($event['table_id'], "id=", $event['line_id']);
            } // удаляем добавленную анкету
        }

        // Чистим память
        $this->fatal_error_memory_reserve = '';

        // Запоминаем ошибку
        $this->dbg_error = $dbg_error;

        // Заполняем стек вызовов
        $this->dbgs = debug_backtrace();
        $this->dbgs_clean = array();
        $clean = 0;
        foreach ($this->dbgs as $dbg) {
            if (($clean == 0) &&
                ($dbg['function'] != 'generate_error') &&
                (substr($dbg['file'], -strlen('sql_functions.php')) != 'sql_functions.php') &&
                (substr($dbg['file'], -strlen('sql_functions.php')) != 'mysql_connect.php') &&
                (substr($dbg['file'], -strlen('bm.errors.php')) != 'bm.errors.php')
            ) {
                $clean = 1;
            }
            if ($clean) {
                $this->dbgs_clean[] = $dbg;
            }
        }
        $this->dbgs = $this->dbgs_clean;

        $error_text_res = "";
        // Если ошибка происходит в процессе вычисления, генерируем сообщение по стеку вычислений
        if ($this->ob_calc_start == 1) {  // Ошибка внутри вычисления
            $this->ob_calc_start = 0; // Обнуляем, чтобы избежать двойной обработки
            $this->dbg_line = $this->dbgs[0]['line'];
            $error_text_res = $this->form_calc_error_message();
        } else { // Ошибка не в вычислении выводим back trace
            $error_text_res = $this->form_system_error_message();
        }
        $this->insert_log_crash($error_text_res);
        if (function_exists('cb_die')) {
            cb_die($error_text_res, 0, isset($config['cb_die_show_cat']) ? $config['cb_die_show_cat'] : 1);
        } else {
            echo '<pre>' . \strip_tags($error_text_res) . '</pre>';
        }
    }


    // Сформировать текст ошибки вычисления
    function form_calc_error_message()
    {
        global $user, $config, $lang, $calc_stack;
        $error_text_res = ($this->tags ? "<div class='error_text_total'><div class='error_text_header'>" . $lang['Calc_error'] . ".</div>\n" : $lang['Calc_error'] . ".\n");
        $error_text_res .= ($this->tags ? "<a href='#' onclick='this.style.display=\"none\";this.nextSibling.style.display=\"block\"' class='error_link_in_detail'>" . $lang['In_detail'] . "</a><div class='error_div_in_detail'>\n" : "");
        $i = 0;
        foreach ($calc_stack as $one_calc) {
            $error_text_res .= ($this->tags ? "<div class='error_calc_block' style='padding: 0px 0px 5px " . ($i * 10) . "px;'>" : "");
            $i++;
            if ($i == count($calc_stack)) { // Последнее вычисление - текущее
                $calc_code = explode("\n", $one_calc['calculate']);
                if ($one_calc['report_id']) { // Отчет
                    $error_text_res .= ($this->tags ? "<a href='edit_report.php?report=" . $one_calc['report_id'] . "' target='_blank'>" : '') . $this->form_display_tags($one_calc['report_name'],
                            $this->tags) . ($this->tags ? '</a>' : ' ');
                } else { // Таблица
                    $error_text_res .= ($this->tags ? "<a href='edit_tabrep.php?table=" . $one_calc['table_id'] . "' target='_blank'>" : '') . $this->form_display_tags($one_calc['table_name'],
                            $this->tags) . ($this->tags ? '</a>' : '') . '.' . ($this->tags ? "<a href='edit_calc.php?calc_id=" . $one_calc['id'] . "' target='_blank'>" : "") . $this->form_display_tags($one_calc['name'],
                            $this->tags) . ($this->tags ? "</a>" : "") . " ";
                }
                if ($user['group_id'] == 1 || $config['admin_mode'] == $_SERVER['REMOTE_ADDR']) {  // Администратор, выводим полный текст ошибки
                    $error_text_res .= ": " . ($this->tags ? "<br>\n" : "\n");
                    $error_text_res .= ($this->tags ? "<div style='padding: 10px 0px 0px 10px;'>" : "");
                    if ($this->dbg_line > 1) {
                        $error_text_res .= ($this->dbg_line - 1) . ": " . $this->form_display_tags($calc_code[$this->dbg_line - 2],
                                $this->tags) . ($this->tags ? "<br>\n" : "\n");
                    }
                    $error_text_res .= $this->dbg_line . ": " . ($this->tags ? "<span style='color:red'>" : "") . $this->form_display_tags($calc_code[$this->dbg_line - 1],
                            $this->tags) . ($this->tags ? "</span><br>\n" : "\n");
                    if (isset($calc_code[$this->dbg_line])) {
                        $error_text_res .= ($this->dbg_line + 1) . ": " . $this->form_display_tags($calc_code[$this->dbg_line],
                                $this->tags) . ($this->tags ? "<br>\n" : "\n");
                    }
                    $error_text_res .= ($this->tags ? "</div>" : "");
                } else {  // Не администратор, не выводим полный текст ошибки
                    $error_text_res .= " ($this->dbg_line)";
                }
            } else { // Вычисления по стеку, предыдущие
                $error_text_res .= ($this->tags ? "<a href='edit_tabrep.php?table=" . $one_calc['table_id'] . "' target='_blank'>" : '') . $this->form_display_tags($one_calc['table_name'],
                        $this->tags) . ($this->tags ? '</a>' : '') . '.' . ($this->tags ? "<a href='edit_calc.php?calc_id=" . $one_calc['id'] . "' target='_blank'>" : "") . $this->form_display_tags($one_calc['name'],
                        $this->tags) . ($this->tags ? "</a>" : "") . " ";

            }
            $error_text_res .= ($this->tags ? "</div>" : "\n");
        }
        $error_text_res .= ($this->tags ? "<div class='error_text_block'>" . $this->dbg_error . "</div></div></div>\n" : $this->dbg_error . "\n");
        return $error_text_res;
    }

    function form_system_error_message()
    {
        global $user, $config, $lang, $calc_stack;
        $error_text_res = ($this->tags ? "<div style='text-align:left;'><div style='font-size:18px; padding: 5px 0px 5px 0px;'>" . $lang['System_error'] . ".</div>\n" : $lang['System_error'] . ".\n");
        $error_text_res .= ($this->tags ? "<a href='#' onclick='this.style.display=\"none\";this.nextSibling.style.display=\"block\"'>" . $lang['In_detail'] . "</a><div style='display:none;'>\n" : "");

        $i = 0;
        $error_text_res .= ($this->tags ? "<div style='font-size:16px; padding: 5px 0px 5px 0px;'>Error generated:</div>" : "Error generated:\n");
        foreach ($this->dbgs as $dbg) {
            if (substr($dbg['file'], -strlen('sql_functions.php')) == 'mysql_connect.php') {
                continue;
            }
            if (substr($dbg['file'], -strlen('sql_functions.php')) == 'mysql_connect.php') {
                continue;
            }
            if (substr($dbg['file'], -strlen('sql_functions.php')) == 'sql_functions.php') {
                continue;
            }
            $error_text_res .= ($this->tags ? "<div style='font-size:14px; padding: 0px 0px 0px " . ($i * 10) . "px;'>" : "");
            $error_text_res .= $dbg['file'] . "(" . $dbg['line'] . ") : " . $dbg['function'] . "()";
            $error_text_res .= ($this->tags ? "</div>" : "\n");
            $i++;
        }
        $error_text_res .= ($this->tags ? "<div style='font-weight:bold;font-size:14px; padding: 15px 0px 10px 0px;'>" . $this->dbg_error . "</div></div></div>\n" : $this->dbg_error . "\n");
        return $error_text_res;
    }


    // Сохранить данные о падении программы
    function insert_log_crash($in_detail)
    {
        global $user, $report_id;

        if (!isset($GLOBALS['sql_global_link']) || !function_exists('sql_select_field') || !sql_table_exists(LOGS_CRASH_TABLE)) {
            // Если отсутствует подключение к БД, запись в БД не выполнять
            return;
        }

        // Не вставляем более 2 ошибки в минуту у пользователя
        $result = @sql_select_field(LOGS_CRASH_TABLE, 'date', 'user_id=', $user['id'], " and date>'",
            date('Y-m-d H:i:s', time() - 30), "'");
        if (!$result) {
            return;
        } // Ошибка подключения к б.д.
        $log_line = @sql_fetch_assoc($result);
        if ($log_line) {
            return;
        } // Уже есть ошибка за последние 30 секунд
        $result = @sql_delete(LOGS_CRASH_TABLE, "date<'", date('Y-m-d H:i:s', time() - 60 * 60 * 24 * 10),
            "'"); // Храним иcторию не более 10 дней

        $cat_globals = array();
        foreach ($GLOBALS as $k => $v) {
            if ($k != 'GLOBALS' &&
                $k != 'lang' &&
                $k != 'smarty' &&
                $k != 'one_cat' &&
                $k != 'user_cats' &&
                $k != 'table_cache'
            ) {
                $cat_globals[$k] = $v;
            }
        }

        $ins_arr = array(
            'guid' => time() . "_" . $user['id'] . "_" . rand(0, 999),
            'date' => date('Y-m-d H:i:s'),
            'user_id' => $user['id'],
            'table_id' => $_REQUEST['table'],
            'report_id' => $report_id,
            'message' => $this->dbg_error,
            'in_detail' => $in_detail,
            'url' => $_SERVER['REQUEST_URI'],
            // 'global_vars' => serialize($cat_globals),  //---> пока отменили, т.к. может быть слишком большой размер у данного поля
        );
        if (isset($GLOBALS["config"]['bm_error_enable_call_stack']) && $GLOBALS["config"]['bm_error_enable_call_stack']) {
            $ins_arr['call_stack'] = serialize($this->dbgs);
        }
        global $show_sql_request;
        @sql_insert(LOGS_CRASH_TABLE, $ins_arr);
    }

    // Обработка вывода конекста, в случае возникновения ошибок
    function convert_fatal_error_text($context)
    {
        global $user, $config, $lang, $calc_stack, $qst_id, $qst_rand, $event;
        if ($qst_id) { // для внешних форм
            sql_insert(QST_ANSWERS_TABLE, array(
                'qst_id' => $qst_id,
                'r' => $qst_rand,
                'answer' => $lang['Qst_error_message']
            )); // передаем сообщение об ошибке
            if ($event['is_new_line']) {
                data_delete($event['table_id'], "id=", $event['line_id']);
            } // удаляем добавленную анкету
        }

        $e = error_get_last();
        $e_type = $e["type"];
        if (!(($e_type & E_COMPILE_ERROR) || ($e_type & E_ERROR) || ($e_type & E_CORE_ERROR) || ($e_type & E_RECOVERABLE_ERROR) || ($e_type & E_PARSE))) {
            return $context; // Фатальной ошибки не произошло, либо была промежуточная ошибка другого типа
        };
        $this->fatal_error_memory_reserve = '';

        $f_error_pos = strpos($context, "<br />\n<b>Fatal error</b>:  ");
        if ($f_error_pos === false) {
            // возможно есть промежуточные обработчики, например xdebug
            // т.к. корректно обработать ошибку не возможно, то отбрасываем предыдущий текст
        }
        $clear_text = substr($context, 0, $f_error_pos);


        $this->dbg_error = $e['message'];
        $dbg_file = $e['file'];
        $this->dbg_line = $e['line'];
        if ($this->ob_calc_start == 1) {  // Ошибка внутри вычисления
            $error_text_res = $this->form_calc_error_message();
            $this->insert_log_crash($error_text_res);
        } else { // Ошибка в коде программы
            // return 'Internal fatal error';
        }
        $context = cb_die($clear_text . $error_text_res, 0, 1, 1);
        return $context;
    }

    // Сформировать вывод, с учтетом отображения тегов или без.
    function form_display_tags($text)
    {
        if ($this->tags) {
            return str_replace(" ", "&nbsp;", form_display($text));
        } else {
            return $text;
        }
    }

}
global $bm_errors;
$bm_errors = new bm_errors();
$bm_errors->self_link = &$bm_errors;
