<?php

namespace CleantalkSP\SpbctWP;

use CleantalkSP\SpbctWP\Scanner\Cure;
use CleantalkSP\SpbctWP\Scanner\CureLog\CureLog;
use CleantalkSP\SpbctWP\Scanner\DBTrigger\DBTriggerService;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronController;
use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\FileSystemActions;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\FrontendPagesActions;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\ScanResultsTableActions;
use CleantalkSP\SpbctWP\Scanner\ScannerAjaxEndpoints;
use CleantalkSP\SpbctWP\Scanner\Services\SendFileToCloudService;
use CleantalkSP\Variables\Post;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\CloudAnalysisActions;

class ListTable
{
    /**
     * @psalm-suppress PossiblyUnusedProperty
     */
    public $args = array(); // Input arguments

    public $id = ''; // Table id
    public $type; // Short description

    // SQL query params
    public $sql = array(
        'add_col'            => array(), // Additional cols to select
        'except_cols'        => array(), // Cols to except from query
        'table'              => '',      // Table name
        'where'              => '',      // Where clause
        'order_by'           => '',      // Order By
        'order_by_direction' => '',      // Desc / Asc
        'group_by'           => '',
        'offset'             => 0,       // Limit from
        'limit'              => 20,      // Limit till
        'get_array'          => false,   // Give an array on output
    );
    public $rows = array(); // SQL Result

    // Pagination params
    public $pagination = array();

    // Callbacks
    public $func_data_prepare = null; // Function to process items before output
    public $func_data_get = null; // Function to receive data
    public $func_data_total = null; // Function to get total items

    // ROWS
    public $items = array(); // Items to output
    public $items_count = 0; // Amount to output
    public $items_total = 0; // Amount total

    // COLS
    public $columns = array();
    public $columns_names;
    public $columns_amount;

    // Misc
    public $sortable = array();      // Sortable columns
    public $order_by = array();      // Current sorting
    public $actions = array();       // Row actions
    public $bulk_actions = array();  // Bulk actions
    public $bulk_actions_all = true; // Bulk actions "Apply to all"

    // HTML output
    public $html_before = ''; // HTML before table
    public $html_after = ''; // HTML after table
    public $if_empty_items = 'No data given.'; // HTML message

    public function __construct($args = array())
    {
        $this->args = json_encode($args);

        $this->id   = ! empty($args['id']) ? $args['id'] : 'table_' . rand(0, 10000);
        $this->type = ! empty($args['type']) ? $args['type'] : 'unknown';

        $this->items   = ! empty($args['items']) ? $args['items'] : $this->items;
        $this->columns = ! empty($args['columns']) ? $args['columns'] : $this->columns;

        $this->items_count = count((array)$this->items);
        $this->items_total = count((array)$this->items);

        // HTML output
        $this->html_before    = ! empty($args['html_before']) ? $args['html_before'] : $this->html_before;
        $this->html_after     = ! empty($args['html_after']) ? $args['html_after'] : $this->html_after;
        $this->if_empty_items = ! empty($args['if_empty_items']) ? $args['if_empty_items'] : $this->if_empty_items;

        if ( isset($args['pagination']['page']) ) {
            $this->pagination['page'] = $args['pagination']['page'];
        }
        if ( isset($args['pagination']['per_page']) ) {
            $this->pagination['per_page'] = $args['pagination']['per_page'];
        }

        // SQL shit

        if ( ! empty($args['sql']) ) {
            $this->sql['add_col']     = ! empty($args['sql']['add_col']) ? $args['sql']['add_col'] : $this->sql['add_col'];
            $this->sql['except_cols'] = ! empty($args['sql']['except_cols']) ? $args['sql']['except_cols'] : $this->sql['except_cols'];
            $this->sql['table']       = ! empty($args['sql']['table']) ? $args['sql']['table'] : $this->sql['table'];
            $this->sql['where']       = ! empty($args['sql']['where']) ? $args['sql']['where'] : $this->sql['where'];
            $this->sql['group_by']    = ! empty($args['sql']['group_by']) ? $args['sql']['group_by'] : $this->sql['group_by'];
            $this->sql['offset']      = ! empty($args['sql']['offset']) ? $args['sql']['offset'] : $this->sql['offset'];
            $this->sql['limit']       = ! empty($args['sql']['limit']) ? $args['sql']['limit'] : $this->sql['limit'];
            $this->sql['get_array']   = ! empty($args['sql']['get_array']) ? $args['sql']['get_array'] : $this->sql['get_array'];
        }

        $this->sql['offset'] = isset($this->pagination['page']) ? ($this->pagination['page'] - 1) * $this->pagination['per_page'] : $this->sql['offset'];
        $this->sql['limit']  = isset($this->pagination['per_page']) ? $this->pagination['per_page'] : $this->sql['limit'];
        $this->sql['limit']  = ! empty($args['sql']['limit_force']) ? $args['sql']['limit_force'] : $this->sql['limit'];

        $this->func_data_prepare = ! empty($args['func_data_prepare']) ? $args['func_data_prepare'] : $this->func_data_prepare;
        $this->func_data_get     = ! empty($args['func_data_get']) ? $args['func_data_get'] : $this->func_data_get;
        $this->func_data_total   = ! empty($args['func_data_total']) ? $args['func_data_total'] : $this->func_data_total;

        $this->columns_names  = array_keys($this->columns);
        $this->columns_amount = count($this->columns);

        if ( in_array('cb', $this->columns_names) ) {
            $this->columns_names = array_slice($this->columns_names, 1);
        }
        if ( ! empty($this->sql['add_col']) ) {
            $this->columns_names = array_merge($this->columns_names, $this->sql['add_col']);
        }
        if ( ! empty($this->sql['except_cols']) ) {
            $this->columns_names = array_diff($this->columns_names, $this->sql['except_cols']);
        }

        $this->sortable = ! empty($args['sortable']) ? $args['sortable'] : $this->sortable;
        $this->order_by = ! empty($args['order_by']) ? $args['order_by'] : $this->order_by;

        // END OF SQL shit

        $this->actions          = ! empty($args['actions']) ? $args['actions'] : $this->actions;
        $this->bulk_actions     = ! empty($args['bulk_actions']) ? $args['bulk_actions'] : $this->bulk_actions;
        $this->bulk_actions_all = isset($args['bulk_actions_all']) ? $args['bulk_actions_all'] : $this->bulk_actions_all;
    }

    public function getData()
    {
        global $wpdb;

        // Getting total of items
        // by using given function ('function_name') of method (['Class', 'method'])
        if (
            $this->func_data_total &&
            (
                ( is_string($this->func_data_total) && function_exists($this->func_data_total) ) ||
                ( is_array($this->func_data_total) && count($this->func_data_total) === 2 && method_exists($this->func_data_total[0], $this->func_data_total[1]) )
            )
        ) {
            $this->items_total = call_user_func_array($this->func_data_total, array());
            // by using direct SQL request
        } else {
            $total             = $wpdb->get_results(
                sprintf(
                    'SELECT COUNT(*) as cnt'
                    . ' FROM %s'
                    . '%s',
                    $this->sql['table'], // TABLE
                    $this->sql['where']  // WHERE
                ),
                OBJECT_K
            );
            $this->items_total = key($total);
        }

        // Getting data
        // by using given function ('function_name') of method (['Class', 'method'])
        if (
            $this->func_data_get &&
            (
                ( is_string($this->func_data_get) && function_exists($this->func_data_get) ) ||
                ( is_array($this->func_data_get) && count($this->func_data_get) === 2 && method_exists($this->func_data_get[0], $this->func_data_get[1]) )
            )
        ) {
            $param = array($this->sql['offset'], $this->sql['limit']);
            if ( $this->order_by ) {
                $param[] = current($this->order_by);
                $param[] = key($this->order_by);
            }
            $this->rows = call_user_func_array($this->func_data_get, $param);
            // by using direct SQL request
        } else {
            $columns = array();
            foreach ( $this->columns_names as $columns_name ) {
                $columns[] = $this->sql['table'] . '.' . $columns_name;
            }

            // Sanitize ORDER BY column - must be a known column name
            $order_by_column = key($this->order_by);
            $allowed_columns = ! empty($this->sortable) ? $this->sortable : $this->columns_names;
            if ( ! in_array($order_by_column, $allowed_columns, true) ) {
                $order_by_column = reset($allowed_columns);
            }

            // Sanitize ORDER BY direction - only allow 'asc' or 'desc'
            $order_by_direction = strtolower(current($this->order_by));
            if ( ! in_array($order_by_direction, array('asc', 'desc'), true) ) {
                $order_by_direction = 'asc';
            }

            // 1) Build base query with sprintf (identifiers only - columns, table, order by)
            $sql_base = sprintf(
                'SELECT %s FROM %s',
                implode(', ', $columns),
                $this->sql['table']
            );

            // 2) ORDER BY (sanitized identifiers)
            $sql_order = sprintf(' ORDER BY %s %s', $order_by_column, $order_by_direction);

            // 3) Use prepare() for LIMIT/OFFSET, escape % in WHERE for prepare() compatibility
            $sql_where = ! empty($this->sql['where']) ? str_replace('%', '%%', $this->sql['where']) : '';
            $sql_full = $sql_base . $sql_where . $sql_order . ' LIMIT %d, %d';
            if ($this->sql['limit'] !== false && $this->sql['limit'] !== 0) {
                $sql_prepared = $wpdb->prepare(
                    $sql_full,
                    (int) $this->sql['offset'],
                    (int) $this->sql['limit']
                );
            } else {
                $sql_prepared = $wpdb->prepare(
                    $sql_full,
                    (int) $this->sql['offset'],
                    PHP_INT_MAX
                );
            }

            $this->rows = $wpdb->get_results($sql_prepared);
        }

        // Adding actions to each row
        if ($this->rows) {
            foreach ( $this->rows as &$row ) {
                if ( is_object($row) ) {
                    $row->actions = array_flip(array_keys($this->actions));
                }
                if ( is_array($row) ) {
                    $row['actions'] = array_flip(array_keys($this->actions));
                }
            }
        }
        unset($row);

        $this->items_count = count((array)$this->rows);

        // Execute given function to prepare data
        if (
            $this->func_data_prepare &&
            (
                ( is_string($this->func_data_prepare) && function_exists($this->func_data_prepare) ) ||
                ( is_array($this->func_data_prepare) && count($this->func_data_prepare) === 2 && method_exists($this->func_data_prepare[0], $this->func_data_prepare[1]) )
            )
        ) {
            call_user_func_array($this->func_data_prepare, array(&$this));
        } else {
            // Changing $this in function
            $this->prepareDataDefault();
        }
    }

    private function prepareDataDefault()
    {
        if ( $this->items_count ) {
            foreach ( $this->rows as $key => $row ) {
                foreach ( $this->columns as $column_name => $column ) {
                    $this->items[$key][$column_name] = $row->$column_name;
                    if ( isset($column['primary']) ) {
                        $this->items[$key]['uid'] = $row->$column_name;
                    }
                    if ( ! empty($this->actions) ) {
                        $this->items[$key]['actions'] = $this->actions;
                    }
                }
            }
        }
    }

    public function display()
    {
        if ( $this->items_count == 0 ) {
            echo $this->if_empty_items;

            return;
        }

        echo '<div id="' . $this->id . '" type="' . $this->type . '" class="tbl-root">';

        echo $this->html_before;

        $this->displayBulkActionControls();
        $this->displayPaginationControls();

        ?>
        <table class="wp-list-table widefat fixed striped">
            <thead>
            <tr>
                <?php
                $this->displayColumnHeaders(); ?>
            </tr>
            </thead>

            <tbody>
            <?php
            $this->displayRows($this->id); ?>
            </tbody>

            <tfoot>
            <tr>
                <?php
                $this->displayColumnHeaders(); ?>
            </tr>
            </tfoot>

        </table>
        <?php

        echo $this->html_after;

        $this->displayBulkActionControls();
        $this->displayPaginationControls();

        echo '</div>';
    }

    /**
     * Return template for different blocks
     *
     * @return string|boolean
     */
    public function getTemplate()
    {
        if ( $this->items_count == 0 ) {
            return false;
        }

        ob_start();
        echo '<div id="' . $this->id . '" type="' . $this->type . '" class="tbl-root">';

        echo $this->html_before;

        $this->displayBulkActionControls();
        $this->displayPaginationControls();

        ?>
        <table class="wp-list-table widefat fixed striped">
            <thead>
            <tr>
                <?php
                $this->displayColumnHeaders(); ?>
            </tr>
            </thead>

            <tbody>
            <?php
            $this->displayRows($this->id); ?>
            </tbody>

            <tfoot>
            <tr>
                <?php
                $this->displayColumnHeaders(); ?>
            </tr>
            </tfoot>

        </table>
        <?php

        echo $this->html_after;

        $this->displayBulkActionControls();
        $this->displayPaginationControls();

        echo '</div>';

        return ob_get_clean();
    }

    public function displayBulkActionControls()
    {
        if ( ! empty($this->bulk_actions) ) {
            echo '<div class="tbl-bulk_actions--wrapper">';
            echo '<select class="tbl-select">';
            echo "<option value='-1'>Bulk actions</option>";
            foreach ( $this->bulk_actions as $action_key => $action ) {
                echo "<option value='{$action_key}'>{$action['name']}</option>";
            }
            echo '</select>';
            echo '<button type="button" class="tbl-button tbl-button---white_blue tbl-bulk_actions--apply">'
                 . __('Apply to selected')
                 . '<img class="tbl-preloader--small tbl-preloader--in_button" src="' . SPBC_PATH . '/images/preloader2.gif" />'
                 . '</button>';

            if ( $this->bulk_actions_all ) {
                echo '<button type="button" class="tbl-button tbl-bulk_actions-all--apply">'
                    . __('Apply to all')
                    . '<img class="tbl-preloader--small tbl-preloader--in_button" src="' . SPBC_PATH . '/images/preloader2.gif" />'
                    . '</button>';
            }

            echo '</div>';
        }
    }

    public function displayPaginationControls()
    {
        if ( ! empty($this->pagination) && $this->items_total > $this->pagination['per_page'] ) {
            $next_page = $this->pagination['page'] + 1 > ceil(
                $this->items_total / $this->pagination['per_page']
            ) ? $this->pagination['page'] : $this->pagination['page'] + 1;
            echo "<div class='tbl-pagination--wrapper'
				prev_page='" . ($this->pagination['page'] - 1 ?: 1) . "'
				next_page='$next_page'
				last_page='" . ceil($this->items_total / $this->pagination['per_page']) . "'
			>";
                echo "<form>";
                    echo '<div style="display: flex;align-items: center;flex-wrap: wrap;">';
                        echo "<span class='tbl-pagination--total'>{$this->items_total} Entries</span>";
                        echo '<div style="display: flex;flex-wrap: wrap;">';
                            echo '<div>';
                                echo '<button type="button" class="tbl-button tbl-pagination--button tbl-pagination--start"><i class="spbc-icon-to-start"></i></button>';
                                echo '<button type="button" class="tbl-button tbl-pagination--button tbl-pagination--prev"><i class="spbc-icon-fast-bw"></i></button>';
                            echo '</div>';
                            echo '<div>';
                                echo "<input type='text' class='tbl-pagination--curr_page' value='{$this->pagination['page']}'/>";
                                echo '<span class="tbl-pagination--total"> of ' . ceil($this->items_total / $this->pagination['per_page']) . '</span>';
                                echo '<button type="submit" class="tbl-button tbl-pagination--button tbl-pagination--go">' . __('Go') . '</button>';
                            echo '</div>';
                            echo '<div>';
                                echo '<button type="button" class="tbl-button tbl-pagination--button tbl-pagination--next"><i class="spbc-icon-fast-fw"></i></button>';
                                echo '<button type="button" class="tbl-button tbl-pagination--button tbl-pagination--end"><i class="spbc-icon-to-end"></i></button>';
                                echo '<img class="tbl-preloader--small" src="' . SPBC_PATH . '/images/preloader2.gif" />';
                            echo '</div>';
                        echo '</div>';
                    echo '</div>';
                echo "</form>";
            echo '</div>';
        }
    }

    public function displayColumnHeaders()
    {
        foreach ( $this->columns as $column_key => $column ) {
            $tag = ('cb' === $column_key) ? 'td' : 'th';

            $id = $column_key;

            $classes = "manage-column column-$column_key";
            $classes .= isset($column['primary']) ? ' column-primary' : '';
            $classes .= isset($column['class']) ? ' ' . $column['class'] : '';

            // Sorting
            if ( in_array($column_key, $this->sortable) && count($this->rows) != 1 ) {
                $classes .= ' tbl-column-sortable';
                $classes .= isset($this->order_by[$column_key]) ? ' tbl-column-sorted' : '';

                $sort_direction      = isset($this->order_by[$column_key]) && $this->order_by[$column_key] == 'asc' ? 'desc' : 'asc';
                $sort_direction_attr = 'sort_direction="' . $sort_direction . '"';

                $sort_classes = 'tbl-sorting_indicator';
                $sort_classes .= isset($this->order_by[$column_key]) ? ' tbl-sorting_indicator--sorted' : '';
                $sort_classes .= isset($this->order_by[$column_key]) ? ' spbc-icon-sort-alt-' . ($sort_direction == 'desc' ? 'up' : 'down') : ' spbc-icon-sort-alt-down';

                $sortable = "<i class='$sort_classes'></i>";
            } else {
                $sortable            = '';
                $sort_direction_attr = '';
            }

            $hint = isset($column['hint']) ? '<i class="spbc_hint--icon spbc-icon-help-circled"></i><span class="spbc_hint--text">' . $column['hint'] . '</span>' : '';
            $style = '';
            if (isset($column['width_percent'])) {
                $header_length = !empty($column['width_percent']) ? $column['width_percent'] . '%' : '';
                $style = "style='width:$header_length'";
            }
            // Out
            echo "<$tag id='$id' class='$classes' $sort_direction_attr $style>{$column['heading']}$sortable$hint</$tag>";
        }
        unset($column_key);
    }

    public function displayRows($id, $return = false)
    {
        $out = '';
        $blocks_count = 0;

        foreach ( $this->items as $item ) {
            $item = (array)$item;

            $out .= '<tr>';
            $scheduled = !empty($item['uid']) && in_array($item['uid'], spbc_get_list_of_scheduled_suspicious_files_to_send());
            $gray_start = $scheduled ? '<span style="color: darkgray">' : '';
            $gray_end = $scheduled ? '</span>' : '';

            $hide_show_mob_class_name = '';
            $hide_mob_class_name = '';
            $blocks_count += 1;
            $rows_count = 0;

            foreach ( $this->columns as $column_key => $column ) {
                $classes = "$column_key column-$column_key";
                $classes .= isset($column['primary']) ? ' column-primary' : '';
                $classes .= isset($column['class']) ? ' ' . $column['class'] : '';

                if ( 'cb' === $column_key ) {
                    $out .= '<th scope="row" class="check-column">';
                    $out .= $this->displayColumnCb($item['cb']);
                    $out .= '</th>';
                } elseif ( method_exists($this, 'display__column_' . $column['heading']) ) {
                    $out .= call_user_func(
                        array($this, '_column_' . $column['heading']),
                        $item,
                        $classes
                    );
                } else {
                    $class_key = "'" .  $id . "'";
                    $rows_count += 1;
                    if ($rows_count != 1) {
                        $hide_show_mob_class_name = $id . '_block_' . $blocks_count;
                        $hide_mob_class_name = 'mob_entries';
                    }
                    $mob_class_block = 'mob_block_' . $blocks_count;
                    $column_heading = $column['heading'];
                    $no_code_header = isset($column['no_code_header']) ? $column['no_code_header'] : '';
                    $out .= "<td class='$classes $hide_mob_class_name $mob_class_block $hide_show_mob_class_name' data-before='$column_heading' data-defore-no-code='$no_code_header'>";
                    $out .= isset($item[$column_key]) ? $gray_start . $item[$column_key] . $gray_end : '-';
                    $out .= isset($column['primary'])
                        ? '<button type="button" onclick="spbcShowHideRows(' . $blocks_count . ',' . $class_key . ')" class="toggle-row"><span class="screen-reader-text">' . __(
                            'Show more details'
                        ) . '</span></button>'
                        : '';
                    if ( isset($column['primary']) && ! empty($this->actions) && ! empty($item['uid']) ) {
                        $out .= $this->displayRowActions($item['uid'], $item);
                    }


                    $out .= '</td>';
                }
            }
            unset($column_key, $column['heading']);
            $out .= $scheduled ? '</div>' : '';

            $out .= '</tr>';
        }
        unset($item);

        if ( $return ) {
            return $out;
        }

        echo $out;
    }

    public function displayColumnCb($id)
    {
        return '<input type="checkbox" name="item[]" class="cb-select" id="cb-select-' . $id . '" value="' . $id . '" />';
    }

    public function displayRowActions($uid, $item)
    {
        $homeUrl = get_option('home') . '/';
        $pageId = !empty($item['page_id']) ? 'page_id="' . esc_attr($item['page_id']) . '"' : '';

        $output = sprintf(
            '<div class="row-actions" uid="%s" cols_amount="%s" %s>',
            esc_attr($uid),
            esc_attr($this->columns_amount),
            $pageId
        );

        $naming_data = self::getRowActionsElementNamingData();
        $default_tooltip = __('No description for this action provided.', 'security_malware_firewall');

        $action_elements = [];

        foreach ($this->actions as $action_key => $action) {
            if (!isset($item['actions'][$action_key])) {
                continue;
            }

            if (isset($action['type']) && $action['type'] === 'link') {
                $action_elements[] = $this->renderLinkAction($action, $uid, $homeUrl, $item, $action_key, $naming_data, $default_tooltip);
            } else {
                $action_elements[] = $this->renderButtonAction($action, $action_key, $naming_data, $default_tooltip);
            }
        }

        if (!empty($action_elements)) {
            $output .= implode(' | ', $action_elements);
        }

        $output .= '</div>';
        $output .= sprintf(
            '<img class="tbl-preloader--tiny" src="%s" alt="%s" />',
            esc_url(SPBC_PATH . '/images/preloader2.gif'),
            esc_attr__('Loading', 'security_malware_firewall')
        );

        return $output;
    }

    /**
     * Render link action element
     */
    private static function renderLinkAction($action, $uid, $homeUrl, $item, $action_key, $naming_data = [], $default_tooltip = '')
    {
        $href = $homeUrl;

        if (!empty($action['local']) && isset($action['href']) && !empty($action['href'])) {
            $href .= $action['href'];
        }

        if (!empty($action['uid'])) {
            $href .= $uid;
        }

        if (!empty($action['edit_post_link']) && preg_match('/=(\d+)$/', $item['page_url'], $matches)) {
            $href .= "post.php?post={$matches[1]}&action=edit";
        }

        $target = !empty($action['target']) ? $action['target'] : '_self';
        $tooltip = isset($naming_data[$action_key], $naming_data[$action_key]['tip'])
        ? $naming_data[$action_key]['tip']
        : $default_tooltip;

        return sprintf(
            '<span data-tooltip="%s" class="tbl-row_action tbl-row_action_tooltip"><a href="%s" target="%s">%s</a></span>',
            esc_html($tooltip),
            esc_url($href),
            esc_attr($target),
            esc_html($action['name'])
        );
    }

    /**
     * Render button action element
     */
    private static function renderButtonAction($action, $action_key, $naming_data = [], $default_tooltip = '')
    {
        $classes = [
            'tbl-row_action',
            "tbl-row_action--{$action_key}",
            'tbl-row_action_tooltip'
        ];

        if (!isset($action['handler'])) {
            $classes[] = 'tbl-row_action--ajax';
        }

        $classString = implode(' ', $classes);
        $handler = isset($action['handler'])
            ? sprintf(' onclick="%s"', esc_attr($action['handler']))
            : sprintf(' row-action="%s"', esc_attr($action_key));

        $tooltip = isset($naming_data[$action_key], $naming_data[$action_key]['tip'])
            ? $naming_data[$action_key]['tip']
            : $default_tooltip;

        return sprintf(
            '<span data-tooltip="%s" class="%s"%s>%s</span>',
            esc_attr($tooltip),
            esc_attr($classString),
            $handler,
            esc_html($action['name'])
        );
    }

    /**
     * Returns array of row actions, each action contains title and tip.
     * @return array[]
     */
    public static function getRowActionsElementNamingData()
    {
        global $spbc;
        return array(
            'delete'     => array(
                'title' => esc_html__('Delete', 'security-malware-firewall'),
                'tip'   => esc_html__('Delete the chosen file from your website file system in a safe way. You should be careful with this action as there is no turing back.', 'security-malware-firewall')
            ),
            'delete_quarantine' => array(
                'title' => esc_html__('Delete from quarantine', 'security-malware-firewall'),
                'tip'   => esc_html__('Permanently delete the quarantined copy of the chosen file. Your live website files are not affected. This cannot be undone.', 'security-malware-firewall')
            ),
            'view'       => array(
                'title' => esc_html__('View', 'security-malware-firewall'),
                'tip'   => esc_html__('View the chosen file.', 'security-malware-firewall')
            ),
            'send'       => array(
                'title' => esc_html__('Send for Analysis', 'security-malware-firewall'),
                'tip'   => esc_html__('Send the chosen file to the ' . $spbc->data["wl_brandname"] . ' Cloud for analysis.', 'security-malware-firewall'),
            ),
            'approve'    => array(
                'title' => esc_html__('Approve', 'security-malware-firewall'),
                'tip'   => esc_html__('Approve the chosen file so it will not be scanned again. You can always disapprove it in the "Approved" category.', 'security-malware-firewall')
            ),
            'quarantine' => array(
                'title' => esc_html__('Quarantine', 'security-malware-firewall'),
                'tip'   => esc_html__('Put the chosen file to quarantine where it can not harm the website.', 'security-malware-firewall')
            ),
            'download' => array(
                'title' => esc_html__('Download', 'security-malware-firewall'),
                'tip'   => esc_html__('Download quarantined file (new window).', 'security-malware-firewall')
            ),
            'replace'    => array(
                'title' => esc_html__('Replace', 'security-malware-firewall'),
                'tip'   => esc_html__('Restore the initial state of the chosen file if the file is accessible. It applies only to the WordPress core files.', 'security-malware-firewall')
            ),
            'view_bad'   => array(
                'title' => esc_html__('View Malicious Code', 'security-malware-firewall'),
                'tip'   => esc_html__('View malicious code that was found by the scanner, so you can inspect it more clearly.', 'security-malware-firewall')
            ),
            'cure'   => array(
                'title' => esc_html__('Cure', 'security-malware-firewall'),
                'tip'   => esc_html__('Do attempt to cure file. Cure process result can be seen in the Cure Log tab.', 'security-malware-firewall')
            ),
            'restore'   => array(
                'title' => esc_html__('Restore', 'security-malware-firewall'),
                'tip'   => esc_html__('Restore file state to the state before cure or quarantine', 'security-malware-firewall')
            ),
            'copy_file_info'   => array(
                'title' => esc_html__('Copy file info', 'security-malware-firewall'),
                'tip'   => esc_html__('Copy the necessary file info. Could be requested by the support team.', 'security-malware-firewall')
            ),
            'check_analysis_status'   => array(
                'title' => esc_html__('Refresh the analysis status', 'security-malware-firewall'),
                'tip'   => esc_html__('Available if file status were not changed automatically.', 'security-malware-firewall')
            ),
        );
    }

    public static function ajaxBulkActionHandler()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $ids = ScanResultsTableActions::getFilesByCategory(Post::getString('status'));

        switch ( Post::getString('add_action') ) {
            case 'approve':
                $out = ScanResultsTableActions::bulkApproveFile($ids);
                break;
            case 'disapprove':
                $out = ScanResultsTableActions::bulkDisapproveFile($ids);
                break;
            case 'approve_page':
                $out = FrontendPagesActions::pageApproveBulk('approve');
                break;
            case 'disapprove_page':
                $out = FrontendPagesActions::pageApproveBulk('disapprove');
                break;
            case 'send':
                $out = CloudAnalysisActions::bulkSendFileForAnalysis($ids);
                break;
            case 'check_analysis_status':
                $out = ScannerAjaxEndpoints::checkFilesAnalysisStatus(true, $ids);
                break;
            case 'cure':
                //this method has own ajax handler
                Cure::cureSelectedAction();
                break;
            case 'restore':
                //this method has own ajax handler
                Cure::restoreSelectedAction();
                break;
            case 'delete_from_analysis_log':
                $out = ScannerAjaxEndpoints::deleteFileFromAnalysisLog(true);
                break;
            default:
                wp_send_json(array('error' => 'UNKNOWN ACTION'));
        }

        if ( isset($out['error'], $out['error_detail']) ) {
            $out['error_comment'] = '';
            foreach ( $out['error_detail'] as $error_detail ) {
                $out['error_comment'] .= 'File path: ' . $error_detail['file_path'] . ' Error: ' . $error_detail['error'] . '<br>';
            }
        }

        wp_send_json($out);
    }

    public static function spbcCheckFileExist($direct_id = null)
    {
        global $wpdb;

        $root_path = spbc_get_root_path();
        $file_id = !empty($direct_id)
            ? $direct_id
            : Post::get('id', null, 'word');
        $file_id = preg_match('@[a-zA-Z0-9]{32}@', $file_id)
            ? $file_id
            : null;

        $sql = 'SELECT fast_hash, path, source_type, source, source_status, version, mtime,
        weak_spots, full_hash, real_full_hash, status, checked_signatures, checked_heuristic, q_path
        FROM ' . SPBC_TBL_SCAN_FILES . '
        WHERE fast_hash = %s
        LIMIT %d';

        $sql_result = $file_id !== null ? $wpdb->get_results($wpdb->prepare($sql, $file_id, 1), ARRAY_A) : array();

        if (is_array($sql_result) && !empty($sql_result[0])) {
            $file_info  = $sql_result[0];
            $file_path = $root_path . $file_info['path'];

            if ($file_info['status'] === 'QUARANTINED') {
                $file_path = $file_info['q_path'];
            }

            if (!file_exists($file_path)) {
                // Try to remove Cure record
                $cure_log = new CureLog();
                $cure_log->deleteCureLogAndBackupRecords($file_id);

                $res = ScanResultsTableActions::removeFileFromTable($file_id);

                if ($res === false) {
                    return array(
                        'error' => __('File not exists and must be removed from log, but something went wrong.', 'security-malware-firewall'),
                        'error_type' => 'FILE_NOT_EXISTS_DB_ERROR'
                    );
                }

                return array(
                    'error' => __("The file doesn't exist and will be deleted from the log.", 'security-malware-firewall'),
                    'error_type' => 'FILE_NOT_EXISTS'
                );
            }
        }

        return false;
    }

    public static function ajaxRowActionHandler()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $action = Post::getString('add_action');
        $is_frontend_malware_action = $action === 'disapprove_page' || $action === 'approve_page';

        if ( $action !== 'restore' && ! $is_frontend_malware_action &&
            strpos($action, 'oscron_task') === false &&
            strpos($action, 'delete-trigger') === false
        ) {
            $check_file_exist_result = self::spbcCheckFileExist();

            if (isset($check_file_exist_result['error'])) {
                $out = array(
                    'html'       => '<div class="spbc-popup-msg popup--red">'
                                    . $check_file_exist_result['error']
                                    . '</div>',
                    'success'    => true,
                    'color'      => 'black',
                    'background' => 'rgba(110, 110, 240, 0.7)',
                );

                wp_send_json($out);
            }
        }

        try {
            switch ( $action ) {
                case 'approve':
                    self::ajaxRowActionHandlerApprove();
                    break;
                case 'disapprove':
                    self::ajaxRowActionHandlerDisapprove();
                    break;
                case 'disapprove_page':
                    self::ajaxRowActionHandlerPageApprovementProcess('disapprove');
                    break;
                case 'approve_page':
                    self::ajaxRowActionHandlerPageApprovementProcess('approve');
                    break;
                case 'delete':
                    self::ajaxRowActionHandlerDelete();
                    break;
                case 'delete_quarantine':
                    self::ajaxRowActionHandlerDeleteQuarantine();
                    break;
                case 'replace':
                    self::ajaxRowActionHandlerReplace();
                    break;
                case 'send':
                    self::ajaxRowActionHandlerSend();
                    break;
                case 'quarantine':
                    self::ajaxRowActionHandlerQuarantine();
                    break;
                case 'restore':
                    self::ajaxRowActionHandlerQuarantineRestore();
                    break;
                case 'download':
                    self::ajaxRowActionHandlerDownload();
                    break;
                case 'check_analysis_status':
                    self::ajaxRowActionHandlerCheckAnalysisStatus();
                    break;
                case 'copy_file_info':
                    self::ajaxRowActionHandlerCopyFileInfo();
                    break;
                case 'disable_oscron_task':
                    self::ajaxRowActionHandlerDisableOSCronTask();
                    break;
                case 'enable_oscron_task':
                    self::ajaxRowActionHandlerApproveOSCronTask();
                    break;
                case 'delete-trigger':
                    self::ajaxRowActionHandlerDeleteTrigger();
                    break;
                default:
                    wp_send_json(array('temp_html' => '<div class="spbc-popup-msg popup--red">UNKNOWN ACTION</div>'));
            }
        } catch (\Exception $e) {
            wp_send_json(array('temp_html' => '<div class="spbc-popup-msg popup--red">' . esc_html($e->getMessage()) . '</div>'));
        }
    }

    public static function ajaxRowActionHandlerApproveOSCronTask()
    {
        global $spbc;
        $result = OSCronController::enableTask(Post::get('id', null, 'word'));
        if (true === $result) {
            $out = array(
                'html'              => '<div class="spbc-popup-msg popup--green">'
                    . OSCronLocale::getInstance()->controller__task_approved
                    . '</div>',
                'success'           => true,
                'color'             => 'black',
                'background'        => 'rgba(110, 240, 110, 0.7)',
            );
            wp_send_json($out);
        } else {
            wp_send_json_error(esc_html($result));
        }
    }

    public static function ajaxRowActionHandlerDeleteTrigger()
    {
        global $spbc;
        $result = DBTriggerService::deleteTrigger(Post::get('id', null, 'word'));
        if ($result) {
            $out = array(
                'html' => '<div class="spbc-popup-msg popup--red">'
                    . __('Trigger has been deleted.', 'security-malware-firewall')
                    . '</div>',
            );
            wp_send_json($out);
        } else {
            wp_send_json_error(esc_html((string)$result));
        }
    }

    public static function ajaxRowActionHandlerDisableOSCronTask()
    {
        global $spbc;
        $result = OSCronController::disableTask(Post::get('id', null, 'word'));
        if (true === $result) {
            $out = array(
                'html'              => '<div class="spbc-popup-msg popup--red">'
                    . OSCronLocale::getInstance()->controller__task_disabled
                    . '</div>',
                'success'           => true,
                'color'             => 'black',
                'background'        => 'rgba(240, 110, 110, 0.7)',
            );
            wp_send_json($out);
        } else {
            wp_send_json_error(esc_html($result));
        }
    }

    public static function ajaxRowActionHandlerApprove()
    {
        $out = ScanResultsTableActions::approveFile(Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'              => '<div class="spbc-popup-msg popup--green">'
                                       . __('File has been approved.', 'security-malware-firewall')
                                       . '</div>',
                'success'           => true,
                'color'             => 'black',
                'background'        => 'rgba(110, 240, 110, 0.7)',
                'original_response' => $out,
            );
        }

        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerDisapprove()
    {
        $out = ScannerAjaxEndpoints::disapproveFile(true, Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'       => '<div class="spbc-popup-msg popup--red">'
                                . __('File has been disapproved.', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(240, 110, 110, 0.7)',
            );
        }

        wp_send_json($out);
    }

    /**
     * Ajax wrapper to approve or disapprove a page.
     * @params string $action  Could be 'approve' or 'disapprove'
     * @throws \Exception if internal handler failed
     */
    public static function ajaxRowActionHandlerPageApprovementProcess($action)
    {
        // handle page, throws exception on fail
        FrontendPagesActions::pageApprove($action);
        //success
        $out = array(
            'html'       => '<div class="spbc-popup-msg popup--red">'
                . __('Page has been processed.', 'security-malware-firewall')
                . '</div>',
            'success'    => true,
            'color'      => 'black',
            'background' => 'rgba(240, 110, 110, 0.7)',
        );
        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerDelete()
    {
        $out = FileSystemActions::deleteFile(Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'       => '<div class="spbc-popup-msg popup--red">'
                                . __('File has been deleted.', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(240, 110, 110, 0.7)',
            );
        }

        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerDeleteQuarantine()
    {
        $out = ScannerAjaxEndpoints::deleteFromQuarantine(true, Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'       => '<div class="spbc-popup-msg popup--red">'
                                . __('File has been deleted from quarantine.', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(240, 110, 110, 0.7)',
            );
        }

        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerReplace()
    {
        $out = ScannerAjaxEndpoints::replaceFileWithOriginal(true, Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'       => '<div class="spbc-popup-msg popup--green">'
                                . __('File has been replaced.', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(240, 110, 110, 0.7)',
            );
        }

        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerSend()
    {
        $file_fast_hash = Post::getString('id');
        $out = SendFileToCloudService::sendFile($file_fast_hash);

        if ( empty($out['error']) ) {
            $args  = spbc_list_table__get_args_by_type('analysis_log');
            $table = new ListTable($args);
            $table->getData();
            $file_info_from_scan_results_table = $table->getTemplate();

            $out = array(
                'html'  => '<div class="spbc-popup-msg popup--green">'
                                . __('Thank you! We will check the file(s).', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(110, 110, 240, 0.7)',
            );

            if ($file_info_from_scan_results_table) {
                $out['updated_template'] = $file_info_from_scan_results_table;
                $out['updated_template_type'] = 'analysis_log';
            }
        }

        if ( isset($out['error'], $out['error_type']) && $out['error_type'] == 'FILE_NOT_EXISTS' ) {
            $args  = spbc_list_table__get_args_by_type('analysis_log');
            $table = new ListTable($args);
            $table->getData();
            $file_info_from_scan_results_table = $table->getTemplate();

            $out = array(
                'html'  => '<div class="spbc-popup-msg popup--red">'
                                . $out['error']
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(110, 110, 240, 0.7)',
            );

            if ($file_info_from_scan_results_table) {
                $out['updated_template'] = $file_info_from_scan_results_table;
                $out['updated_template_type'] = 'analysis_log';
            }
        }

        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerQuarantine()
    {
        $out = ScannerAjaxEndpoints::doQuarantineFile(true, Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'       => '<div class="spbc-popup-msg popup--red">'
                                . __('File has been put into a corner.', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(110, 110, 240, 0.7)',
            );
        }

        wp_send_json($out);
    }

    public static function ajaxRowActionHandlerQuarantineRestore()
    {
        $out = ScannerAjaxEndpoints::restoreFromQuarantine(true, Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'       => '<div class="spbc-popup-msg popup--green">'
                                . __('File has been restored.', 'security-malware-firewall')
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(110, 110, 240, 0.7)',
            );
        }

        wp_send_json($out);
    }

    private static function ajaxRowActionHandlerDownload()
    {
        //@ToDo NOT IMPLEMENTED YET
    }

    public static function ajaxRowActionHandlerCheckAnalysisStatus()
    {
        $out = ScannerAjaxEndpoints::checkFilesAnalysisStatus(true, Post::getString('id'));

        if ( empty($out['error']) ) {
            $out = array(
                'html'  => '<div class="spbc-popup-msg popup--green">'
                                . __(
                                    'Status checked and update successfully. Please, refresh the page to see the result.',
                                    'security-malware-firewall'
                                )
                                . '</div>',
                'success'    => true,
                'color'      => 'black',
                'background' => 'rgba(110, 110, 240, 0.7)',
            );
        }

        wp_send_json($out);
    }

    /**
     * Apply user filter to SQL args if 'users' parameter is present in POST
     *
     * @param array $args Table arguments with SQL configuration
     * @return array Modified args with user filter applied
     */
    private static function applyUserFilter(array $args)
    {
        global $wpdb;

        $user_filter = Post::getString('users');
        if ( ! empty($user_filter)) {
            $user_filter = sanitize_text_field($user_filter);
            $user_where  = $wpdb->prepare('user_login = %s', $user_filter);

            if ( ! empty($args['sql']['where']) ) {
                 $args['sql']['where'] .= ' AND ' . $user_where;
            } else {
                $args['sql']['where'] = ' WHERE ' . $user_where;
            }
        }
        return $args;
    }

    public static function ajaxPaginationHandler()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $type                       = Post::getString('type');
        $args                       = spbc_list_table__get_args_by_type($type);
        $args['pagination']['page'] = Post::getInt('page');
        $args                       = self::applyUserFilter($args);

        $table                      = new ListTable($args);
        $table->getData();
        $table->display();

        die();
    }

    public static function ajaxSortHandler()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $type             = Post::getString('type');
        $order_by         = Post::getString('order_by');
        $order            = Post::getString('order');
        $args             = spbc_list_table__get_args_by_type($type);
        $args             = self::applyUserFilter($args);

        // Sanitize order direction - only allow 'asc' or 'desc'
        $order = in_array(strtolower($order), array('asc', 'desc'), true) ? strtolower($order) : 'asc';

        // Validate order_by column against allowed sortable columns
        $allowed_sortable = ! empty($args['sortable']) ? $args['sortable'] : array();
        if ( in_array($order_by, $allowed_sortable, true) ) {
            $args['order_by'] = array((string)$order_by => $order);
        }

        $table            = new ListTable($args);
        $table->getData();
        $table->display();

        die();
    }

    /** @psalm-suppress PossiblyUnusedMethod */
    public static function ajaxSwitchTable()
    {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');

        $type  = Post::getString('type');
        $args  = spbc_list_table__get_args_by_type($type);
        $args  = self::applyUserFilter($args);

        $table = new ListTable($args);
        if ($table->type == 'links') {
            $table->sql["limit"] = false;
        }
        $table->getData();
        $table->display();

        die();
    }

    /**
     * @param $arr
     *
     * @return mixed
     * @psalm-suppress PossiblyUnusedMethod
     */
    public static function stripslashesArray($arr)
    {
        foreach ( $arr as $_key => &$value ) {
            if ( is_string($value) ) {
                $value = stripslashes($value);
            } elseif ( is_array($value) ) {
                $value = self::stripslashesArray($value);
            }
        }
        unset($value);

        return $arr;
    }

    /**
     * Output JSON with pscan_file_id got by file_id(fast_hash)
     * @return void
     *
     */
    public static function ajaxRowActionHandlerCopyFileInfo()
    {
        $file_id = Post::getString('id');
        if ( !empty($file_id) ) {
            $file_info = ScanResultsTableActions::getFileByID($file_id);
            $file_info = array(
                $file_id => array(
                    'path'              => !empty($file_info['path']) ? urlencode($file_info['path']) : null,
                    'full_hash'         => !empty($file_info['full_hash']) ? $file_info['full_hash'] : null,
                    'real_full_hash'    => !empty($file_info['real_full_hash']) ? $file_info['real_full_hash'] : null,
                    'pscan_file_id'     => !empty($file_info['pscan_file_id']) ? $file_info['pscan_file_id'] : null,
                    'pscan_status' => !empty($file_info['pscan_status']) ? $file_info['pscan_status'] : null,
                    'last_sent'         => !empty($file_info['last_sent']) ? $file_info['last_sent'] : null,
                )
            );
            $file_info = json_encode($file_info);

            if ( $file_info !== false ) {
                $file_info = array ('file_info' => $file_info);
                wp_send_json_success($file_info);
            }
        }
        wp_send_json_error(array('error' => 'Can not get file info.'));
    }
}
