<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    errorlog
 */

/**
 * Module page class.
 */
class Module_admin_errorlog
{
    /**
     * Find details of the module.
     *
     * @return ?array Map of module info (null: module is disabled)
     */
    public function info() : ?array
    {
        $info = [];
        $info['author'] = 'Chris Graham';
        $info['organisation'] = 'Composr';
        $info['hacked_by'] = null;
        $info['hack_version'] = null;
        $info['version'] = 2;
        $info['locked'] = false;
        $info['min_cms_version'] = 11.0;
        $info['addon'] = 'errorlog';
        return $info;
    }

    /**
     * Find entry-points available within this module.
     *
     * @param  boolean $check_perms Whether to check permissions
     * @param  ?MEMBER $member_id The member to check permissions as (null: current user)
     * @param  boolean $support_crosslinks Whether to allow cross links to other modules (identifiable via a full-page-link rather than a screen-name)
     * @param  boolean $be_deferential Whether to avoid any entry-point (or even return null to disable the page in the Sitemap) if we know another module, or page_group, is going to link to that entry-point. Note that "!" and "browse" entry points are automatically merged with container page nodes (likely called by page-groupings) as appropriate.
     * @return ?array A map of entry points (screen-name=>language-code/string or screen-name=>[language-code/string, icon-theme-image]) (null: disabled)
     */
    public function get_entry_points(bool $check_perms = true, ?int $member_id = null, bool $support_crosslinks = true, bool $be_deferential = false) : ?array
    {
        if (!addon_installed('errorlog')) {
            return null;
        }

        return [
            'browse' => ['ERRORLOG', 'menu/adminzone/audit/errorlog'],
            'cron' => ['CRON_HOOKS', 'admin/tool']
        ];
    }

    public $title;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none)
     */
    public function pre_run() : ?object
    {
        $error_msg = new Tempcode();
        if (!addon_installed__messaged('errorlog', $error_msg)) {
            return $error_msg;
        }

        $type = get_param_string('type', 'browse');

        require_lang('errorlog');

        if ($type == 'browse') {
            set_helper_panel_tutorial('tut_disaster');
            set_helper_panel_text(comcode_lang_string('DOC_ERRORLOG'));

            $this->title = get_screen_title('ERRORLOG');

            if (!php_function_allowed('ini_set')) {
                attach_message(do_lang_tempcode('ERROR_LOGGING_PROBABLY_BROKEN'), 'warn');
            }
        }

        if ($type == 'cron') {
            set_helper_panel_tutorial('tut_configuration');
            set_helper_panel_text(comcode_lang_string('DOC_CRON_HOOKS'));

            $this->title = get_screen_title('CRON_HOOKS');
        }

        if ($type == 'delete_log') {
            $this->title = get_screen_title('DELETE_LOG');
        }

        if ($type == 'clear_log') {
            $this->title = get_screen_title('CLEAR_LOG');
        }

        if ($type == 'download_log') {
            $this->title = get_screen_title('DOWNLOAD_LOG');
        }

        if ($type == 'init_log') {
            $this->title = get_screen_title('INIT_LOG');
        }

        if ($type == 'enable_cron_hook') {
            $this->title = get_screen_title('ENABLE_CRON_HOOK');
        }

        if ($type == 'disable_cron_hook') {
            $this->title = get_screen_title('DISABLE_CRON_HOOK');
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution
     */
    public function run() : object
    {
        $type = get_param_string('type', 'browse');

        if ($type == 'browse') {
            return $this->show_logs();
        }

        if ($type == 'cron') {
            return $this->show_cron_progression_table();
        }

        if ($type == 'delete_log') {
            return $this->delete_log();
        }

        if ($type == 'clear_log' || $type == 'init_log') {
            return $this->clear_log();
        }

        if ($type == 'download_log') {
            return $this->download_log();
        }

        if ($type == 'enable_cron_hook') {
            return $this->enable_cron_hook();
        }

        if ($type == 'disable_cron_hook') {
            return $this->disable_cron_hook();
        }

        return new Tempcode(); // Should not get here
    }

    /**
     * Show the main UI.
     *
     * @return Tempcode The result of execution
     */
    public function show_logs() : object
    {
        require_code('files');
        require_css('errorlog');

        $maximum_size = 1024 * 64;
        $max_google_pages = 6;
        $default_max_per_page = 30;

        $start = get_param_integer('start', 0);
        $max = get_param_integer('max', $default_max_per_page);

        // Read in errors
        if (!GOOGLE_APPENGINE) {
            if (is_readable(get_custom_file_base() . '/data_custom/errorlog.php')) {
                if (filesize(get_custom_file_base() . '/data_custom/errorlog.php') > $maximum_size) {
                    $myfile = fopen(get_custom_file_base() . '/data_custom/errorlog.php', 'rb');
                    flock($myfile, LOCK_SH);
                    fseek($myfile, -$maximum_size, SEEK_END);
                    $lines = explode("\n", fread($myfile, $maximum_size));
                    flock($myfile, LOCK_UN);
                    fclose($myfile);
                    unset($lines[0]);
                    $lines[] = '...';
                    attach_message(do_lang_tempcode('ERROR_LOG_TRUNCATED', 'notice'));
                } else {
                    $lines = cms_file_safe(get_custom_file_base() . '/data_custom/errorlog.php');
                }
            } else {
                $lines = [];
            }
            $stuff = [];
            foreach ($lines as $line) {
                $_line = trim($line);

                if (($_line != '') && (strpos($_line, '<?php') === false)) {
                    $matches = [];
                    if (preg_match('#^\[([^\]]*)\] ([^:]*): (CRITICAL|ERROR|WARNING|INFO|DEBUG|)[\s]?(.*)#', $_line, $matches) != 0) {
                        $stuff[] = [$matches[1], $matches[2], $matches[3], $matches[4], ''];
                    } elseif (preg_match('#^\[([^\]]*)\] (CRITICAL|ERROR|WARNING|INFO|DEBUG|)[\s]?(.*)#', $_line, $matches) != 0) {
                        $stuff[] = [$matches[1], do_lang('NA'), $matches[2], $matches[3], ''];
                    } elseif ((preg_match('#^TELEMETRY (\d*)#', $_line, $matches) != 0) && (count($stuff) > 0)) { // We have a telemetry ID #
                        $stuff[count($stuff) - 1][4] = $matches[1];
                    } elseif (count($stuff) > 0) { // Additional lines for error message, so append them
                        $stuff[count($stuff) - 1][3] .= "\n" . $_line;
                    }
                }
            }
        } else {
            $stuff = [];

            require_once('google/appengine/api/log/LogService.php');

            $_log_service = 'google\appengine\api\log\LogService';
            $log_service = new $_log_service();
            $options = [];
            $options['include_app_logs'] = true;
            $options['minimum_log_level'] = eval('return $log_service::LEVEL_WARNING;'); // = PHP notice
            $options['batch_size'] = $max * $max_google_pages;

            $logs = $log_service->fetch($options);
            foreach ($logs as $log) {
                $app_logs = $log->getAppLogs();
                foreach ($app_logs as $app_log) {
                    $message = $app_log->getMessage();

                    $level = $app_log->getLevel();
                    $_level = 'INFO';
                    if ($level == eval('return $log_service::LEVEL_WARNING;')) {
                        $_level = 'WARNING';
                    } elseif ($level == eval('return $log_service::LEVEL_ERROR;')) {
                        $_level = 'ERROR';
                    } elseif ($level == eval('return $log_service::LEVEL_CRITICAL;')) {
                        $_level = 'CRITICAL';
                    } else {
                        continue;
                    }

                    $time = intval($app_log->getTimeUsec() / 1000000.0);

                    $stuff[] = [date('D-M-Y', $time) . ' ' . date('H:i:s', $time), do_lang('NA'), $_level, $message, ''];
                }
            }
        }

        // Put errors into table
        $sortables = ['date_and_time' => do_lang_tempcode('DATE_TIME')];
        $test = explode(' ', get_param_string('sort', 'date_and_time DESC', INPUT_FILTER_GET_COMPLEX), 2);
        if (count($test) == 1) {
            $test[1] = 'DESC';
        }
        list($sortable, $sort_order) = $test;
        if (((cms_strtoupper_ascii($sort_order) != 'ASC') && (cms_strtoupper_ascii($sort_order) != 'DESC')) || (!array_key_exists($sortable, $sortables))) {
            log_hack_attack_and_exit('ORDERBY_HACK');
        }
        if ($sort_order == 'DESC') {
            $stuff = array_reverse($stuff);
        }

        require_code('templates_results_table');
        $header_row = results_header_row([do_lang_tempcode('DATE_TIME'), do_lang_tempcode('TYPE'), do_lang_tempcode('LOG_LEVEL'), do_lang_tempcode('RELAYED'), do_lang_tempcode('MESSAGE')], $sortables, 'sort', $sortable . ' ' . $sort_order);
        $result_entries = new Tempcode();
        for ($i = $start; $i < $start + $max; $i++) {
            if (!array_key_exists($i, $stuff)) {
                break;
            }

            list($log_date, $log_type, $log_level, $log_message, $telemetry_id) = $stuff[$i];

            if ($log_level == '') {
                $log_level = 'WARNING'; // Default to warning if a log does not contain a level
            }

            $message = str_replace(get_file_base(), '', $log_message);

            $td_class = cms_mb_strtolower($log_level);

            if (is_numeric($telemetry_id)) {
                $telemetry = hyperlink(get_brand_page_url(['page' => 'telemetry', 'type' => $telemetry_id, 'lang' => get_lang(get_member())], ''), do_lang('YES'), true, true);
            } else {
                $telemetry = do_lang_tempcode('NO');
            }

            $result_entries->attach(static_evaluate_tempcode(results_entry([
                $log_date,
                $log_type,
                $log_level,
                $telemetry,
                $message,
            ], true, 'errorlog', '4469d055e697470a8cb58e8415debaaa', $td_class)));
        }
        $errors = results_table(do_lang_tempcode('ERRORLOG'), $start, 'start', $max, 'max', $i, $header_row, $result_entries, $sortables, $sortable, $sort_order, 'sort', new Tempcode(), ['180px', '180px', '90px', '75px'], 'errorlog');

        // Read in end of any other log files we find
        require_all_lang();
        $logs = [];
        $dh = opendir(get_custom_file_base() . '/data_custom');
        while (($filename = readdir($dh)) !== false) {
            if (substr($filename, -4) == '.log') {
                $myfile_file_charset = null;
                $lines = [];
                $myfile = @cms_fopen_text_read(get_custom_file_base() . '/data_custom/' . $filename, $myfile_file_charset, true);
                if ($myfile !== false) {
                    // Get last 40000 bytes of log
                    fseek($myfile, -40000, SEEK_END);
                    $data = '';
                    while (!feof($myfile)) {
                        $line = cms_fgets($myfile, $myfile_file_charset);
                        if ($line !== false) {
                            $data .= $line;
                        }
                    }
                    flock($myfile, LOCK_UN);
                    fclose($myfile);

                    // Split into lines
                    $lines = explode("\n", $data);

                    // Mark if we have truncated the start
                    if (!empty($lines)) {
                        if (strlen($data) == 40000) {
                            $lines[0] = '...';
                        }
                    }

                    // FUDGE: Any special support for reformatting particular logs
                    foreach ($lines as $i => $line) {
                        // Special support for permission log
                        $matches = [];
                        if (preg_match('#^\s+has_privilege: (\w+)#', $line, $matches) != 0) {
                            $looked_up = do_lang('PRIVILEGE_' . $matches[1], null, null, null, null, false);
                            if ($looked_up !== null) {
                                $line = str_replace($matches[1], $looked_up, $line);
                                $lines[$i] = $line;
                            }
                        }
                    }
                }

                // Put lines back together
                $log = implode("\n", $lines);

                // Action URLs
                $download_url = new Tempcode();
                $clear_url = new Tempcode();
                $add_url = new Tempcode();
                if ($log != '') {
                    $download_url = build_url(['page' => '_SELF', 'type' => 'download_log', 'id' => basename($filename, '.log')], '_SELF');
                }
                if ($log != '') {
                    $clear_url = build_url(['page' => '_SELF', 'type' => 'clear_log', 'id' => basename($filename, '.log')], '_SELF');
                }
                $delete_url = build_url(['page' => '_SELF', 'type' => 'delete_log', 'id' => basename($filename, '.log')], '_SELF');

                // Template-ready
                $logs[$filename] = [
                    'LOG' => $log,
                    'DOWNLOAD_URL' => $download_url,
                    'CLEAR_URL' => $clear_url,
                    'DELETE_URL' => $delete_url,
                    'ADD_URL' => $add_url,
                ];
            }
        }
        closedir($dh);

        // Other logs that may be create-able...
        $hook_obs = find_all_hook_obs('systems', 'logs', 'Hook_logs_');
        foreach ($hook_obs as $hook_ob) {
            $logs_available = $hook_ob->enumerate_logs();
            foreach (array_keys($logs_available) as $filename) {
                if ((!isset($logs[$filename])) && ($filename != 'errorlog.php')) { // if not already handled
                    // Action URLs
                    $add_url = build_url(['page' => '_SELF', 'type' => 'init_log', 'id' => basename($filename, '.log')], '_SELF');

                    // Template-ready
                    $logs[$filename] = [
                        'LOG' => null,
                        'DOWNLOAD_URL' => new Tempcode(),
                        'CLEAR_URL' => new Tempcode(),
                        'DELETE_URL' => new Tempcode(),
                        'ADD_URL' => $add_url,
                    ];
                }
            }
        }

        ksort($logs);

        // Put it all together...

        $clear_url = build_url(['page' => '_SELF', 'type' => 'clear_log', 'id' => 'errorlog'], '_SELF');

        $tpl = do_template('ERRORLOG_SCREEN', [
            '_GUID' => '9186c7beb6b722a52f39e2cbe16aded6',
            'TITLE' => $this->title,
            'ERRORS' => $errors,
            'LOGS' => $logs,
            'CLEAR_URL' => $clear_url,
        ]);

        require_code('templates_internalise_screen');

        return internalise_own_screen($tpl);
    }

    /**
     * Generate system scheduler progression table.
     *
     * @return Tempcode The progression table
     */
    public function show_cron_progression_table() : object
    {
        // Upgrade pending?
        require_code('version');
        $version_files = float_to_raw_string(cms_version_number(), 10, true);
        $version_db = strval(cms_version_time_db());
        if ((get_value('version') != $version_files) || (get_value('cns_version') != $version_files) || (get_value('db_version', '', true) != $version_db)) {
            attach_message(do_lang_tempcode('CRON_UPGRADE_PENDING'), 'warn');
        } else {
            // Not configured or not running?
            $last_cron = get_value('last_cron');
            if (($last_cron === null) || (intval($last_cron) < time() - 60 * 60 * 24)) {
                attach_message(do_lang_tempcode('CRON_NOT_RUNNING', escape_html(get_tutorial_url('tut_configuration'))), 'warn');
            }
        }

        require_code('templates_results_table');
        $_header_row = [
            do_lang_tempcode('LABEL'),
            do_lang_tempcode('QUEUED_ITEMS'),
            do_lang_tempcode('TIME_BETWEEN_RUNS'),
            do_lang_tempcode('LAST_RUN'),
            do_lang_tempcode('TIME'),
            do_lang_tempcode('ERRORS'),
            do_lang_tempcode('ENABLED'),
            do_lang_tempcode('LOCKED'),
            do_lang_tempcode('ACTIONS'),
        ];
        $header_row = results_header_row($_header_row);

        $_result_entries = [];
        $cron_progression = list_to_map('c_hook', $GLOBALS['SITE_DB']->query_select('cron_progression', ['*']));
        $cron_hooks = find_all_hook_obs('systems', 'cron', 'Hook_cron_');
        foreach ($cron_hooks as $hook => $object) {
            $label = $hook;
            $num_queued = null;
            $minutes_between_runs = 0;
            $available = true;
            $last_run = null;
            $last_execution_secs = null;
            $last_error = '';
            $enabled = true;

            if (isset($cron_progression[$hook])) {
                $last_run = $cron_progression[$hook]['c_last_run'];
                $last_execution_secs = $cron_progression[$hook]['c_last_execution_secs'];
                $last_error = $cron_progression[$hook]['c_last_error'];
                $enabled = ($cron_progression[$hook]['c_enabled'] == 1);
            }

            $info = $object->info($last_run, null); // Low priority for calculating queued items
            if ($info !== null) {
                $label = $info['label'];
                $num_queued = $info['num_queued'];
                $minutes_between_runs = $info['minutes_between_runs'];
            } else {
                $available = false;
            }

            $actions = new Tempcode();
            if ($enabled) {
                $actions->attach(do_template('COLUMNED_TABLE_ACTION', [
                    '_GUID' => '389946a7549670a785b404612ea456e3',
                    'URL' => build_url(['page' => '_SELF', 'type' => 'disable_cron_hook', 'id' => $hook], '_SELF'),
                    'NAME' => $label,
                    'ACTION_TITLE' => do_lang_tempcode('DISABLE_CRON_HOOK'),
                    'ICON' => 'admin/delete2',
                    'GET' => false,
                ]));
            } else {
                $actions->attach(do_template('COLUMNED_TABLE_ACTION', [
                    '_GUID' => '1ca6b538b04468743043532ff0bd0403',
                    'URL' => build_url(['page' => '_SELF', 'type' => 'enable_cron_hook', 'id' => $hook], '_SELF'),
                    'NAME' => $label,
                    'ACTION_TITLE' => do_lang_tempcode('ENABLE_CRON_HOOK'),
                    'ICON' => 'admin/add',
                    'GET' => false,
                ]));
            }
            if ($available) {
                $keep = symbol_tempcode('KEEP');
                $actions->attach(do_template('COLUMNED_TABLE_ACTION', [
                    '_GUID' => '87fd7ab2c12752925c469678d43c8c46',
                    'URL' => find_script('cron_bridge') . '?limit_hooks=' . urlencode($hook) . '&manual_run=1&verbose=1&force=1' . $keep->evaluate(),
                    'NAME' => $label,
                    'ACTION_TITLE' => do_lang_tempcode('EXECUTE_SCRIPT'),
                    'ICON' => 'admin/sync',
                    'GET' => true,
                    'NEW_WINDOW' => true,
                ]));
            }

            $_label = new Tempcode();
            $_label->attach(do_template('ANCHOR', ['_GUID' => 'b0a57cc72db9fe96887fdcb42cda561e', 'NAME' => 'cron_' . $hook]));
            $_label->attach(make_string_tempcode(escape_html($label)));
            if (!$available) {
                $_label->attach(' (' . do_lang('UNAVAILABLE') . ')');
            }

            if ($num_queued === null) {
                $queue = do_lang_tempcode('UNKNOWN_EM');
            } else {
                $queue = make_string_tempcode(escape_html(integer_format($num_queued)));
                if (array_key_exists('queued_details_url', $info)) {
                    $queue = hyperlink($info['queued_details_url'], $queue, false, false);
                }
                if (method_exists($object, 'queued_details_tooltip')) {
                    $_label->attach(div($object->queued_details_tooltip()));
                }
            }

            $locked = get_value('cron_currently_running__' . $hook, '0', true);
            $locked_status = do_lang_tempcode('NO');
            if ($locked == '1') {
                $probably_slow = get_value_newer_than('cron_currently_running__' . $hook, time() - 60, true);
                if ($probably_slow === null) { // Do not warn if the hook was locked less than 60 seconds ago
                    $locked_status = do_lang_tempcode('YES');
                    attach_message(do_lang_tempcode('LOCKED_HOOKS'), 'warn');
                } else {
                    $locked_status = do_lang_tempcode('IS_LOCKED_RUNNING');
                }
            }

            $_result_entries[$label] = [
                $_label,
                $queue,
                ($minutes_between_runs == 0) ? make_string_tempcode('<em>(0)</em>') : display_time_period($minutes_between_runs * 60),
                ($last_run === null) ? do_lang_tempcode('NA_EM') : make_string_tempcode(escape_html(get_timezoned_date_time($last_run))),
                ($last_execution_secs === null) ? do_lang_tempcode('NA_EM') : make_string_tempcode(escape_html(display_time_period($last_execution_secs))),
                ($last_error == '') ? do_lang_tempcode('NONE_EM') : make_string_tempcode(escape_html($last_error)),
                do_lang_tempcode($enabled ? 'YES' : 'NO'),
                $locked_status,
                $actions,
            ];
        }

        cms_mb_ksort($_result_entries, SORT_NATURAL | SORT_FLAG_CASE);

        $result_entries = new Tempcode();
        foreach ($_result_entries as $label => $details) {
            $td_class = '';
            if (strpos($details[0]->evaluate(), do_lang('UNAVAILABLE')) !== false) {
                $td_class = 'disabled';
            } elseif (strpos($details[6]->evaluate(), do_lang('NO')) !== false) {
                $td_class = 'critical';
            } elseif (strpos($details[7]->evaluate(), do_lang('UNAVAILABLE')) !== false) {
                $td_class = 'error';
            }
            $result_entries->attach(results_entry($details, true, null, '392f1980c81e4083885cb177c911e619', $td_class));
        }

        $table = results_table(do_lang_tempcode('CRON_HOOKS'), 0, 'start', 1000, 'max', 1000, $header_row, $result_entries);

        $tpl = do_template('RESULTS_TABLE_SCREEN', [
            '_GUID' => '5e2911f8e43aec63f5a45f2a8fc375cd',
            'RESULTS_TABLE' => $table,
            'TITLE' => $this->title,
            'FILTERS_ROW_A' => null,
            'FILTERS_ROW_B' => null,
            'FILTERS_HIDDEN' => null,
            'URL' => null,
        ]);

        require_code('templates_internalise_screen');
        return internalise_own_screen($tpl);
    }

    /**
     * Enable a system scheduler hook.
     *
     * @return Tempcode The result of execution
     */
    protected function enable_cron_hook() : object
    {
        $hook = get_param_string('id');
        return $this->configure_cron_hook($hook, 1);
    }

    /**
     * Disable a system scheduler hook.
     *
     * @return Tempcode The result of execution
     */
    protected function disable_cron_hook() : object
    {
        $hook = get_param_string('id');
        return $this->configure_cron_hook($hook, 0);
    }

    /**
     * Configure a system scheduler hook.
     *
     * @param  ID_TEXT $hook The hook
     * @param  BINARY $enabled Whether the hook is enabled
     * @return Tempcode The result of execution
     */
    protected function configure_cron_hook(string $hook, int $enabled) : object
    {
        // Update cron_progression table
        if ($GLOBALS['SITE_DB']->query_select_value_if_there('cron_progression', 'c_hook', ['c_hook' => $hook]) !== null) {
            $GLOBALS['SITE_DB']->query_update('cron_progression', ['c_enabled' => $enabled], ['c_hook' => $hook], '', 1);
        } else {
            $GLOBALS['SITE_DB']->query_insert('cron_progression', [
                'c_hook' => $hook,
                'c_last_run' => null,
                'c_last_execution_secs' => null,
                'c_last_error' => '',
                'c_enabled' => $enabled,
            ]);
        }

        $url = build_url(['page' => '_SELF', 'type' => 'cron'], '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Delete log actualiser.
     *
     * @return Tempcode The result of execution
     */
    public function delete_log() : object
    {
        $log_file = filter_naughty(get_param_string('id'));
        if ($log_file == 'errorlog') {
            $log_file .= '.php';
        } else {
            $log_file .= '.log';
        }

        unlink(get_custom_file_base() . '/data_custom/' . $log_file);

        $url = build_url(['page' => '_SELF'], '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Clear/init log actualiser.
     *
     * @return Tempcode The result of execution
     */
    public function clear_log() : object
    {
        $log_file = filter_naughty(get_param_string('id'));
        if ($log_file == 'errorlog') {
            $log_file .= '.php';
        } else {
            $log_file .= '.log';
        }

        require_code('files');
        cms_file_put_contents_safe(get_custom_file_base() . '/data_custom/' . $log_file, '');

        $url = build_url(['page' => '_SELF'], '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Download log actualiser.
     *
     * @return Tempcode The result of execution
     */
    public function download_log() : object
    {
        $log_file = filter_naughty(get_param_string('id'));
        if ($log_file == 'errorlog') {
            $log_file .= '.php';
        } else {
            $log_file .= '.log';
        }

        cms_ini_set('ocproducts.xss_detect', '0');

        header('Content-Type: text/plain; charset=' . get_charset());

        echo cms_file_get_contents_safe(get_custom_file_base() . '/data_custom/' . $log_file, FILE_READ_LOCK | FILE_READ_BOM);

        $GLOBALS['SCREEN_TEMPLATE_CALLED'] = '';
        exit();

        return new Tempcode();
    }
}
