<?php
/*
  Plugin Name: WP Reset PRO
  Plugin URI: https://wpreset.com/
  Description: Easily undo any change on the site by restoring a snapshot, or reset the entire site or any of its parts to the default values.
  Version: 6.19
  Author: WebFactory Ltd
  Author URI: https://www.webfactoryltd.com/
  Text Domain: wp-reset

  Copyright 2015 - 2025  Web factory Ltd  (email: wpreset@webfactoryltd.com)

  This program is NOT free software.

  ██╗    ██╗███████╗██████╗ ███████╗ █████╗  ██████╗████████╗ ██████╗ ██████╗ ██╗   ██╗
  ██║    ██║██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗╚██╗ ██╔╝
  ██║ █╗ ██║█████╗  ██████╔╝█████╗  ███████║██║        ██║   ██║   ██║██████╔╝ ╚████╔╝
  ██║███╗██║██╔══╝  ██╔══██╗██╔══╝  ██╔══██║██║        ██║   ██║   ██║██╔══██╗  ╚██╔╝
  ╚███╔███╔╝███████╗██████╔╝██║     ██║  ██║╚██████╗   ██║   ╚██████╔╝██║  ██║   ██║
   ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═╝     ╚═╝  ╚═╝ ╚═════╝   ╚═╝    ╚═════╝ ╚═╝  ╚═╝   ╚═╝
*/

// include only file
if (!defined('ABSPATH')) {
    die('Do not open this file directly.');
}


define('WP_RESET_FILE', __FILE__);

require_once dirname(__FILE__) . '/wp-reset-utility.php';
require_once dirname(__FILE__) . '/wp-reset-tools.php';
// require_once dirname(__FILE__) . '/wp-reset-collections.php';
require_once dirname(__FILE__) . '/wp-reset-cloud.php';
require_once dirname(__FILE__) . '/wf-licensing.php';

class WP_Reset_Collections {
    function get_collections() { return array(); }
    function get_collections_keyed() { return array(); }
    function tab_collections() { echo '<div class="card"><p class="mb0">Collections feature is disabled.</p></div>'; }
    function reload_collections() { return true; }
    function add_new_collection($data) { return new WP_Error(1, 'Collections disabled'); }
    function get_collection_card($data) { return ''; }
    function add_collection_item($data) { return new WP_Error(1, 'Collections disabled'); }
    function get_collection_item($data) { return ''; }
    function edit_collection_item($data) { return new WP_Error(1, 'Collections disabled'); }
    function delete_collection($data) { return new WP_Error(1, 'Collections disabled'); }
    function delete_collection_item($data) { return new WP_Error(1, 'Collections disabled'); }
    function edit_collection_name($data) { return new WP_Error(1, 'Collections disabled'); }
    function get_collection_item_details($id, $item_id) { return false; }
    function license_activation($slug, $key) { return false; }
    function check_license_activation($slug) { return false; }
}
global $wp_reset_collections;
$wp_reset_collections = new WP_Reset_Collections();

// load WP-CLI commands, if needed
if (defined('WP_CLI') && WP_CLI) {
    require_once dirname(__FILE__) . '/wp-reset-cli.php';
}
add_filter('pre_http_request', function($preempt, $parsed_args, $url) {
    if (strpos($url, 'dashboard.wpreset.com') !== false) {
        if (strpos($url, '/api/v1/') !== false) {
            if (strpos($url, 'action=deactivate_license') !== false) {
                return array('headers' => array(), 'body' => '{"success":true,"data":"License deactivated."}', 'response' => array('code' => 200, 'message' => 'OK'));
            }
            if (strpos($url, 'action=validate_license') !== false) {
                return array('headers' => array(), 'body' => '{"success":true,"data":{"license_key":"B5E0B5F8-DD8689E6-ACA49DD6-E6E1A930","valid_until":"2075-01-01","name":"WP Reset PRO Tier 3","meta":{"white_label":true,"wpr_cloud":false,"white_label_support_text":""},"error":""}}', 'response' => array('code' => 200, 'message' => 'OK'));
            }
        }
    }
    return $preempt;
}, 10, 3);

add_action('wp_ajax_wf_licensing_wpr_validate', function() {
    wp_send_json_success(true);
}, 1);

add_action('init', function() {
    $license = array('license_key' => 'B5E0B5F8-DD8689E6-ACA49DD6-E6E1A930', 'error' => '', 'valid_until' => '2075-01-01', 'last_check' => time(), 'name' => 'WP Reset PRO Tier 3', 'meta' => array('white_label' => true, 'wpr_cloud' => false, 'white_label_support_text' => ''), 'access_key' => wp_generate_password(32, false));
    if (!get_option('wf_licensing_wpr') || get_option('wf_licensing_wpr')['license_key'] != 'B5E0B5F8-DD8689E6-ACA49DD6-E6E1A930') {
        update_option('wf_licensing_wpr', $license);
    }
});

class WP_Reset
{
    protected static $instance = null;
    public $version = 0;
    public $er_version = 1.37;
    public $plugin_url = '';
    public $plugin_dir = '';
    public $licensing_servers = array('https://dashboard.wpreset.com/api/');
    public $snapshots_folder = 'wp-reset-snapshots-export';
    public $autosnapshots_folder = 'wp-reset-autosnapshots';
    protected $options = array();
    public $log = false;
    private $wp_config_constants = false;
    private $delete_count = 0;
    private $staging_totals = array('files', 'bytes');
    public $core_tables = array('commentmeta', 'comments', 'links', 'options', 'postmeta', 'posts', 'term_relationships', 'term_taxonomy', 'termmeta', 'terms', 'usermeta', 'users');
    public $cloud_services = array('dropbox' => 'Dropbox', 'gdrive' => 'Google Drive', 'pcloud' => 'pCloud', 'pcloudeu' => 'pCloud EU', 'icedrive' => 'Icedrive');

    /**
     * Creates a new WP_Reset object and implements singleton
     *
     * @return WP_Reset
     */
    static function getInstance()
    {
        if (!is_a(self::$instance, 'WP_Reset')) {
            self::$instance = new WP_Reset();
        }

        return self::$instance;
    } // getInstance


    /**
     * Initialize properties, hook to filters and actions
     *
     * @return null
     */
    private function __construct()
    {
        $this->version = $this->get_plugin_version();
        $this->plugin_dir = plugin_dir_path(__FILE__);
        $this->plugin_url = plugin_dir_url(__FILE__);
        $this->load_options();

        add_action('admin_menu', array($this, 'admin_menu'));
        add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
        add_action('wp_ajax_wp_reset_dismiss_notice', array($this, 'ajax_dismiss_notice'));
        add_action('wp_ajax_wp_reset_run_tool', array($this, 'ajax_run_tool'));
        add_action('wp_ajax_wp_reset_submit_survey', array($this, 'ajax_submit_survey'));
        add_action('admin_print_scripts', array($this, 'remove_admin_notices'));
        add_action('admin_footer', array($this, 'admin_footer'));
        add_action('admin_notices', array($this, 'notice_successful_reset'));

        add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($this, 'plugin_action_links'));
        add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
        add_filter('admin_footer_text', array($this, 'admin_footer_text'));
        add_action('admin_action_wpr_clear_log', array($this, 'clear_log'));
        add_action('admin_action_wpr_delete_temporary_files', array($this, 'delete_temporary_files'));
        add_action('admin_action_wpr_delete_snapshot_tables', array($this, 'delete_snapshot_tables'));
        add_action('wp_before_admin_bar_render', array($this, 'admin_bar'));
        add_filter('mainwp_child_extra_execution', array($this, 'mainwp_child_extra_execution'), 10, 2);

        add_filter('install_plugins_table_api_args_featured', array($this, 'featured_plugins_tab'));

        add_action('plugins_loaded', array($this, 'admin_actions'));
        add_action('admin_head-tools_page_wp-reset', array($this, 'rebrand_css'));

        $this->core_tables = array_map(function ($tbl) {
            global $wpdb;
            return $wpdb->prefix . $tbl;
        }, $this->core_tables);
    } // __construct


    /**
     * Get plugin version from file header
     *
     * @return string
     */
    function get_plugin_version()
    {
        $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');

        return $plugin_data['version'];
    } // get_plugin_version


    /**
     * Actions to run on load, but init would be too early as not all classes are initialized
     *
     * @return null
     */
    function admin_actions()
    {
        global $wp_reset_cloud, $wp_reset_js_notice, $wp_reset_licensing;

        $options = $this->get_options();

        $wp_reset_licensing = new WF_Licensing_WPR(array(
            'prefix' => 'wpr',
            'licensing_servers' => $this->licensing_servers,
            'version' => $this->version,
            'plugin_file' => __FILE__,
            'skip_hooks' => false,
            'debug' => false,
            'js_folder' => trailingslashit(plugin_dir_url(__FILE__)) . 'js/'
        ));

        if ($this->get_rebranding() !== false) {
            $this->cloud_services = array();
        }

        $wp_reset_cloud = new WP_Reset_Cloud();


        add_filter('wf_licensing_license_formatted_wpr', function ($return) {
            return str_replace('Wpr', 'WP Reset', $return);
        });

        add_filter('wf_licensing_wpr_query_server_data', function ($lcdata) {
            global $wp_reset, $wp_reset_cloud;
            $snapshots = $wp_reset->get_snapshots();
            $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();
            $snapshot_stats = array();
            $snapshot_stats = array();
            for ($i = 45; $i >= 0; $i--) {
                $snapshot_stats[gmdate("Y-m-d", strtotime('-' . $i . ' days'))] = array('m' => 0, 'a' => 0, 'cm' => 0, 'ca' => 0);
            }

            foreach ($snapshots as $snapshot) {
                $snapshot_date = gmdate('Y-m-d', strtotime($snapshot['timestamp']));
                if (!array_key_exists($snapshot_date, $snapshot_stats)) {
                    continue;
                }

                if (array_key_exists('auto', $snapshot) && $snapshot['auto'] == true) {
                    $snapshot_stats[$snapshot_date]['a']++;
                } else {
                    $snapshot_stats[$snapshot_date]['m']++;
                }
            }

            foreach ($cloud_snapshots as $snapshot) {
                $snapshot_date = gmdate('Y-m-d', strtotime($snapshot['timestamp']));
                if (!array_key_exists($snapshot_date, $snapshot_stats)) {
                    continue;
                }
                if (array_key_exists('auto', $snapshot) && $snapshot['auto'] == true) {
                    $snapshot_stats[$snapshot_date]['ca']++;
                } else {
                    $snapshot_stats[$snapshot_date]['cm']++;
                }
            }

            $recovery_url = $wp_reset->wpr_recovery_path(true);
            if ($recovery_url !== false) {
                $lcdata['meta']['ers_url'] = $recovery_url;
            }
            $lcdata['meta']['stats'] = $snapshot_stats;
            return $lcdata;
        });

        $this->update_license_storage();

        add_action('wf_licensing_wprvalidate_ajax', function () {
            global $wp_reset_licensing, $wp_reset_collections;
            if ($wp_reset_licensing->is_active()) {
                $wp_reset_collections->get_collections(true);
            }
        });

        if ($wp_reset_licensing->is_active('wpr_cloud')) {
            $plugin_name = $this->get_rebranding('name');
            if ($plugin_name === false) {
                $plugin_name = 'WP Reset';
            }
            $this->cloud_services = array_merge(array('wpreset' => $plugin_name . ' Cloud'), $this->cloud_services);
        } else {
            if ($options['cloud_service'] == 'wpreset') {
                $options['cloud_service'] = 'none';
                $this->update_options('options', $options);
            }
        }

        //phpcs:ignore nonce not needed as link can be opened directly
        if (isset($_GET['wpr_wl']) && current_user_can('manage_options')) { //phpcs:ignore
            if ($_GET['wpr_wl'] == 'true') { //phpcs:ignore
                $options['whitelabel'] = true;
            } else {
                $options['whitelabel'] = false;
            }
            $this->update_options('options', $options);
        }

        if (isset($_GET['wpr_debug']) && current_user_can('manage_options')) { //phpcs:ignore
            if ($_GET['wpr_debug'] == 'true') { //phpcs:ignore
                $options['debug'] = true;
            } else {
                $options['debug'] = false;
            }
            $this->update_options('options', $options);
        }

        if (isset($_GET['wpr_wp_debug']) && current_user_can('manage_options')) { //phpcs:ignore
            if ($_GET['wpr_wp_debug'] == 'true') { //phpcs:ignore
                $this->wpconfig_read_constants();
                $this->wpconfig_toggle_debug(true);
            } else {
                $this->wpconfig_toggle_debug(false);
            }
        }

        if (isset($_GET['authorize_cloud']) && current_user_can('manage_options')) { //phpcs:ignore
            $result = $wp_reset_cloud->cloud_authorize_get_token($_GET['authorize_cloud'], $_GET['code']); //phpcs:ignore
            switch ($_GET['authorize_cloud']) { //phpcs:ignore
                case 'dropbox':
                    $cloud_name = 'Dropbox';
                    break;
                case 'gdrive':
                    $cloud_name = 'Google Drive';
                    break;
                case 'pcloud':
                    $cloud_name = 'pCloud';
                    break;
                case 'pcloudeu':
                    $cloud_name = 'pCloud EU';
                    break;
                case 'icedrive':
                    $cloud_name = 'Icedrive';
                    break;
            }

            if (is_wp_error($result)) {
                $wp_reset_js_notice = array('type' => 'error', 'text' => $result->get_error_message());
            } else {
                $wp_reset_js_notice = array('type' => 'success', 'text' => $cloud_name . ' connected successfully!');
            }
        }

        $this->prune_autosnapshots();
        $this->prune_cloud_autosnapshots();
    } // admin_actions


    function update_license_storage()
    {
        global $wp_reset_licensing;

        $new = array();

        // nothing to update
        if (!array_key_exists('license', $this->options)) {
            return false;
        }

        $new['license_key'] = $this->options['license']['license_key'];
        if ($this->options['license']['license_active']) {
            $new['error'] = '';
        } else {
            $new['error'] = 'Unknown error. Please reactivate the license.';
        }
        $new['valid_until'] = $this->options['license']['license_expires'];
        $new['last_check'] = time();
        $new['name'] = $this->options['license']['license_type'];
        $new['meta'] = array();

        if ($wp_reset_licensing->update_license($new)) {
            unset($this->options['license']);
            update_option('wp-reset', $this->options);

            return true;
        }

        return false;
    } // update_license_storage


    /**
     * Load and prepare the options array. If needed create a new DB entry.
     *
     * @return array
     */
    private function load_options()
    {
        $options = get_option('wp-reset', array('options' => array()));
        $change = false;

        if (empty($options['meta'])) {
            $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'reset_count' => 0);
            $change = true;
        }

        if (!isset($options['dismissed_notices']) || !is_array($options['dismissed_notices'])) {
            $options['dismissed_notices'] = array();
            $change = true;
        }

        if (!isset($options['autouploader']) || !is_array($options['autouploader'])) {
            $options['autouploader'] = array();
            $change = true;
        }

        $def_options = array('tools_snapshots' => false, 'events_snapshots' => false, 'snapshots_autoupload' => false, 'autosnapshots_autoupload' => false, 'snapshots_upload_delete' => false, 'scheduled_snapshots' => false, 'prune_snapshots' => false, 'prune_snapshots_details' => 'days-5', 'prune_cloud_snapshots' => false, 'prune_cloud_snapshots_details' => 'days-5', 'adminbar_snapshots' => true, 'optimize_tables' => false, 'snapshots_size_alert' => 1000, 'throttle_ajax' => false, 'fix_datetime' => false, 'alternate_db_connection' => false, 'ajax_snapshots_export' => false, 'cloud_snapshots' => false, 'onboarding_done' => false, 'whitelabel' => false, 'debug' => false, 'cloud_service' => 'none', 'cloud_data' => array('dropbox' => false, 'gdrive' => false, 'icedrive' => false));

        if (!array_key_exists('cloud_data', $options['options'])) {
            $options['options']['cloud_data'] = array('dropbox' => false, 'gdrive' => false, 'icedrive' => false);
        }

        if (!array_key_exists('icedrive', $options['options']['cloud_data'])) {
            $options['options']['cloud_data']['icedrive'] = false;
        }

        if (!array_key_exists('alternate_db_connection', $options['options'])) {
            $options['options']['alternate_db_connection'] = false;
        }

        if (sizeof($options['options']) < sizeof($def_options)) {
            $options['options'] = array_merge($def_options, (array) $options['options']);
            $change = true;
        }

        if ($change) {
            update_option('wp-reset', $options, true);
        }

        $this->options = $options;
        return $options;
    } // load_options


    /**
     * Log WP Reset event
     *
     * @return array
     */
    function log($type = 'success', $message = false)
    {
        if (!$this->log) {
            $this->log = get_option('wp-reset-log', array());
        }

        if ($this->log !== false && count($this->log) > 5000) {
            $this->log = array_slice($this->log, -5000, 5000, true);
        }

        if ($message !== false) {
            $this->log[] = array('time' => time(), 'type' => $type, 'message' => $message);
        }
        update_option('wp-reset-log', $this->log);
    }


    /**
     * Get log
     *
     * @return array
     */
    function get_log()
    {
        return get_option('wp-reset-log', array());
    }


    /**
     * Helper function for adding plugins to featured list
     *
     * @return array
     */
    function featured_plugins_tab($args)
    {
        add_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);
        return $args;
    } // featured_plugins_tab


    /**
     * Add plugins to featured plugins list
     *
     * @return object
     */
    function plugins_api_result($res, $action, $args)
    {
        remove_filter('plugins_api_result', array($this, 'plugins_api_result'), 10, 3);

        $res = $this->add_plugin_featured('eps-301-redirects', $res);
        $res = $this->add_plugin_featured('wp-force-ssl', $res);

        return $res;
    } // plugins_api_result


    /**
     * Add single plugin to featured list
     *
     * @return object
     */
    function add_plugin_featured($plugin_slug, $res)
    {
        // check if plugin is already on the list
        if (!empty($res->plugins) && is_array($res->plugins)) {
            foreach ($res->plugins as $plugin) {
                if (is_object($plugin) && !empty($plugin->slug) && $plugin->slug == $plugin_slug) {
                    return $res;
                }
            } // foreach
        }

        $plugin_info = get_transient('wf-plugin-info-' . $plugin_slug);

        if (!$plugin_info) {
            $plugin_info = plugins_api('plugin_information', array(
                'slug' => $plugin_slug,
                'is_ssl' => is_ssl(),
                'fields' => array(
                    'banners' => true,
                    'reviews' => true,
                    'downloaded' => true,
                    'active_installs' => true,
                    'icons' => true,
                    'short_description' => true,
                ),
            ));
            if (!is_wp_error($plugin_info)) {
                set_transient('wf-plugin-info-' . $plugin_slug, $plugin_info, DAY_IN_SECONDS * 7);
            }
        }

        if (!empty($res->plugins) && is_array($res->plugins) && $plugin_info && is_object($plugin_info)) {
            array_unshift($res->plugins, $plugin_info);
        }

        return $res;
    } // add_plugin_featured


    /**
     * Get log
     *
     * @return array
     */
    function get_autouploader()
    {
        $this->load_options();
        return $this->options['autouploader'];
    }


    /**
     * Get formatted snapshot name for log entries
     *
     * @return array
     */
    function log_format_snapshot_name($uid)
    {
        return '"' . $uid . '"';
    }


    /**
     * Clear log
     *
     * @return array
     */
    function clear_log()
    {
        check_admin_referer('wpr_clear_log');
        if (!current_user_can('manage_options')) {
            return false;
        }

        update_option('wp-reset-log', array());

        if (!empty($_GET['redirect'])) {
            wp_safe_redirect(sanitize_url(wp_unslash($_GET['redirect'])));
        }

        return true;
    }

    /**
     * Get meta part of plugin options
     *
     * @return array
     */
    function get_meta()
    {
        return $this->options['meta'];
    } // get_meta


    /**
     * Get license part of plugin options
     *
     * @return array
     */
    function get_license()
    {
        global $wp_reset_licensing;
        return $wp_reset_licensing->get_license();
    } // get_license


    /**
     * Get all dismissed notices, or check for one specific notice
     *
     * @param string  $notice_name  Optional. Check if specified notice is dismissed.
     *
     * @return bool|array
     */
    function get_dismissed_notices($notice_name = '')
    {
        $notices = $this->options['dismissed_notices'];

        if (empty($notice_name)) {
            return $notices;
        } else {
            if (empty($notices[$notice_name])) {
                return false;
            } else {
                return true;
            }
        }
    } // get_dismissed_notices


    /**
     * Get options part of plugin options
     *
     * @return array
     */
    function get_options()
    {
        return $this->options['options'];
    } // get_options


    /**
     * Get all plugin options
     *
     * @return array
     */
    function get_all_options()
    {
        return $this->options;
    } // get_all_options


    /**
     * Update specified plugin options key
     *
     * @param string  $key   Data to save.
     * @param string  $data  Option key.
     *
     * @return bool
     */
    function update_options($key, $data)
    {
        if (false === in_array($key, array('meta', 'license', 'dismissed_notices', 'options', 'autouploader'))) {
            user_error('Unknown options key.', E_USER_ERROR);
            return false;
        }

        $this->options[$key] = $data;
        $tmp = update_option('wp-reset', $this->options);

        return $tmp;
    } // set_options


    /**
     * Add plugin menu entry under Tools menu
     *
     * @return null
     */
    function admin_menu()
    {
        $page_title = $this->get_rebranding('name');
        if ($page_title === false || empty($page_title)) {
            $page_title = 'WP Reset PRO';
        }

        $menu_title = $this->get_rebranding('short_name');
        if ($menu_title === false || empty($menu_title)) {
            $menu_title = 'WP Reset PRO';
        }

        add_management_page($page_title, $menu_title, 'administrator', 'wp-reset', array($this, 'plugin_page'));
    } // admin_menu


    /**
     * Dismiss notice via AJAX call
     *
     * @return null
     */
    function ajax_dismiss_notice()
    {
        check_ajax_referer('wp-reset_dismiss_notice');

        if (!current_user_can('administrator')) {
            wp_send_json_error(esc_html__('You are not allowed to run this action.', 'wp-reset'));
        }

        $notice_name = trim(sanitize_text_field(wp_unslash($_GET['notice_name'] ?? '')));
        if (!$this->dismiss_notice($notice_name)) {
            wp_send_json_error(esc_html__('Notice is already dismissed.', 'wp-reset'));
        } else {
            wp_send_json_success();
        }
    } // ajax_dismiss_notice


    /**
     * Dismiss notice by adding it to dismissed_notices options array
     *
     * @param string  $notice_name  Notice to dismiss.
     *
     * @return bool
     */
    function dismiss_notice($notice_name)
    {
        if ($this->get_dismissed_notices($notice_name)) {
            return false;
        } else {
            $notices = $this->get_dismissed_notices();
            $notices[$notice_name] = true;
            $this->update_options('dismissed_notices', $notices);
            return true;
        }
    } // dismiss_notice


    /**
     * Returns all WP pointers
     *
     * @return array
     */
    function get_pointers()
    {
        $pointers = array();

        $plugin_name = $this->get_rebranding('name');
        if ($plugin_name === false) {
            $plugin_name = '<b style="font-weight: 800;">WP Reset PRO</b>';
        }

        $pointers['welcome'] = array('target' => '#menu-tools', 'edge' => 'left', 'align' => 'right', 'content' => 'Thank you for installing the ' . esc_html($plugin_name) . ' plugin!<br>Open <a href="' . admin_url('tools.php?page=wp-reset') . '">Tools - ' . esc_html($plugin_name) . '</a> to access resetting tools and start developing &amp; debugging faster.');

        return $pointers;
    } // get_pointers


    /**
     * Enqueue CSS and JS files
     *
     * @return null
     */
    function admin_enqueue_scripts($hook)
    {
        global $wp_reset_collections, $wp_reset_js_notice, $wp_reset_licensing, $wp_reset_cloud;
        // welcome pointer is shown on all pages except WPR, to admins, until dismissed
        $pointers = $this->get_pointers();
        $dismissed_notices = $this->get_dismissed_notices();
        $license = $wp_reset_licensing->get_license();
        $options = $this->get_options();
        $current_user = wp_get_current_user();

        foreach ($dismissed_notices as $notice_name => $tmp) {
            if ($tmp) {
                unset($pointers[$notice_name]);
            }
        } // foreach

        if (!empty($pointers) && !$this->is_plugin_page() && current_user_can('administrator')) {
            $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-reset_dismiss_notice');

            wp_enqueue_style('wp-pointer');

            wp_enqueue_script('wp-reset-pointers', $this->plugin_url . 'js/wp-reset-pointers.js', array('jquery'), $this->version, true);
            wp_enqueue_script('wp-pointer');
            wp_localize_script('wp-pointer', 'wp_reset_pointers', $pointers);
        }

        if (!$this->is_plugin_page() && !$options['adminbar_snapshots']) {
            return;
        }

        $snapshots = $this->get_snapshots();
        $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();
        if (!is_array($cloud_snapshots)) {
            $cloud_snapshots = array();
        }
        $pending_autoupload_snaphots = false;
        foreach ($snapshots as $uid => $snapshot) {
            if (array_key_exists('autoupload', $snapshot) && $snapshot['autoupload'] == true && !array_key_exists($uid, $cloud_snapshots)) {
                $pending_autoupload_snaphots = true;
            }
        }

        if (!$pending_autoupload_snaphots || !array_key_exists($options['cloud_service'], $this->cloud_services)) {
            $options['autosnapshots_autoupload'] = false;
        }

        $js_localize = array(
            'undocumented_error' => esc_html__('An undocumented error has occurred. Please refresh the page and try again.', 'wp-reset'),
            'documented_error' => esc_html__('An error has occurred.', 'wp-reset'),
            'plugin_name' => esc_html__('WP Reset PRO', 'wp-reset'),
            'is_plugin_page' => (int) $this->is_plugin_page(),
            'current_screen' => $hook,
            'autouploader_key' => 'wpr_autouploader_time_' . preg_replace("/[^a-zA-Z0-9]+/", "", home_url()),
            'whitelabel' => (int) !WP_Reset_Utility::whitelabel_filter(),
            'settings_url' => admin_url('tools.php?page=wp-reset'),
            'icon_url' => $this->plugin_url . 'img/wp-reset-icon.png',
            'invalid_confirmation' => esc_html__('Please type "reset" in the confirmation field.', 'wp-reset'),
            'invalid_confirmation_title' => esc_html__('Invalid confirmation', 'wp-reset'),
            'cancel_button' => esc_html__('Cancel', 'wp-reset'),
            'ok_button' => esc_html__('OK', 'wp-reset'),
            'confirm_title' => esc_html__('Are you sure you want to proceed?', 'wp-reset'),
            'doing_reset' => esc_html__('Resetting in progress. Please wait.', 'wp-reset'),
            'snapshots_autoupload' => $options['snapshots_autoupload'],
            'autosnapshots_autoupload' => $options['autosnapshots_autoupload'],
            'snapshot_success' => esc_html__('Snapshot created', 'wp-reset'),
            'snapshot_wait' => esc_html__('Creating snapshot. Please wait.', 'wp-reset'),
            'snapshot_confirm' => esc_html__('Create snapshot', 'wp-reset'),
            'snapshot_importing' => esc_html__('Importing snapshot. Please wait.', 'wp-reset'),
            'snapshot_imported' => esc_html__('Snapshot Imported.', 'wp-reset'),
            'snapshot_placeholder' => esc_html__('Snapshot name or brief description, ie: before plugin install', 'wp-reset'),
            'snapshot_text' => esc_html__('Enter snapshot name or brief description', 'wp-reset'),
            'snapshot_title' => esc_html__('Create a new snapshot', 'wp-reset'),
            'staging_success' => esc_html__('Staging copy deployed', 'wp-reset'),
            'staging_wait' => esc_html__('Deploying staging copy. Please wait.', 'wp-reset'),
            'staging_confirm' => esc_html__('Delpoy staging copy', 'wp-reset'),
            'staging_placeholder' => esc_html__('Staging copy name or short description, ie: testing plugins for checkout', 'wp-reset'),
            'staging_text' => esc_html__('Enter staging copy name or brief description', 'wp-reset'),
            'staging_title' => esc_html__('Deploy a new staging copy', 'wp-reset'),
            'export_wait' => esc_html__('Exporting snapshot. Please wait.', 'wp-reset'),
            'export_success' => esc_html__('Snapshot exported', 'wp-reset'),
            'import_wait' => esc_html__('Importing snapshot. Please wait.', 'wp-reset'),
            'import_success' => esc_html__('Snapshot imported', 'wp-reset'),
            'snapshots_delete_confirm' => esc_html__('Are you sure you want to delete all your user created and automatic snapshots?', 'wp-reset'),
            'collection_add_success' => esc_html__('New collection added!', 'wp-reset'),
            'collection_add_wait' => esc_html__('Creating collection. Please wait.', 'wp-reset'),
            'collection_add_confirm' => esc_html__('Add new collection', 'wp-reset'),
            'collection_add_placeholder' => esc_html__('Collection name, ie: plugins for client sites', 'wp-reset'),
            'collection_add_text' => esc_html__('Enter collection name or brief description', 'wp-reset'),
            'collection_add_title' => esc_html__('Add new collection', 'wp-reset'),
            'cloud_snapshot_uploading' => esc_html__('Snapshot uploading to cloud', 'wp-reset'),
            'cloud_snapshot_uploaded' => esc_html__('Snapshot uploaded to cloud', 'wp-reset'),
            'cloud_snapshot_downloading' => esc_html__('Downloading snapshot from cloud', 'wp-reset'),
            'cloud_snapshot_downloaded' => esc_html__('Snapshot downloaded from cloud', 'wp-reset'),
            'cloud_snapshot_deleting' => esc_html__('Deleting snapshot from cloud', 'wp-reset'),
            'cloud_snapshot_deleted' => esc_html__('Snapshot deleted from cloud', 'wp-reset'),
            'cloud_snapshots_refresh' => esc_html__('Refreshing cloud snapshots', 'wp-reset'),
            'cloud_snapshots_refreshed' => esc_html__('Snapshots refreshed', 'wp-reset'),
            'max_upload_size' => wp_max_upload_size(),
            'nonce_dismiss_notice' => wp_create_nonce('wp-reset_dismiss_notice'),
            'nonce_run_tool' => wp_create_nonce('wp-reset_run_tool'),
            'nonce_do_reset' => wp_create_nonce('wp-reset_do_reset'),
            'cloud_service' => array_key_exists($options['cloud_service'], $this->cloud_services) ? 1 : 0,
            'rebranding' => $this->get_rebranding() === false ? 0 : $this->get_rebranding()
        );

        if ($hook == 'plugins.php') {
            $rebranding = $this->get_rebranding();
            if (false !== $rebranding) {
                wp_enqueue_script('wp-reset-branding', $this->plugin_url . 'js/branding.js', array('jquery'), $this->version, true);
                wp_localize_script('wp-reset-branding', 'wpr_rebranding', $rebranding);
            }

            $js_localize['space_usage_total'] = 0;
            foreach ($snapshots as $snapshot) {
                if (false === $snapshot['local']) {
                    continue;
                }
                $js_localize['space_usage_total'] += $snapshot['tbl_size'];
            }

            $js_localize['space_usage_total'] += $this->folder_size(trailingslashit(WP_CONTENT_DIR) . 'wp-reset-autosnapshots');
            $js_localize['space_usage_total'] += $this->folder_size(trailingslashit(WP_CONTENT_DIR) . 'wp-reset-snapshots-export');

            if ($js_localize['space_usage_total'] > 0) {
                $js_localize['space_usage_total'] = WP_Reset_Utility::format_size($js_localize['space_usage_total']);
            }
        }

        if ($this->is_plugin_page()) {
            wp_enqueue_style('plugin-install');
            wp_enqueue_style('wp-reset', $this->plugin_url . 'css/wp-reset.css', array(), $this->version);
            wp_enqueue_script('plugin-install');
            wp_enqueue_script('jquery-ui-tabs');
            wp_enqueue_script('jquery-ui-core');
            wp_enqueue_script('jquery-ui-position');
            add_thickbox();

            $support_text = 'My site details: WP ' . get_bloginfo('version') . ', WPR v' . $this->get_plugin_version() . ', ';
            if (!empty($license['license_key'])) {
                $support_text .= 'license key: ' . $license['license_key'] . '.';
            } else {
                $support_text .= 'no license info.';
            }
            if (strtolower($current_user->display_name) != 'admin' && strtolower($current_user->display_name) != 'administrator') {
                $support_name = $current_user->display_name;
            } else {
                $support_name = '';
            }

            $plugins = get_plugins();
            $active_plugins = get_option('active_plugins');

            $installed_plugins = array();
            foreach ($plugins as $plugin => $plugin_data) {
                if ($plugin == 'wp-reset/wp-reset.php') {
                    continue;
                }
                $plugin_slug = explode('/', $plugin);
                $installed_plugins[] = array(
                    'slug' => $plugin_slug[0],
                    'name' => $plugin_data['Name'],
                    'active' => in_array($plugin, $active_plugins) ? 1 : 0
                );
            }

            $installed_themes = array();
            $themes = wp_get_themes();
            foreach ($themes as $theme => $theme_data) {
                $installed_themes[] = array(
                    'slug' => $theme,
                    'name' => $theme_data['Name'],
                    'child_theme' => $theme != $theme_data['Template'] ? true : false,
                    'template' => $theme_data['Template']
                );
            }

            if ($wp_reset_licensing->is_active()) {
                $collections = $wp_reset_collections->get_collections_keyed();

                $js_localize = array_merge($js_localize, array(
                    'collections' => $collections,
                    'installed_plugins' => $installed_plugins,
                    'installed_themes' => $installed_themes,
                    'activating' => esc_html__('Activating', 'wp-reset'),
                    'deactivating' => esc_html__('Deactivating', 'wp-reset'),
                    'deleting' => esc_html__('Deleting', 'wp-reset'),
                    'installing' => esc_html__('Installing', 'wp-reset'),
                    'activating_license' => esc_html__('Attempting to activate license for ', 'wp-reset'),
                    'activating_license_failed' => esc_html__('Activating License Failed for ', 'wp-reset'),
                    'activating_license_unknown' => esc_html__('Licence activation method unknown for ', 'wp-reset'),
                    'activate_failed' => esc_html__('Could not activate', 'wp-reset'),
                    'deactivate_failed' => esc_html__('Could not deactivate', 'wp-reset'),
                    'delete_failed' => esc_html__('Could not delete', 'wp-reset'),
                    'install_failed' => esc_html__('Could not install', 'wp-reset'),
                    'install_failed_existing' => esc_html__('is already installed', 'wp-reset'),
                    'admin_email' => get_bloginfo('admin_email'),
                    'onboarding_done' => $options['onboarding_done']
                ));

                if (!empty($wp_reset_js_notice)) {
                    $js_localize['js_notice'] = $wp_reset_js_notice;
                }
            }

            $tools_autosnapshot = array(
                'reset_theme_options' => 'Before running the reset theme options tool',
                'delete_transients' => 'Before running the delete transients tool',
                'delete_content' => 'Before running the delete content tool',
                'delete_widgets' => 'Before running the delete widgets tool',
                'truncate_custom_tables' => 'Before running the empty tables tool',
                'drop_custom_tables' => 'Before running the delete tables tool',
                'reset_options' => 'Before running the options reset tool',
                'reset_site' => 'Before running the site reset tool',
            );

            // If tools snapshots is disabled we set this to false as JS will check and skip autosnapshots if it's not object
            if ($options['tools_snapshots'] != 1) {
                $tools_autosnapshot = false;
            }

            $js_localize = array_merge($js_localize, array(
                'support_name' => $support_name,
                'support_text' => $support_text,
                'tools_autosnapshot' => $tools_autosnapshot
            ));
        } else {
            wp_enqueue_style('wp-reset', $this->plugin_url . 'css/wp-reset-global.css', array(), $this->version);
        }

        wp_enqueue_style('wp-reset-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
        wp_enqueue_style('wp-reset-select2', $this->plugin_url . 'css/select2.css', array(), $this->version);
        wp_enqueue_style('wp-reset-tooltipster', $this->plugin_url . 'css/tooltipster.bundle.min.css', array(), $this->version);

        wp_enqueue_script('wp-reset-sweetalert2', $this->plugin_url . 'js/wp-reset-libs.min.js', array('jquery'), $this->version, true);
        wp_enqueue_script('wp-reset-select2', $this->plugin_url . 'js/select2.min.js', array('jquery'), $this->version, true);
        wp_enqueue_script('wp-reset', $this->plugin_url . 'js/wp-reset.js', array('jquery'), $this->version, true);
        wp_localize_script('wp-reset', 'wp_reset', $js_localize);

        // fix for aggressive plugins that include their CSS on all pages
        if ($this->is_plugin_page()) {
            wp_dequeue_style('uiStyleSheet');
            wp_dequeue_style('wpcufpnAdmin');
            wp_dequeue_style('unifStyleSheet');
            wp_dequeue_style('wpcufpn_codemirror');
            wp_dequeue_style('wpcufpn_codemirrorTheme');
            wp_dequeue_style('collapse-admin-css');
            wp_dequeue_style('jquery-ui-css');
            wp_dequeue_style('tribe-common-admin');
            wp_dequeue_style('file-manager__jquery-ui-css');
            wp_dequeue_style('file-manager__jquery-ui-css-theme');
            wp_dequeue_style('wpmegmaps-jqueryui');
            wp_dequeue_style('wp-botwatch-css');
            wp_dequeue_style('njt-filebird-admin');
            wp_dequeue_style('ihc_jquery-ui.min.css');
            wp_dequeue_style('badgeos-juqery-autocomplete-css');
            wp_dequeue_style('mainwp');
            wp_dequeue_style('mainwp-responsive-layouts');
            wp_dequeue_style('jquery-ui-style');
            wp_dequeue_style('additional_style');
            wp_dequeue_style('wobd-jqueryui-style');
            wp_dequeue_style('wpdp-style3');
            wp_dequeue_style('jquery_smoothness_ui');
            wp_dequeue_style('uap_main_admin_style');
            wp_dequeue_style('uap_font_awesome');
            wp_dequeue_style('uap_jquery-ui.min.css');
        }
    } // admin_enqueue_scripts

    function rebrand_css()
    {
        if ($this->get_rebranding() !== false) {
            echo '<style></style>';
            self::wp_kses_wf('<style>' . $this->get_rebranding('admin_css_predefined') . $this->get_rebranding('admin_css') . '</style>');
        }
    }

    /**
     * Add tools to admin bar
     *
     * @return void
     */
    function admin_bar()
    {
        global $wp_admin_bar, $wp_reset_licensing;

        if (!$wp_reset_licensing->is_active() || !is_admin()) {
            return;
        }

        $plugin_name = $this->get_rebranding('name');
        if ($plugin_name == false) {
            $plugin_name = 'WP Reset';
        }

        $options = $this->get_options();

        if (
            !$options['adminbar_snapshots'] ||
            !$wp_reset_licensing->is_active() ||
            false === current_user_can('administrator') ||
            false === apply_filters('wp_reset_show_admin_bar', true)
        ) {
            return;
        }

        $title = '<div class="wpr-adminbar-icon"><img style="height: 22px; padding: 4px; margin-bottom: -10px;" src="' . $this->plugin_url . '/img/wp-reset-icon-small.png" alt="' . esc_html($plugin_name) . '" title="' . esc_html($plugin_name) . '"></div> <span class="ab-label">' . esc_html($plugin_name) . '</span>';

        $wp_admin_bar->add_node(array(
            'id'    => 'wpr-reset-ab',
            'title' => $title,
            'href'  => '#',
            'parent' => '',
        ));

        $wp_admin_bar->add_node(array(
            'id'    => 'wpr-reset',
            'title' => 'Reset',
            'href'  => admin_url('tools.php?page=wp-reset#tab-reset'),
            'parent' => 'wpr-reset-ab'
        ));

        $wp_admin_bar->add_node(array(
            'id'    => 'wpr-tools',
            'title' => 'Tools',
            'href'  => admin_url('tools.php?page=wp-reset#tab-tools'),
            'parent' => 'wpr-reset-ab'
        ));

        $wp_admin_bar->add_node(array(
            'id'    => 'wpr-snapshots',
            'title' => 'Snapshots',
            'href'  => admin_url('tools.php?page=wp-reset#tab-snapshots'),
            'parent' => 'wpr-reset-ab'
        ));

        $wp_admin_bar->add_node(array(
            'id'    => 'wpr-collections',
            'title' => 'Collections',
            'href'  => admin_url('tools.php?page=wp-reset#tab-collections'),
            'parent' => 'wpr-reset-ab'
        ));

        $wp_admin_bar->add_node(array(
            'id'    => 'wpr-create-snapshot',
            'title' => 'Create Snapshot',
            'href'  => '#',
            'meta'  => array('class' => 'wpr-admin-bar-create-snapshot'),
            'parent' => 'wpr-reset-ab'
        ));

        /*
    - reset
 - tools
 - snapshots
 */
    } // admin_bar


    /**
     * Remove all WP notices on WPR page
     *
     * @return null
     */
    function remove_admin_notices()
    {
        if (!$this->is_plugin_page()) {
            return false;
        }

        global $wp_filter;
        unset($wp_filter['user_admin_notices'], $wp_filter['admin_notices']);
    } // remove_admin_notices


    /**
     * Add HelpScout Beacon to footer
     *
     * @return null
     */
    function admin_footer()
    {
        if (!$this->is_plugin_page() || !WP_Reset_Utility::whitelabel_filter()) {
            return;
        }

        echo '<script type="text/javascript">!function(e,t,n){function a(){var e=t.getElementsByTagName("script")[0],n=t.createElement("script");n.type="text/javascript",n.async=!0,n.src="https://beacon-v2.helpscout.net",e.parentNode.insertBefore(n,e)}if(e.Beacon=n=function(t,n,a){e.Beacon.readyQueue.push({method:t,options:n,data:a})},n.readyQueue=[],"complete"===t.readyState)return a();e.attachEvent?e.attachEvent("onload",a):e.addEventListener("load",a,!1)}(window,document,window.Beacon||function(){});</script>';
    } // admin_footer


    /**
     * Check if WP-CLI is available and running
     *
     * @return bool
     */
    static function is_cli_running()
    {
        if (!is_null($value = apply_filters('wp-reset-override-is-cli-running', null))) {
            return (bool) $value;
        }

        if (defined('WP_CLI') && WP_CLI) {
            return true;
        } else {
            return false;
        }
    } // is_cli_running


    /**
     * Check if core WP Webhooks and WPR addon plugins are installed and activated
     *
     * @return bool
     */
    function is_webhooks_active()
    {
        if (!function_exists('is_plugin_active') || !function_exists('get_plugin_data')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        if (false == is_plugin_active('wp-webhooks/wp-webhooks.php')) {
            return false;
        }

        if (false == is_plugin_active('wpwh-wp-reset-webhook-integration/wpwhpro-wp-reset-webhook-integration.php')) {
            return false;
        }

        return true;
    } // is_webhooks_active


    /**
     * Check if given plugin is installed
     *
     * @param [string] $slug Plugin slug
     * @return boolean
     */
    function is_plugin_installed($slug)
    {
        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        $all_plugins = get_plugins();

        if (!empty($all_plugins[$slug])) {
            return true;
        } else {
            return false;
        }
    } // is_plugin_installed


    /**
     * Deletes all transients.
     *
     * @return int  Number of deleted transient DB entries
     */
    function do_delete_transients()
    {
        global $wpdb;

        do_action('wp_reset_before_delete_transients');

        $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '\_transient\_%' OR option_name LIKE '\_site\_transient\_%'"); //phpcs:ignore

        wp_cache_flush();

        do_action('wp_reset_delete_transients', $count);

        return $count;
    } // do_delete_transients


    /**
     * Resets all theme options (mods).
     *
     * @param bool $all_themes Delete mods for all themes or just the current one
     *
     * @return int  Number of deleted mod DB entries
     */
    function do_reset_theme_options($params = array())
    {
        global $wpdb;

        $params = shortcode_atts(array('all_themes' => true), (array) $params);

        do_action('wp_reset_before_reset_theme_options', $params);

        $count = $wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE 'theme_mods\_%' OR option_name LIKE 'mods\_%'"); //phpcs:ignore

        do_action('wp_reset_reset_theme_options', $count);

        return $count;
    } // do_reset_theme_options


    /**
     * Deletes all files in uploads folder.
     *
     * @return int  Number of deleted files and folders.
     */
    function do_delete_uploads()
    {
        $upload_dir = wp_get_upload_dir();
        $this->delete_count = 0;

        $this->delete_folder($upload_dir['basedir'], $upload_dir['basedir']);

        do_action('wp_reset_delete_uploads', $this->delete_count);

        return $this->delete_count;
    } // do_delete_uploads


    /**
     * Deletes non-default folders in wp-content folder.
     *
     * @return int  Number of deleted files and folders.
     */
    function do_delete_wp_content()
    {
        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);
        $this->delete_count = 0;
        $whitelisted_folders = array('mu-plugins', 'plugins', 'themes', 'uploads', $this->snapshots_folder, $this->autosnapshots_folder);

        $dirs = glob($wp_content_dir . '*', GLOB_ONLYDIR);
        foreach ($dirs as $dir) {
            if (false == in_array(basename($dir), $whitelisted_folders)) {
                $this->delete_folder($dir, $dir);
                if (is_dir($dir)) {
                    rmdir($dir); //phpcs:ignore
                }
                $this->delete_count++;
            }
        }

        do_action('wp_reset_delete_wp_content', $this->delete_count, $dirs);

        return $this->delete_count;
    } // do_delete_wp_content

    function folder_size($path)
    {
        $bytestotal = 0;
        $path = realpath($path);
        if ($path !== false && $path != '' && file_exists($path)) {
            foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) as $object) {
                $bytestotal += $object->getSize();
            }
        }
        return $bytestotal;
    }

    /**
     * Recursively deletes a folder
     *
     * @param string $folder  Recursive param.
     * @param string $base_folder  Base folder.
     *
     * @return bool
     */
    public function delete_folder($folder, $base_folder)
    {
        if (!file_exists($folder)) {
            return true;
        }

        $files = array_diff(scandir($folder), array('.', '..'));

        foreach ($files as $file) {
            if (is_dir($folder . DIRECTORY_SEPARATOR . $file)) {
                $this->delete_folder($folder . DIRECTORY_SEPARATOR . $file, $base_folder);
            } else {
                $tmp = @wp_delete_file($folder . DIRECTORY_SEPARATOR . $file);
                $this->delete_count += (int) $tmp;
            }
        } // foreach

        if ($folder != $base_folder) {
            if (is_dir($folder)) {
                $tmp = rmdir($folder); //phpcs:ignore
            }
            $this->delete_count += (int) $tmp;
            return $tmp;
        } else {
            return true;
        }
    } // delete_folder

    /**
     * Recursively scan folder
     *
     * @param string $source
     * @param string $destination
     *
     * @return bool
     */
    function scan_folder($dir, &$results = array())
    {
        $files = scandir($dir);

        foreach ($files as $key => $value) {
            $path = realpath($dir . DIRECTORY_SEPARATOR . $value);
            if (!is_dir($path)) {
                $results[] = $path;
            } else if ($value != '.' && $value != '..') {
                $this->scan_folder($path, $results);
                $results[] = $path;
            }
        }

        return $results;
    }

    /**
     * Recursively copies a folder
     *
     * @param string $source
     * @param string $destination
     *
     * @return bool
     */
    function copy_folder($source, $destination)
    {
        $dir = opendir($source);
        mkdir($destination); //phpcs:ignore
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                if (is_dir($source . '/' . $file)) {
                    $this->copy_folder($source . '/' . $file, $destination . '/' . $file);
                } else {
                    copy($source . '/' . $file, $destination . '/' . $file);
                }
            }
        }
        closedir($dir);
    } // copy_folder

    /**
     * Check if there are any large files before we copy
     *
     * @return bool
     */
    function check_large_files($fs, $src, $max_size_mb = 500)
    {
        $src = untrailingslashit($src);
        $max_bytes = $max_size_mb * 1024 * 1024;

        $list = $fs->dirlist($src, false, true);
        if (!is_array($list)) {
            return new WP_Error('scan_failed', "Could not read directory: $src");
        }

        foreach ($list as $name => $info) {
            $path = $src . '/' . $name;

            // Skip symlinks
            if ($info['type'] === 'l') {
                continue;
            }

            if ($info['type'] === 'f') {
                if (!empty($info['size']) && $info['size'] > $max_bytes) {
                    $size_mb = round($info['size'] / 1048576, 1);
                    return new WP_Error(
                        'large_file',
                        "Large file detected ({$size_mb} MB): {$path}"
                    );
                }
            } elseif ($info['type'] === 'd') {
                $inner = $this->check_large_files($fs, $path, $max_size_mb);
                if (is_wp_error($inner)) {
                    return $inner;
                }
            }
        }

        return true; // no oversized files found
    }

    /**
     * Recursively copies a folder using WP_Filesystem
     *
     * @return bool
     */
    function wpfs_copy_folder($fs, $src, $dest, array $exclude = [])
    {
        $src  = untrailingslashit($src);
        $dest = untrailingslashit($dest);

        // Initialize totals if not already set
        if (empty($this->staging_totals)) {
            $this->staging_totals = [
                'files' => 0,
                'bytes' => 0,
            ];
        }

        // Normalize excludes to always start with a slash
        $exclude = array_map(fn($ex) => '/' . ltrim($ex, '/'), $exclude);

        if (!$fs->exists($dest)) {
            if (!$fs->mkdir($dest, FS_CHMOD_DIR)) {
                return new WP_Error('copy_mkdir', "Failed to create destination directory: $dest");
            }
        }

        $list = $fs->dirlist($src, false, true);
        if (!is_array($list)) {
            return new WP_Error('copy_list', "Could not read source directory: $src");
        }

        foreach ($list as $name => $info) {
            $from = $src . '/' . $name;
            $to   = $dest . '/' . $name;

            // Skip symlinks
            if ($info['type'] === 'l') {
                continue;
            }

            // Skip excluded paths
            foreach ($exclude as $ex) {
                if (strpos($from, $src . $ex) === 0) {
                    continue 2;
                }
            }

            if ($info['type'] === 'd') {
                if (!$fs->exists($to) && !$fs->mkdir($to, FS_CHMOD_DIR)) {
                    return new WP_Error('copy_mkdir2', "Failed to create directory: $to");
                }

                $inner = $this->wpfs_copy_folder($fs, $from, $to, $exclude);
                if (is_wp_error($inner)) {
                    return $inner;
                }
            } else {
                if (!$fs->copy($from, $to, true, FS_CHMOD_FILE)) {
                    return new WP_Error('copy_file', "Failed copying: $from → $to");
                }

                $file_size = 0;
                if (isset($info['size']) && is_numeric($info['size'])) {
                    $file_size = (int)$info['size'];
                } else {
                    $size_val = $fs->size($from);
                    if (is_numeric($size_val)) {
                        $file_size = (int)$size_val;
                    }
                }

                $this->staging_totals['files']++;
                $this->staging_totals['bytes'] += $file_size;
            }
        }

        return true;
    }


    /**
     * Deactivate all plugins
     *
     * @param array  keep_wp_reset - Keep WP Reset active and installed, silent_deactivate - Skip individual plugin deactivation functions when deactivating
     *
     * @return int  Number of deactivated plugins.
     */
    function do_deactivate_plugins($params = array())
    {
        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        if (!function_exists('request_filesystem_credentials')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        $wp_reset_basename = plugin_basename(WP_RESET_FILE);
        $params = shortcode_atts(array('keep_wp_reset' => true, 'silent_deactivate' => false), (array) $params);

        $active_plugins = (array) get_option('active_plugins', array());
        if ($params['keep_wp_reset']) {
            if (($key = array_search($wp_reset_basename, $active_plugins)) !== false) {
                unset($active_plugins[$key]);
            }
        }

        if (!empty($active_plugins)) {
            deactivate_plugins($active_plugins, $params['silent_deactivate'], false);
        }

        do_action('wp_reset_deactivate_plugins', $active_plugins, $params);

        return sizeof($active_plugins);
    } // do_delete_plugins


    /**
     * Delete all plugins
     *
     * @param array  keep_wp_reset - Keep WP Reset active and installed
     *
     * @return int  Number of deleted plugins.
     */
    function do_delete_plugins($params = array())
    {
        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        if (!function_exists('request_filesystem_credentials')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        $wp_reset_basename = plugin_basename(WP_RESET_FILE);
        $params = shortcode_atts(array('keep_wp_reset' => true), (array) $params);

        $all_plugins = get_plugins();
        if ($params['keep_wp_reset']) {
            unset($all_plugins[$wp_reset_basename]);
        }

        if (!empty($all_plugins)) {
            delete_plugins(array_keys($all_plugins));
        }

        do_action('wp_reset_delete_plugins', $all_plugins, $params);

        return sizeof($all_plugins);
    } // do_delete_plugins


    /**
     * Delete all themes
     *
     * @param array  $params keep_default_theme -  Keep default theme
     *
     * @return int  Number of deleted themes.
     */
    function do_delete_themes($params)
    {
        global $wp_version;

        if (!function_exists('delete_theme')) {
            require_once ABSPATH . 'wp-admin/includes/theme.php';
        }

        if (!function_exists('request_filesystem_credentials')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        $current_theme = wp_get_theme();
        $params = shortcode_atts(array('keep_default_theme' => false, 'keep_current_theme' => false), (array) $params);

        if (version_compare($wp_version, '5.3', '>=') === true) {
            $default_theme = 'twentytwenty';
        } elseif (version_compare($wp_version, '5.0', '>=') === true) {
            $default_theme = 'twentynineteen';
        } else {
            $default_theme = 'twentyseventeen';
        }

        $all_themes = wp_get_themes(array('errors' => null));

        if ($params['keep_default_theme']) {
            unset($all_themes[$default_theme]);
        }

        if ($params['keep_current_theme'] && is_object($current_theme)) {
            $parent_theme = $current_theme->parent();
            if ($parent_theme != false) {
                unset($all_themes[$parent_theme->get_stylesheet()]);
            }
            unset($all_themes[$current_theme->get_stylesheet()]);
        }

        foreach ($all_themes as $theme_slug => $theme_details) {
            delete_theme($theme_slug);
        }

        if (!$params['keep_default_theme'] && !$params['keep_current_theme']) {
            update_option('template', '');
            update_option('stylesheet', '');
            update_option('current_theme', '');
        }

        do_action('wp_reset_delete_themes', $all_themes, $params);

        return sizeof($all_themes);
    } // do_delete_themes


    /**
     * Truncate custom tables
     *
     * @return int  Number of truncated tables.
     */
    function do_truncate_custom_tables($params)
    {
        global $wpdb;
        $cnt = 0;

        $custom_tables = $this->get_custom_tables();

        $params = shortcode_atts(array('tables' => array()), (array) $params);

        if (empty($params['tables'])) {
            do_action('wp_reset_truncate_custom_tables', $custom_tables, $params, 0);
            return 0;
        }

        do_action('wp_reset_before_truncate_custom_tables', $params);

        foreach ($custom_tables as $tbl) {
            if (in_array($tbl['name'], $params['tables']) || in_array('__all', $params['tables'])) {
                $wpdb->query('SET foreign_key_checks = 0'); //phpcs:ignore
                $wpdb->query('TRUNCATE TABLE `' . $tbl['name'] . '`'); //phpcs:ignore
                $cnt++;
            }
        } // foreach

        do_action('wp_reset_truncate_custom_tables', $custom_tables, $params, $cnt);

        return $cnt;
    } // do_truncate_custom_tables


    /**
     * Drop custom tables
     *
     * @return int  Number of dropped tables.
     */
    function do_drop_custom_tables($params)
    {
        global $wpdb;
        $cnt = 0;
        $custom_tables = $this->get_custom_tables();

        $params = shortcode_atts(array('tables' => array()), (array) $params);

        if (empty($params['tables'])) {
            do_action('wp_reset_drop_custom_tables', $custom_tables, $params, 0);
            return 0;
        }

        do_action('wp_reset_before_drop_custom_tables', $params);

        foreach ($custom_tables as $tbl) {
            if (in_array($tbl['name'], $params['tables']) || in_array('__all', $params['tables'])) {
                $wpdb->query('SET foreign_key_checks = 0'); //phpcs:ignore
                $wpdb->query('DROP TABLE IF EXISTS `' . $tbl['name'] . '`'); //phpcs:ignore
                $cnt++;
            }
        } // foreach

        do_action('wp_reset_drop_custom_tables', $custom_tables, $params, $cnt);

        return $cnt;
    } // do_drop_custom_tables


    /**
     * Delete .htaccess file
     *
     * @return bool|WP_Error Action status.
     */
    function do_delete_htaccess()
    {
        global $wp_filesystem;

        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        $htaccess_path = $this->get_htaccess_path();
        clearstatcache();

        do_action('wp_reset_delete_htaccess', $htaccess_path);

        if (!$wp_filesystem->is_readable($htaccess_path)) {
            return new WP_Error(1, 'Htaccess file does not exist; there\'s nothing to delete.');
        }

        if (!$wp_filesystem->is_writable($htaccess_path)) {
            return new WP_Error(1, 'Htaccess file is not writable.');
        }

        if ($wp_filesystem->delete($htaccess_path, false, 'f')) {
            return true;
        } else {
            return new WP_Error(1, 'Unknown error. Unable to delete htaccess file.');
        }
    } // do_delete_htaccess


    /**
     * Reset user roles
     *
     * @return bool|WP_Error Action status.
     */
    function do_reset_user_roles()
    {
        global $wp_roles, $wp_user_roles;

        $wp_user_roles = null;
        $wp_roles->roles = array();
        $wp_roles->role_objects = array();
        $wp_roles->role_names = array();
        $wp_roles->use_db = true;

        require_once(ABSPATH . '/wp-admin/includes/schema.php');
        populate_roles();
        $wp_roles = new WP_Roles();

        return true;
    } // do_reset_user_roles


    /**
     * Restore .htaccess file
     *
     * @return bool|WP_Error Action status.
     */
    function do_restore_htaccess()
    {
        global $wp_filesystem;

        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        $htaccess_path = $this->get_htaccess_path();
        clearstatcache();

        do_action('wp_reset_restore_htaccess', $htaccess_path);

        if ($wp_filesystem->delete($htaccess_path, false, 'f')) {
            require_once ABSPATH . 'wp-admin/includes/misc.php';
            if (save_mod_rewrite_rules()) {
                return true;
            } else {
                return new WP_Error(1, 'Unknown error. Unable to restore htaccess file.');
            }
        } else {
            return new WP_Error(1, 'Unknown error. Unable to delete htaccess file.');
        }
    } // do_delete_htaccess


    /**
     * Get .htaccess file path.
     *
     * @return string
     */
    function get_htaccess_path()
    {
        if (!function_exists('get_home_path')) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
        }

        if ($this->is_cli_running()) {
            $_SERVER['SCRIPT_FILENAME'] = ABSPATH;
        }

        $filepath = get_home_path() . '.htaccess';

        return $filepath;
    } // get_htaccess_path


    function search_wporg($search, $themes = false)
    {
        $results = array();

        if ($themes) {
            $find = 'themes';
        } else {
            $find = 'plugins';
        }

        $request_params = array('sslverify' => false, 'timeout' => 25, 'redirection' => 2);
        $url = 'https://api.wordpress.org/' . $find . '/info/1.2/?action=query_' . $find . '&request[page]=1&request[per_page]=20&request[locale]=en_US&request[search]=' . $search . '&request[wp_version]=5.2&request[fields][short_description]=0&request[fields][description]=0&request[fields][tested]=0&request[fields][requires]=0&request[fields][rating]=0&request[fields][ratings]=0&request[fields][downloaded]=0&request[fields][downloadlink]=0&request[fields][last_updated]=0&request[fields][ratings]=0&request[fields][added]=0&request[fields][tags]=0&request[fields][compatibility]=0&request[fields][homepage]=0&request[fields][versions]=0&request[fields][donate_link]=0&request[fields][reviews]=0&request[fields][group]=0&request[fields][contributors]=0&request[fields][active_installs]=0&request[fields][icons]=0&request[fields][author_profile]=0&request[fields][requires_php]=0&request[fields][num_ratings]=0&request[fields][support_threads]=0&request[fields][support_threads_resolved]=0&request[fields][download_link]=0';
        $response = wp_remote_get($url, $request_params);
        $body = wp_remote_retrieve_body($response);
        $wp_search_results = @json_decode($body, true);

        if (is_wp_error($response) || empty($body) || !is_array($wp_search_results)) {
            return array();
        } else {
            foreach ($wp_search_results[$find] as $wp_result) {
                $results[] = array('id' => $wp_result['slug'], 'text' => html_entity_decode($wp_result['name']));
            }
        }

        return $results;
    }

    /**
     * Run one tool via AJAX call
     *
     * @return null
     */
    function ajax_run_tool()
    {
        check_ajax_referer('wp-reset_run_tool');
        if (!current_user_can('administrator')) {
            wp_send_json_error(esc_html__('You are not allowed to run this action.', 'wp-reset'));
        }

        if (!class_exists('ZipArchive')) {
            wp_send_json_error(esc_html__('The PHP ZipArchive Library is missing or disabled! Please contact your host to enable it before running WP Reset tools', 'wp-reset'));
        }

        global $wp_reset_tools, $wp_reset_collections, $wp_reset_cloud, $wp_reset_licensing;
        $options = $this->get_options();
        $tool = trim(sanitize_text_field(wp_unslash($_REQUEST['tool'] ?? '')));
        $extra_data = wp_unslash($_REQUEST['extra_data'] ?? false); //phpcs:ignore
        if (is_string($extra_data)) {
            $extra_data = trim($extra_data);
            $extra_data = substr($extra_data, 0, 255);
        } elseif (is_array($extra_data)) {
            $extra_data = array_slice($extra_data, 0, 20);
        } else {
            $extra_data = false;
        }

        if ($options['debug']) {
            ini_set('display_errors', 1); //phpcs:ignore
            error_reporting(E_ALL); //phpcs:ignore
        }


        if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { //phpcs:ignore
            @set_time_limit(900); //phpcs:ignore
        }

        if ($options['throttle_ajax'] == true) {
            usleep(1000000);
        }

        if ($tool == 'test') {
            sleep(wp_rand(0, 2));
            if (wp_rand(0, 10) % 2) {
                wp_send_json_error('Error message example.');
            } else {
                wp_send_json_success(wp_rand(0, 100));
            }
        } elseif ($tool == 'reload_collections') {
            $res = $wp_reset_collections->reload_collections();
            if (is_wp_error($res)) {
                $this->log('error', 'Error refreshing collections: ' . $res->get_error_message());
                wp_send_json_error($res->get_error_message());
            } else {
                $this->log('info', 'Collections refreshed sucessfully');
                wp_send_json_success();
            }
        } elseif ($tool == 'wp_slug_search') {
            $search = false;
            if (!empty($extra_data['search'])) {
                $search = $extra_data['search'];
            }
            wp_send_json_success($this->search_wporg($search, filter_var($extra_data['theme'], FILTER_VALIDATE_BOOLEAN)));
        } elseif ($tool == 'add_new_collection') {
            $res = $wp_reset_collections->add_new_collection($extra_data);
            if (is_wp_error($res)) {
                $this->log('error', 'Error creating ' . $extra_data['name'] . ' collection: ' . $res->get_error_message());
                wp_send_json_error($res->get_error_message());
            } else {
                $tmp = $wp_reset_collections->get_collection_card($res);
                $this->log('info', 'Collection ' . $extra_data['name'] . ' created sucessfully');
                wp_send_json_success($tmp);
            }
        } elseif ($tool == 'add_collection_item') {
            $extra_data = @$_REQUEST;
            $res = $wp_reset_collections->add_collection_item($extra_data);
            if (is_wp_error($res)) {
                $this->log('error', 'Error adding ' . $extra_data['slug'] . ' to collection: ' . $res->get_error_message());
                wp_send_json_error($res->get_error_message());
            } else {
                $tmp = $wp_reset_collections->get_collection_item($res);
                $this->log('info', 'Collection item ' . $extra_data['slug'] . ' added to collection sucessfully');
                wp_send_json_success(array('item' => $tmp, 'collections' => $wp_reset_collections->get_collections_keyed()));
            }
        } elseif ($tool == 'edit_collection_item') {
            $extra_data = @$_REQUEST;
            $res = $wp_reset_collections->edit_collection_item($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                $tmp = $wp_reset_collections->get_collection_item($res);
                wp_send_json_success(array('item' => $tmp, 'collections' => $wp_reset_collections->get_collections_keyed()));
            }
        } elseif ($tool == 'delete_collection') {
            $res = $wp_reset_collections->delete_collection($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'delete_collection_item') {
            $res = $wp_reset_collections->delete_collection_item($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success(array('collections' => $wp_reset_collections->get_collections_keyed()));
            }
        } elseif ($tool == 'edit_collection_name') {
            $res = $wp_reset_collections->edit_collection_name($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'edit_snapshot_name') {
            $res = $wp_reset_tools->edit_snapshot_name($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'save_snapshot_options') {
            $res = $wp_reset_tools->save_snapshot_options($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'before_reset') {
            $active_plugins = get_option('active_plugins');
            set_transient('wpr_active_plugins', $active_plugins, 100);
            remove_all_actions('update_option_active_plugins');
            update_option('active_plugins', array(plugin_basename(__FILE__)));
            wp_send_json_success();
        } elseif ($tool == 'site_reset') {
            $res = $this->do_reinstall($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'delete_transients') {
            $cnt = $this->do_delete_transients();
            wp_send_json_success($cnt);
        } elseif ($tool == 'purge_cache') {
            $wp_reset_tools->do_purge_cache();
            wp_send_json_success();
        } elseif ($tool == 'delete_widgets') {
            $cnt = $wp_reset_tools->do_delete_widgets();
            wp_send_json_success($cnt);
        } elseif ($tool == 'delete_wp_cookies') {
            wp_clear_auth_cookie();
            wp_send_json_success();
        } elseif ($tool == 'reset_theme_options') {
            $cnt = $this->do_reset_theme_options(array('all_themes' => true));
            wp_send_json_success($cnt);
        } elseif ($tool == 'reset_options') {
            $res = $wp_reset_tools->do_reset_options($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success(true);
            }
        } elseif ($tool == 'delete_content') {
            $res = $wp_reset_tools->do_delete_content($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'delete_themes') {
            $cnt = $this->do_delete_themes($extra_data);
            wp_send_json_success($cnt);
        } elseif ($tool == 'deactivate_plugins') {
            $cnt = $this->do_deactivate_plugins($extra_data);
            wp_send_json_success($cnt);
        } elseif ($tool == 'delete_plugins') {
            $cnt = $this->do_delete_plugins($extra_data);
            wp_send_json_success($cnt);
        } elseif ($tool == 'delete_uploads') {
            $cnt = $this->do_delete_uploads();
            wp_send_json_success($cnt);
        } elseif ($tool == 'delete_wp_content') {
            $cnt = $this->do_delete_wp_content();
            wp_send_json_success($cnt);
        } elseif ($tool == 'delete_htaccess') {
            $tmp = $this->do_delete_htaccess();
            if (is_wp_error($tmp)) {
                wp_send_json_error($tmp->get_error_message());
            } else {
                wp_send_json_success($tmp);
            }
        } elseif ($tool == 'restore_htaccess') {
            $tmp = $this->do_restore_htaccess();
            if (is_wp_error($tmp)) {
                wp_send_json_error($tmp->get_error_message());
            } else {
                wp_send_json_success($tmp);
            }
        } elseif ($tool == 'reset_user_roles') {
            $tmp = $this->do_reset_user_roles();
            if (is_wp_error($tmp)) {
                wp_send_json_error($tmp->get_error_message());
            } else {
                wp_send_json_success($tmp);
            }
        } elseif ($tool == 'drop_custom_tables') {
            $cnt = $this->do_drop_custom_tables($extra_data);
            wp_send_json_success($cnt);
        } elseif ($tool == 'truncate_custom_tables') {
            $cnt = $this->do_truncate_custom_tables($extra_data);
            wp_send_json_success($cnt);
        } elseif ($tool == 'delete_mu_plugins') {
            $cnt = $wp_reset_tools->do_delete_mu_plugins($extra_data);
            if (is_wp_error($cnt)) {
                wp_send_json_error($cnt->get_error_message());
            } else {
                wp_send_json_success($cnt);
            }
        } elseif ($tool == 'activate_dropin') {
            $res = $wp_reset_tools->do_activate_dropin($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'deactivate_dropin') {
            $res = $wp_reset_tools->do_deactivate_dropin($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'delete_dropin') {
            $res = $wp_reset_tools->do_delete_dropin($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'delete_mu_plugin') {
            $res = $wp_reset_tools->do_delete_mu_plugin($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'delete_dropins') {
            $cnt = $wp_reset_tools->do_delete_dropins($extra_data);
            if (is_wp_error($cnt)) {
                wp_send_json_error($cnt->get_error_message());
            } else {
                wp_send_json_success($cnt);
            }
        } elseif ($tool == 'delete_snapshot') {
            $res = $this->do_delete_snapshot($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'delete_snapshots') {
            $cnt = $this->do_delete_snapshots($extra_data);
            if (is_wp_error($cnt)) {
                wp_send_json_error($cnt->get_error_message());
            } else {
                wp_send_json_success($cnt);
            }
        } elseif ($tool == 'delete_cloud_snapshots') {
            $cnt = $this->do_delete_cloud_snapshots($extra_data);
            if (is_wp_error($cnt)) {
                wp_send_json_error($cnt->get_error_message());
            } else {
                wp_send_json_success($cnt);
            }
        } elseif ($tool == 'download_snapshot') {
            $res = $this->do_export_snapshot($extra_data['uid']);

            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else if (is_array($res) && $res['ajax'] === true) {
                $steps = array();
                foreach ($res['tbl_names'] as $table) {
                    $steps[] = array('uid' => $res['uid'], 'action' => 'export', 'data' => $table, 'description' => 'Exporting table <i>' . $table . '</i>');
                }
                $steps[] = array('uid' => $res['uid'], 'action' => 'verify_export', 'data' => false, 'description' => 'Verifying exported snapshot');
                wp_send_json_success($steps);
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'export_snapshot_step') {
            $res = $this->export_snapshot_step($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'restore_snapshot') {
            $res = $this->do_restore_snapshot($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'compare_snapshots') {
            $res = $this->do_compare_snapshots($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'cloud_action') {
            $res = $wp_reset_cloud->cloud_action($extra_data);

            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'create_snapshot') {
            $res = $this->do_create_snapshot($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else if ($res['ajax'] == true) {
                $steps = array();
                foreach ($res['tbl_names'] as $table) {
                    $steps[] = array('uid' => $res['uid'], 'action' => 'copy', 'data' => $table, 'description' => 'Copying table <i>' . $table . '</i>');
                }
                $steps[] = array('uid' => $res['uid'], 'action' => 'verify_integrity', 'data' => false, 'description' => 'Verifying snapshot');
                wp_send_json_success($steps);
            } else {
                $tmp = $this->get_snapshot_row($res, 'user');
                wp_send_json_success(array('html' => $tmp, 'uid' => $res['uid']));
            }
        } elseif ($tool == 'create_snapshot_step') {
            $res = $this->create_snapshot_step($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                $tmp = $this->get_snapshot_row($res, 'user');
                wp_send_json_success(array('html' => $tmp, 'uid' => $res['uid'], 'auto' => $res['auto']));
            }
        } elseif ($tool == 'deploy_stage') {
            $res = $this->do_deploy_stage($extra_data);
            //TODO: Steps
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else if ($res['ajax'] == true) {
                $steps = array();
                $steps[] = array('uid' => $res['uid'], 'action' => 'copy_files', 'data' => '', 'description' => 'Copying files</i>');
                foreach ($res['tbl_names'] as $table) {
                    $steps[] = array('uid' => $res['uid'], 'action' => 'copy_table', 'data' => $table, 'description' => 'Copying table <i>' . $table . '</i>');
                }
                $steps[] = array('uid' => $res['uid'], 'action' => 'update_db', 'data' => false, 'description' => 'Updating database');
                $steps[] = array('uid' => $res['uid'], 'action' => 'setting_up', 'data' => false, 'description' => 'Setting up the stage');
                $steps[] = array('uid' => $res['uid'], 'action' => 'verify_integrity', 'data' => false, 'description' => 'Verifying integrity');
                wp_send_json_success(array('steps' => $steps, 'stage' => $res));
            } else {
                $tmp = $this->get_stage_row($res, 'user');
                wp_send_json_success(array('html' => $tmp, 'uid' => $res['uid']));
            }
        } elseif ($tool == 'deploy_stage_step') {
            //TODO: Run Steps
            $res = $this->deploy_stage_step($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                $tmp = $this->get_stage_row($res, 'user');
                wp_send_json_success(array('html' => $tmp, 'uid' => $res['uid'], 'stage' => $res));
            }
        } elseif ($tool == 'delete_stage') {
            $res = $this->do_delete_stage($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success();
            }
        } elseif ($tool == 'get_table_details') {
            $res = WP_Reset_Utility::get_table_details();
            wp_send_json_success($res);
        } elseif ($tool == 'onboarding_done') {
            $options['onboarding_done'] = true;
            $this->update_options('options', $options);
        } elseif ($tool == 'test_snapshot') {
            $start_time = microtime(true);
            $snapshot = array(
                'name' => 'Onboarding Test',
                'auto' => true,
                'plugins' => array(
                    'WP Reset PRO' => plugin_dir_path(WP_PLUGIN_DIR . '/wp-reset/wp-reset.php')
                )
            );

            $res = $this->do_create_snapshot($snapshot);
            if (is_wp_error($res)) {
                $options['tools_snapshots'] = false;
                $options['events_snapshots'] = false;
                $options['snapshots_autoupload'] = false;
                $options['autosnapshots_autoupload'] = false;
                $options['onboarding_done'] = true;
                $this->update_options('options', $options);
                wp_send_json_error($res->get_error_message());
            } else {
                $this->do_delete_snapshot($res['uid']);
                $duration = round((microtime(true) - $start_time) * 100) / 100;
                if ($duration < 10) {
                    $options['tools_snapshots'] = true;
                    $options['events_snapshots'] = true;
                } else {
                    $options['tools_snapshots'] = false;
                    $options['events_snapshots'] = false;
                }
                $options['onboarding_done'] = true;
                $this->update_options('options', $options);
                wp_send_json_success(array('auto' => $options['tools_snapshots']));
            }
        } elseif ($tool == 'install_recovery') {
            $res = $this->do_install_recovery();
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'uninstall_recovery') {
            $res = $this->do_uninstall_recovery();
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'update_recovery') {
            $res = $this->do_update_recovery();
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'email_recovery') {
            $res = $this->send_recovery_script_email($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'change_single_option') {
            $options[$extra_data['option']] = filter_var($extra_data['value'], FILTER_VALIDATE_BOOLEAN);
            $this->update_options('options', $options);
        } elseif ($tool == 'switch_wp_version') {
            $res = $wp_reset_tools->do_switch_wp_version($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif ($tool == 'refresh_wp_versions') {
            if ($wp_reset_tools->get_wordpress_versions(true) !== false) {
                wp_send_json_success();
            } else {
                wp_send_json_error(esc_html__('WordPress list could not be refreshed! Reload the page and try again.', 'wp-reset'));
            }
        } elseif ($tool == 'import_snapshot') {
            if (!empty($_FILES['snapshot_zip']) && $_FILES['snapshot_zip']['error'] == 0) { //phpcs:ignore
                $import_folder = $this->export_dir_path();
                if (is_wp_error($import_folder)) {
                    wp_send_json_error($import_folder->get_error_message());
                }
                $import_file = $import_folder . '/' . $_FILES['snapshot_zip']['name']; //phpcs:ignore

                move_uploaded_file($_FILES['snapshot_zip']['tmp_name'], $import_file); //phpcs:ignore

                $import_res = $this->do_import_snapshot($import_file, true);

                if (is_wp_error($import_res)) {
                    wp_send_json_error($import_res->get_error_message());
                } else {
                    wp_send_json_success($import_res);
                }
            } else {
                wp_send_json_error(esc_html__('File upload error!', 'wp-reset'));
            }
        } elseif ($tool == 'import_snapshot_step') {
            $res = $this->import_snapshot_step($extra_data);
            if (is_wp_error($res)) {
                wp_send_json_error($res->get_error_message());
            } else {
                wp_send_json_success($res);
            }
        } elseif (
            $tool == 'check_delete_theme' ||
            $tool == 'check_install_theme' ||
            $tool == 'check_activate_theme'
        ) {

            $theme = wp_get_theme(sanitize_text_field(wp_unslash($_GET['slug'] ?? '')));
            if ($theme->exists()) {
                $current_theme = wp_get_theme();
                if ($theme->get_stylesheet() == $current_theme->get_stylesheet()) {
                    wp_send_json_success('active');
                } else {
                    wp_send_json_success('inactive');
                }
            } else {
                wp_send_json_success('deleted');
            }
        } elseif ($tool == 'install_theme') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            $slug = sanitize_text_field(wp_unslash($_GET['slug'] ?? ''));

            if (isset($_GET['extra_data'])) {
                $source = sanitize_text_field(wp_unslash($_GET['extra_data']['source'] ?? ''));
            } else {
                $source = 'repo';
            }

            @include_once ABSPATH . 'wp-admin/includes/theme.php';
            @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
            @include_once ABSPATH . 'wp-admin/includes/file.php';
            @include_once ABSPATH . 'wp-admin/includes/misc.php';

            wp_cache_flush();

            $theme = wp_get_theme($slug);

            if ($theme->exists()) {
                // Theme is already installed
                wp_send_json_success();
            } else {
                // Install Theme
                $skin      = new WP_Ajax_Upgrader_Skin();
                $upgrader  = new Theme_Upgrader($skin);

                if ($source == 'zip') {
                    $collection_item = $wp_reset_collections->get_collection_item_details(sanitize_text_field(wp_unslash($_GET['extra_data']['collection_id'] ?? '')), sanitize_text_field(wp_unslash($_GET['extra_data']['collection_item_id'] ?? '')));
                    if ($collection_item['location'] == 'wpreset') {
                        $upgrader->install(trailingslashit($wp_reset_cloud->cloud_url) . $collection_item['zip_url']);
                    } else if ($collection_item['location'] == 'pcloud' || $collection_item['location'] == 'pcloudeu') {
                        global $wp_reset_cloud;

                        if (($options['cloud_service'] != 'pcloud' && $options['cloud_service'] != 'pcloudeu') || is_wp_error($wp_reset_cloud->get_pcloud_client())) {
                            set_transient('wf_install_error_' . $slug, 'You need to connect to the pCloud account where this collection item is stored in order to install it.', 300);
                        }

                        try {
                            $pcloudFile = new pCloud\File();
                            $download_result = $pcloudFile->download((int)$collection_item['zip_filename'], $this->export_dir_path());
                            $url = $this->export_dir_path(basename($download_result), true);
                        } catch (Exception $e) {
                            set_transient('wf_install_error_' . $slug, 'This collection item was not found in the current pCloud account. Please connect to the pCloud account you used when adding it. ' . $e->getMessage(), 300);
                        }

                        $upgrader->install($url);
                    } else if ($collection_item['location'] == 'icedrive') {
                        global $wp_reset_cloud;

                        if ($options['cloud_service'] != 'icedrive' || is_wp_error($wp_reset_cloud->get_icedrive_client())) {
                            set_transient('wf_install_error_' . $slug, 'You need to connect to the Icedrive account where this collection item is stored in order to install it.', 300);
                        }

                        try {
                            $icedriveFile = $this->icedrive->request('GET', $collection_item['zip_filename']);
                            file_put_contents($wp_reset->export_dir_path(basename($collection_item['zip_filename'])), $icedriveFile['body']);
                            $download_checksum = $this->file_checksum($wp_reset->export_dir_path(basename($collection_item['zip_filename'])));

                            $url = $this->export_dir_path(basename($collection_item['zip_filename']), true);
                        } catch (Exception $e) {
                            set_transient('wf_install_error_' . $slug, 'This collection item was not found in the current Icedrive account. Please connect to the Icedrive account you used when adding it. ' . $e->getMessage(), 300);
                        }

                        $upgrader->install($url);
                    } else {
                        $upgrader->install($collection_item['zip_url']);
                    }
                } else {
                    $upgrader->install('https://downloads.wordpress.org/theme/' . $slug . '.latest-stable.zip');
                }

                wp_send_json_success();
            }
        } elseif ($tool == 'activate_theme') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            $stylesheet = sanitize_text_field(wp_unslash($_GET['slug'] ?? ''));
            $theme = wp_get_theme($stylesheet);
            if ($theme->exists()) {
                switch_theme($stylesheet);
            }
            wp_send_json_success();
        } elseif ($tool == 'delete_theme') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            $stylesheet = sanitize_text_field(wp_unslash($_GET['slug'] ?? ''));
            $theme = wp_get_theme($stylesheet);
            if ($theme->exists()) {
                $res = delete_theme($stylesheet);
            }
            wp_send_json_success();
        } elseif (
            $tool == 'check_deactivate_plugin' ||
            $tool == 'check_delete_plugin' ||
            $tool == 'check_install_plugin' ||
            $tool == 'check_activate_plugin'
        ) {
            $slug = sanitize_text_field(wp_unslash($_GET['slug'] ?? ''));
            $path = $this->get_plugin_path($slug);
            if (false !== ($error = get_transient('wf_install_error_' . $slug))) {
                delete_transient('wf_install_error_' . $slug);
                wp_send_json_success($error);
            }

            if (false !== $path) {
                $active_plugins = (array) get_option('active_plugins', array());
                if (false !== array_search($path, $active_plugins)) {
                    wp_send_json_success('active');
                } else {
                    wp_send_json_success('inactive');
                }
            } else {
                wp_send_json_success('deleted');
            }
        } elseif ($tool == 'install_plugin') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            $slug = sanitize_text_field(wp_unslash($_GET['slug'] ?? ''));

            if (isset($_GET['extra_data'])) {
                $source = sanitize_text_field(wp_unslash($_GET['extra_data']['source'] ?? ''));
            } else {
                $source = 'repo';
            }

            @include_once ABSPATH . 'wp-admin/includes/plugin.php';
            @include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
            @include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
            @include_once ABSPATH . 'wp-admin/includes/file.php';
            @include_once ABSPATH . 'wp-admin/includes/misc.php';

            wp_cache_flush();

            $path = $this->get_plugin_path($slug);

            if (false !== $path) {
                // Plugin is already installed
                wp_send_json_success();
            } else {
                // Install Plugin

                $skin      = new WP_Ajax_Upgrader_Skin();
                $upgrader = new Plugin_Upgrader($skin);

                if ($source == 'zip') {
                    $collection_item = $wp_reset_collections->get_collection_item_details(sanitize_text_field(wp_unslash($_GET['extra_data']['collection_id'] ?? '')), sanitize_text_field(wp_unslash($_GET['extra_data']['collection_item_id'] ?? '')));

                    if ($collection_item['location'] == 'wpreset') {
                        $upgrader->install(trailingslashit($wp_reset_cloud->cloud_url) . $collection_item['zip_url']);
                    } else if ($collection_item['location'] == 'pcloud' || $collection_item['location'] == 'pcloudeu') {
                        global $wp_reset_cloud;

                        if (($options['cloud_service'] != 'pcloud' && $options['cloud_service'] != 'pcloudeu') || is_wp_error($wp_reset_cloud->get_pcloud_client())) {
                            set_transient('wf_install_error_' . $slug, 'You need to connect to the pCloud account where this collection item is stored in order to install it.', 300);
                        }

                        try {
                            $pcloudFile = new pCloud\File();
                            $download_result = $pcloudFile->download((int)$collection_item['zip_filename'], $this->export_dir_path());
                            $url = $this->export_dir_path(basename($download_result), true);
                        } catch (Exception $e) {
                            echo esc_html($e->getMessage());
                            set_transient('wf_install_error_' . $slug, 'This collection item was not found in the current pCloud account. Please connect to the pCloud account you used when adding it. ' . $e->getMessage(), 300);
                        }

                        $upgrader->install($url);
                    } else if ($collection_item['location'] == 'icedrive') {
                        global $wp_reset_cloud;

                        if ($options['cloud_service'] != 'icedrive' || is_wp_error($wp_reset_cloud->get_pcloud_client())) {
                            set_transient('wf_install_error_' . $slug, 'You need to connect to the Icedrive account where this collection item is stored in order to install it.', 300);
                        }

                        try {
                            $icedrive_client = $wp_reset_cloud->get_icedrive_client();
                            $icedriveFile = $icedrive_client->request('GET', $collection_item['zip_filename']);
                            file_put_contents($this->export_dir_path(basename($collection_item['zip_filename'])), $icedriveFile['body']);

                            $url = $this->export_dir_path(basename($collection_item['zip_filename']), true);
                        } catch (Exception $e) {
                            set_transient('wf_install_error_' . $slug, 'This collection item was not found in the current Icedrive account. Please connect to the Icedrive account you used when adding it. ' . $e->getMessage(), 300);
                        }

                        $upgrader->install($url);
                    } else {
                        $upgrader->install($collection_item['zip_url']);
                    }
                } else {
                    $upgrader->install('https://downloads.wordpress.org/plugin/' . $slug . '.latest-stable.zip');
                }

                wp_send_json_success();
            }
        } elseif ($tool == 'activate_plugin') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            if (isset($_GET['extra_data'])) {
                $source = sanitize_text_field(wp_unslash($_GET['extra_data']['source'] ?? 'unknown'));
            } else {
                $source = 'repo';
            }

            $path = $this->get_plugin_path(sanitize_text_field(wp_unslash($_GET['slug'] ?? '')));
            activate_plugin($path);

            wp_send_json_success();
        } elseif (
            $tool == 'activate_license_plugin' ||
            $tool == 'activate_license_theme'
        ) {
            $result = $wp_reset_collections->license_activation(sanitize_text_field(wp_unslash($_GET['slug'] ?? '')), sanitize_text_field(wp_unslash($_GET['extra_data']['license_key'] ?? '')));
            if (is_wp_error($result)) {
                wp_send_json_error($result->get_error_message());
            } else {
                if ($result !== false) {
                    wp_send_json_success($result);
                }
            }
            wp_send_json_error('unknown');
        } elseif (
            $tool == 'check_activate_license_plugin' ||
            $tool == 'check_activate_license_theme'
        ) {
            $result = $wp_reset_collections->check_license_activation(sanitize_text_field(wp_unslash($_GET['slug'] ?? '')));
            if (is_wp_error($result)) {
                wp_send_json_error($result->get_error_message());
            } else {
                if ($result !== false) {
                    wp_send_json_success($result);
                }
            }
            wp_send_json_error('unknown');
        } elseif ($tool == 'deactivate_plugin') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            $path = $this->get_plugin_path(sanitize_text_field(wp_unslash($_GET['slug'] ?? '')));
            if ($path !== false) {
                deactivate_plugins($path, false, false);
                wp_send_json_success();
            } else {
                wp_send_json_error();
            }
        } elseif ($tool == 'delete_plugin') {
            $GLOBALS['wpr_autosnapshot_done'] = true;

            $path = $this->get_plugin_path(sanitize_text_field(wp_unslash($_GET['slug'] ?? '')));
            if ($path !== false) {
                delete_plugins(array($path));
                wp_send_json_success();
            } else {
                wp_send_json_error();
            }
        } else if ($tool == 'autouploader_step') {
            wp_send_json_success($this->autosnapshots_uploader_step());
        } else if ($tool == 'autouploader_status') {
            global $wp_reset_cloud;
            $autouploader = $this->get_autouploader();
            $snapshots = $this->get_snapshots();
            $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();
            $statuses = array();

            foreach ($autouploader['snapshots'] as $uid => $snapshot) {
                $statuses[$uid] = array(
                    'status' => $snapshot['status'],
                    'message' => @$snapshot['message']
                );
            }

            foreach ($cloud_snapshots as $uid => $snapshot) {
                $statuses[$uid] = array(
                    'status' => 'finished',
                    'message' => 'Finished uploading'
                );
            }
            wp_send_json_success($statuses);
        } else {
            wp_send_json_error(esc_html__('Unknown tool.', 'wp-reset'));
        }
    } // ajax_run_tool

    function autosnapshots_uploader_reset()
    {
        $autouploader = array('snapshots' => array(), 'current_snapshot' => false, 'status' => 'waiting', 'message' => 'Waiting for new autosnapshots');
        $this->update_options('autouploader', $autouploader);

        if (!empty($_GET['redirect'])) { //phpcs:ignore
            wp_safe_redirect(sanitize_url(wp_unslash($_GET['redirect']))); //phpcs:ignore
        }

        return true;
    }

    function autosnapshots_uploader_step()
    {
        global $wp_reset_cloud;

        $options = $this->get_options();
        $snapshots = $this->get_snapshots();
        $autouploader = $this->get_autouploader();

        $return_message = '';
        //$autouploader = array('snapshots' => array(), 'current_snapshot' => false);
        $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();

        if (array_key_exists('started', $autouploader) && $autouploader['started'] > 0) {
            if ($autouploader['started'] + 300 > time()) {
                return array('uid' => $autouploader['current_snapshot'], 'status' => $autouploader['status'], 'message' => $autouploader['message']);
            } else {
                $autouploader['status'] = 'error';
                if ($autouploader['current_snapshot'] != false) {
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'error';
                }
                $autouploader['message'] = 'Unknow error occured' . (isset($autouploader['snapshots'][$autouploader['current_snapshot']]['message']) ? ' last message was: ' . $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] : '');
                $autouploader['current_snapshot'] = false;
                $autouploader['started'] = false;
                $this->update_options('autouploader', $autouploader);
                return array('uid' => $autouploader['current_snapshot'], 'status' => 'error', 'message' => $autouploader['message']);
            }
        }

        if (!array_key_exists('snapshots', $autouploader)) {
            $autouploader['snapshots'] = array();
        }

        foreach ($snapshots as $snapshot) {
            if (array_key_exists('autoupload', $snapshot) && $snapshot['autoupload'] == true) {
                if (!array_key_exists($snapshot['uid'], $autouploader['snapshots']) && !array_key_exists($snapshot['uid'], $cloud_snapshots)) {
                    $this->log('error', 'Autouploader: Add snapshot ID ' . $snapshot['uid'] . ' to queue');
                    $autouploader['snapshots'][$snapshot['uid']] = array(
                        'steps' => array(),
                        'status' => 'pending',
                        'message' => 'Pending upload'
                    );
                }
            }
        }

        foreach ($autouploader['snapshots'] as $uid => $snapshot) {
            if (!array_key_exists($uid, $snapshots)) {
                unset($autouploader['snapshots'][$uid]);
            }
        }
        $this->update_options('autouploader', $autouploader);

        if (count($autouploader['snapshots']) == 0) {
            $autouploader = array('snapshots' => array(), 'current_snapshot' => false, 'status' => 'waiting', 'message' => 'Waiting for new autosnapshots');
            $this->update_options('autouploader', $autouploader);
            $this->log('info', 'Autouploader: No snapshots pending');
            return false;
        }


        if ($autouploader['current_snapshot'] == false || !array_key_exists($autouploader['current_snapshot'], $autouploader['snapshots']) || $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] == 'pending' || $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] == 'error' || !array_key_exists($autouploader['current_snapshot'], $autouploader['snapshots'])) {
            $autouploader['current_snapshot'] = false;
            foreach ($autouploader['snapshots'] as $uid => $snapshot) {
                if ($snapshot['status'] != 'error') {
                    $autouploader['current_snapshot'] = $uid;
                    break;
                }
            }

            if ($autouploader['current_snapshot'] == false) {
                return;
            }

            $this->log('info', 'Autouploader: New snapshot to process ' . $autouploader['current_snapshot']);

            $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'exporting';
            $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'] = array();
            $autouploader['snapshots'][$autouploader['current_snapshot']]['current_step'] = false;

            $autouploader['status'] = 'exporting';
            $autouploader['message'] = 'Preparing to export snapshot';
            $autouploader['started'] = time();
            $this->update_options('autouploader', $autouploader);
            $res = $this->do_export_snapshot($autouploader['current_snapshot'], true);

            if (is_wp_error($res)) {
                $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'error';
                $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = $res->get_error_message();
                $autouploader['status'] = 'error';
                $autouploader['message'] = 'Error preparing snapshot <strong>' . $autouploader['current_snapshot'] . '</strong> for export: ' . $res->get_error_message();
            } else {
                foreach ($res['tbl_names'] as $table) {
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][] = array('uid' => $res['uid'], 'action' => 'export', 'data' => $table, 'description' => 'Exporting table <i>' . $table . '</i>');
                }
                $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][] = array('uid' => $res['uid'], 'action' => 'verify_export', 'data' => false, 'description' => 'Verifying exported snapshot');
            }
        }

        if ($autouploader['current_snapshot'] == false) {
            $this->log('info', 'Autouploader: No snapshot to autoupload');
            return false;
        }

        if ($autouploader['snapshots'][$autouploader['current_snapshot']]['status'] == 'exporting') {
            //Get current step
            if (count($autouploader['snapshots'][$autouploader['current_snapshot']]['steps']) > 0) {
                $current_step = $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0];
                switch ($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['action']) {
                    case 'export':
                        $autouploader['status'] = 'exporting';
                        $autouploader['message'] = 'Exporting table ' . $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['data'];
                        $this->log('info', 'Autouploader: Exporting table ' . $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['data']);
                        break;
                    case 'verify_export':
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'exporting';
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Verifying exported file';
                        $this->log('info', 'Autouploader: Verifying exported file');
                        break;
                }

                $autouploader['started'] = time();
                $this->update_options('autouploader', $autouploader);
                $res = $this->export_snapshot_step($current_step);
                $autouploader['started'] = false;

                if (is_wp_error($res)) {
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'error';
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = $res->get_error_message();
                } else {
                    switch ($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['action']) {
                        case 'export':
                            $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'exporting';
                            $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Exported table ' . $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['data'];
                            break;
                        case 'verify_export':
                            $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'exporting';
                            $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Verified exported file';
                            break;
                    }
                    unset($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]);
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'] = array_values($autouploader['snapshots'][$autouploader['current_snapshot']]['steps']);
                }
            } else {
                $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'uploading';
            }
        }

        if ($autouploader['snapshots'][$autouploader['current_snapshot']]['status'] == 'uploading') {
            if (count($autouploader['snapshots'][$autouploader['current_snapshot']]['steps']) == 0) {
                $this->log('info', 'Autouploader: Preparing cloud upload');
                $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'uploading';
                $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Preparing snapshot for upload';
                $autouploader['started'] = time();
                $this->update_options('autouploader', $autouploader);
                $res = $wp_reset_cloud->cloud_action(array('action' => 'snapshot_upload', 'parameters' => $autouploader['current_snapshot']));
                if (is_wp_error($res)) {
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'error';
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = $res->get_error_message();
                    $this->log('error', 'Error preparing snapshot for upload ' . $res->get_error_message());
                } else {
                    if ($res['continue'] == 1) {
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0] = $res;
                    }
                    $this->log('info', 'Autouploader: Cloud response is ' . serialize($res));
                    if ($res['action'] == 'snapshots_refresh') {
                        $wp_reset_cloud->cloud_action(array('action' => 'snapshots_refresh'));
                    }
                }
                $autouploader['started'] = false;
            } else {
                $this->log('info', 'Autouploader: Running cloud step ' . serialize($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]));
                $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'uploading';
                $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Uploading ... ';
                switch ($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['action']) {
                    case 'snapshots_refresh':
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Refreshing cloud snapshots';
                        break;
                    case 'upload_part':
                        if (!empty($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['message'])) {
                            $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['message'];
                        }
                        break;
                    case 'snapshot_delete_local':
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Deleting local copy of snapshot';
                        break;
                }
                $autouploader['started'] = time();
                $this->update_options('autouploader', $autouploader);
                $res = $wp_reset_cloud->cloud_action($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]);
                $do_refresh = false;
                if ($autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0]['action'] == 'snapshot_delete_local') {
                    $do_refresh = true;
                }
                if (is_wp_error($res)) {
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'error';
                    $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = $res->get_error_message();
                    $this->log('error', 'Cloud error: ' . serialize($res));
                } else {
                    if (is_array($res) && array_key_exists('continue', $res) && $res['continue'] == 1 && $res['action'] != 'snapshots_refresh') {
                        $this->log('info', 'Cloud next step: ' . serialize($res));
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['steps'][0] = $res;
                    } else {
                        $wp_reset_cloud->cloud_action(array('action' => 'snapshots_refresh'));
                        $this->log('info', 'Cloud: Snapshot uploaded');
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['status'] = 'finished';
                        $autouploader['snapshots'][$autouploader['current_snapshot']]['message'] = 'Snapshot ' . $autouploader['current_snapshot'] . ' uploaded sucessfully!';
                    }
                }
                if ($do_refresh) {
                    $this->log('info', 'Autouploader: Refreshing snapshots');
                    $wp_reset_cloud->cloud_action(array('action' => 'snapshots_refresh'));
                }
                $autouploader['started'] = false;
            }
        }

        if ($autouploader['snapshots'][$autouploader['current_snapshot']]['status'] == 'finished') {
            if ($options['snapshots_upload_delete'] != true && array_key_exists($autouploader['current_snapshot'], $snapshots)) {
                unset($snapshots[$autouploader['current_snapshot']]['autoupload']);
                update_option('wp-reset-snapshots', $snapshots);
            }

            $res = $wp_reset_cloud->cloud_action(array('action' => 'snapshots_refresh'));
            if (is_wp_error($res)) {
                $this->log('error', 'Cloud: Error refreshing snapshots');
            } else {
                $this->log('info', 'Cloud: Snapshots Refreshed!');
            }
            unset($autouploader['snapshots'][$autouploader['current_snapshot']]);
            $autouploader['current_snapshot'] = false;
        }

        $this->update_options('autouploader', $autouploader);
        return array('uid' => $autouploader['current_snapshot'], 'status' => $autouploader['status'], 'message' => $autouploader['message']);
    }

    /**
     * Get full path, file name or URL of recovery script installed in wp-content
     *
     * @return string
     */
    function wpr_staging_path($url = false, $uid = false)
    {
        $wp_root_dir = trailingslashit(ABSPATH);
        $staging_path = $wp_root_dir . 'wpr_staging';

        if ($url == true) {
            return site_url('/wpr_staging/' . $uid . '/');
        }

        if (!is_dir($staging_path)) {
            $folder = wp_mkdir_p($staging_path);
            if (!$folder) {
                return new WP_Error(1, 'Unable to create ' . $folder . ' folder.');
            }
        }

        if ($uid !== false) {
            $staging_path = trailingslashit($staging_path) . $uid;
        }

        return trailingslashit($staging_path);
    } // wpr_recovery_path

    /**
     * Get full path, file name or URL of recovery script installed in wp-content
     *
     * @return string
     */
    function wpr_recovery_path($url = false, $full = false)
    {
        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);
        $search = glob($wp_content_dir . 'wpr_recovery_*');

        if (empty($search)) {
            return false;
        }

        if ($full) {
            return $search[0];
        }

        if ($url) {
            return content_url(basename($search[0]));
        }

        return basename($search[0]);
    } // wpr_recovery_path


    /**
     * Intall recovery script
     *
     * @return bool|wp_error
     */
    function do_install_recovery()
    {
        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);

        $recovery_filename = $this->wpr_recovery_path();
        if (!$recovery_filename) {
            $recovery_filename = 'wpr_recovery_' . substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz', 32)), 0, 32) . '.php';
        }

        $recovery_files = new ZipArchive;
        $recovery_files->open(dirname(WP_RESET_FILE) . '/misc/wpr_recovery.zip');

        if ($recovery_file_contents = $recovery_files->getFromName('wpr_recovery.php')) {
            file_put_contents($wp_content_dir . $recovery_filename, $recovery_file_contents);
        } else {
            return new WP_Error('1', 'An error occurred extracting the recovery script.');
        }

        if (!file_exists($wp_content_dir . $recovery_filename)) {
            return new WP_Error('1', 'An error occurred writing the recovery script to wp-content.');
        }

        $password = substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz', 8)), 0, 8);
        $this->update_recovery_password($password);

        return array('url' => content_url($recovery_filename), 'pass' => $password);
    } // do_install_recovery


    /**
     * Update recovery script
     *
     * @return bool|wp_error
     */
    function do_update_recovery()
    {
        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);

        $recovery_filename = $this->wpr_recovery_path();
        if (!$recovery_filename) {
            $recovery_filename = 'wpr_recovery_' . substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz', 32)), 0, 32) . '.php';
            $recovery_password = substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz', 8)), 0, 8);
        } else {
            $recovery_password = $this->get_recovery_password();
        }

        $recovery_files = new ZipArchive;
        $recovery_files->open(dirname(WP_RESET_FILE) . '/misc/wpr_recovery.zip');

        if ($recovery_file_contents = $recovery_files->getFromName('wpr_recovery.php')) {
            file_put_contents($wp_content_dir . $recovery_filename, $recovery_file_contents);
        } else {
            return new WP_Error('1', 'An error occurred extracting the recovery script.');
        }

        if (!file_exists($wp_content_dir . $recovery_filename)) {
            return new WP_Error('1', 'An error occurred writing the recovery script to wp-content.');
        }

        $this->update_recovery_password($recovery_password);

        return array('url' => content_url($recovery_filename), 'pass' => $recovery_password);
    } // do_install_recovery


    /**
     * Uninstall recovery script
     *
     * @return bool|wp_error
     */
    function do_uninstall_recovery()
    {
        $recovery_filename = $this->wpr_recovery_path(false, true);

        wp_delete_file($recovery_filename);

        if (file_exists($recovery_filename)) {
            return new WP_Error('1', 'Could not delete ' . $recovery_filename);
        }

        return true;
    } // do_uninstall_recovery

    /**
     * Intall/update recovery script
     *
     * @return bool|wp_error
     */
    function send_recovery_script_email($admin_email)
    {
        $headers = array('Content-Type: text/html; charset=UTF-8');
        $body = '<b>Thank you for installing WP Reset!<br>';

        $body = 'WP Reset also provides you with a standalone Emergency recovery script that you can upload to your website to fix your WordPress installation if it is no longer accessible, such as in a White Screen of Death (WSOD) situation<br />';
        $body .= 'The Emergency recovery script provides you with the following tools:<br />';
        $body .= '<ul>';
        $body .= '<li>View server information</li>';
        $body .= '<li>Scan WordPress core files for changes</li>';
        $body .= '<li>Reinstall WordPress core files</li>';
        $body .= '<li>Activate/Deactivate plugins</li>';
        $body .= '<li>Deactivate themes</li>';
        $body .= '<li>Reset user database prefix</li>';
        $body .= '<li>Create Administator accounts</li>';
        $body .= '<li>Update WordPress Address and Site Address</li>';
        $body .= '</ul>';

        $body .= '<br /><br />';

        $recovery_url = $this->wpr_recovery_path(true);
        if ($recovery_url !== false) {
            $body .= 'The Emergency recovery script has been installed on your website and you can access it here: <br />';
            $body .= '<a href="' . $recovery_url . '" target="_blank">' . $recovery_url . '</a>';
            $recovery_password = $this->get_recovery_password();
            $body .= '<br />The password for the Emergency recovery script is ' . (!empty($recovery_password) ? '<strong style="color:#F00">' . $recovery_password . '</strong>' : '<strong style="color:#F00">not set</strong>');
            $body .= '<br /><br />';
        }

        $body .= 'To install the WP Reset Emergency recovery script follow these instructions:';
        $body .= '<ul>';
        $body .= '<li><a href="' . trailingslashit($this->licensing_servers[0]) . 'wpr_recovery_download.php">Download the WP Reset Emergency recovery script</a></li>';
        $body .= '<li>In order to prevent unauthorized acccess, the downloaded script already has a random, unique file name but if you wish you can rename it</li>';
        $body .= '<li>Upload the recovery script to your website via FTP or though your Hostin Provider\'s Control Panel</li>';
        $body .= '<li>Open the recovery script and perform the recovery actions you need</li>';
        $body .= '</ul>';

        if (!wp_mail($admin_email, 'WP Reset Emergency recovery', $body, $headers)) {
            return new WP_Error('1', 'An error occurred sending the email with the Emergency recovery script installation instructions.');
        }

        return true;
    }


    function get_recovery_password()
    {
        $constant = 'WPR_RECOVERY_PASS';

        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);

        $recovery_filename = $wp_content_dir . $this->wpr_recovery_path();

        if (!$recovery_filename) {
            return new WP_Error('1', 'Emergency recovery script not installed.');
        }

        $file_contents = file_get_contents($recovery_filename);

        preg_match_all('/define\(\s*[\'|"]\s*(' . $constant . ')\s*[\'|"]\s*,\s*(false|true|[\'|"].*[\'|"])\s*\);/i', $file_contents, $matches);

        if (count($matches[2]) > 0) {
            return trim(trim($matches[2][0], '\''), '"');
        } else {
            return false;
        }
    }


    function get_recovery_version()
    {
        $constant = 'WPR_RECOVERY_VER';

        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);

        $recovery_filename = $wp_content_dir . $this->wpr_recovery_path();

        if (!$recovery_filename) {
            return new WP_Error('1', 'Emergency recovery script not installed.');
        }

        $file_contents = file_get_contents($recovery_filename);

        preg_match_all('/define\(\s*[\'|"]\s*(' . $constant . ')\s*[\'|"]\s*,\s*(false|true|[\'|"].*[\'|"])\s*\);/i', $file_contents, $matches);

        if (count($matches[2]) > 0) {
            return trim(trim($matches[2][0], '\''), '"');
        } else {
            return '1.0';
        }
    }


    function update_recovery_password($new_value)
    {
        $constant = 'WPR_RECOVERY_PASS';

        $wp_content_dir = trailingslashit(WP_CONTENT_DIR);

        $recovery_filename = $wp_content_dir . $this->wpr_recovery_path();

        if (!$recovery_filename) {
            return new WP_Error('1', 'Emergency recovery script not installed.');
        }

        $file_contents = file_get_contents($recovery_filename);

        // if define already exists update it
        if (preg_match_all('/define\([\'|"]\s*(' . $constant . ')\s*[\'|"]\s*,\s*(false|true|[\'|"].*[\'|"])\s*\);/i', $file_contents, $matches)) {
            if (is_bool($new_value)) {
                if ($new_value) {
                    $file_contents = str_replace($matches[0], "define('" . $constant . "', true);", $file_contents);
                } else {
                    $file_contents = str_replace($matches[0], "define('" . $constant . "', false);", $file_contents);
                }
            } else {
                $file_contents = str_replace($matches[0], "define('" . $constant . "', '" . $new_value . "');", $file_contents);
            }
            file_put_contents($recovery_filename, $file_contents, LOCK_EX);
        } else {
            // if define does not exists insert it in a new line before require_once(ABSPATH.'wp-settings.php');
            if (is_bool($new_value)) {
                if ($new_value) {
                    $new_define_line_contents = 'define(\'' . $constant . '\', true);';
                } else {
                    $new_define_line_contents = 'define(\'' . $constant . '\', false);';
                }
            } else {
                $new_define_line_contents = 'define(\'' . $constant . '\', \'' . $new_value . '\');';
            }

            $config_file = file($recovery_filename);
            foreach ($config_file as $line_num => $line) {
                if (strpos(str_replace(' ', '', str_replace('"', '\'', $line)), 'require_once(ABSPATH.\'wp-settings.php\');') !== false) {
                    $wp_settings_require_line = $line_num;
                    break;
                }
            }
            array_splice($config_file, $wp_settings_require_line, 0, $new_define_line_contents . PHP_EOL);
            file_put_contents($recovery_filename, implode('', $config_file), LOCK_EX);
        }
    } // update_define

    /**
     * Reinstall / reset the WP site
     * There are no failsafes in the function - it reinstalls when called
     * Redirects when done if not CLI
     *
     * @param array  $params  Optional.
     *
     * @return null
     */
    function do_reinstall($params = array())
    {
        global $current_user, $wpdb;

        // only admins can reset; double-check
        if (!$this->is_cli_running() && !current_user_can('administrator')) {
            return new WP_Error('1', 'Only administrators can reset.');
        }

        $params = shortcode_atts(array('reactivate_webhooks' => false, 'reactivate_theme' => false, 'reactivate_wpreset' => false, 'reactivate_plugins' => false, 'mainwp_reset' => false, 'user_id' => false, 'user_name' => false), (array) $params);

        // make sure the function is available to us
        if (!function_exists('wp_install')) {
            require ABSPATH . 'wp-admin/includes/upgrade.php';
        }

        $GLOBALS['wpr_autosnapshot_done'] = true;
        do_action('wp_reset_before_reset_site', $params);

        // save values that need to be restored after reset
        $blogname = get_option('blogname');
        $blog_public = get_option('blog_public');
        $wplang = get_option('WPLANG');
        $siteurl = get_option('siteurl');
        $home = get_option('home');
        $snapshots = $this->get_snapshots();
        $gmt_offset = get_option('gmt_offset');
        $timezone_string = get_option('timezone_string');
        $wf_licensing_wpr = get_option('wf_licensing_wpr');

        $active_plugins = get_transient('wpr_active_plugins');
        if (false === $active_plugins) {
            $active_plugins = get_option('active_plugins');
        }
        $active_theme = wp_get_theme();

        if (!empty($params['reactivate_webhooks'])) {
            $wpwh1 = get_option('wpwhpro_active_webhooks');
            $wpwh2 = get_option('wpwhpro_activate_translations');
            $wpwh3 = get_option('ironikus_webhook_webhooks');
        }

        // for WP-CLI
        if (!$current_user->ID) {
            $tmp = get_users(array('role' => 'administrator', 'order' => 'ASC', 'order_by' => 'ID'));
            if (empty($tmp[0]->user_login)) {
                return new WP_Error(1, 'Reset failed. Unable to find any admin users in database.');
            }
            $current_user = $tmp[0];
        }

        if (!empty($params['mainwp_reset'])) {
            $mainwp_child_actions_data_checked = get_option('mainwp_child_actions_data_checked');
            $mainwp_child_actions_saved_data = get_option('mainwp_child_actions_saved_data');
            $mainwp_child_actions_saved_number_of_days = get_option('mainwp_child_actions_saved_number_of_days');
            $mainwp_child_activated_once = get_option('mainwp_child_activated_once');
            $mainwp_child_auth = get_option('mainwp_child_auth');
            $mainwp_child_auto_purge_cache = get_option('mainwp_child_auto_purge_cache');
            $mainwp_child_branding_settings = get_option('mainwp_child_branding_settings');
            $mainwp_child_clone_from_server_last_folder = get_option('mainwp_child_clone_from_server_last_folder');
            $mainwp_child_clone_sites = get_option('mainwp_child_clone_sites');
            $mainwp_child_cloud_flair_enabled = get_option('mainwp_child_cloud_flair_enabled');
            $mainwp_child_connected_admin = get_option('mainwp_child_connected_admin');
            $mainwp_child_nonce = get_option('mainwp_child_nonce');
            $mainwp_child_openssl_sign_algo = get_option('mainwp_child_openssl_sign_algo');
            $mainwp_child_pingnonce = get_option('mainwp_child_pingnonce');
            $mainwp_child_plugin_version = get_option('mainwp_child_plugin_version');
            $mainwp_child_pubkey = get_option('mainwp_child_pubkey');
            $mainwp_child_server = get_option('mainwp_child_server');
            $mainwp_child_siteid = get_option('mainwp_child_siteid');
            $mainwp_child_update_version = get_option('mainwp_child_update_version');
            $mainwp_premium_updates = get_option('mainwp_premium_updates');

            $current_user = get_userdata($params['user_id']);
        }

        // delete custom tables with WP's prefix
        $prefix = str_replace('_', '\_', $wpdb->prefix);
        $tables = $wpdb->get_col("SHOW TABLES LIKE '{$prefix}%'"); //phpcs:ignore
        foreach ($tables as $table) {
            $wpdb->query('SET foreign_key_checks = 0'); //phpcs:ignore
            $wpdb->query("DROP TABLE `$table`"); //phpcs:ignore
        }

        $old_user_pass = $current_user->user_pass;

        // supress errors for WP_CLI
        $result = wp_install($blogname, $current_user->user_login, $current_user->user_email, $blog_public, '', md5(wp_rand()), $wplang);
        $user_id = $result['user_id'];

        // restore user pass
        $query = $wpdb->prepare("UPDATE {$wpdb->users} SET user_pass = %s, user_activation_key = %s WHERE ID = %d LIMIT 1", array($old_user_pass, '', $user_id)); //phpcs:ignore
        $wpdb->query($query); //phpcs:ignore
        $current_user->user_pass = $old_user_pass;

        // restore rest of the settings including WP Reset's
        update_option('siteurl', $siteurl);
        update_option('home', $home);
        update_option('wp-reset', $this->options);
        update_option('wp-reset-snapshots', $snapshots);
        update_option('gmt_offset', $gmt_offset);
        update_option('timezone_string', $timezone_string);
        update_option('wf_licensing_wpr', $wf_licensing_wpr);

        update_option($wpdb->prefix . 'user_roles', array(
            'administrator' =>
            array(
                'name' => 'Administrator',
                'capabilities' =>
                array(
                    'switch_themes' => true,
                    'edit_themes' => true,
                    'activate_plugins' => true,
                    'edit_plugins' => true,
                    'edit_users' => true,
                    'edit_files' => true,
                    'manage_options' => true,
                    'moderate_comments' => true,
                    'manage_categories' => true,
                    'manage_links' => true,
                    'upload_files' => true,
                    'import' => true,
                    'unfiltered_html' => true,
                    'edit_posts' => true,
                    'edit_others_posts' => true,
                    'edit_published_posts' => true,
                    'publish_posts' => true,
                    'edit_pages' => true,
                    'read' => true,
                    'level_10' => true,
                    'level_9' => true,
                    'level_8' => true,
                    'level_7' => true,
                    'level_6' => true,
                    'level_5' => true,
                    'level_4' => true,
                    'level_3' => true,
                    'level_2' => true,
                    'level_1' => true,
                    'level_0' => true,
                    'edit_others_pages' => true,
                    'edit_published_pages' => true,
                    'publish_pages' => true,
                    'delete_pages' => true,
                    'delete_others_pages' => true,
                    'delete_published_pages' => true,
                    'delete_posts' => true,
                    'delete_others_posts' => true,
                    'delete_published_posts' => true,
                    'delete_private_posts' => true,
                    'edit_private_posts' => true,
                    'read_private_posts' => true,
                    'delete_private_pages' => true,
                    'edit_private_pages' => true,
                    'read_private_pages' => true,
                    'delete_users' => true,
                    'create_users' => true,
                    'unfiltered_upload' => true,
                    'edit_dashboard' => true,
                    'update_plugins' => true,
                    'delete_plugins' => true,
                    'install_plugins' => true,
                    'update_themes' => true,
                    'install_themes' => true,
                    'update_core' => true,
                    'list_users' => true,
                    'remove_users' => true,
                    'promote_users' => true,
                    'edit_theme_options' => true,
                    'delete_themes' => true,
                    'export' => true,
                ),
            ),
            'editor' =>
            array(
                'name' => 'Editor',
                'capabilities' =>
                array(
                    'moderate_comments' => true,
                    'manage_categories' => true,
                    'manage_links' => true,
                    'upload_files' => true,
                    'unfiltered_html' => true,
                    'edit_posts' => true,
                    'edit_others_posts' => true,
                    'edit_published_posts' => true,
                    'publish_posts' => true,
                    'edit_pages' => true,
                    'read' => true,
                    'level_7' => true,
                    'level_6' => true,
                    'level_5' => true,
                    'level_4' => true,
                    'level_3' => true,
                    'level_2' => true,
                    'level_1' => true,
                    'level_0' => true,
                    'edit_others_pages' => true,
                    'edit_published_pages' => true,
                    'publish_pages' => true,
                    'delete_pages' => true,
                    'delete_others_pages' => true,
                    'delete_published_pages' => true,
                    'delete_posts' => true,
                    'delete_others_posts' => true,
                    'delete_published_posts' => true,
                    'delete_private_posts' => true,
                    'edit_private_posts' => true,
                    'read_private_posts' => true,
                    'delete_private_pages' => true,
                    'edit_private_pages' => true,
                    'read_private_pages' => true,
                ),
            ),
            'author' =>
            array(
                'name' => 'Author',
                'capabilities' =>
                array(
                    'upload_files' => true,
                    'edit_posts' => true,
                    'edit_published_posts' => true,
                    'publish_posts' => true,
                    'read' => true,
                    'level_2' => true,
                    'level_1' => true,
                    'level_0' => true,
                    'delete_posts' => true,
                    'delete_published_posts' => true,
                ),
            ),
            'contributor' =>
            array(
                'name' => 'Contributor',
                'capabilities' =>
                array(
                    'edit_posts' => true,
                    'read' => true,
                    'level_1' => true,
                    'level_0' => true,
                    'delete_posts' => true,
                ),
            ),
            'subscriber' =>
            array(
                'name' => 'Subscriber',
                'capabilities' =>
                array(
                    'read' => true,
                    'level_0' => true,
                ),
            ),
        ));

        // remove password nag
        if (get_user_meta($user_id, 'default_password_nag')) {
            update_user_meta($user_id, 'default_password_nag', false);
        }
        if (get_user_meta($user_id, $wpdb->prefix . 'default_password_nag')) {
            update_user_meta($user_id, $wpdb->prefix . 'default_password_nag', false);
        }

        $meta = $this->get_meta();
        $meta['reset_count']++;
        $this->update_options('meta', $meta);

        // reactivate theme
        if (!empty($params['reactivate_theme'])) {
            switch_theme($active_theme->get_stylesheet());
        }

        // reactivate WP Reset
        if (!empty($params['reactivate_wpreset'])) {
            activate_plugin(plugin_basename(__FILE__));
        }

        // reactivate WP Webhooks
        if (!empty($params['reactivate_webhooks'])) {
            activate_plugin('wp-webhooks/wp-webhooks.php');
            activate_plugin('wpwh-wp-reset-webhook-integration/wpwhpro-wp-reset-webhook-integration.php');

            update_option('wpwhpro_active_webhooks', $wpwh1);
            update_option('wpwhpro_activate_translations', $wpwh2);
            update_option('ironikus_webhook_webhooks', $wpwh3);
        }

        // reactivate all plugins
        if (!empty($params['reactivate_plugins'])) {
            foreach ($active_plugins as $plugin_file) {
                activate_plugin($plugin_file);
            }
        }

        if (!empty($params['mainwp_reset'])) {
            activate_plugin('mainwp-child/mainwp-child.php');

            update_option('mainwp_child_actions_data_checked', $mainwp_child_actions_data_checked);
            update_option('mainwp_child_actions_saved_data', $mainwp_child_actions_saved_data);
            update_option('mainwp_child_actions_saved_number_of_days', $mainwp_child_actions_saved_number_of_days);
            update_option('mainwp_child_activated_once', $mainwp_child_activated_once);
            update_option('mainwp_child_auth', $mainwp_child_auth);
            update_option('mainwp_child_auto_purge_cache', $mainwp_child_auto_purge_cache);
            update_option('mainwp_child_branding_settings', $mainwp_child_branding_settings);
            update_option('mainwp_child_clone_from_server_last_folder', $mainwp_child_clone_from_server_last_folder);
            update_option('mainwp_child_clone_sites', $mainwp_child_clone_sites);
            update_option('mainwp_child_cloud_flair_enabled', $mainwp_child_cloud_flair_enabled);
            update_option('mainwp_child_connected_admin', $params['user_name']);
            update_option('mainwp_child_nonce', $mainwp_child_nonce);
            update_option('mainwp_child_openssl_sign_algo', $mainwp_child_openssl_sign_algo);
            update_option('mainwp_child_pingnonce', $mainwp_child_pingnonce);
            update_option('mainwp_child_plugin_version', $mainwp_child_plugin_version);
            update_option('mainwp_child_pubkey', $mainwp_child_pubkey);
            update_option('mainwp_child_server', $mainwp_child_server);
            update_option('mainwp_child_siteid', $mainwp_child_siteid);
            update_option('mainwp_child_update_version', $mainwp_child_update_version);
            update_option('mainwp_premium_updates', $mainwp_premium_updates);
        }

        do_action('wp_reset_reset_site', $params);

        if (!$this->is_cli_running()) {
            wp_clear_auth_cookie();
            wp_set_auth_cookie($user_id);

            return admin_url() . '?wp-reset=success';
        }
    } // do_reinstall


    /**
     * Add "Open WP Reset Tools" action link to plugins table, left part
     *
     * @param array  $links  Initial list of links.
     *
     * @return array
     */
    function plugin_action_links($links)
    {
        $plugin_tools_title = $this->get_rebranding('name');
        if ($plugin_tools_title !== false) {
            $plugin_tools_title = 'Open ' . $plugin_tools_title . ' Tools';
        } else {
            $plugin_tools_title = 'Open WP Reset PRO Tools';
        }
        $settings_link = '<a href="' . admin_url('tools.php?page=wp-reset') . '" title="' . $plugin_tools_title . '">' . esc_html__('Open Reset Tools', 'wp-reset') . '</a>';

        array_unshift($links, $settings_link);

        return $links;
    } // plugin_action_links


    /**
     * Add links to plugin's description in plugins table
     *
     * @param array  $links  Initial list of links.
     * @param string $file   Basename of current plugin.
     *
     * @return array
     */
    function plugin_meta_links($links, $file)
    {
        if ($file !== plugin_basename(__FILE__)) {
            return $links;
        }

        if (!WP_Reset_Utility::whitelabel_filter()) {
            unset($links[1]);
            unset($links[2]);
            return $links;
        }

        $support_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right', '/support/') . '" title="' . esc_html__('Get help', 'wp-reset') . '">' . esc_html__('Support', 'wp-reset') . '</a>';
        $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . esc_html__('Plugin Homepage', 'wp-reset') . '">' . esc_html__('Plugin Homepage', 'wp-reset') . '</a>';

        $links[] = $support_link;
        $links[] = $home_link;

        return $links;
    } // plugin_meta_links


    /**
     * Test if we're on WPR's admin page
     *
     * @return bool
     */
    function is_plugin_page()
    {
        $current_screen = get_current_screen();

        if (!empty($current_screen) && $current_screen->id == 'tools_page_wp-reset') {
            return true;
        } else {
            return false;
        }
    } // is_plugin_page


    /**
     * Add powered by text in admin footer
     *
     * @param string  $text  Default footer text.
     *
     * @return string
     */
    function admin_footer_text($text)
    {
        if (!$this->is_plugin_page() || !WP_Reset_Utility::whitelabel_filter()) {
            return $text;
        }

        if ($this->get_rebranding() !== false) {
            $text = $this->get_rebranding('footer_text');
        } else {
            $text = '<i class="wpr-footer"><a href="' . $this->generate_web_link('admin_footer') . '" title="' . esc_html__('Visit WP Reset page for more info', 'wp-reset') . '" target="_blank">WP Reset PRO</a> v' . $this->version . '</i>';
        }


        return $text;
    } // admin_footer_text


    /**
     * Loads plugin's translated strings
     *
     * @return null
     */
    function load_textdomain()
    {
        load_plugin_textdomain('wp-reset'); //phpcs:ignore
    } // load_textdomain


    /**
     * Inform the user that WordPress has been successfully reset
     *
     * @return null
     */
    function notice_successful_reset()
    {
        global $current_user;

        // only admins can see notifications
        if (!current_user_can('administrator')) {
            return;
        }

        if (!empty($_GET['wp-reset']) && sanitize_text_field(wp_unslash($_GET['wp-reset'])) == 'success') { //phpcs:ignore
            /* translators: %1$s is the user, %2$s WP Reset settings page URL */
            self::wp_kses_wf('<div id="message" class="updated"><p>' . sprintf(__('<b>Site has been reset</b> to default settings. User "%1$s" was restored with the password unchanged. Open <a href="%2$s">WP Reset PRO</a> to do another reset.', 'wp-reset'), $current_user->user_login, admin_url('tools.php?page=wp-reset')) . '</p></div>');
        }
    } // notice_successful_reset


    /**
     * Generate a button that initiates backup creation and download
     *
     * @param string  $description  Snapshot description; if set user won't be asked for one
     *
     * @return string
     */
    function get_create_snapshot_button($description = '')
    {
        $out = '';
        $out .= '<a data-snapshot-description="' . $description . '" class="button tools-create-snapshot" href="#">Create snapshot 2</a>';

        return $out;
    } // get_create_snapshot_button


    /**
     * Generate card header including title and action buttons
     *
     * @param string  $title  Card title.
     * @param string  $card_id  Card element #ID.
     * @param array  $params  Individual icons arguments
     *
     * @return string
     */
    function get_card_header($title, $card_id, $params = array())
    {
        $params = shortcode_atts(array('documentation_link' => false, 'iot_button' => false, 'collapse_button' => false, 'create_snapshot' => false, 'deploy_stage' => false), (array) $params);

        if (false == WP_Reset_Utility::whitelabel_filter() || $this->get_rebranding() !== false) {
            $params['documentation_link'] = false;
        }

        $out = '';
        $out .= '<h4 id="' . $card_id . '"><span class="card-name">' . htmlspecialchars($title) . '</span>';
        $out .= '<div class="card-header-right">';
        if ($params['documentation_link']) {
            $out .= '<a data-tool-title="' . esc_attr($title) . '" class="documentation-link" href="#" title="' . esc_html__('Open documentation for this tool', 'wp-reset') . '" data-tooltip="' . __('Open documentation for this tool', 'wp-reset') . '"><span class="dashicons dashicons-editor-help"></span></a>';
        }
        if ($params['iot_button']) {
            $out .= '<a class="scrollto" href="#iot" title="Jump to Index of Tools" data-tooltip="Jump to Index of Tools"><span class="dashicons dashicons-screenoptions"></span></a>';
        }
        if ($params['create_snapshot']) {
            $out .= '<a id="create-new-snapshot-primary" title="Create a new snapshot" href="#" class="button button-primary create-new-snapshot">' . esc_html__('Create Snapshot', 'wp-reset') . '</a>';
        }

        if ($params['deploy_stage']) {
            $out .= '<a id="deploy-new-stage" title="Deploy a new staging copy" href="#" class="button button-primary deploy-new-stage">' . esc_html__('Deploy New Staging Copy', 'wp-reset') . '</a>';
        }

        if ($params['collapse_button']) {
            $out .= '<a class="toggle-card" href="#" title="' . esc_html__('Collapse / expand box', 'wp-reset') . '" data-tooltip="' . esc_html__('Collapse / expand box', 'wp-reset') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
        }
        $out .= '</div></h4>';

        return $out;
    } // get_card_header


    /**
     * Generates the complete TR for a single snapshot
     *
     * @param array $ss Snapshot array with all params
     * @param string $ss_type user or auto
     * @return string
     */
    function get_snapshot_row($ss, $ss_type = 'user')
    {
        $options = $this->get_options();

        $out = '';

        if (!empty($ss['name'])) {
            $name = $ss['name'];
        } else {
            $name = 'created on ' . gmdate(get_option('date_format'), strtotime($ss['timestamp'])) . ' @ ' . gmdate(get_option('time_format'), strtotime($ss['timestamp']));
        }

        $out .= '<tr ';

        if (isset($ss['partial']) && $ss['partial'] == true) {
            $out .= 'class="snapshot-partial"';
        }

        $out .= ' id="wpr-ss-' . $ss['uid'] . '" data-ss-uid="' . $ss['uid'] . '" data-ss-type="' . $ss_type . '">';
        $out .= '<td class="ss-checkbox"><input type="checkbox" name="' . ($ss['auto'] == true ? 'selected_autosnapshots' : 'selected_snapshots') . '" value="' . $ss['uid'] . '" /> </td>';
        $out .= '<td class="ss-date">';
        if (current_time('timestamp') - strtotime($ss['timestamp']) > 12 * HOUR_IN_SECONDS) {
            $out .= gmdate(get_option('date_format'), strtotime($ss['timestamp'])) . '<br>@ ' . gmdate(get_option('time_format'), strtotime($ss['timestamp']));
        } else {
            $out .= human_time_diff(strtotime($ss['timestamp']), current_time('timestamp')) . ' ago';
        }
        $out .= '</td>';

        $out .= '<td class="ss-name">';
        if (!empty($ss['name'])) {
            $out .= '<b class="snapshot-name">' . htmlspecialchars(html_entity_decode($ss['name'])) . '</b>';
        } else {
            $out .= '<b class="snapshot-name"><i>Click to add description.</i></b>';
        }
        $out .= '<br>';
        $out .= 'Contains ' . $ss['tbl_core'] . ' core ';
        if ($ss['tbl_custom']) {
            $out .= '&amp; ' . $ss['tbl_custom'] . ' custom table' . ($ss['tbl_custom'] == 1 ? '' : 's');
        } else {
            $out .= 'tables';
        }
        $out .= ' totaling ' . number_format($ss['tbl_rows']) . ' rows';
        if (!empty($ss['plugins'])) {
            $out .= ' as well as files for ' . sizeof($ss['plugins']) . ' plugin' . (sizeof($ss['plugins']) == 1 ? '' : 's') . ': ' . implode(', ', array_keys($ss['plugins']));;
        }
        if (!empty($ss['themes'])) {
            $out .= ' as well as files for ' . sizeof($ss['themes']) . ' theme' . (sizeof($ss['themes']) == 1 ? '' : 's') . ': ' . implode(', ', array_keys($ss['themes']));;
        }

        $out .= '<br />';

        if (isset($ss['local']) && $ss['local'] == true) {
            $out .= '<span title="Available locally" data-tooltip="Available locally" class="dashicons"><svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="hdd" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class="svg-inline--fa fa-hdd fa-w-18 fa-3x"><path fill="currentColor" d="M567.403 235.642L462.323 84.589A48 48 0 0 0 422.919 64H153.081a48 48 0 0 0-39.404 20.589L8.597 235.642A48.001 48.001 0 0 0 0 263.054V400c0 26.51 21.49 48 48 48h480c26.51 0 48-21.49 48-48V263.054c0-9.801-3-19.366-8.597-27.412zM153.081 112h269.838l77.913 112H75.168l77.913-112zM528 400H48V272h480v128zm-32-64c0 17.673-14.327 32-32 32s-32-14.327-32-32 14.327-32 32-32 32 14.327 32 32zm-96 0c0 17.673-14.327 32-32 32s-32-14.327-32-32 14.327-32 32-32 32 14.327 32 32z" class=""></path></svg></span>';
        }
        if (isset($ss['cloud']) && $ss['cloud'] == true) {
            $out .= '<span title="Available in the cloud" data-tooltip="Available in the cloud" class="dashicons dashicons-cloud"></span>';
        }

        $out .= '</td>';

        $out .= '<td class="ss-size">';
        $tmp = $ss['tbl_size'];
        if (!empty($ss['file_size'])) {
            $tmp += $ss['file_size'];
        }
        $out .= WP_Reset_Utility::format_size($tmp);
        $out .= '</td>';

        $out .= '<td class="ss-actions">';
        $out .= '<div class="dropdown">
        <a class="button dropdown-toggle" href="#">Actions</a>
        <div class="dropdown-menu">';
        $menu_item = false;
        if (!(isset($ss['partial']) && $ss['partial'] == true)) {
            if (isset($ss['local']) && $ss['local'] == true) {
                $menu_item = true;
                $out .= '<a data-btn-confirm="Restore snapshot" data-text-wait="Restoring snapshot. Please wait." data-text-confirm="Are you sure you want to restore the selected snapshot? There is NO UNDO.' . esc_html('<br>') . 'Restoring the snapshot will delete all current standard and custom tables and replace them with tables from the snapshot." data-text-done="Snapshot has been restored. Click OK to reload the page with new data." title="Restore snapshot by overwriting current database tables" href="#" class="ss-action restore-snapshot dropdown-item" data-ss-uid="' . $ss['uid'] . '">Restore snapshot</a>';
                $out .= '<a data-title="Current DB tables compared to snapshot %s" data-wait-msg="Comparing. Please wait." data-name="' . $name . '" title="Compare snapshot to current database tables" href="#" class="ss-action compare-snapshot dropdown-item" data-ss-uid="' . $ss['uid'] . '">Compare snapshot to current data</a>';
                $out .= '<a data-success-msg="' . esc_html('Snapshot export created!<br><a href=\'%s\'>Download it</a>') . '" data-wait-msg="Exporting snapshot. Please wait." title="Download snapshot as gzipped SQL dump" href="#" class="ss-action download-snapshot dropdown-item" data-ss-uid="' . $ss['uid'] . '">Download snapshot</a>';
                $out .= '<a title="Edit snapshot description" href="#" class="ss-action edit-snapshot-description dropdown-item">Edit snapshot description</a>';
            }
        }

        if (isset($ss['local']) && $ss['local'] == true) {
            $menu_item = true;
            $out .= '<a data-btn-confirm="Delete snapshot" data-text-wait="Deleting snapshot. Please wait." data-text-confirm="Are you sure you want to delete the selected snapshot? There is NO UNDO' . esc_html('<br><br>') . 'Deleting the snapshot will not affect the active database tables in any way.' . esc_html('<br><br>') . 'If the snapshot is uploaded to the cloud it will ONLY be deleted locally. Cloud copy will not be touched." data-text-done="Snapshot has been deleted" title="Permanently delete snapshot" href="#" class="ss-action delete-snapshot delete-button dropdown-item" data-ss-uid="' . $ss['uid'] . '">Delete snapshot from local website</a>';
        }


        if (!empty($options['cloud_service']) && array_key_exists($options['cloud_service'], $this->cloud_services) && !(isset($ss['partial']) && $ss['partial'] == true)) {
            if (isset($ss['local']) && $ss['local'] == true && !(isset($ss['cloud']) && $ss['cloud'] == true)) {
                $menu_item = true;
                $out .= '<a data-text-wait="Uploading snapshot to ' . $this->cloud_services[$options['cloud_service']] . '. Please wait." data-text-done-singular="Snapshot has been uploaded." title="Upload Snapshot to ' . $this->cloud_services[$options['cloud_service']] . '" href="#" class="ss-action cloud-upload-snapshot dropdown-item" data-ss-uid="' . $ss['uid'] . '"><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span> Upload Snapshot to ' . $this->cloud_services[$options['cloud_service']] . '</a>';
            }

            if (isset($ss['cloud']) && $ss['cloud'] == true) {
                $menu_item = true;
                $out .= '<a data-text-wait="Downloading snapshot from ' . $this->cloud_services[$options['cloud_service']] . '. Please wait." data-text-done-singular="Snapshot has been downloaded from ' . $this->cloud_services[$options['cloud_service']] . '" title="Download snapshot from ' . $this->cloud_services[$options['cloud_service']] . '" href="#" class="ss-action cloud-download-snapshot dropdown-item" data-ss-uid="' . $ss['uid'] . '"><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span> Download Snapshot from ' . $this->cloud_services[$options['cloud_service']] . '</a>';
                $out .= '<a data-btn-confirm="Delete snapshot from ' . $this->cloud_services[$options['cloud_service']] . '" data-text-wait="Deleting snapshot from ' . $this->cloud_services[$options['cloud_service']] . '. Please wait." data-text-confirm="Are you sure you want to delete the selected snapshot from ' . $this->cloud_services[$options['cloud_service']] . '? There is NO UNDO." data-text-done-singular="Snapshot has been deleted from ' . $this->cloud_services[$options['cloud_service']] . '." title="Delete Snapshot from ' . $this->cloud_services[$options['cloud_service']] . '" href="#" class="ss-action cloud-delete-snapshot delete-button dropdown-item" data-ss-uid="' . $ss['uid'] . '"><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span> Delete Snapshot from ' . $this->cloud_services[$options['cloud_service']] . '</a>';
            }
        }
        if ($menu_item == false) {
            $out .= '<a href="#tab-settings" class="dropdown-item">No cloud service enabled</a>';
        }
        $out .= '</div></div>';
        $out .= '</td>';
        $out .= '</tr>';

        return $out;
    } // get_snapshot_row


    /**
     * Generates the complete TR for a single stage
     *
     * @param array $stage Stage array with all params
     * @return string
     */
    function get_stage_row($stage)
    {
        $options = $this->get_options();

        $out = '';

        if (!empty($stage['name'])) {
            $name = $stage['name'];
        } else {
            $name = 'created on ' . gmdate(get_option('date_format'), strtotime($stage['timestamp'])) . ' @ ' . gmdate(get_option('time_format'), strtotime($stage['timestamp']));
        }

        $out .= '<tr ';
        if (isset($stage['partial']) && $stage['partial'] == true) {
            $out .= 'class="stage-partial"';
        }
        $out .= ' id="wpr-stage-' . $stage['uid'] . '" data-stage-uid="' . $stage['uid'] . '">';
        $out .= '<td class="stage-checkbox"><input type="checkbox" name="selected_stages" value="' . $stage['uid'] . '" /> </td>';
        $out .= '<td class="stage-date">';
        if (current_time('timestamp') - strtotime($stage['timestamp']) > 12 * HOUR_IN_SECONDS) {
            $out .= gmdate(get_option('date_format'), strtotime($stage['timestamp'])) . '<br>@ ' . gmdate(get_option('time_format'), strtotime($stage['timestamp']));
        } else {
            $out .= human_time_diff(strtotime($stage['timestamp']), current_time('timestamp')) . ' ago';
        }
        $out .= '</td>';

        $out .= '<td class="stage-name">';
        if (!empty($stage['name'])) {
            $out .= '<b class="stage-name">' . htmlspecialchars(html_entity_decode($stage['name'])) . '</b>';
            $out .= ' <a title="View stage" href="' . site_url('/wpr_staging/' . $stage['uid']) . '" target="blank">View stage</a>';
        } else {
            $out .= '<b class="stage-name"><i>Click to add description.</i></b>';
        }
        $out .= '<br>';
        $out .= 'Contains ' . number_format($stage['files_count']) . ' files, ' . $stage['tbl_core'] . ' core ';
        if ($stage['tbl_custom']) {
            $out .= '&amp; ' . $stage['tbl_custom'] . ' custom table' . ($stage['tbl_custom'] == 1 ? '' : 's');
        } else {
            $out .= 'tables';
        }
        $out .= ' totaling ' . number_format($stage['tbl_rows']) . ' rows';
        if (!empty($stage['plugins'])) {
            $out .= ' as well as files for ' . sizeof($stage['plugins']) . ' plugin' . (sizeof($stage['plugins']) == 1 ? '' : 's') . ': ' . implode(', ', array_keys($stage['plugins']));
        }
        if (!empty($stage['themes'])) {
            $out .= ' as well as files for ' . sizeof($stage['themes']) . ' theme' . (sizeof($stage['themes']) == 1 ? '' : 's') . ': ' . implode(', ', array_keys($stage['themes']));
        }

        $out .= '</td>';

        $out .= '<td class="stage-size">';
        $tmp = $stage['tbl_size'];
        if (!empty($stage['file_size'])) {
            $tmp += $stage['file_size'];
        }
        $out .= WP_Reset_Utility::format_size($tmp);
        $out .= '</td>';

        $out .= '<td class="stage-actions">';
        $out .= '<div class="dropdown">
        <a class="button dropdown-toggle" href="#">Actions</a>
        <div class="dropdown-menu">';
        if (!(isset($stage['partial']) && $stage['partial'] == true)) {
            if (isset($stage['local']) && $stage['local'] == true) {
                $out .= '<a title="View stage" href="' . site_url('/wpr_staging/' . $stage['uid']) . '" target="blank" class="dropdown-item">View stage</a>';
            }
        }

        $out .= '<a data-btn-confirm="Delete stage" data-text-wait="Deleting stage. Please wait." data-text-confirm="Are you sure you want to delete the selected stage? There is NO UNDO' . esc_html('<br><br>') . 'Deleting the stage will not affect the live website tables in any way.' . esc_html('<br><br>') . '." data-text-done="Stage has been deleted" title="Permanently delete stage" href="#" class="stage-action delete-stage delete-button dropdown-item" data-stage-uid="' . $stage['uid'] . '">Delete stage</a>';
        $out .= '</div></div>';
        $out .= '</td>';
        $out .= '</tr>';

        return $out;
    } // get_stage_row


    /**
     * Generate tool icons and description detailing what it modifies or doesn't
     *
     * @param bool  $modify_files  Does the tool modify files?
     * @param bool  $modify_db  Does the tool modify the database?
     * @param bool  $plural  Is there more than one tool in the set?
     *
     * @return string
     */
    function get_tool_icons($modify_files = false, $modify_db = false, $plural = false)
    {
        $out = '';

        $out .= '<p class="tool-icons">';
        $out .= '<i class="icon-doc-text-inv' . ($modify_files ? ' red' : '') . '"></i> ';
        $out .= '<i class="icon-database' . ($modify_db ? ' red' : '') . '"></i> ';

        if ($plural) {
            if ($modify_files && $modify_db) {
                $out .= 'these tools <b>modify files &amp; the database</b>.';
            } elseif (!$modify_files && $modify_db) {
                $out .= 'these tools <b>modify the database</b> but they don\'t modify any files.';
            } elseif ($modify_files && !$modify_db) {
                $out .= 'these tools <b>modify files</b> but they don\'t modify the database.';
            } else {
                $out .= 'these tools don\'t modify any files or the database.';
            }
        } else {
            if ($modify_files && $modify_db) {
                $out .= 'this tool <b>modifies files &amp; the database</b>.';
            } elseif (!$modify_files && $modify_db) {
                $out .= 'this tool <b>modifies the database</b> but it doesn\'t modify any files.';
            } elseif ($modify_files && !$modify_db) {
                $out .= 'this tool <b>modifies files</b> but it doesn\'t modify the database.';
            } else {
                $out .= 'this tool doesn\'t modify any files or the database.';
            }
        }
        $out .= '</p>';

        return $out;
    } // get_tool_icons


    /**
     * Main/starter function for displaying plugin's admin page
     *
     * @return null
     */
    function plugin_page()
    {
        // double check for admin privileges
        if (!current_user_can('administrator')) {
            wp_die(esc_html__('Sorry, you are not allowed to access this page.', 'wp-reset'));
        }

        global $wp_reset_collections, $wp_reset_licensing;
        $options = $this->get_options();

        echo '<div class="wrap">';

        echo '<header>';
        echo '<div class="wpr-container">';
        if ($options['debug'] == true) {
            echo '<span style="font-size: 48px; line-height: 30px; width: 50px; color: #dd3036;" class="dashicons dashicons-admin-tools"></span>';
        }

        $logo = $this->get_rebranding('logo_url');
        $plugin_name = $this->get_rebranding('name');
        if ($logo === false || empty($logo)) {
            self::wp_kses_wf('<img id="logo-icon" src="' . $this->plugin_url . 'img/wp-reset-logo.png" title="' . esc_html__('WP Reset PRO', 'wp-reset') . '" alt="' . esc_html__('WP Reset PRO', 'wp-reset') . '">');
        } else {
            self::wp_kses_wf('<img id="logo-icon" src="' . $logo . '" title="' . esc_html($plugin_name) . '" alt="' . esc_html($plugin_name) . '">');
        }


        echo '</div>';
        echo '</header>';

        echo '<div id="loading-tabs"><img class="wpr_rotating" src="' . esc_url($this->plugin_url) . 'img/wp-reset-icon.png' . '" alt="Loading. Please wait." title="Loading. Please wait."></div>';
        echo '<div id="wp-reset-tabs" class="ui-tabs" style="display: none;">';

        echo '<nav>';
        echo '<div class="wpr-container">';
        echo '<ul class="wpr-main-tab">';
        if ($wp_reset_licensing->is_active()) {
            echo '<li><a href="#tab-reset">' . esc_html__('Reset', 'wp-reset') . '</a></li>';
            echo '<li><a href="#tab-tools">' . esc_html__('Tools', 'wp-reset') . '</a></li>';
            echo '<li><a href="#tab-snapshots">' . esc_html__('Snapshots', 'wp-reset') . '</a></li>';
            if ($wp_reset_licensing->is_active('staging')) {
                echo '<li><a href="#tab-staging">' . esc_html__('Staging', 'wp-reset') . '</a></li>';
            }
            echo '<li><a href="#tab-collections">' . esc_html__('Collections', 'wp-reset') . '</a></li>';
            echo '<li><a href="#tab-settings">' . esc_html__('Settings', 'wp-reset') . '</a></li>';
            echo '<li><a href="#tab-debug">' . esc_html__('Debug', 'wp-reset') . '</a></li>';
            echo '<li><a href="#tab-support">' . esc_html__('Support', 'wp-reset') . '</a></li>';
        }
        if (WP_Reset_Utility::whitelabel_filter()) {
            echo '<li><a href="#tab-license">' . esc_html__('License', 'wp-reset') . '</a></li>';
        }

        echo '</ul>';
        echo '</div>'; // container
        echo '</nav>';

        echo '<div id="wpr-notifications">';
        echo '<div class="wpr-container">';
        $this->custom_notifications();
        echo '</div>';
        echo '</div>'; // wpr-notifications

        // tabs
        echo '<div class="wpr-container">';
        echo '<div id="wpr-content">';

        if ($wp_reset_licensing->is_active()) {
            echo '<div style="display: none;" id="tab-reset">';
            $this->tab_reset();
            echo '</div>';

            echo '<div style="display: none;" id="tab-tools">';
            $this->tab_tools();
            echo '</div>';

            echo '<div style="display: none;" id="tab-snapshots">';
            $this->tab_snapshots();
            echo '</div>';

            if ($wp_reset_licensing->is_active('staging')) {
                echo '<div style="display: none;" id="tab-staging">';
                $this->tab_staging();
                echo '</div>';
            }

            echo '<div style="display: none;" id="tab-collections">';
            $wp_reset_collections->tab_collections();
            echo '</div>';

            echo '<div style="display: none;" id="tab-debug">';
            $this->tab_debug();
            echo '</div>';
        }

        echo '<div style="display: none;" id="tab-settings">';
        $this->tab_settings();
        echo '</div>';

        echo '<div style="display: none;" id="tab-support">';
        $this->tab_support();
        echo '</div>';

        if (WP_Reset_Utility::whitelabel_filter()) {
            echo '<div style="display: none;" id="tab-license">';
            $this->tab_license();
            echo '</div>';
        }

        if ($options['debug'] == true) {
            echo '<div style="display: none;" id="tab-debug">';
            $this->tab_debug();
            echo '</div>';
        }

        echo '</div>'; // content
        echo '</div>'; // container
        echo '</div>'; // wp-reset-tabs

        echo '</div>'; // wrap
    } // plugin_page


    /**
     * Echoes all custom plugin notitications
     *
     * @return null
     */
    private function custom_notifications()
    {
        $notice_shown = false;
    } // custom_notifications

    /**
     * Determine the wp-config.php file location
     *
     * @return string wp-config.php absolute path
     */
    private function wpconfig_file()
    {
        if (file_exists(ABSPATH . 'wp-config.php')) {
            $file = ABSPATH . 'wp-config.php';
        } elseif (@file_exists(dirname(ABSPATH) . '/wp-config.php') && !@file_exists(dirname(ABSPATH) . '/wp-settings.php')) {
            $file = dirname(ABSPATH) . '/wp-config.php';
        } else {
            return new WP_Error(1, 'Can\'t find wp-config.php.');
        }

        if (!is_writable($file)) { //phpcs:ignore
            return new WP_Error(1, 'wp-config.php file (' . $file . ') is not writeable');
        }

        return $file;
    }

    /**
     * Toggle WP_DEBUG and WP_DEBUG_LOG ON/OFF by setting value to true or false
     * If WP_DEBUG is already enabled and WP_DEBUG_LOG is set to a custom path WP Reset will read it from there, 
     * otherwise it will set WP_DEBUG_LOG to true and use the default wp-content/debug.log path
     *
     * @return null
     */
    private function wpconfig_toggle_debug($wpdebug = false)
    {
        if (!current_user_can('administrator')) {
            return new WP_Error(1, 'Can only call as admin');
        }

        $wpconfig_file_path = $this->wpconfig_file();

        if (is_wp_error($wpconfig_file_path)) {
            return $wpconfig_file_path;
        }

        $wp_config_src = file_get_contents($wpconfig_file_path);

        $wpconfig_read_constants = $this->wpconfig_read_constants();

        if (is_wp_error($wpconfig_read_constants)) {
            return $wpconfig_read_constants;
        }

        // Enable WP_DEBUG
        if (isset($this->wp_config_constants['WP_DEBUG'])) {
            $wp_config_src = preg_replace(
                sprintf('/(?<=^|;|<\?php\s|<\?\s)(\s*?)%s/m', preg_quote(trim($this->wp_config_constants['WP_DEBUG']['src']), '/')),
                '$1' . 'define(\'WP_DEBUG\', ' . ($wpdebug ? 'true' : 'false') . ' );',
                $wp_config_src
            );
        } else {
            //add
            if (false !== strpos($wp_config_src, "Happy publishing")) {
                $anchor = '/* That\'s all, stop editing! Happy publishing. */';
            } elseif (false !== strpos($wp_config_src, "Happy blogging")) {
                $anchor = '/* That\'s all, stop editing! Happy blogging. */';
            } else {
                $anchor = 'EOF';
            }

            if ('EOF' === $anchor) {
                $wp_config_src = $wp_config_src . PHP_EOL . 'define(\'WP_DEBUG\', ' . ($wpdebug ? 'true' : 'false') . ' );';
            } else {
                if (false === strpos($wp_config_src, $anchor)) {
                    return new WP_Error(1, 'Updating wp-config.php file failed. No changes have been made.');
                }

                $new_src  = 'define(\'WP_DEBUG\', ' . ($wpdebug ? 'true' : 'false') . ' );' . PHP_EOL . $anchor;
                $wp_config_src = str_replace($anchor, $new_src, $wp_config_src);
            }
        }

        // Enable WP_DEBUG_LOG
        if (isset($this->wp_config_constants['WP_DEBUG_LOG'])) {
            $wp_config_src = preg_replace(
                sprintf('/(?<=^|;|<\?php\s|<\?\s)(\s*?)%s/m', preg_quote(trim($this->wp_config_constants['WP_DEBUG_LOG']['src']), '/')),
                '$1' . 'define(\'WP_DEBUG_LOG\', ' . ($wpdebug ? 'true' : 'false') . ' );',
                $wp_config_src
            );
        } else {
            //add
            if (false !== strpos($wp_config_src, "Happy publishing")) {
                $anchor = '/* That\'s all, stop editing! Happy publishing. */';
            } elseif (false !== strpos($wp_config_src, "Happy blogging")) {
                $anchor = '/* That\'s all, stop editing! Happy blogging. */';
            } else {
                $anchor = 'EOF';
            }

            if ('EOF' === $anchor) {
                $wp_config_src = $wp_config_src . PHP_EOL . 'define(\'WP_DEBUG_LOG\', ' . ($wpdebug ? 'true' : 'false') . ' );';
            } else {
                if (false === strpos($wp_config_src, $anchor)) {
                    return new WP_Error(1, 'Updating wp-config.php file failed. No changes have been made.');
                }

                $new_src  = 'define(\'WP_DEBUG_LOG\', ' . ($wpdebug ? 'true' : 'false') . ' );' . PHP_EOL . $anchor;
                $wp_config_src = str_replace($anchor, $new_src, $wp_config_src);
            }
        }

        $result = file_put_contents($wpconfig_file_path, $wp_config_src, LOCK_EX);

        if (false === $result) {
            return new WP_Error(1, 'Writing wp-config.php file failed. No changes have been made.');
        }

        return true;
    }

    /**
     * Update table prefix in a wp-config.php file specified in $stage_config_path
     *
     * @return null
     */
    private function update_config_table_prefix($fs, $stage_config_path, $new_prefix)
    {
        if (!$fs->exists($stage_config_path)) {
            return new WP_Error('cfg_missing', "wp-config.php not found at: {$stage_config_path}");
        }

        $config = $fs->get_contents($stage_config_path);
        if ($config === false) {
            return new WP_Error('cfg_read', "Unable to read: {$stage_config_path}");
        }

        // Replace existing $table_prefix line or insert one before wp-settings.php
        $pattern = '/^\s*\$table_prefix\s*=\s*[\'"][^\'"]+[\'"]\s*;\s*$/m';
        $replacement = '$table_prefix = \'' . addslashes($new_prefix) . '\';';

        if (preg_match($pattern, $config)) {
            $config = preg_replace($pattern, $replacement, $config, 1);
        } else {
            $req_pat = '/^\s*require_once\s*\(?\s*.*wp-settings\.php.*\)\s*;\s*$/mi';
            if (preg_match($req_pat, $config)) {
                $config = preg_replace($req_pat, $replacement . "\n" . '$0', $config, 1);
            } else {
                $config .= "\n" . $replacement . "\n";
            }
        }

        if (!$fs->put_contents($stage_config_path, $config, FS_CHMOD_FILE)) {
            return new WP_Error('cfg_write', "Failed writing: {$stage_config_path}");
        }

        return true;
    }

    /**
     * Read constants from wp-config.php
     *
     * @return array of constants as keys and values 
     */
    public function wpconfig_read_constants($force = false)
    {
        if (!current_user_can('administrator')) {
            return new WP_Error(1, 'Can only call as admin');
        }

        if ($this->wp_config_constants !== false && !$force) {
            return $this->wp_config_constants;
        }

        $this->wp_config_constants = array();

        $wpconfig_file_path = $this->wpconfig_file();

        if (is_wp_error($wpconfig_file_path)) {
            return $wpconfig_file_path;
        }

        $src = file_get_contents($wpconfig_file_path);

        $constants = array();

        foreach (token_get_all($src) as $token) {
            if (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT), true)) {
                $src = str_replace($token[1], '', $src);
            }
        }

        preg_match_all('/(?<=^|;|<\?php\s|<\?\s)(\h*define\s*\(\s*[\'"](\w*?)[\'"]\s*)(,\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*)((?:,\s*(?:true|false)\s*)?\)\s*;)/ims', $src, $constants);

        if (!empty($constants[0]) && !empty($constants[1]) && !empty($constants[2]) && !empty($constants[3]) && !empty($constants[4]) && !empty($constants[5])) {
            foreach ($constants[2] as $index => $name) {
                $this->wp_config_constants[$name] = array(
                    'src'   => $constants[0][$index],
                    'val' => $constants[4][$index]
                );
            }
        }

        if (empty($this->wp_config_constants)) {
            return new WP_Error(1, 'wp-config.php file could not be read correctly');
        }

        return $this->wp_config_constants;
    }


    function file_tail($filepath, $lines = 500)
    {
        $f = fopen($filepath, "r"); //phpcs:ignore
        if ($f === false) return false;

        $buffer = 4096; // Buffer size
        fseek($f, 0, SEEK_END);
        $position = ftell($f);
        $chunk = '';
        $lines_found = 0;

        while ($position > 0 && $lines_found <= $lines) {
            $position = max($position - $buffer, 0);
            fseek($f, $position);
            $chunk = fread($f, $buffer) . $chunk; //phpcs:ignore
            $lines_found = substr_count($chunk, "\n");
        }

        fclose($f); //phpcs:ignore

        $lines_array = explode("\n", $chunk);
        return array_slice($lines_array, -$lines);
    }



    /**
     * Echoes content for WordPress log
     *
     * @return null
     */
    private function print_wordpress_debug_log()
    {
        $log_file = defined('WP_DEBUG_LOG') && !is_bool(WP_DEBUG_LOG) ? WP_DEBUG_LOG : WP_CONTENT_DIR . '/debug.log';

        if (file_exists($log_file)) {
            $log_lines = $this->file_tail($log_file, 500);
            $log_lines = array_reverse($log_lines);

            $log_levels = array(
                'PHP Notice' => 'notice',
                'PHP Warning' => 'warning',
                'PHP Fatal error' => 'error',
                'PHP Parse error' => 'error'
            );

            if (count($log_lines) == 0) {
                echo 'Debug log is empty. Refresh the page to reload it.';
            }

            foreach ($log_lines as $line) {
                preg_match('/^\[(.*?)\]\s(.*?)\:\s(.*?)$/i', $line, $matches);
                if (count($matches) >= 4) {
                    $date = $matches[1];
                    $level = $matches[2];
                    $message = htmlspecialchars($matches[3]); // Escape HTML characters for security
                    $class = isset($log_levels[$level]) ? $log_levels[$level] : 'other';

                    // Output log row with date, log level, and message
                    echo '<div class="wpr-log-row">';
                    echo '<div class="wpr-log-date">' . esc_html($date) . '<br /><div class="wpr-log-' . esc_attr($class) . '">' . esc_html($level) . '</div></div>';
                    echo '<div class="wpr-log-message">' . esc_html($message) . '</div>';
                    echo '</div>';
                }
            }
        } else {
            echo 'Debug log file not found.';
        }
    }

    /**
     * Echoes content for log tab
     *
     * @return null
     */
    private function tab_debug()
    {
        global $wpdb;
        $options = $this->get_options();
        $plugin_name = $this->get_rebranding('name');
        $this->wpconfig_read_constants(true);

        if ($plugin_name === false) {
            $plugin_name = 'WP Reset';
        }

        echo '<div class="card">';
        echo '<h4><span class="card-name">WordPress Debug Log</span><div class="card-header-right"></div></h4>';
        echo '<div class="card-body">';
        echo '<p>The WP_DEBUG feature in WordPress allows developers to debug their code by displaying error messages, notices, and warnings directly on the website. However, accessing these debug messages typically requires direct access to the server\'s error logs. This feature aims to simplify the debugging process by providing users with a convenient way to enable WP_DEBUG and view debug log contents directly from the WordPress admin panel.</p>';

        if (is_wp_error($this->wp_config_constants)) {
            echo 'There has been an error reading your wp-config.php file: ' . esc_html($this->wp_config_constants->get_error_message());
        } else {

            if ($this->wp_config_constants['WP_DEBUG']['val'] !== 'true') {
                echo '<a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_wp_debug=true')) . '" data-confirm-title="Are you sure you want to enable WordPress Debug mode?" data-text-confirm="Debug mode will enable the WordPress debug by setting WP_DEBUG to true and you will be able to view the debug.log in the WP Reset Debug tab." data-btn-confirm="Enable WordPress debug mode" class="enable-debug-mode button button-delete">Enable WordPress Debug</a>';
            } else {
                echo '<p>WordPress Debug is enabled. Press the <strong>Disable WordPress Debug</strong> button below to disable it. If you need to disable it manually, you can follow these instructions:</p>';
                echo '<p>
                    <ol>
                    <li>Access the WordPress installation directory on the server.</li>
                    <li>Locate the wp-config.php file, which contains essential configuration settings for the WordPress site.</li>
                    <li>Open the wp-config.php file using a text editor or file manager.</li>
                    <li>Search for the line that defines the WP_DEBUG constant. It typically looks like this: define("WP_DEBUG", true);</li>
                    <li>To disable WP_DEBUG, simply change the value from true to false, like so: define("WP_DEBUG", false);</li>
                    <li>Save the changes to the wp-config.php file and close the editor.</li>
                    </ol>';
                echo '</p>';
                echo '<a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_wp_debug=false')) . '" class="button button-delete disable-debug-mode">Disable WordPress Debug</a>';

                echo '<div class="wpr-log-wrapper">';
                self::wp_kses_wf($this->print_wordpress_debug_log());
                echo '</div>';
            }
        }


        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        echo '<h4><span class="card-name">' . esc_html($plugin_name) . ' Debug</span><div class="card-header-right"></div></h4>';
        echo '<div class="card-body">';
        echo '<p class="red">Only use the ' . esc_html($plugin_name) . ' Debug Mode if you know what you are doing or have been instructed to do so by support. While debug mode is enabled, any PHP notices and errors will be printed in AJAX responses (if WordPress Debug Mode is enabled, then they will only appear in the debug log), so you should not leave the Debug mode enabled during normal use.</p>';

        echo '<p>';
        if ($options['debug'] == true) {
            echo '<a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_debug=false')) . '" class="button button-delete disable-debug-mode">Disable debug mode</a>';
        } else {
            echo '<a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_debug=true')) . '" data-confirm-title="Are you sure you want to enable debug mode?" data-text-confirm="Debug mode will enable the Debug Tools tab which contains the Log and other tools related only to WP Reset. Only use this if you know what you are doing or have been instructed to do so by support. While debug mode is enabled, any PHP notices and errors will be printed in AJAX responses, so you should not leave the Debug mode enabled during normal use." data-btn-confirm="Enable debug mode" class="enable-debug-mode button button-delete">Enable debug mode</a>';
        }
        echo '</p></div>';
        echo '</div>';

        if ($options['debug'] == true) {
            echo '<div class="card">';
            echo '<h4><span class="card-name">Log</span><div class="card-header-right"></div></h4>';
            echo '<div class="card-body">';

            echo '<div class="wpr-log-wrapper">';
            $log = $this->get_log();
            if (count($log) == 0) {
                echo 'No log entries';
            } else {
                $log = array_reverse($log, true);
                echo '<ul>';
                foreach ($log as $id => $message) {
                    echo '<li class="wpr-log-message"><strong>[' . esc_html(gmdate('Y-m-d H:i:s', $message['time'])) . ']</strong> <span class="wpr-log-message-' . esc_html($message['type']) . '">' . esc_html($message['message']) . '</span></li>';
                }
                echo '</ul>';
            }
            echo '</div>';
            echo '<p><a class="button button-delete" href="' . esc_url(add_query_arg(array('_wpnonce' => wp_create_nonce('wpr_clear_log'), 'action' => 'wpr_clear_log', 'redirect' => urlencode(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? '')))), admin_url('admin.php'))) . '">Clear Log</a>';

            echo '</div>';
            echo '</div>';

            //Cleanup files
            echo '<div class="card">';
            echo '<h4><span class="card-name">WP Reset Files</span><div class="card-header-right"></div></h4>';
            echo '<div class="card-body"><p>';

            $total_size = 0;
            $total_size += $this->folder_size(trailingslashit(WP_CONTENT_DIR) . 'wp-reset-autosnapshots');
            $total_size += $this->folder_size(trailingslashit(WP_CONTENT_DIR) . 'wp-reset-snapshots-export');

            if ($total_size == 0) {
                echo 'There are no temporary files';
            } else {
                echo 'Temporary files used by WP Reset total ' . esc_html(WP_Reset_Utility::format_size($total_size));
                echo '<p><a class="button button-delete" href="' . esc_url(add_query_arg(array('_wpnonce' => wp_create_nonce('wpr_delete_temporary_files'), 'action' => 'wpr_delete_temporary_files', 'redirect' => urlencode(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])))), admin_url('admin.php'))) . '">Delete temporary snapshot files</a></p>';
            }

            echo '</p></div>';
            echo '</div>';

            //Orphaned snapshot tables
            echo '<div class="card">';
            echo '<h4><span class="card-name">Snapshots</span><div class="card-header-right"></div></h4>';
            echo '<div class="card-body"><p>';

            $snapshots = $this->get_snapshots();
            $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); //phpcs:ignore
            $uid_length = 7;
            if (strlen($wpdb->prefix) > 10) {
                $uid_length = 5;
            }

            $orphaned_snapshots = array();

            foreach ($tables as $table) {
                if ($uid_length !== stripos($table[0], $wpdb->prefix)) {
                    continue;
                }
                $current_id = substr($table[0], 0, ($uid_length - 1));

                if (array_key_exists($current_id, $snapshots)) {
                    continue;
                }

                if (!array_key_exists($current_id, $orphaned_snapshots)) {
                    $orphaned_snapshots[$current_id] = array();
                }
                $orphaned_snapshots[$current_id][] = $table[0];
            } // foreach

            if (empty($orphaned_snapshots)) {
                echo 'There are no orphaned snapshot tables';
            } else {
                echo '<p>The tables below <strong>appear</strong> to have belonged to snapshots that have been deleted. They could have remained behind because of a timeout or other errors when deleting the associated snapshot.</p>';
                echo '<p class="red">Double check the tables names and make sure you want to delete them. There is NO UNDO!</p>';
                foreach ($orphaned_snapshots as $uid => $snapshot) {
                    if (array_key_exists($uid, $snapshots)) {
                        continue;
                    }

                    echo '<hr />';
                    echo '<div>';
                    echo 'Prefix/Snapshot ID: <strong>' . esc_attr($uid) . '</strong><br />';
                    echo '<ul>';
                    echo '<li>Tables:</li>';
                    foreach ($snapshot as $table) {
                        echo '<li>' . esc_attr($table) . '</li>';
                    }
                    echo '</ul>';
                    echo '<a class="button button-delete" href="' . esc_url(add_query_arg(array('_wpnonce' => wp_create_nonce('wpr_delete_snapshot_tables'), 'action' => 'wpr_delete_snapshot_tables', 'uid' => esc_attr($uid), 'redirect' => urlencode(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])))), admin_url('admin.php'))) . '">Delete the tables prefixed with ' . esc_attr($uid) . '_</a>';
                    echo '</div>';
                }
            }

            echo '</p></div>';
            echo '</div>';
        }
    }


    function get_rebranding($key = false)
    {
        $license = $this->get_license();
        if (is_array($license) && array_key_exists('meta', $license) && is_array($license['meta']) && array_key_exists('rebrand', $license['meta']) && !empty($license['meta']['rebrand'])) {
            if (!empty($key)) {
                return $license['meta']['rebrand'][$key];
            }
            return $license['meta']['rebrand'];
        } else {
            return false;
        }
    }

    /**
     * Echoes content for license tab
     *
     * @return null
     */
    private function tab_license()
    {
        global $wp_reset_licensing;
        $options = $this->get_license();
        $rebranding = $this->get_rebranding();

        echo '<div class="card">';
        echo '<h4><span class="card-name">License</span><div class="card-header-right"></div></h4>';
        echo '<div class="card-body">';

        if ($rebranding === false) {
            echo '<p>License key is visible on the screen, right after purchasing. You can also find it in the confirmation email sent to the email address provided on purchase.<br>
        If you don\'t have a license - <a target="_blank" href="' . esc_url($this->generate_web_link('license-tab')) . '">purchase one now</a>. In case of problems please <a href="#" class="open-beacon">contact support</a>.</p>';
            echo '<p>You can manage your licenses in the <a target="_blank" href="' . esc_url($this->generate_dashboard_link('license-tab')) . '">WP Reset Dashboard</a></p>';
        }
        echo '<table class="form-table"><tbody><tr>
    <th scope="row"><label for="license-key">License Key</label></th>
    <td>
    <input class="regular-text" type="text" id="license-key" value="" placeholder="' . (empty($options['license_key']) ? '12345678-12345678-12345678-12345678' : esc_html(substr(esc_attr($options['license_key']), 0, 8)) . '-************************') . '">
    </td></tr>';

        echo '<tr><th scope="row"><label for="">' . esc_html__('License Status', 'wp-reset') . '</label></th><td>';
        if ($wp_reset_licensing->is_active()) {
            $license_formatted = $wp_reset_licensing->get_license_formatted();
            echo '<b style="color: #66b317;">Active</b><br>';
            if ($this->get_rebranding() === false) {
                echo 'Type: ' . esc_html($license_formatted['name_long']);
                echo '<br>Valid ' . esc_html($license_formatted['valid_until']) . '</td>';
            }
        } else { // not active
            echo '<strong style="color: #ea1919;">Inactive</strong>';
            if (!empty($wp_reset_licensing->get_license('error'))) {
                echo '<br>Error: ' . esc_html($wp_reset_licensing->get_license('error'));
            }
        }
        echo '</td></tr>';

        echo '<tr><td colspan="2"><br>';
        echo '<a href="#" id="save-license" data-text-wait="Validating. Please wait." class="button button-primary">Save &amp; Activate License</a>';

        if ($wp_reset_licensing->is_active()) {
            echo '&nbsp; &nbsp;<a href="#" id="deactivate-license" class="button button-delete">Deactivate License</a>';
        } else {
            echo '&nbsp; &nbsp;<a href="#" class="button button-primary" data-text-wait="Validating. Please wait." id="wpr_keyless_activation">Keyless Activation</a>';
        }
        echo '</td></tr>';
        echo '</tbody></table>';

        echo '</div>';
        echo '</div>';

        if ($wp_reset_licensing->is_active('white_label')) {
            echo '<div class="card">';
            echo '<h4><span class="card-name">White-Label License Mode</span><div class="card-header-right"></div></h4>';
            echo '<div class="card-body">';
            echo '<p>Enabling the white-label license mode hides the License tab, removes everything except the ERS from the Support tab, and removes all visible mentions of WebFactory Ltd.<br>To disable it append <strong>&amp;wpr_wl=false</strong> to the WP Reset settings page URL.
          Or save this URL and open it when you want to disable the white-label license mode:<br> <a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_wl=false')) . '">' . esc_url(admin_url('tools.php?page=wp-reset&wpr_wl=false')) . '</a></p>';
            echo '<p><a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_wl=true')) . '" class="button button-secondary">Enable White-Label License Mode</a></p>';
            echo '</div>';
            echo '</div>';
        }
    } // tab_license


    /**
     * Echoes content for reset tab
     *
     * @return null
     */
    private function tab_reset()
    {
        global $wpdb;
        $current_user = wp_get_current_user();

        $plugin_name = $this->get_rebranding('name');
        if ($plugin_name == false) {
            $plugin_name = 'WP Reset';
        }

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Please read carefully before proceeding', 'wp-reset'), 'reset-description', array('collapse_button' => true)));
        echo '<div class="card-body">';
        echo '<p>The following table details what data will be deleted (reset or destroyed) when a selected reset tool is run. Please read it! ';
        if (WP_Reset_Utility::whitelabel_filter() && $this->get_rebranding() === false) {
            echo 'If something is not clear <a href="#" class="open-beacon">contact support</a> before running any tools. It\'s better to ask than to be sorry!';
        }
        echo '</p>';
        echo '<p><i class="dashicons dashicons-trash red" style="vertical-align: bottom;"></i> - tool WILL delete, reset or destroy the noted data<br>';
        echo '<i class="dashicons dashicons-yes" style="vertical-align: bottom;"></i> - tool will not touch the noted data in any way</p>';

        echo '<table id="reset-details" class="">';
        echo '<tr>';
        echo '<th>&nbsp;</th>';
        echo '<th>Options Reset</th>';
        echo '<th>Site Reset</th>';
        echo '<th>Nuclear Reset</th>';
        echo '</tr>';
        $rows = array();
        $rows['Posts, pages &amp; custom post types'] = array(0, 1, 1);
        $rows['Comments'] = array(0, 1, 1);
        $rows['Media'] = array(0, 1, 1);
        $rows['Media files'] = array(0, 0, 1);
        $rows['Users'] = array(0, 1, 1);
        $rows['Current user - ' . $current_user->user_login] = array(0, 0, 0);
        $rows['Widgets'] = array(1, 1, 1);
        $rows['Transients'] = array(1, 1, 1);
        $rows['Settings &amp; Options (from WP, plugins &amp; themes)'] = array(1, 1, 1);
        $rows['Site title, WP address, site address,<br>search engine visibility, timezone'] = array(0, 0, 0);
        $rows['Site language'] = array(0, 0, 1);
        $rows['Data in all default WP tables'] = array(0, 1, 1);
        $rows['Custom database tables with prefix ' . $wpdb->prefix] = array(0, 1, 1);
        $rows['Other database tables'] = array(0, 0, 0);
        $rows['Plugin Files'] = array(0, 0, 1);
        $rows['MU Plugin Files'] = array(0, 0, 1);
        $rows['Drop-in Files'] = array(0, 0, 1);
        $rows['Theme Files'] = array(0, 0, 1);
        $rows['All files in uploads'] = array(0, 0, 1);
        $rows['Custom folders in wp-content'] = array(0, 0, 1);

        foreach ($rows as $tool => $opt) {
            echo '<tr>';
            echo '<td>' . esc_html($tool) . '</td>';
            if (empty($opt[0])) {
                echo '<td><i class="dashicons dashicons-yes" title="Data will NOT be deleted, reset or modified" data-tooltip="Data will NOT be deleted, reset or modified"></i></td>';
            } else {
                echo '<td><i class="dashicons dashicons-trash red" title="Data WILL BE deleted, reset or modified" data-tooltip="Data WILL BE deleted, reset or modified"></i></td>';
            }
            if (empty($opt[1])) {
                echo '<td><i class="dashicons dashicons-yes" title="Data will NOT be deleted, reset or modified" data-tooltip="Data will NOT be deleted, reset or modified"></i></td>';
            } else {
                echo '<td><i class="dashicons dashicons-trash red" title="Data WILL BE deleted, reset or modified" data-tooltip="Data WILL BE deleted, reset or modified"></i></td>';
            }
            if (empty($opt[2])) {
                echo '<td><i class="dashicons dashicons-yes" title="Data will NOT be deleted, reset or modified" data-tooltip="Data will NOT be deleted, reset or modified"></i></td>';
            } else {
                echo '<td><i class="dashicons dashicons-trash red" title="Data WILL BE deleted, reset or modified" data-tooltip="Data WILL BE deleted, reset or modified"></i></td>';
            }
            echo '</tr>';
        } // foreach $rows
        echo '</table>';

        echo '<p><b>' . esc_html__('What happens when I run any Reset tool?', 'wp-reset') . '</b></p>';
        echo '<ul class="plain-list">';
        self::wp_kses_wf('<li>' . __('remember, always <b>make a backup</b> first', 'wp-reset') . '</li>');
        echo '<li>' . esc_html__('you will have to confirm the action one more time', 'wp-reset') . '</li>';
        echo '<li>' . esc_html__('see the table above to find out what exactly will be reset or deleted', 'wp-reset') . '</li>';
        echo '<li>' . esc_html__('site title, WordPress URL, site URL, site language, search engine visibility and current user will always be restored', 'wp-reset') . '</li>';
        echo '<li>' . esc_html__('you will be logged out, automatically logged back in and taken to the admin dashboard', 'wp-reset') . '</li>';
        echo '<li>' . esc_html($plugin_name) . ' ' . esc_html__('plugin will be reactivated if that option is chosen', 'wp-reset') . '</li>';
        echo '</ul>';

        echo '<p><b>' . esc_html__('WP-CLI Support', 'wp-reset') . '</b><br>';
        /* translators: %s is the "wp help reset" command */
        echo '' . esc_html(sprintf(__('All tools available via GUI are available in WP-CLI as well. To get the list of commands run %s. Instead of the active user, the first user with admin privileges found in the database will be restored. ', 'wp-reset'), '<code>wp help reset</code>'));
        /* translators: %s is the "--yes" parameter */
        echo esc_html(sprintf(__('All actions have to be confirmed. If you want to skip confirmation use the standard %s option. Please be careful and backup first.', 'wp-reset'), '<code>--yes</code>') . '</p>');

        echo '</p></div></div>'; // card description

        $theme =  wp_get_theme();
        $theme_name = $theme->get('Name');
        if (empty($theme_name)) {
            $theme_name = '<i>no active theme</i>';
        }
        $active_plugins = get_option('active_plugins');

        // options reset
        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Options Reset', 'wp-reset'), 'tool-options-reset', array('collapse_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>Options table will be reset to default values meaning all WP core settings, widgets, theme settings and customizations, and plugin settings will be gone. Other content and files will not be touched including posts, pages, custom post types, comments and other data stored in separate tables. Site URL and name will be kept as well. Please see the <a href="#reset-details" class="scrollto">table above</a> for details.</p>';

        self::wp_kses_wf($this->get_tool_icons(false, true));

        echo '<p><br><label for="reset-options-reactivate-theme"><input type="checkbox" id="reset-options-reactivate-theme" value="1"> ' . esc_html__('Reactivate current theme', 'wp-reset') . ' - ' . esc_html($theme_name) . '</label></p>';
        echo '<p><label for="reset-options-reactivate-plugins"><input type="checkbox" id="reset-options-reactivate-plugins" value="1"> Reactivate ' . esc_html(sizeof($active_plugins)) . ' currently active plugin' . (sizeof($active_plugins) != 1 ? 's' : '') . ' (' . esc_html($plugin_name) . ' will reactivate by default)</label></p>';

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to reset all options?" data-btn-confirm="Reset all options" data-text-wait="Resetting options. Please wait." data-text-confirm="All options stored in the WP options table will be reset.' . esc_html($this->get_autosnapshot_tools_modal('Before running the options reset tool')) . '" data-text-done="All options have been reset. Reload the page to see changes." data-text-done-singular="All options have been reset. Reload the page to see changes." class="button button-delete" href="#" id="reset-options">Reset all options</a></p>';
        echo '</div>';
        echo '</div>'; // options reset

        // reset
        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Site Reset', 'wp-reset'), 'tool-site-reset', array('collapse_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>WordPress will be reset and returned to default settings. All data stored in the database will be erased including all content such as posts, pages and media. No files will be deleted or modified. Please see the <a href="#reset-details" class="scrollto">table above</a> for details.</p>';

        self::wp_kses_wf($this->get_tool_icons(false, true));

        echo '<p><br><label for="site-reset-reactivate-theme"><input type="checkbox" id="site-reset-reactivate-theme" value="1"> ' . esc_html__('Reactivate current theme', 'wp-reset') . ' - ' . esc_html($theme_name) . '</label></p>';
        /* translators: %s is the WP Reset plugin name */
        echo '<p><label for="site-reset-reactivate-wpreset"><input type="checkbox" id="site-reset-reactivate-wpreset" value="1" checked> ' . esc_html(sprintf(__('Reactivate %s plugin', 'wp-reset'), $plugin_name)) . '</label></p>';
        echo '<p><label for="site-reset-reactivate-plugins"><input type="checkbox" id="site-reset-reactivate-plugins" value="1"> ' . esc_html__('Reactivate all currently active plugins', 'wp-reset') . '</label></p>';
        self::wp_kses_wf('<p>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress" button.', 'wp-reset') . '</p>');
        echo '<p><input id="site_reset_confirm" type="text" placeholder="' . esc_attr__('Type in "reset"', 'wp-reset') . '" value="" autocomplete="off"> &nbsp;';
        echo '<a data-confirm-title="Are you sure you want to reset the site?" data-text-wait="Resetting site. Please wait." data-text-confirm="The site will be reset to default values. All content will be deleted.' . esc_html($this->get_autosnapshot_tools_modal('Before running the site reset tool')) . '" data-text-done="Done. Page will reload in a few seconds." class="button button-delete" href="#" id="site-reset">' . esc_html__('Reset Site', 'wp-reset') . '</a></p>';
        echo '</div>';
        echo '</div>'; // reset

        // nuclear reset
        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Nuclear Site Reset', 'wp-reset'), 'tool-nuclear-reset', array('collapse_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All data will be deleted or reset (see the <a href="#reset-details" class="scrollto">explanation table</a> for details). All data stored in the database including custom tables with <code>' . esc_html($wpdb->prefix) . '</code> prefix, as well as all files in wp-content, themes and plugins folders. The only thing restored after reset will be your user account so you can log in again, and the basic WP settings like site URL. Please see the <a href="#reset-details" class="scrollto">table above</a> for details.</p>';

        self::wp_kses_wf($this->get_tool_icons(true, true));

        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete files shared by multiple sites in the WP network.</p>';
        } else {
            /* translators: %s is the WP Reset plugin name */
            echo '<p><br><label for="nuclear-reset-reactivate-wpreset"><input type="checkbox" id="nuclear-reset-reactivate-wpreset" value="1" checked> ' . esc_html(sprintf(__('Reactivate %s plugin', 'wp-reset'), $plugin_name)) . '</label></p>';

            self::wp_kses_wf('<p>' . __('Type <b>reset</b> in the confirmation field to confirm the reset and then click the "Reset WordPress &amp; Delete All Custom Files &amp; Data" button. <b>There is NO UNDO.', 'wp-reset') . '</b></p>');

            echo '<p class="mb0"><input id="nuclear_reset_confirm" type="text" placeholder="' . esc_attr__('Type in "reset"', 'wp-reset') . '" value="" autocomplete="off"> &nbsp;';
            echo '<a data-confirm-title="Are you sure you want to reset WordPress and delete all custom files?" data-btn-confirm="Reset WP &amp; Delete All Custom Files &amp; Data" data-text-wait="Resetting. Please wait." data-text-confirm="WordPress will be completely reset and all files (plugins, themes, uploads) deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' will not make any backups." data-text-done="Done. Page will reload in a few seconds." class="button button-delete" href="#" id="nuclear-reset">' . esc_html__('Reset WordPress &amp; Delete All Custom Files &amp; Data', 'wp-reset') . '</a></p>';
        }
        echo '</div>';
        echo '</div>'; // nuclear reset
    } // tab_reset


    /**
     * Echoes content for tools tab
     *
     * @return null
     */
    private function tab_tools()
    {
        global $wpdb, $wp_version, $wp_reset_tools;

        $plugin_name = $this->get_rebranding('name');
        if ($plugin_name === false) {
            $plugin_name = 'WP Reset';
        }
        $tools = array(
            'tool-reset-theme-options' => 'Reset Theme Options',
            'tool-delete-transients' => 'Delete Transients',
            'tool-purge-cache' => 'Purge Cache',
            'tool-delete-local-data' => 'Delete Local Data',
            'tool-reset-user-roles' => 'Reset User Roles',
            'tool-delete-content' => 'Delete Content',
            'tool-delete-widgets' => 'Delete Widgets',
            'tool-delete-themes' => 'Delete Themes',
            'tool-delete-plugins' => 'Delete Plugins',
            'tool-manage-mu-plugins' => 'Manage MU Plugins',
            'tool-manage-dropins' => 'Manage Drop-ins',
            'tool-delete-uploads' => 'Clean Uploads Folder',
            'tool-delete-wp-content' => 'Clean wp-content Folder',
            'tool-empty-delete-custom-tables' => 'Empty or Delete Custom Tables',
            'tool-switch-wp-version' => 'Switch WP Version',
            'tool-delete-htaccess' => 'Delete .htaccess File'
        );

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Index of Tools', 'wp-reset'), 'iot', array('collapse_button' => true)));
        echo '<div class="card-body">';
        $i = 0;
        $tools_nb = sizeof($tools);
        foreach ($tools as $tool_id => $tool_name) {
            if ($i == 0) {
                echo '<div class="third">';
                echo '<ul class="mb0 plain-list">';
            }
            if ($i == 6 || $i == 11) {
                echo '</div>';
                echo '<div class="third">';
                echo '<ul class="mb0 plain-list">';
            }

            echo '<li><a title="Jump to ' . esc_html($tool_name) . ' tool" class="scrollto" href="#' . esc_html($tool_id) . '">' . esc_html($tool_name) . '</a></li>';

            if ($i == $tools_nb - 1) {
                echo '</ul>';
                echo '</div>'; // third
            }
            $i++;
        } // foreach tools
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Reset Theme Options', 'wp-reset'), 'tool-reset-theme-options', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        self::wp_kses_wf('<p>' . __('All options (mods) for all themes will be reset; not just for the active theme. The tool works only for themes that use the <a href="https://codex.wordpress.org/Theme_Modification_API" target="_blank">WordPress theme modification API</a>. If options are saved in some other custom way they won\'t be reset.', 'wp-reset') . '</p>');

        self::wp_kses_wf($this->get_tool_icons(false, true));

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to reset all theme options?" data-btn-confirm="Reset theme options" data-text-wait="Resetting theme options. Please wait." data-text-confirm="All options (mods) for all themes will be reset.' . esc_html($this->get_autosnapshot_tools_modal('Before running the reset theme options tool')) . '" data-text-done="Options for %n themes have been reset." data-text-done-singular="Options for one theme have been reset." class="button button-delete" href="#" id="reset-theme-options">Reset theme options</a></p>';
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete Transients', 'wp-reset'), 'tool-delete-transients', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All transient related database entries will be deleted. Including expired and non-expired transients, and orphaned transient timeout entries.</p>';

        self::wp_kses_wf($this->get_tool_icons(false, true));

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all transients?" data-btn-confirm="Delete all transients" data-text-wait="Deleting transients. Please wait." data-text-confirm="All database entries related to transients will be deleted.' . esc_html($this->get_autosnapshot_tools_modal('Before running the delete transients tool')) . '" data-text-done="%n transient database entries have been deleted." data-text-done-singular="One transient database entry has been deleted." class="button button-delete" href="#" id="delete-transients">Delete all transients</a></p>';
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Purge Cache', 'wp-reset'), 'tool-purge-cache', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All cache objects stored in both files and the database will be deleted. Along with WP object cache and transients, cache from the following plugins will be purged: W3 Total Cache, WP Cache, LiteSpeed Cache, Endurance Page Cache, SiteGround Optimizer, WP Fastest Cache and Swift Performance.</p>';

        self::wp_kses_wf($this->get_tool_icons(true, true));

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to purge all cache?" data-btn-confirm="Purge cache" data-text-wait="Purging cache. Please wait." data-text-confirm="All cache objects will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="Cache has been purged." data-text-done-singular="Cache has been purged." class="button button-delete" href="#" id="purge-cache">Purge cache</a></p>';
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete Local Data', 'wp-reset'), 'tool-delete-local-data', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All local storage and session storage data will be deleted. Cookies without a custom set path will be deleted as well. WP cookies are not touched, with Delete Local Data button.<br>Deleting all WordPress cookies (including authentication cookies) will delete all WP related cookies and user (you) will be logged out on the next page reload.
    </p>';

        self::wp_kses_wf($this->get_tool_icons(false, false));

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all local data?" data-btn-confirm="Delete local data" data-text-wait="Deleting local data. Please wait." data-text-confirm="All local data; cookies, local storage and local session will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make backups of local data." data-text-done="%n local data objects have been deleted." data-text-done-singular="One local data object has been deleted." class="button button-delete" href="#" id="delete-local-data">Delete local data</a><a data-confirm-title="Are you sure you want to delete all WP related cookies?" data-btn-confirm="Delete all WordPress cookies" data-text-wait="Deleting WP cookies. Please wait." data-text-confirm="All WP cookies including authentication ones will be deleted. You will have to log in again. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make backups of cookies." data-text-done="All WP cookies have been deleted." data-text-done-singular="All WP cookies been deleted." class="button button-delete" href="#" id="delete-wp-cookies">Delete all WordPress cookies</a></p>';
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Reset User Roles', 'wp-reset'), 'tool-reset-user-roles', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>Default user roles\' capabilities will be reset to their default values. All custom roles will be deleted.<br>Users that had custom roles will not be assigned any default ones and might not be able to log in. Roles have to be (re)assigned to them manually.</p>';
        self::wp_kses_wf($this->get_tool_icons(false, true));

        echo '<p class="mb0">';
        echo '<a data-confirm-title="Are you sure you want to reset your user roles\' capabilities and delete all custom roles?" data-text-wait="Resetting user roles. Please wait." data-text-confirm="Default user roles\' capabilities will be reset to their default values. All custom roles will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done-singular="User roles have been reset." class="button button-delete" href="#" id="reset-user-roles">Reset user roles</a>';
        echo '</p>';

        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete Content', 'wp-reset'), 'tool-delete-content', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>Besides content, all linked or child records (for selected content) will be deleted to prevent creating orphaned rows in the database. For instance, for posts that\'s posts, post meta, and comments related to posts. Delete process does not call any WP hooks such as <i>before_delete_post</i>. Choosing a post type or taxonomy does not delete that parent object it deletes the child objects. Parent objects are defined in code. If you want to remove them, remove their code definition. When media is deleted, files are left in the uploads folder. To delete files use the <a class="scrollto" href="#tool-delete-uploads">Clean Uploads Folder</a> tool. Deleting users does not affect the current, logged in user account. All orphaned objects will be reassigned to him.</p>';

        self::wp_kses_wf($this->get_tool_icons(false, true));

        $post_types = get_post_types('', false, 'and');
        $taxonomies = get_taxonomies('', false, 'and');

        echo '<p><select size="6" multiple id="delete-content-types">';
        echo '<option value="_comments">Comments (' . ((int) $wpdb->get_var("SELECT COUNT(comment_id) FROM $wpdb->comments")) . ')</option>'; //phpcs:ignore
        echo '<option value="_users">Users (' . ((int) $wpdb->get_var("SELECT COUNT(id) FROM $wpdb->users")) . ')</option>'; //phpcs:ignore
        foreach ($post_types as $type) {
            $count = wp_count_posts($type->name, 'readable');
            $tmp = 0;
            foreach ($count as $cnt) {
                $tmp += (int) $cnt;
            }
            echo '<option value="' . esc_html($type->name) . '">Post type - ' . esc_html($type->label) . ' (' . esc_html($tmp) . ')</option>';
        } // foreach post types
        foreach ($taxonomies as $tax) {
            echo '<option value="_tax_' . esc_html($tax->name) . '">Taxonomy - ' . esc_html($tax->label) . ' (' . esc_html(wp_count_terms($tax->name)) . ')</option>';
        } // foreach post types

        echo '</select><br>';
        echo 'Select content object(s) you want to delete. Use ctrl + click to select multiple objects.</p>';

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all content for the selected post types?" data-text-wait="Deleting content. Please wait." data-text-confirm="All content and its metadata for the selected post types will be deleted.' . esc_html($this->get_autosnapshot_tools_modal('Before running the delete content tool')) . '" data-text-done="%n objects have been deleted." data-text-done-singular="One object has been deleted." class="button button-delete" href="#" id="delete-content">Delete content</a></p>';
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete Widgets', 'wp-reset'), 'tool-delete-widgets', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All widgets, orphaned, active and inactive ones, as well as widgets in active and inactive sidebars will be deleted including their settings. After deleting, WordPress will automatically recreate default, empty database entries related to widgets. So, no matter how many times users run the tool it will never return "no data deleted". That\'s expected and normal.</p>';

        self::wp_kses_wf($this->get_tool_icons(false, true));

        echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all widgets?" data-btn-confirm="Delete widgets" data-text-wait="Deleting widgets. Please wait." data-text-confirm="All widgets, active and inactive ones will be deleted.' . esc_html($this->get_autosnapshot_tools_modal('Before running the delete widgets tool')) . '" data-text-done="%n database rows related to widgets have been deleted." data-text-done-singular="One database row related to widgets has been deleted." class="button button-delete" href="#" id="delete-widgets">Delete widgets</a></p>';
        echo '</div>';
        echo '</div>';

        $theme =  wp_get_theme();

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete Themes', 'wp-reset'), 'tool-delete-themes', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete themes for all sites in the network since they all share the same theme files.</p>';
        } else {
            /* translators: %s is the name of the theme that will be deleted */
            echo '<p>' . esc_html(sprintf(__('All themes will be deleted. Including the currently active theme - %s', 'wp-reset'), $theme->get('Name'))) . '</p>';

            self::wp_kses_wf($this->get_tool_icons(true, true));

            echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all themes?" data-btn-confirm="Delete all themes" data-text-wait="Deleting all themes. Please wait." data-text-confirm="All themes will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="%n themes have been deleted." data-text-done-singular="One theme has been deleted." class="button button-delete" href="#" id="delete-themes">Delete all themes</a></p>';
        }
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete Plugins', 'wp-reset'), 'tool-delete-plugins', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete plugins for all sites in the network since they all share the same plugin files.</p>';
        } else {
            /* translators: %s is the WP Reset plugin name */
            self::wp_kses_wf('<p>' . sprintf(__('All plugins will be deleted except for %s which will remain active.', 'wp-reset'), $plugin_name) . '</p>');

            self::wp_kses_wf($this->get_tool_icons(true, true));

            echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all plugins?" data-btn-confirm="Delete plugins" data-text-wait="Deleting plugins. Please wait." data-text-confirm="All plugins except ' . esc_html($plugin_name) . ' will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="%n plugins have been deleted." data-text-done-singular="One plugin has been deleted." class="button button-delete" href="#" id="delete-plugins">Delete plugins</a></p>';
        }
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Manage Must-Use (MU) Plugins', 'wp-reset'), 'tool-manage-mu-plugins', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>MU Plugins are located in <code>/wp-content/mu-plugins/</code> and are, as the name suggests, must-use plugins that are automatically activated by WP and can\'t be deactiavated via the <a href="' . esc_url(admin_url('plugins.php?plugin_status=mustuse')) . '" target="_blank">plugins interface</a>, although if any are used, they are listed in the "Must Use" tab.</p>';

        self::wp_kses_wf($this->get_tool_icons(true, false));

        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete plugins for all sites in the network since they all share the same plugin files.</p>';
        } else {
            $mu_plugins = get_mu_plugins();
            if (!empty($mu_plugins)) {
                echo '<ul class="plain-list">';
                foreach ($mu_plugins as $mu_file => $mu_plugin) {
                    echo '<li>';
                    echo esc_html($mu_plugin['Name']) . ' ';
                    echo ' <a data-confirm-title="Are you sure you want to delete the ' . esc_html($mu_plugin['Name']) . ' must-use plugin?" data-btn-confirm="Delete must-use plugin" data-text-wait="Deleting must-use plugin. Please wait." data-text-confirm="Must-use plugin will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="Must-use plugin has been deleted. Click OK to reload the page." data-mu-file="' . esc_html($mu_file) . '" class="button-delete delete-mu-plugin" href="#">Delete</a>';
                    echo '</li>';
                } // foreach
                echo '</ul>';

                echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all must-use plugins?" data-btn-confirm="Delete must-use plugins" data-text-wait="Deleting Must-Use plugins. Please wait." data-text-confirm="All must-use plugins will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="%n must-use plugins have been deleted." data-text-done-singular="One must-use plugin has been deleted." class="button button-delete" href="#" id="delete-mu-plugins">Delete all must-use plugins</a></p>';
            } else {
                echo '<p>No must-use plugins found.</p>';
            }
        }

        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Manage Drop-ins', 'wp-reset'), 'tool-manage-dropins', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>Drop-ins are pieces of code found in <code>/wp-content/</code> that replace default, built-in WordPress functionality. Most often used are <code>db.php</code> and <code>advanced-cache.php</code> that implement custom DB and cache functionality. They can\'t be deactivated via the <a href="' . esc_url(admin_url('plugins.php?plugin_status=dropins')) . '" target="_blank">plugins interface</a> but if any are present they are listed in the "Drop-in" tab.<br>Disabled drop-ins are renamed to: <code>_disabled_wpr_original_filename.php</code></p>';

        self::wp_kses_wf($this->get_tool_icons(true, false));

        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would delete plugins for all sites in the network since they all share the same plugin files.</p>';
        } else {
            $dropins = _get_dropins();
            $has_dropins = false;

            echo '<ul class="plain-list">';
            foreach ($dropins as $dropin_file => $dropin) {
                if (($active_dropin = file_exists(trailingslashit(WP_CONTENT_DIR) . $dropin_file)) ||
                    file_exists(trailingslashit(WP_CONTENT_DIR) . '_disabled_wpr_' . $dropin_file)
                ) {
                    $has_dropins = true;

                    if ($active_dropin) {
                        $plugin_details = get_plugin_data(trailingslashit(WP_CONTENT_DIR) . $dropin_file);
                    } else {
                        $plugin_details = get_plugin_data(trailingslashit(WP_CONTENT_DIR) . '_disabled_wpr_' . $dropin_file);
                    }
                    echo '<li>';
                    if (!empty($plugin_details['Name'])) {
                        echo esc_html($plugin_details['Name']) . ' <code>' . esc_html($dropin_file) . '</code>';
                    } else {
                        echo esc_html($dropin[0]) . ' <code>' . esc_html($dropin_file) . '</code>';
                    }
                    if ($active_dropin) {
                        echo ' <a data-confirm-title="Are you sure you want to deactivate the ' . esc_html($dropin_file) . ' drop-in?" data-btn-confirm="Deactivate drop-in" data-text-wait="Deactivating drop-in. Please wait." data-text-confirm="If the site becomes unresponsive, and you can\'t access WP admin use the Emergency Recovery Script to regain access." data-text-done="Drop-in has been deactivated. Click OK to reload the page." data-dropin-file="' . esc_html($dropin_file) . '" class="deactivate-dropin" href="#">Deactivate</a>';
                    } else {
                        echo ' <a data-confirm-title="Are you sure you want to activate the ' . esc_html($dropin_file) . ' drop-in?" data-btn-confirm="Activate drop-in" data-text-wait="Activating drop-in. Please wait." data-text-confirm="If the site becomes unresponsive, and you can\'t access WP admin use the Emergency Recovery Script to regain access." data-text-done="Drop-in has been activated. Click OK to reload the page." data-dropin-file="_disabled_wpr_' . esc_html($dropin_file) . '" class="activate-dropin" href="#">Activate</a>';
                    }
                    echo ' <a data-confirm-title="Are you sure you want to delete the ' . esc_html($dropin_file) . ' drop-in?" data-btn-confirm="Delete drop-in" data-text-wait="Deleting drop-in. Please wait." data-text-confirm="Drop-in will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="Drop-in has been deleted. Click OK to reload the page." data-dropin-file="' . (!$active_dropin ? '_disabled_wpr_' : '') . esc_html($dropin_file) . '" class="button-delete delete-dropin" href="#">Delete</a>';
                    echo '</li>';
                }
            } // foreach dropins
            echo '</ul>';

            if ($has_dropins) {
                echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all drop-ins?" data-btn-confirm="Delete drop-ins" data-text-wait="Deleting drop-ins. Please wait." data-text-confirm="All drop-ins will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="%n drop-ins have been deleted." data-text-done-singular="One drop-in has been deleted." class="button button-delete" href="#" id="delete-dropins">Delete all drop-ins</a></p>';
            } else {
                echo 'No drop-ins found.';
            }
        }

        echo '</div>';
        echo '</div>';

        $upload_dir = wp_upload_dir(gmdate('Y/m'), true);
        $upload_dir['basedir'] = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $upload_dir['basedir']);

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Clean Uploads Folder', 'wp-reset'), 'tool-delete-uploads', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All files in <code>' . esc_html($upload_dir['basedir']) . '</code> folder will be deleted. Including folders and subfolders, and files in subfolders. Files associated with <a href="' . esc_url(admin_url('upload.php')) . '">media</a> entries will be deleted too.</p>';

        self::wp_kses_wf($this->get_tool_icons(true, false));

        if (false != $upload_dir['error']) {
            echo '<p class="mb0"><span style="color:#dd3036;"><b>Tool is not available.</b></span> Folder is not writeable by WordPress. Please check file and folder access rights.</p>';
        } else {
            echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all files &amp; folders in uploads folder?" data-btn-confirm="Delete everything in uploads folder" data-text-wait="Deleting uploads. Please wait." data-text-confirm="All files and folders in uploads will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="%n files &amp; folders have been deleted." data-text-done-singular="One file or folder has been deleted." class="button button-delete" href="#" id="delete-uploads">Delete all files &amp; folders in uploads folder</a></p>';
        }
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Clean wp-content Folder', 'wp-reset'), 'tool-delete-wp-content', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        echo '<p>All folders and their content in <code>wp-content</code> folder except the following ones will be deleted: <code>mu-plugins</code>, <code>plugins</code>, <code>themes</code>, <code>uploads</code>, <code>wp-reset-autosnapshots</code>, <code>wp-reset-snapshots-export</code>.</p>';

        self::wp_kses_wf($this->get_tool_icons(true, false));

        if (false === is_writable(trailingslashit(WP_CONTENT_DIR))) { //phpcs:ignore
            echo '<p class="mb0"><span style="color:#dd3036;"><b>Tool is not available.</b></span> Folder is not writeable by WordPress. Please check file and folder access rights.</p>';
        } else {
            echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete all folders in wp-content folder?" data-btn-confirm="Delete folders in wp-content folder" data-text-wait="Cleaning wp-content. Please wait." data-text-confirm="All folders in wp-content will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="%n files &amp; folders have been deleted." data-text-done-singular="One folder or folder has been deleted." class="button button-delete" href="#" id="delete-wp-content">Clean wp-content folder</a></p>';
        }
        echo '</div>';
        echo '</div>';

        $custom_tables = $this->get_custom_tables(true);

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Empty or Delete Custom Tables', 'wp-reset'), 'tool-empty-delete-custom-tables', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        /* translators: %s WordPress table prefix, i.e. wp_ */
        self::wp_kses_wf('<p>' . sprintf(__('This action affects only custom tables with %s prefix. Core WP tables and other tables in the database that do not have that prefix will not be deleted/emptied. Deleting (dropping) tables completely removes them from the database. Emptying (truncating) removes all content from them, but keeps the structure intact.</p>', 'wp-reset'), '<code>' . $wpdb->prefix . '</code>'));

        self::wp_kses_wf($this->get_tool_icons(false, true, true));

        if ($custom_tables) {
            echo '<p>';
            echo '<select multiple size="6" id="empty-delete-tables-tables">';
            echo '<option value="__all">All custom tables - ' . number_format(sizeof($custom_tables)) . '</value>';
            foreach ($custom_tables as $tbl) {
                echo '<option value="' . esc_html($tbl['name']) . '">' . esc_html($tbl['name']) . '</value>';
            } // foreach
            echo '</select><br>';
            echo 'Select the table(s) to truncate or delete. Use ctrl + click to select multiple tables.</p>';

            echo '<p class="mb0"><a data-confirm-title="Are you sure you want to empty selected custom tables?" data-btn-confirm="Empty custom tables" data-text-wait="Emptying custom tables. Please wait." data-text-confirm="Selected custom tables will be emptied.' . esc_html($this->get_autosnapshot_tools_modal('Before running the empty tables tool')) . '" data-text-done="%n custom tables have been emptied." data-text-done-singular="One custom table has been emptied." class="button button-delete" href="#" id="truncate-custom-tables">Empty (truncate) selected custom tables</a>';
            echo '<a data-confirm-title="Are you sure you want to delete selected custom tables?" data-btn-confirm="Delete custom tables" data-text-wait="Deleting custom tables. Please wait." data-text-confirm="Selected custom tables will be deleted.' . esc_html($this->get_autosnapshot_tools_modal('Before running the delete tables tool')) . '" data-text-done="%n custom tables have been deleted." data-text-done-singular="One custom table has been deleted." class="button button-delete" href="#" id="drop-custom-tables">Delete (drop) selected custom tables</a></p>';
        } else {
            echo '<p>' . esc_html__('There are no custom tables. There\'s nothing for this tool to empty or delete.', 'wp-reset') . '</p>';
        }
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Switch WP Version', 'wp-reset'), 'tool-switch-wp-version', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would change the WP version for all sites in the network since they all share the same core files.</p>';
        } else {
            echo '<p>Replace current WordPress version with the selected new version. Switching from a previous version, to a newer version is mostly supported and properly handled by the WP installer. Reverting WordPress, rolling back WordPress to a previous version is not supported. Results may vary!</p>';

            self::wp_kses_wf($this->get_tool_icons(true, true));

            $wp_versions = $wp_reset_tools->get_wordpress_versions();

            echo '<label for="select-wp-version">Available WordPress versions:</label> ';
            echo '<select id="select-wp-version">';
            echo '<option value="">select WordPress version</option>';
            foreach ($wp_versions as $version => $release_date) {
                if ($release_date == 'bleeding') {
                    echo '<option value="bleeding">WordPress v' . esc_html($version) . ' (Bleeding edge nightly)' . ($wp_version == $version ? ' - installed' : '') . '</option>';
                } else if ($release_date == 'point') {
                    echo '<option value="point-' . esc_html(substr($version, 0, 3)) . '">WordPress v' . esc_html($version) . ' (Point release nightly)' . ($wp_version == $version ? ' - installed' : '') . '</option>';
                } else {
                    echo '<option value="' . esc_html($version) . '">WordPress v' . esc_html($version) . ' (' . esc_html(gmdate('Y-m-d', $release_date)) . ')' . ($wp_version == $version ? ' - installed' : '') . '</option>';
                }
            }
            echo '</select>';
            echo ' <a data-text-wait="Refreshing list of WordPress versions. Please Wait" id="refresh-wp-versions" class="button" href="#">Refresh list</a>';

            echo '<p class="mb0">';
            echo '<a data-confirm-title="Are you sure you want to switch the WordPress version?" data-text-wait="Switching WP  version. Please wait." data-text-confirm="This tool will replace your current WordPress installation with the new selected version. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="WordPress v%n has been installed. Reload the page to finish the process." class="button button-delete" href="#" id="switch-wp-version">Switch WordPress version</a>';
            echo '</p>';
        }
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header(esc_html__('Delete .htaccess File', 'wp-reset'), 'tool-delete-htaccess', array('collapse_button' => true, 'iot_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';
        if (is_multisite()) {
            echo '<p class="mb0 wpmu-error">This tool is <b>not compatible</b> with WP multisite (WPMU). Using it would change the .htaccess file for all sites in the network since they all use the same .htaccess file.</p>';
        } else {
            /* translators: %s is the .htaccess path */
            self::wp_kses_wf('<p>' . sprintf(__('This action deletes the .htaccess file located in %s', 'wp-reset'), '<code>' . esc_html($this->get_htaccess_path()) . '</code>') . '</p>');

            echo '<p>If you need to edit the .htaccess file, install the free <a href="' . esc_url(admin_url('plugin-install.php?tab=plugin-information&plugin=wp-htaccess-editor&TB_iframe=true&width=600&height=550')) . '" class="thickbox open-plugin-details-modal">WP Htaccess Editor</a> plugin. It automatically creates a backup when a user edits .htaccess as well as checks for syntax errors.</p>';

            self::wp_kses_wf($this->get_tool_icons(true, false));

            echo '<p class="mb0"><a data-confirm-title="Are you sure you want to delete the .htaccess file?" data-btn-confirm="Delete .htaccess file" data-text-wait="Deleting .htaccess file. Please wait." data-text-confirm="Htaccess file will be deleted. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="Htaccess file has been deleted." data-text-done-singular="Htaccess file has been deleted." class="button button-delete" href="#" id="delete-htaccess">Delete .htaccess file</a>';
            echo '<a data-confirm-title="Are you sure you want to restore the .htaccess file?" data-btn-confirm="Restore .htaccess file" data-text-wait="Restoring .htaccess file. Please wait." data-text-confirm="Htaccess file will be restored to default. There is NO UNDO. ' . esc_html($plugin_name) . ' does not make any file backups." data-text-done="Htaccess file has been restored." data-text-done-singular="Htaccess file has been restored." class="button" href="#" id="restore-htaccess">Restore .htaccess to default WP values</a></p>';
        }
        echo '</div>';
        echo '</div>';
    } // tab_tools

    /**
     * Echoes content for settings tab
     *
     * @return null
     */
    private function tab_settings()
    {
        $options = $this->get_options();
        $cloud_options = array_merge(array('none' => 'Disabled'), $this->cloud_services);
        $plugin_name = $this->get_rebranding('name');
        if ($plugin_name === false) {
            $plugin_name = 'WP Reset';
        }

        if (count($this->cloud_services) > 0) {
            echo '<div class="card">';
            self::wp_kses_wf($this->get_card_header('Cloud', 'cloud', array('collapse_button' => true)));
            echo '<div class="card-body">';

            echo '<p>Cloud feature lets you offload snapshots on the selected service to add redundancy and save space on your hosting account. It also stores ZIP files for pro plugins from collections.</p>';
            echo '<div class="sub-option-group">Cloud service: <select id="option_cloud_service">';

            WP_Reset_Utility::create_select_options($cloud_options, $options['cloud_service']);
            echo '</select></div>';

            echo '<p>Currently supported cloud services: WP Reset Cloud, Dropbox, Google Drive, pCloud.<br>';
            echo 'Switching the cloud service will not cause any snapshots or collections to be lost. They will be available when you switch back to the cloud service they are saved on.</p>';
            echo '</div>';
            echo '</div>';
        }

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header('Options', 'snapshots-options', array('collapse_button' => 1)));

        echo '<div class="card-body" id="snapshot-options-group">';
        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_tools_snapshots', array('saved_value' => $options['tools_snapshots']));
        echo '<div class="option-group-desc">Automatically create snapshots before running ' . esc_html($plugin_name) . ' tools</div>';
        echo '</div>';
        if (is_multisite()) {
            echo '<p class="wpmu-error mb0">Creating auto snapshots when doing updated and manipulating themes &amp; plugins is <b>not available on WP multisite</b> (WPMU).</p>';
        } else {
            echo '<div class="option-group">';
            WP_Reset_Utility::create_toogle_switch('option_events_snapshots', array('saved_value' => $options['events_snapshots']));
            echo '<div class="option-group-desc">Automatically create snapshots when doing updates and manipulating plugins &amp; themes</div>';
            echo '</div>';
        }

        if (count($this->cloud_services) > 0) {
            echo '<div class="option-group">';
            WP_Reset_Utility::create_toogle_switch('option_snapshots_autoupload', array('saved_value' => $options['snapshots_autoupload']));
            echo '<div class="option-group-desc">Automatically upload user created snapshots to cloud</div>';
            echo '</div>';

            echo '<div class="option-group">';
            WP_Reset_Utility::create_toogle_switch('option_autosnapshots_autoupload', array('saved_value' => $options['autosnapshots_autoupload']));
            echo '<div class="option-group-desc">Automatically upload automatically created snapshots to cloud</div>';
            echo '</div>';
        }

        if (count($this->cloud_services) > 0) {
            echo '<div class="option-group">';
            WP_Reset_Utility::create_toogle_switch('option_snapshots_upload_delete', array('saved_value' => $options['snapshots_upload_delete']));
            echo '<div class="option-group-desc">Automatically delete snapshots after they are uploaded to cloud</div>';
            echo '</div>';
        }

        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_prune_snapshots', array('saved_value' => $options['prune_snapshots'], 'class' => 'has-suboption'));
        echo '<div class="option-group-desc">Automatically delete automatic snapshots</div>';
        $prune_details = array(
            'days-1' => 'older than one day',
            'days-3' => 'older than 3 days',
            'days-5' => 'older than 5 days',
            'days-7' => 'older than 7 days',
            'days-10' => 'older than 10 days',
            'days-15' => 'older than 15 days',
            'days-30' => 'older than 30 days',
            'cnt-10' => 'when there are more than 10 snapshots saved',
            'cnt-20' => 'when there are more than 20 snapshots saved',
            'cnt-50' => 'when there are more than 50 snapshots saved',
            'cnt-100' => 'when there are more than 100 snapshots saved',
            'size-50' => 'when total snapshot size exceeds 50 MB',
            'size-100' => 'when total snapshot size exceeds 100 MB',
            'size-200' => 'when total snapshot size exceeds 200 MB',
            'size-500' => 'when total snapshot size exceeds 500 MB',
            'size-1000' => 'when total snapshot size exceeds 1000 MB',
            'size-2000' => 'when total snapshot size exceeds 2000 MB',
            'size-3000' => 'when total snapshot size exceeds 3000 MB',
        );
        echo '<div class="sub-option-group">Delete automatic snapshots: <select id="option_prune_snapshots_details" style="width:295px;">';
        WP_Reset_Utility::create_select_options($prune_details, $options['prune_snapshots_details']);
        echo '</select></div></div>';

        if (count($this->cloud_services) > 0) {
            echo '<div class="option-group">';
            WP_Reset_Utility::create_toogle_switch('option_prune_cloud_snapshots', array('saved_value' => $options['prune_cloud_snapshots'], 'class' => 'has-suboption'));
            echo '<div class="option-group-desc">Automatically delete automatic snapshots uploaded to the cloud</div>';
            $cloud_prune_details = array(
                'days-1' => 'older than one day',
                'days-3' => 'older than 3 days',
                'days-5' => 'older than 5 days',
                'days-7' => 'older than 7 days',
                'days-10' => 'older than 10 days',
                'days-15' => 'older than 15 days',
                'days-30' => 'older than 30 days',
                'cnt-10' => 'when there are more than 10 snapshots saved',
                'cnt-20' => 'when there are more than 20 snapshots saved',
                'cnt-50' => 'when there are more than 50 snapshots saved',
                'cnt-100' => 'when there are more than 100 snapshots saved',
                'size-50' => 'when total snapshot size exceeds 50 MB',
                'size-100' => 'when total snapshot size exceeds 100 MB',
                'size-200' => 'when total snapshot size exceeds 200 MB',
                'size-500' => 'when total snapshot size exceeds 500 MB',
                'size-1000' => 'when total snapshot size exceeds 1000 MB',
                'size-2000' => 'when total snapshot size exceeds 2000 MB',
                'size-3000' => 'when total snapshot size exceeds 3000 MB',
            );
            echo '<div class="sub-option-group">Delete automatic snapshots from cloud: <select id="option_prune_cloud_snapshots_details" style="width:295px;">';
            WP_Reset_Utility::create_select_options($cloud_prune_details, $options['prune_cloud_snapshots_details']);
            echo '</select></div></div>';
        }

        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_ajax_snapshots_export', array('saved_value' => $options['ajax_snapshots_export']));
        echo '<div class="option-group-desc">Export snapshots table by table, slower but can be needed for large databases</div>';
        echo '</div>';

        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_adminbar_snapshots', array('saved_value' => $options['adminbar_snapshots']));
        echo '<div class="option-group-desc">Show ' . esc_html($plugin_name) . ' menu to administrators in admin bar</div>';
        echo '</div>';


        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_optimize_tables', array('saved_value' => $options['optimize_tables']));
        echo '<div class="option-group-desc">Optimize tables before creating snapshots (slower but improves accuracy when using the "Compare snapshot to current data" tool)</div>';
        echo '</div>';


        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_throttle_ajax', array('saved_value' => $options['throttle_ajax']));
        echo '<div class="option-group-desc">Throttle AJAX requests (some servers allow only a limited number of requests per second so all snapshot related AJAX requests have to be slowed down)</div>';
        echo '</div>';


        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_fix_datetime', array('saved_value' => $options['fix_datetime']));
        echo '<div class="option-group-desc">Fix mysql zero dates (enable this if you encounter errors related to date columns with zero dates when importing or downloading snapshots from the cloud )</div>';
        echo '</div>';


        echo '<div class="option-group">';
        echo '<label for="size">Display an alert when the total database space used by locally stored snapshots exceeds:</label> <select id="option_snapshots_size_alert">';
        WP_Reset_Utility::create_select_options(array('100' => '100MB', '250' => '250MB', '500' => '500MB', '1000' => '1 GB', '2000' => '2 GB', '0' => 'Disable Alert'), $options['snapshots_size_alert']);
        echo '</select>';
        echo '</div>';

        echo '<div class="option-group">';
        WP_Reset_Utility::create_toogle_switch('option_alternate_db_connection', array('saved_value' => $options['alternate_db_connection']));
        echo '<div class="option-group-desc">Use alternate MySQL connection. Enable if you encounter connection errors exporting/importing snapshots.</div>';
        echo '</div>';


        echo '<hr>';
        echo '<p><a href="#" class="button" id="save-snapshot-options">Save options</a></p>';
        echo '</div>';
        echo '</div>';
    } // tab_settings

    /**
     * Echoes content for support tab
     *
     * @return null
     */
    private function tab_support()
    {
        global $wp_reset_licensing;

        $options = $this->get_options();
        $plugin_name = $this->get_rebranding('name');
        if ($plugin_name === false) {
            $plugin_name = 'WP Reset';
        }

        if (!WP_Reset_Utility::whitelabel_filter()) {
            $license = $wp_reset_licensing->get_license();
            if (!empty($license['meta']['white_label_support_text'])) {
                echo '<div class="card">';
                self::wp_kses_wf($this->get_card_header('Info & Contact', 'custom-support-text', array()));
                echo '<div class="card-body">';
                echo '<p>' . esc_html($license['meta']['white_label_support_text']) . '</p>';
                echo '</div>';
                echo '</div>';
            }
        }

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header('Emergency Recovery Script', 'emergency-recovery-script', array('collapse_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';

        echo '<p>Emergency recovery script is a standalone, single-file, WP independent script created to recovery a WP site in the most difficult situations. When access to admin is not possible, when core files are compromised (accidental delete or malware), when you get the white-screen or can\'t log in for whatever reason. If WP is running normally, and you can access the admin, then there\'s no reason to use it.</p>';
        echo '<p>There are two ways to use the script: have it ready on the site in case of emergency, or upload it only when needed. On production sites, when big and potentially dangerous changes rarely happen, we suggest uploading it only when needed. On test sites have it ready because there\'s a higher probability that you\'ll need it.</p><hr>';

        $recovery_url = $this->wpr_recovery_path(true);

        if ($recovery_url !== false) {
            $recovery_password = $this->get_recovery_password();
            $recovery_version = $this->get_recovery_version();
            echo '<p><b>Emergency recovery script v' . esc_html($recovery_version) . ' is enabled</b> and available on the URL below. <b>Do NOT share</b> the URL with anyone. Its name is randomly generated, so nobody except you can get to it. Please bookmark the URL. When WP dies you won\'t be able to open this page.<br><a href="' . esc_url($recovery_url) . '" target="_blank">' . esc_url($recovery_url) . '</a></p>';
            echo 'The password for the Emergency recovery script is ' . (!empty($recovery_password) ? '<strong style="color:#F00">' . esc_html($recovery_password) . '</strong>' : '<strong style="color:#F00">not set</strong>, please remove and reinstall the script to automatically set a random password.') . '</strong>';

            echo '<p>';
            echo '<a class="button button-delete delete-recovery-script" href="#">Remove emergency recovery script</a>';
            if (version_compare($this->er_version, $recovery_version, '>')) {
                echo '<a class="button button-update update-recovery-script" href="#">Update emergency recovery script to v' . esc_html($this->er_version) . '</a>';
            }
            echo '</p>';
        } else {
            echo '<p>Emergency recovery script is not enabled.';
            if ($this->get_rebranding() === false) {
                echo 'You can always get it from the <a href="' . esc_url(trailingslashit($this->licensing_servers[0]) . 'wpr_recovery_download.php') . '" target="_blank">WP Reset website</a> and upload manually when needed or enable it with the button below so it\'s accessible on a secret URL.';
            }
            echo '</p>';
            echo '<p><a class="button install-recovery-script" href="#">Enable emergency recovery script</a></p>';
        }
        echo '</div>';
        echo '</div>';

        if (WP_Reset_Utility::whitelabel_filter()) {

            if ($this->get_rebranding() === false) {
                echo '<div class="card">';
                self::wp_kses_wf($this->get_card_header('Documentation', 'documentation-support', array()));
                echo '<div class="card-body">';
                /* translators: %s is the WP Reset documentation link */
                self::wp_kses_wf('<p>' . sprintf(__('All tools and functions are explained in detail in %s. We did our best to describe how things work on both the code level and an "average user" level. Most tools have direct links (look for the <span class="dashicons dashicons-editor-help"></span> icon) to specific parts of documentation.', 'wp-reset'), '<a href="' . $this->generate_web_link('support-tab', '/documentation/') . '" target="_blank">the documentation</a>') . '</p>');
                echo '<p>If needed you can always <a href="#" class="open-onboarding">run onboarding</a> again to help you test and setup ' . esc_html($plugin_name) . '</p>';
                echo '</div>';
                echo '</div>';

                echo '<div class="card">';
                self::wp_kses_wf($this->get_card_header('MainWP WP Reset Extension', 'documentation-support', array()));
                echo '<div class="card-body">';
                echo '<p>You can download the WP Reset PRO MainWP Extension, install and activate it on your MainWP Dashboard, then in your MainWP Dashboard you can access it under Extensions -> WP Reset PRO.</p>';
                echo '<p>Child websites that have WP Reset PRO version 6.14 or newer installed will show WP Reset information and allow you to reset them remotely.</p>';
                echo '<p class="red">Please note that the extension is in beta, so use it with caution.<p>';
                echo '<p><a href="https://dashboard.wpreset.com/wp-content/uploads/2024/03/wp-reset-mainwp-extension.zip">Download the WP Reset PRO MainWP extension.</a></p>';
                echo '</div>';
                echo '</div>';

                echo '<div class="card">';
                self::wp_kses_wf($this->get_card_header('Contact Support', 'contact-support', array()));
                echo '<div class="card-body">';
                echo '<p>Please don\'t hesitate to get in touch if you need any help. Try to be as detailed as possible so we can provide the best answer in the first reply. If you\'re unable to use our support widget in the lower right corner - <a href="mailto:wpreset@webfactoryltd.com">email us</a>.</p>';
                echo '<p>';
                echo '<a href="#" class="button button-primary open-beacon">Contact Support</a>';
                if ($options['debug'] == true) {
                    echo '<a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_debug=false')) . '" class="button button-delete disable-debug-mode">Disable debug mode</a>';
                } else {
                    echo '<a href="' . esc_url(admin_url('tools.php?page=wp-reset&wpr_debug=true')) . '" data-confirm-title="Are you sure you want to enable debug mode?" data-text-confirm="Debug mode will enable the Debug Tools tab which contains the Log and other tools related only to WP Reset. Only use this if you know what you are doing or have been instructed to do so by support. While debug mode is enabled, any PHP notices and errors will be printed in AJAX responses, so you should not leave the Debug mode enabled during normal use." data-btn-confirm="Enable debug mode" class="enable-debug-mode button button-delete">Enable debug mode</a>';
                }
                echo '</p>';
                echo '</div>';
                echo '</div>';
            } else {
                echo '<div class="card">';
                self::wp_kses_wf($this->get_card_header('Contact Support', 'contact-support', array()));
                echo '<div class="card-body">';
                self::wp_kses_wf($this->get_rebranding('support_content'));
                echo '</div>';
                echo '</div>';
            }
        }
    } // tab_support


    /**
     * Echoes content for snapshots tab
     *
     * @return null
     */
    private function tab_snapshots()
    {
        global $wpdb, $wp_reset_cloud;
        $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
        $options = $this->get_options();

        $snapshots = $this->get_all_snapshots();

        $snapshots = array_reverse($snapshots);
        $ss_user = array_filter($snapshots, function ($snapshot) {
            return !(bool) @$snapshot['auto'];
        });
        $ss_auto = array_filter($snapshots, function ($snapshot) {
            return (bool) @$snapshot['auto'];
        });

        //snapshots_size_alert
        $space_usage_total = 0;
        $space_usage_user = 0;
        $space_usage_auto = 0;

        foreach ($snapshots as $snapshot) {
            if (false === $snapshot['local']) {
                continue;
            }
            $space_usage_total += $snapshot['tbl_size'];
            if ($snapshot['auto']) {
                $space_usage_auto += $snapshot['tbl_size'];
            } else {
                $space_usage_user += $snapshot['tbl_size'];
            }
        }

        $snapshots = $this->get_snapshots();
        $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); //phpcs:ignore
        $uid_length = 7;
        if (strlen($wpdb->prefix) > 10) {
            $uid_length = 5;
        }

        $orphaned_snapshots = array();

        foreach ($tables as $table) {
            if ($uid_length !== stripos($table[0], $wpdb->prefix)) {
                continue;
            }
            $current_id = substr($table[0], 0, ($uid_length - 1));

            if (array_key_exists($current_id, $snapshots)) {
                continue;
            }

            if (!array_key_exists($current_id, $orphaned_snapshots)) {
                $orphaned_snapshots[$current_id] = array();
            }
            $orphaned_snapshots[$current_id][] = $table[0];
        } // foreach



        if ($space_usage_total / 1000000 > $options['snapshots_size_alert']) {
            echo '<div class="card">';
            self::wp_kses_wf('<span style="color: #dd3036;">' . $this->get_card_header('Snapshot Database Usage Alert', 'snapshots-user', array('collapse_button' => 0, 'create_snapshot' => false, 'snapshot_actions' => false)) . '</span>');
            echo '<div class="card-body">';
            echo '<p style="color: #dd3036;">';
            self::wp_kses_wf('Your snapshots are using ' . WP_Reset_Utility::format_size($space_usage_total) . ' total space in the database!<br />');
            self::wp_kses_wf('User snapshots take up ' . WP_Reset_Utility::format_size($space_usage_user) . '<br />');
            self::wp_kses_wf('Automatic snapshots take up ' . WP_Reset_Utility::format_size($space_usage_auto) . '<br />');
            echo '</p>';
            echo '<p>If there are snapshots that you don\'t need right away but still want to keep them, you can download them or upload them to cloud and then delete them from your website.
            For automatic snapshots you can also enable the "Automatically delete automatic snapshots" option in the <a class="change-tab" data-tab="4" href="#snapshot-options-group">Settings</a> tab to limit the space used up by automatic snapshots.</p>';
            echo '</div>';
            echo '</div>';
        }

        echo '<div class="card">';
        self::wp_kses_wf('<span>' . $this->get_card_header('WP Reset Resource Usage', 'snapshots-user', array('collapse_button' => true, 'create_snapshot' => false, 'snapshot_actions' => false)) . '</span>');
        echo '<div class="card-body">';
        echo '<p>';
        self::wp_kses_wf('Your snapshots are using <strong>' . WP_Reset_Utility::format_size($space_usage_total) . '</strong> total space in the database:<br />');
        self::wp_kses_wf(' - User snapshots take up ' . WP_Reset_Utility::format_size($space_usage_user) . '<br />');
        self::wp_kses_wf(' - Automatic snapshots take up ' . WP_Reset_Utility::format_size($space_usage_auto) . '<br />');
        echo '<p>If there are snapshots that you don\'t need right away but still want to keep them, you can download them to your computer or upload them to cloud and then delete them from your website.
        For automatic snapshots you can also enable the "Automatically delete automatic snapshots" option in the <a class="change-tab" data-tab="4" href="#snapshot-options-group">Settings</a> tab to limit the space used up by automatic snapshots.</p>';

        echo '<hr />';

        if (!empty($orphaned_snapshots)) {
            $total_orphaned = 0;
            foreach ($orphaned_snapshots as $uid => $snapshot) {
                if (array_key_exists($uid, $snapshots)) {
                    continue;
                }
                foreach ($snapshot as $table) {
                    $total_orphaned++;
                }
            }
            echo '<p>There are ' . esc_html($total_orphaned) . ' tables that <strong>appear</strong> to have belonged to snapshots that have been deleted. They could have remained behind because of a timeout or other errors when deleting the associated snapshot.</p>';
            echo '<p>Please enable Debug mode in the Support tab and review the tables in case they need to be deleted.</p>';
            echo '<hr />';
        }



        $autosnapshot_size = $this->folder_size(trailingslashit(WP_CONTENT_DIR) . 'wp-reset-autosnapshots');
        $snapshot_size = $this->folder_size(trailingslashit(WP_CONTENT_DIR) . 'wp-reset-snapshots-export');

        self::wp_kses_wf('Total Disk space used by temporary files such as snapshot download ZIP files is <strong>' . WP_Reset_Utility::format_size($autosnapshot_size + $snapshot_size) . '</strong>');
        echo '<br /><br />';
        echo '<p><a class="button button-delete" href="' . esc_url(add_query_arg(array('_wpnonce' => wp_create_nonce('wpr_delete_temporary_files'), 'action' => 'wpr_delete_temporary_files', 'redirect' => urlencode(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? '')))), admin_url('admin.php'))) . '">Delete temporary files</a></p>';
        echo '</p>';
        echo '</div>';
        echo '</div>';


        echo '<div class="card" id="card-snapshots">';
        self::wp_kses_wf($this->get_card_header('Snapshots', 'snapshots-info', array('collapse_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';

        echo '<p>A snapshot is a copy of all WP database tables, standard and custom ones, saved in the site\'s database. <a href="https://www.youtube.com/watch?v=xBfMmS12vMY" target="_blank">Watch a short video</a> overview and tutorial about Snapshots.</p>';

        echo '<p>Snapshots are primarily a development tool. When using various reset tools we advise using our 1-click snapshot tool available in every tool\'s confirmation dialog. If a full backup that includes files is needed, use one of the <a href="' . esc_url(admin_url('plugin-install.php?s=backup&tab=search&type=term')) . '" target="_blank">backup plugins</a> from the repo.</p>';

        echo '<p>Use snapshots to find out what changes a plugin made to your database or to quickly restore the dev environment after testing database related changes. Restoring a snapshot does not affect other snapshots, or WP Reset settings.</p>';

        $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); //phpcs:ignore

        if (is_array($tables)) {
            foreach ($tables as $table) {
                if (0 !== stripos($table[0], $wpdb->prefix)) {
                    continue;
                }

                if (in_array($table[0], $this->core_tables)) {
                    $tbl_core++;
                } else {
                    $tbl_custom++;
                }
            } // foreach

            echo '<p><b>Currently used WordPress tables</b>, prefixed with <i>' . esc_html($wpdb->prefix) . '</i>, consist of ' . esc_html($tbl_core) . ' standard and ';
            if ($tbl_custom) {
                echo esc_html($tbl_custom) . ' custom table' . ($tbl_custom == 1 ? '' : 's');
            } else {
                echo 'no custom tables';
            }
            echo ' <span id="wpr-table-details"><a href="#" id="show-table-details">(show details)</a></span>';
        }

        echo '<p>You can configure Snapshot Options in the <a class="change-tab" data-tab="4" href="#snapshot-options-group">Settings</a> tab</p>';

        echo '</div>';
        echo '</div>'; // snapshots desc




        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header('User Created Snapshots', 'snapshots-user', array('collapse_button' => 1, 'create_snapshot' => true, 'snapshot_actions' => true)));
        echo '<div class="card-body">';

        echo '<table id="snapshots-table-user">';
        echo '<tr><th></th><th class="ss-date">Date</th><th>Description</th><th class="ss-size">Size</th><th class="ss-actions">';
        echo '<div class="dropdown">
        <a class="button dropdown-toggle" href="#">Actions</a>
        <div class="dropdown-menu">
        <a title="Import a snapshot" href="#" class="dropdown-item import-snapshot">' . esc_html__('Import Snapshot', 'wp-reset') . '</a>';

        echo '<a title="Delete all user snapshots" data-snapshots="selected_user" href="#" class="dropdown-item delete-snapshots delete-button" data-btn-confirm="Delete selected snapshots" data-text-wait="Deleting snapshots. Please wait." data-text-confirm="Are you sure you want to delete the selected user created snapshots? There is NO UNDO.<br>Deleting the snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted.">' . esc_html__('Delete Selected User Snapshots', 'wp-reset') . '</a>';
        echo '<a title="Delete selected user snapshots" data-snapshots="user" href="#" class="dropdown-item delete-snapshots delete-button" data-btn-confirm="Delete all snapshots" data-text-wait="Deleting snapshots. Please wait." data-text-confirm="Are you sure you want to delete all user created snapshots? There is NO UNDO.<br>Deleting the snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted.">' . esc_html__('Delete All User Snapshots', 'wp-reset') . '</a>';

        if (!empty($options['cloud_service']) && array_key_exists($options['cloud_service'], $this->cloud_services)) {
            echo '<a title="Refresh cloud snapshots" href="#" class="dropdown-item refresh-cloud-snapshots"><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span>' . esc_html__('Refresh Cloud Snapshots', 'wp-reset') . '</a>';
            echo '<a title="Delete all user cloud snapshots" data-snapshots="user" href="#" class="dropdown-item delete-cloud-snapshots delete-button" data-btn-confirm="Delete all user snapshots from the cloud" data-text-wait="Deleting user snapshots from the cloud. Please wait." data-text-confirm="Are you sure you want to delete all user snapshots from the cloud? There is NO UNDO.<br>Deleting the cloud snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted."><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span>' . esc_html__('Delete All User Snapshots from the Cloud', 'wp-reset') . '</a>';
            echo '<a title="Delete selected user snapshots" data-snapshots=selected_user href="#" class="dropdown-item delete-cloud-snapshots delete-button" data-btn-confirm="Delete selected user snapshots from the cloud" data-text-wait="Deleting selected user snapshots from the cloud. Please wait." data-text-confirm="Are you sure you want to delete the selected user snapshots from the cloud? There is NO UNDO.<br>Deleting the cloud snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted."><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span>' . esc_html__('Delete Selected User Snapshots from the Cloud', 'wp-reset') . '</a>';
        }

        echo '</div>
        </div>';
        echo '</th></tr>';
        echo '<tr class="table-empty hidden"><td colspan="5" class="textcenter">There are no user created snapshots. <a href="#" class="create-new-snapshot">Create a new snapshot.</a></td></tr>';
        foreach ((array) $ss_user as $ss) {
            self::wp_kses_wf($this->get_snapshot_row($ss, 'user'));
        } // foreach
        echo '</table>';
        echo '</div>';
        echo '</div>';

        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header('Automatic Snapshots', 'snapshots-auto', array('collapse_button' => 1)));
        echo '<div class="card-body">';
        echo '<table id="snapshots-table-auto">';
        echo '<tr><th></th><th class="ss-date">Date</th><th>Description</th><th class="ss-size">Size</th><th class="ss-actions">';
        echo '<div class="dropdown">
        <a class="button dropdown-toggle" href="#">Actions</a>
        <div class="dropdown-menu">
        <a title="Delete all auto snapshots" data-snapshots="auto" href="#" class="dropdown-item delete-snapshots delete-button" data-btn-confirm="Delete all snapshots" data-text-wait="Deleting snapshots. Please wait." data-text-confirm="Are you sure you want to delete all automatic snapshots? There is NO UNDO.<br>Deleting the snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted.">' . esc_html__('Delete All Automatic Snapshots', 'wp-reset') . '</a>
        <a title="Delete selected auto snapshots" data-snapshots=selected_auto href="#" class="dropdown-item delete-snapshots delete-button" data-btn-confirm="Delete selected auto snapshots" data-text-wait="Deleting selected automatic snapshots. Please wait." data-text-confirm="Are you sure you want to delete the selected automatic snapshots? There is NO UNDO.<br>Deleting the snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted.">' . esc_html__('Delete Selected Automatic Snapshots', 'wp-reset') . '</a>';

        if (!empty($options['cloud_service']) && array_key_exists($options['cloud_service'], $this->cloud_services)) {
            echo '<a title="Refresh cloud snapshots" href="#" class="dropdown-item refresh-cloud-snapshots"><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span>' . esc_html__('Refresh Cloud Snapshots', 'wp-reset') . '</a>';
            echo '<a title="Delete all auto snapshots from the cloud" data-snapshots="auto" href="#" class="dropdown-item delete-cloud-snapshots delete-button" data-btn-confirm="Delete all automatic snapshots from the cloud" data-text-wait="Deleting automatic snapshots from the cloud. Please wait." data-text-confirm="Are you sure you want to delete all automatic snapshots from the cloud? There is NO UNDO.<br>Deleting the cloud snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted."><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span>' . esc_html__('Delete All Automatic Snapshots from the Cloud', 'wp-reset') . '</a>';
            echo '<a title="Delete selected auto snapshots from the cloud" data-snapshots=selected_auto href="#" class="dropdown-item delete-cloud-snapshots delete-button" data-btn-confirm="Delete selected automatic snapshots from the cloud" data-text-wait="Deleting selected automatic snapshots from the cloud. Please wait." data-text-confirm="Are you sure you want to delete the selected automatic snapshots from the cloud? There is NO UNDO.<br>Deleting the cloud snapshots will not affect the active database tables in any way." data-text-done="%n snapshots deleted." data-text-done-singular="One snapshot deleted."><span title="Cloud Actions" class="dashicons dashicons-cloud cloud-action-icon"></span>' . esc_html__('Delete Selected Automatic Snapshots from the Cloud', 'wp-reset') . '</a>';
        }
        echo '</div>
        </div>';
        echo '</th></tr>';
        echo '<tr class="table-empty hidden"><td colspan="5" class="textcenter">There are no automatic snapshots.<br>If enabled in <a href="#snapshot-options-group" class="change-tab" data-tab="4">snapshot options</a> they will generate automatically on plugin and theme update, activate, deactivate and similar events.</td></tr>';
        foreach ((array) $ss_auto as $ss) {
            self::wp_kses_wf($this->get_snapshot_row($ss, 'auto'));
        } // foreach
        echo '</table>';
        echo '</div>';
        echo '</div>';
    } // tab_snapshots


    /**
     * Echoes content for staging tab
     *
     * @return null
     */
    private function tab_staging()
    {
        global $wpdb;
        $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
        $options = $this->get_options();

        $stages = $this->get_stages();

        $stages = array_reverse($stages);

        //snapshots_size_alert
        $space_usage_total = 0;

        foreach ($stages as $stage) {
            $space_usage_total += $stage['tbl_size'];
        }

        $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); //phpcs:ignore
        $uid_length = 7;
        if (strlen($wpdb->prefix) > 10) {
            $uid_length = 5;
        }

        foreach ($tables as $table) {
            if ($uid_length !== stripos($table[0], $wpdb->prefix)) {
                continue;
            }
            $current_id = substr($table[0], 0, ($uid_length - 1));

            if (array_key_exists($current_id, $stages)) {
                continue;
            }
        } // foreach


        echo '<div class="card" id="card-staging">';
        self::wp_kses_wf($this->get_card_header('Staging', 'staging-info', array('collapse_button' => true, 'documentation_link' => true)));
        echo '<div class="card-body">';

        echo '<p>The Staging feature lets you instantly clone your live WordPress website into an isolated subfolder where you can test updates, plugins, or custom code without touching the production site.</p>
              <p>When you create a staging copy, the plugin:
              <ul class="plain-list">
                <li>Copies all your files into /wpr_staging/unique-ID/</li>
                <li>Duplicates your database tables under a separate prefix</li>
                <li>Updates configuration, URLs, and serialized data automatically</li>
                <li>Ensures the staging site runs independently — no symlinks, shared tables, or cached connections</li>
              </ul>
              </p>
              <p>Each staging site behaves like a full WordPress installation that mirrors your live site at the moment it was created. It\'s ideal for testing theme changes, plugin updates, or configuration tweaks before applying them to production.</p>
              <p style="font-style: italic;">Note: While each staging copy is functionally isolated, using its own database tables and WordPress environment, it still exists inside your main hosting space. Code running inside a staging site can technically access other files on the same server if a plugin or theme is poorly written, so always be mindful of what you install or activate in a staging environment.</p>';
        echo '</div>';
        echo '</div>'; // snapshots desc


        echo '<div class="card">';
        self::wp_kses_wf($this->get_card_header('Existing Stages', 'staging', array('collapse_button' => 1, 'deploy_stage' => true, 'snapshot_actions' => true)));
        echo '<div class="card-body">';

        echo '<table id="stages-table">';
        echo '<tr><th></th><th class="ss-date">Date</th><th>Description</th><th class="ss-size">Size</th><th class="ss-actions"></th></tr>';

        echo '<tr class="table-empty hidden"><td colspan="5" class="textcenter">There are no stages deployed. <a href="#" class="deploy-new-stage">Deploy New Staging Copy</a></td></tr>';
        foreach ((array) $stages as $stage) {
            self::wp_kses_wf($this->get_stage_row($stage, 'user'));
        } // foreach
        echo '</table>';
        echo '</div>';
        echo '</div>';
    } // tab_snapshots


    /**
     * Helper function for generating UTM tagged links
     *
     * @param string  $placement  Optional. UTM content param.
     * @param string  $page       Optional. Page to link to.
     * @param array   $params     Optional. Extra URL params.
     * @param string  $anchor     Optional. URL anchor part.
     *
     * @return string
     */
    function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '')
    {
        $base_url = 'https://wpreset.com';

        if ('/' != $page) {
            $page = '/' . trim($page, '/') . '/';
        }
        if ($page == '//') {
            $page = '/';
        }

        $parts = array_merge(array('utm_source' => 'wp-reset-pro', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-reset-pro-v' . $this->version), $params);

        if (!empty($anchor)) {
            $anchor = '#' . trim($anchor, '#');
        }

        $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;

        return $out;
    } // generate_web_link


    /**
     * Helper function for generating dashboard UTM tagged links
     *
     * @param string  $placement  Optional. UTM content param.
     * @param string  $page       Optional. Page to link to.
     * @param array   $params     Optional. Extra URL params.
     * @param string  $anchor     Optional. URL anchor part.
     *
     * @return string
     */
    function generate_dashboard_link($placement = '', $page = '/', $params = array(), $anchor = '')
    {
        $base_url = 'https://dashboard.wpreset.com';

        if ('/' != $page) {
            $page = '/' . trim($page, '/') . '/';
        }
        if ($page == '//') {
            $page = '/';
        }

        $parts = array_merge(array('utm_source' => 'wp-reset-pro', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-reset-pro-v' . $this->version), $params);

        if (!empty($anchor)) {
            $anchor = '#' . trim($anchor, '#');
        }

        $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;

        return $out;
    } // generate_dashboard_link


    /**
     * Returns all saved snapshots from DB
     *
     * @return array
     */
    function get_snapshots()
    {
        $snapshots = get_option('wp-reset-snapshots', array());

        if (!is_array($snapshots)) {
            $snapshots = array();
        }

        foreach ($snapshots as $uid => $snapshot) {
            $snapshots[$uid]['name'] = stripslashes($snapshots[$uid]['name']);
            $snapshots[$uid]['local'] = true;
        }

        return $snapshots;
    } // get_snapshots


    /**
     * Returns all saved snapshots from DB
     *
     * @return array
     */
    function get_all_snapshots()
    {
        global $wp_reset_cloud;

        $snapshots = get_option('wp-reset-snapshots', array());

        if (!is_array($snapshots)) {
            $snapshots = array();
        }

        foreach ($snapshots as $uid => $snapshot) {
            $snapshots[$uid]['name'] = stripslashes($snapshots[$uid]['name']);
            $snapshots[$uid]['local'] = true;
        }

        $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();
        if (is_array($cloud_snapshots) && is_array($snapshots)) {
            $cloud_only_snapshots = array_diff_key($cloud_snapshots, $snapshots);
        } else {
            $cloud_snapshots = array();
            $cloud_only_snapshots = array();
        }
        $cloud_common_snapshots = array_intersect_key($snapshots, $cloud_snapshots);

        $snapshots = array_merge($snapshots, $cloud_only_snapshots);

        foreach ($cloud_only_snapshots as $cloud_only_snapshot_uid => $cloud_only_snapshot) {
            $snapshots[$cloud_only_snapshot_uid]['cloud'] = true;
            $snapshots[$cloud_only_snapshot_uid]['local'] = false;
        }

        foreach ($cloud_common_snapshots as $cloud_common_snapshot_uid => $cloud_common_snapshot) {
            $snapshots[$cloud_common_snapshot_uid]['cloud'] = true;
            $snapshots[$cloud_common_snapshot_uid]['cloud_path'] = $cloud_snapshots[$cloud_common_snapshot_uid]['cloud_path'];
            $snapshots[$cloud_common_snapshot_uid]['local'] = true;
        }


        uasort($snapshots, function ($a, $b) {
            return strtotime($a['timestamp']) > strtotime($b['timestamp']) ? 1 : -1;
        });

        return $snapshots;
    } // get_snapshots

    /**
     * Returns all saved stages from DB
     *
     * @return array
     */
    function get_all_stages()
    {
        $stages = get_option('wp-reset-stages', array());

        if (!is_array($stages)) {
            $stages = array();
        }

        foreach ($stages as $uid => $stage) {
            $stages[$uid]['name'] = stripslashes($stages[$uid]['name']);
        }

        uasort($stages, function ($a, $b) {
            return strtotime($a['timestamp']) > strtotime($b['timestamp']) ? 1 : -1;
        });

        return $stages;
    } // get_snapshots


    /**
     * Returns all saved snapshots from DB
     *
     * @return array
     */
    function get_stages()
    {
        $stages = get_option('wp-reset-stages', array());

        if (!is_array($stages)) {
            $stages = array();
        }

        foreach ($stages as $uid => $stage) {
            $stages[$uid]['name'] = stripslashes($stages[$uid]['name']);
            $stages[$uid]['local'] = true;
        }

        return $stages;
    } // get_stages

    /**
     * Verifies stage integrity by comparing metadata and data in DB
     *
     * @param string  $uid  Stage unique 6-char ID.
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function verify_stage_integrity($uid)
    {
        global $wpdb;
        $tbl_core = $tbl_custom = 0;
        $stages = $this->get_stages();

        if (strlen($uid) != 4 && strlen($uid) != 6) {
            return new WP_Error(1, 'Invalid stage ID format.');
        }

        if (!isset($stages[$uid])) {
            return new WP_Error(1, 'Unknown stage ID.');
        }

        $stage = $stages[$uid];
        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore

        if (is_array($table_status)) {
            foreach ($table_status as $table) {
                if (0 !== stripos($table->Name, $uid . '_')) {
                    continue;
                }
                if (empty($table->Engine)) {
                    continue;
                }

                if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
                    $tbl_core++;
                } else {
                    $tbl_custom++;
                }
            } // foreach

            if ($tbl_core != $stage['tbl_core'] || $tbl_custom != $stage['tbl_custom']) {
                return new WP_Error(1, 'Stage data has been compromised. Saved metadata does not match the BB table data (' . $tbl_core . ' / ' . $stage['tbl_core'] . ' core, ' . $tbl_custom . ' / ' . $stage['tbl_custom'] . ' core).');
            }
        } else {
            return new WP_Error(1, 'Can\'t get table status data.');
        }

        return true;
    } // verify_stage_integrity

    /**
     * Returns all custom table names, with prefix
     *
     * @return array
     */
    function get_custom_tables($only_names = false)
    {
        global $wpdb;
        $custom_tables = array();

        if ($only_names) {
            $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N); //phpcs:ignore
            foreach ($tables as $table) {
                if (0 !== stripos($table[0], $wpdb->prefix)) {
                    continue;
                }
                if (false === in_array($table[0], $this->core_tables)) {
                    $custom_tables[] = array('name' => $table[0]);
                }
            } // foreach
        } else {
            $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore
            if (is_array($table_status)) {
                foreach ($table_status as $index => $table) {
                    if (0 !== stripos($table->Name, $wpdb->prefix)) {
                        continue;
                    }
                    if (empty($table->Engine)) {
                        continue;
                    }

                    if (false === in_array($table->Name, $this->core_tables)) {
                        $custom_tables[] = array('name' => $table->Name, 'rows' => $table->Rows, 'data_length' => $table->Data_length, 'index_length' => $table->Index_length);
                    }
                } // foreach
            }
        }

        return $custom_tables;
    } // get_custom tables


    /**
     * Get full path of export file or full directory path without trailing slash, if $file is false
     *
     * @param string $export_file Filename
     * @param bool $url Create URL or file path
     *
     * @return string
     */
    function export_dir_path($file = false, $url = false)
    {
        if ($url) {
            $path = content_url() . '/' . $this->snapshots_folder;
        } else {
            $path = trailingslashit(WP_CONTENT_DIR) . $this->snapshots_folder;
        }

        if ($url === false && !file_exists($path)) {
            $folder = wp_mkdir_p($path);
            if (!$folder) {
                return new WP_Error(1, 'Unable to create ' . $path . ' folder.');
            }
        }

        if (!empty($file)) {
            $path = $path . '/' . $file;
        }

        return $path;
    } // export_dir_path


    /**
     * Get full path of autosnapshot file or just full directory path without trailing slash if $file is false
     *
     * @param string export file
     *
     * @return string
     */
    function autosnapshots_dir_path($file = false, $url = false)
    {
        if ($url) {
            $path = content_url() . '/' . $this->autosnapshots_folder;
        } else {
            $path = trailingslashit(WP_CONTENT_DIR) . $this->autosnapshots_folder;
        }

        if ($url === false && !file_exists($path)) {
            $folder = wp_mkdir_p($path);
            if (!$folder) {
                return new WP_Error(1, 'Unable to create ' . $path . '/ folder.');
            }
        }

        if (!empty($file)) {
            $path = $path . '/' . $file;
        }

        return $path;
    } // autosnapshots_dir_path


    /**
     * Import snapshot
     *
     * @param string  $snapshot absolute path
     *
     * @return bool|WP_Error true on success, or error object on fail.
     */
    function do_import_snapshot($snapshot_zip, $ajax = false)
    {
        global $wpdb;

        $import_zip = new ZipArchive();
        $import_zip->open($snapshot_zip);
        $zip_files = array();

        for ($i = 0; $i < $import_zip->numFiles; $i++) {
            $file_path = $import_zip->statIndex($i);
            $zip_files[] = $file_path['name'];
        }

        if (!in_array('wp-reset-export.json', $zip_files)) {
            return new WP_Error(1, 'Not a valid snapshot export file.');
        }

        $temp_import = $this->export_dir_path('temp_import');
        if (is_wp_error($temp_import)) {
            return $temp_import;
        }

        $import_zip->extractTo($temp_import);
        $import_zip->close();

        $snapshot = json_decode(file_get_contents($temp_import . '/wp-reset-export.json'), true);
        $snapshots = $this->get_snapshots();

        if (!(is_array($snapshot) && array_key_exists('uid', $snapshot))) {
            $this->delete_folder($temp_import, basename($temp_import));
            return new WP_Error(1, 'Snapshot details are invalid or missing.');
        }

        if (array_key_exists($snapshot['uid'], $snapshots)) {
            $this->delete_folder($temp_import, basename($temp_import));
            return new WP_Error(1, 'Uploaded snapshot already exists. Delete the existing one before importing it.');
        }

        if ($snapshot['table_prefix'] != $wpdb->prefix) {
            $this->delete_folder($temp_import, basename($temp_import));
            return new WP_Error(1, 'Table prefix in uploaded snapshot does not match your current table prefix.');
        }

        if ($snapshot['home_url'] != home_url()) {
            $this->delete_folder($temp_import, basename($temp_import));
            return new WP_Error(1, 'Site URL in uploaded snapshot does not match your current site URL.');
        }

        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore
        if (is_array($table_status)) {
            foreach ($table_status as $index => $table) {
                if (stripos($table->Name, $snapshot['uid']) === 0) {
                    $this->delete_folder($temp_import, basename($temp_import));
                    return new WP_Error(1, 'Table ' . $table->Name . ' from uploaded snapshot already exist in the current database.');
                }
            }
        }

        if (!empty($snapshot['plugins']) || !empty($snapshot['themes'])) {
            if (!file_exists($temp_import . '/wp-reset-snapshot-files-' . $snapshot['uid'] . '.zip')) {
                $this->delete_folder($temp_import, basename($temp_import));
                return new WP_Error(1, 'Snapshot files are missing.');
            }

            $dest = $this->autosnapshots_dir_path();
            copy($temp_import . '/wp-reset-snapshot-files-' . $snapshot['uid'] . '.zip', $dest . '/wp-reset-snapshot-files-' . $snapshot['uid'] . '.zip');
        }



        $db_dump_file_gz = gzopen($temp_import . '/wp-reset-snapshot-' . $snapshot['uid'] . '.sql.gz', 'rb');
        $db_dump_file_sql = fopen($temp_import . '/wp-reset-snapshot-' . $snapshot['uid'] . '.sql', 'wb+'); //phpcs:ignore
        while (!gzeof($db_dump_file_gz)) {
            fwrite($db_dump_file_sql, gzread($db_dump_file_gz, 4096)); //phpcs:ignore
        }
        rewind($db_dump_file_sql);
        gzclose($db_dump_file_gz);

        $wprdb = false;

        //Skip Queries if we're doing ajax, just return number of lines to generate steps

        //phpcs:ignore custom mysql connection is needed in some cases
        if (!$ajax) {
            $db_info = $this->get_db_info();
            $wprdb = mysqli_connect($db_info['host'], DB_USER, DB_PASSWORD, DB_NAME, $db_info['port']); //phpcs:ignore
        }

        $line_ending = PHP_EOL;

        $parse_result = $this->parse_sql_dump($wprdb, $db_dump_file_sql, $line_ending, $ajax);

        if ($parse_result['lines'] == 0) {
            $line_ending = "\n";
            $parse_result = $this->parse_sql_dump($wprdb, $db_dump_file_sql, $line_ending, $ajax);
        }
        if ($parse_result['lines'] == 0) {
            $line_ending = "\r\n";
            $parse_result = $this->parse_sql_dump($wprdb, $db_dump_file_sql, $line_ending, $ajax);
        }

        $parse_result['line_ending'] = $line_ending;
        $parse_result['uid'] = $snapshot['uid'];

        if (!$ajax) {
            $wprdb->close();
        }
        fclose($db_dump_file_sql); //phpcs:ignore

        // If doing ajax we just return steps
        if ($ajax) {
            $snapshot['partial'] = true;
            $snapshots[$snapshot['uid']] = $snapshot;
            update_option('wp-reset-snapshots', $snapshots);

            $increment = 10;
            $current_table = 'setup queries';

            for ($line = 0; $line < $parse_result['lines']; $line += $increment) {
                foreach ($parse_result['tables'] as $table => $sql_line) {
                    if ($line > $sql_line) {
                        $current_table = $table;
                        break;
                    }
                }
                $steps[] = array('uid' => $snapshot['uid'], 'action' => 'import', 'start' => $line, 'end' => ($line + $increment), 'line_ending' => $line_ending, 'description' => 'Importing lines <i>' . $line . ' - ' . ($line + $increment) . ' / ' . $current_table . ')</i>');
            }
            $steps[] = array('uid' => $snapshot['uid'], 'action' => 'import_verify', 'description' => 'Verifying imported snapshot</i>');

            return $steps;
        }

        return $this->do_import_snapshot_wrap($snapshot);
    } // do_import_snapshot

    /**
     * Finish snapshot import
     *
     * @param array  $snapshot
     *
     * @return bool|WP_Error true on success, or error object on fail.
     */
    function do_import_snapshot_wrap($snapshot)
    {
        global $wpdb;

        $snapshots = $this->get_snapshots();

        $temp_import = $this->export_dir_path('temp_import');
        if (is_wp_error($temp_import)) {
            return $temp_import;
        }

        $tbl_core = $tbl_custom = 0;
        $tables = $wpdb->get_col("SHOW TABLES LIKE '" . $snapshot['uid'] . "%'"); //phpcs:ignore
        $imported_tables = array();
        foreach ($tables as $table) {
            $imported_tables[] = $table;
            if (in_array(str_ireplace($snapshot['uid'] . '_', '', $table), $this->core_tables)) {
                $tbl_core++;
            } else {
                $tbl_custom++;
            }
        }

        if ($snapshot['tbl_core'] != $tbl_core || $snapshot['tbl_custom'] != $tbl_custom) {
            $this->delete_folder($temp_import, basename($temp_import));
            foreach ($imported_tables as $table) {
                $wpdb->query('DROP TABLE IF EXISTS `' . $table . '`'); //phpcs:ignore
            }
            return new WP_Error(1, 'Imported tables do not match expected table count. ' . $snapshot['tbl_core'] . '/' . $tbl_core . '|' . $snapshot['tbl_custom'] . '/' . $tbl_custom);
        }

        $this->delete_folder($temp_import, basename($temp_import));

        $snapshot['imported'] = current_time('mysql');
        $snapshot['partial'] = false;
        $snapshots[$snapshot['uid']] = $snapshot;
        update_option('wp-reset-snapshots', $snapshots);
        do_action('wp_reset_import_snapshot', $snapshot['uid'], $snapshot);

        return true;
    }

    /**
     * Do snapshot import step
     *
     * @param array  $step_data
     *
     * @return bool|WP_Error true on success, or error object on fail.
     */
    function import_snapshot_step($step_data)
    {
        $options = $this->get_options();
        switch ($step_data['action']) {
            case 'import':
                $db_info = $this->get_db_info();
                $wprdb = mysqli_connect($db_info['host'], DB_USER, DB_PASSWORD, DB_NAME, $db_info['port']); //phpcs:ignore

                $temp_import = $this->export_dir_path('temp_import');
                $db_dump_file_sql = fopen($temp_import . '/wp-reset-snapshot-' . $step_data['uid'] . '.sql', 'r'); //phpcs:ignore

                $current_line = 0;

                $wprdb->query('/*!40030 SET NAMES UTF8 */;
            /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
            /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
            /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
            /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
            /*!40103 SET TIME_ZONE=\'+00:00\' */;
            /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
            /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
            /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=\'NO_AUTO_VALUE_ON_ZERO\' */;
            /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;'); //phpcs:ignore

                while (!feof($db_dump_file_sql)) {
                    $buffer = trim(stream_get_line($db_dump_file_sql, 100000000, ";" . $step_data['line_ending']));

                    if (substr($buffer, 0, 2) == '--' || trim($buffer) == '') {
                        continue;
                    }

                    if (strpos(trim($buffer), '/*!') === 0) {
                        $current_line++;
                        continue;
                    }

                    if ($current_line >= $step_data['start'] && $current_line < $step_data['end']) {
                        $buffer = preg_replace('/[\x09]/', '\t', trim($buffer));
                        if ($options['fix_datetime'] == true) {
                            $buffer = str_replace("datetime NOT NULL DEFAULT '0000-00-00 00:00:00'", "datetime DEFAULT NULL", $buffer);
                            $buffer = str_replace("timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'", "timestamp DEFAULT NULL", $buffer);
                            $buffer = str_replace("'0000-00-00 00:00:00'", "NULL", $buffer);
                        }
                        if (!$wprdb->query($buffer)) {
                            return new WP_Error(1, 'SQL error: ' . $wprdb->error);
                        }
                    } else if ($current_line > $step_data['end']) {
                        break;
                    }

                    $current_line++;
                }

                $wprdb->query('/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
            /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
            /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
            /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
            /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
            /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
            /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;');
                break;
            case 'import_verify':
                $snapshots = $this->get_snapshots();
                return $this->do_import_snapshot_wrap($snapshots[$step_data['uid']]);
                break;
        }

        return true;
    }

    /**
     * Parse SQL dump file using a specified $line_ending
     *
     * @param object $wprdb database handle
     * @param object $db_dump_file_sql SQL file handle
     * @param string $line_ending
     * @param bool   $ajax set to true if called via ajax
     *
     * @return bool|WP_Error true on success, or error object on fail.
     */
    function parse_sql_dump($wprdb, $db_dump_file_sql, $line_ending = PHP_EOL, $ajax = false)
    {
        $options = $this->get_options();
        $tables = array();
        $lines_read = 0;
        rewind($db_dump_file_sql);
        while (!feof($db_dump_file_sql)) {
            $buffer = stream_get_line($db_dump_file_sql, 100000000, ";" . $line_ending);

            if (substr($buffer, 0, 2) == '--' || trim($buffer) == '') {
                continue;
            }

            $lines_read++;
            if (!empty($buffer)) {
                if (!$ajax) {
                    $buffer = preg_replace('/[\x09]/', '\t', trim($buffer));
                    if ($options['fix_datetime'] == true) {
                        $buffer = str_replace("datetime NOT NULL DEFAULT '0000-00-00 00:00:00'", "datetime DEFAULT NULL", $buffer);
                        $buffer = str_replace("timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'", "timestamp DEFAULT NULL", $buffer);
                        $buffer = str_replace("'0000-00-00 00:00:00'", "NULL", $buffer);
                    }
                    $wprdb->query($buffer);
                } else {
                    if (strpos($buffer, 'CREATE TABLE `') === 0) {
                        $query_parts = explode('`', $buffer);
                        $tables[$query_parts[1]] = $lines_read;
                    }
                }
            }
        }

        return array('lines' => $lines_read, 'tables' => $tables);
    }

    /**
     * Creates snapshot of current tables by copying them in the DB and saving metadata; plugin/theme files are included if specified.
     *
     * @param array  $snapshot  Optional snapshot meta and options
     *
     * @return array|WP_Error Snapshot details in array on success, or error object on fail.
     */
    function do_create_snapshot($params = array())
    {
        global $wpdb;
        $options = $this->get_options();
        $snapshots = $this->get_snapshots();
        $snapshot = shortcode_atts(array('name' => '', 'plugins' => array(), 'themes' => array(), 'auto' => false, 'autoupload' => false, 'ajax' => false, 'imported' => false), (array) $params);
        $uid = $this->generate_snapshot_uid();
        $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;

        if (!$uid) {
            return new WP_Error(1, 'Unable to generate a valid snapshot UID.');
        }

        if (!class_exists('ZipArchive')) {
            return new WP_Error(1, 'The PHP ZipArchive Library is missing or disabled! Please contact your host to enable it in order to create snapshots.');
        }

        if ($snapshot['name']) {
            $snapshot['name'] = substr(trim($snapshot['name']), 0, 512);
        }

        $snapshot['uid'] = $uid;
        $snapshot['timestamp'] = current_time('mysql');
        $snapshot['tbl_names'] = array();

        if ($snapshot['ajax']) {
            $snapshot['partial'] = true;
        } else if ($options['autosnapshots_autoupload'] == true && $snapshot['auto'] == true && !empty($options['cloud_service']) && array_key_exists($options['cloud_service'], $this->cloud_services)) {
            $snapshot['autoupload'] = true;
        }

        $zip_folders = array();

        if (!empty($snapshot['plugins']) && is_array($snapshot['plugins'])) {
            foreach ($snapshot['plugins'] as $plugin => $path) {
                if (file_exists($path)) {
                    $zip_folders[] = $path;
                } else {
                    unset($snapshot['plugins'][$plugin]);
                }
            }
        }

        if (!empty($snapshot['themes']) && is_array($snapshot['themes'])) {
            foreach ($snapshot['themes'] as $theme => $path) {
                if (file_exists($path)) {
                    $zip_folders[] = $path;
                } else {
                    unset($snapshot['themes'][$theme]);
                }
            }
        }

        if (count($zip_folders)) {
            $zip_filename = $this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $uid . '.zip');
            $zip = new ZipArchive();
            $zip->open($zip_filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
            foreach ($zip_folders as $zip_folder) {
                $files = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator($zip_folder),
                    RecursiveIteratorIterator::LEAVES_ONLY
                );
                foreach ($files as $name => $file) {
                    if (!$file->isDir()) {
                        $filePath = $file->getRealPath();
                        $relativePath = basename($zip_folder) . '/' . substr($filePath, strlen($zip_folder));
                        $zip->addFile($filePath, $relativePath);
                    }
                }
            }
            $zip->close();
            $snapshot['file_size']  = filesize($zip_filename);
        }

        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore
        if (is_array($table_status)) {
            foreach ($table_status as $index => $table) {
                if (0 !== stripos($table->Name, $wpdb->prefix)) {
                    continue;
                }
                if (empty($table->Engine)) {
                    continue;
                }

                $tbl_rows += $table->Rows;
                $tbl_size += $table->Data_length + $table->Index_length;
                if (in_array($table->Name, $this->core_tables)) {
                    $tbl_core++;
                } else {
                    $tbl_custom++;
                }

                $snapshot['tbl_names'][] = $table->Name;

                if (!$snapshot['ajax']) {
                    if ($options['optimize_tables'] == true) {
                        $wpdb->query('OPTIMIZE TABLE `' . $table->Name . '`'); //phpcs:ignore
                    }
                    $wpdb->query('CREATE TABLE `' . $uid . '_' . $table->Name . '` LIKE `' . $table->Name . '`'); //phpcs:ignore
                    $wpdb->query('INSERT `' . $uid . '_' . $table->Name . '` SELECT * FROM `' . $table->Name . '`'); //phpcs:ignore
                }
            } // foreach
        } else {
            return new WP_Error(1, 'Can\'t get table status data.');
        }

        $snapshot['tbl_core']   = $tbl_core;
        $snapshot['tbl_custom'] = $tbl_custom;
        $snapshot['tbl_rows']   = $tbl_rows;
        $snapshot['tbl_size']   = $tbl_size;

        $snapshots[$uid] = $snapshot;
        update_option('wp-reset-snapshots', $snapshots);
        do_action('wp_reset_create_snapshot', $uid, $snapshot);

        return $snapshot;
    } // create_snapshot

    /**
     * Process a snapshot step
     *
     * @param array  $step_data containing snapshot uid, action and extra data
     *
     * @return bool|WP_Error true on success, WP_Error on error
     */
    function create_snapshot_step($step_data)
    {
        global $wpdb, $wp_reset_cloud;

        $options = $this->get_options();

        if (!isset($step_data['uid'])) {
            return new WP_Error(1, 'Invalid snapshot ID');
        }

        $snapshots = $this->get_snapshots();

        if (!array_key_exists($step_data['uid'], $snapshots)) {
            return new WP_Error(1, 'Unknown snapshot ID');
        }

        $snapshot = $snapshots[$step_data['uid']];

        if (!isset($snapshot['partial']) || $snapshot['partial'] != true) {
            return new WP_Error(1, 'Snapshot already created');
        }

        switch ($step_data['action']) {
            case 'copy':
                $wpdb->show_errors = false;
                $wpdb->suppress_errors = false;

                if ($options['optimize_tables'] == true) {
                    $wpdb->query('OPTIMIZE TABLE ' . $step_data['data']); //phpcs:ignore
                }

                $wpdb->query('CREATE TABLE `' . $step_data['uid'] . '_' . $step_data['data'] . '` LIKE `' . $step_data['data'] . '`'); //phpcs:ignore
                $wpdb->query('INSERT `' . $step_data['uid'] . '_' . $step_data['data'] . '` SELECT * FROM `' . $step_data['data'] . '`'); //phpcs:ignore
                if ($wpdb->last_error !== '') {
                    return new WP_Error(1, 'An database error occurred: ' . $wpdb->last_error);
                }
                break;
            case 'verify_integrity':
                $verify_snapshot = $this->verify_snapshot_integrity($step_data['uid']);
                if ($verify_snapshot === true) {
                    $snapshot['partial'] = false;
                    if ($options['autosnapshots_autoupload'] == true && $snapshot['auto'] == true && !empty($options['cloud_service']) && array_key_exists($options['cloud_service'], $this->cloud_services)) {
                        $snapshot['autoupload'] = true;
                    }
                    $snapshots[$step_data['uid']] = $snapshot;
                    update_option('wp-reset-snapshots', $snapshots);
                } else {
                    $this->do_delete_snapshot($step_data['uid']);
                    return $verify_snapshot;
                }
                break;
            default:
                return new WP_Error(1, 'Unknown snapshot step action');
                break;
        }

        return $snapshot;
    } // create_snapshot_step

    /**
     * Delete snapshot metadata and tables from DB
     *
     * @param string  $uid  Snapshot unique 6-char ID.
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function do_delete_snapshot($uid = '')
    {
        global $wpdb;
        $snapshots = $this->get_snapshots();
        $this->log('info', 'Deleting snapshot ' . $uid);
        if (strlen($uid) != 4 && strlen($uid) != 6) {
            return new WP_Error(1, 'Invalid UID format.');
        }

        if (!isset($snapshots[$uid])) {
            return new WP_Error(1, 'Unknown snapshot ID.');
        }

        $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%'))); //phpcs:ignore
        foreach ($tables as $table) {
            $wpdb->query('DROP TABLE IF EXISTS `' . $table . '`'); //phpcs:ignore
            if ($wpdb->last_error !== '') {
                $this->log('error', 'Failed to delete table ' . $table . ': ' . $wpdb->last_error);
            } else {
                $this->log('info', 'Deleted table ' . $table);
            }
        }

        wp_delete_file($this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $uid . '.zip'));
        wp_delete_file($this->export_dir_path('wp-reset-snapshot-' . $uid . '.zip'));

        $snapshot_copy = $snapshots[$uid];
        unset($snapshots[$uid]);
        update_option('wp-reset-snapshots', $snapshots);

        do_action('wp_reset_delete_snapshot', $uid, $snapshot_copy);
        $this->log('info', 'Deleted snapshot ' . $uid . ' successfully');

        return true;
    } // delete_snapshot


    /**
     * Delete snapshot tables from DB by snapshot UID
     *
     * @param string  $uid  Snapshot unique 6-char ID.
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function delete_snapshot_tables($uid = '')
    {
        check_admin_referer('wpr_delete_snapshot_tables');
        global $wpdb;

        if (empty($uid)) {
            $uid = sanitize_text_field(wp_unslash($_GET['uid'] ?? '')); //phpcs:ignore
        }

        if (!ctype_alpha($uid) || !current_user_can('manage_options') || (strlen($uid) != 4 && strlen($uid) != 6)) {
            return new WP_Error(1, 'Invalid UID format.');
        }

        $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%'))); //phpcs:ignore
        foreach ($tables as $table) {
            $wpdb->query('DROP TABLE IF EXISTS `' . $table . '`'); //phpcs:ignore
            if ($wpdb->last_error !== '') {
                $this->log('error', 'Failed to delete table ' . $table . ': ' . $wpdb->last_error);
            } else {
                $this->log('info', 'Deleted table ' . $table);
            }
        }

        if (!empty($_GET['redirect'])) {
            wp_safe_redirect(sanitize_url(wp_unslash($_GET['redirect'])));
        }

        return true;
    }

    /**
     * Delete temporary files from wp-reset-snapshots-export
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function delete_temporary_files()
    {
        check_admin_referer('wpr_delete_temporary_files');
        if (!current_user_can('manage_options')) {
            return false;
        }

        if (file_exists($this->export_dir_path())) {
            $this->delete_folder($this->export_dir_path(), basename($this->export_dir_path()));
        }

        if (file_exists($this->autosnapshots_dir_path())) {
            $this->delete_folder($this->autosnapshots_dir_path(), basename($this->autosnapshots_dir_path()));
        }

        if (!empty($_GET['redirect'])) {
            wp_safe_redirect(wp_unslash($_GET['redirect']));
        }

        return true;
    }


    /**
     * Delete all/selected snapshots
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function do_delete_snapshots($params = array())
    {
        $snapshots = $this->get_snapshots();
        $deleted_snapshots = 0;

        if ($params['delete'] == 'selected') {
            foreach ($params['ids'] as $uid) {
                if (array_key_exists($uid, $snapshots)) {
                    $delete_result = $this->do_delete_snapshot($uid);
                    if (is_wp_error($delete_result)) {
                        return $delete_result;
                    } else {
                        $deleted_snapshots++;
                    }
                }
            }
        } else {
            foreach ($snapshots as $uid => $snapshot) {
                if (($params['delete'] == 'auto' && $snapshot['auto'] == true) ||
                    ($params['delete'] == 'user' && (!array_key_exists('auto', $snapshot) ||
                        $snapshot['auto'] == false))
                ) {
                    $delete_result = $this->do_delete_snapshot($uid);
                    if (is_wp_error($delete_result)) {
                        return $delete_result;
                    } else {
                        $deleted_snapshots++;
                    }
                }
            }
        }

        return $deleted_snapshots;
    } // do_delete_snapshots


    /**
     * Delete all/selected cloud snapshots
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function do_delete_cloud_snapshots($params = array())
    {
        global $wp_reset_cloud;
        $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();

        $deleted_snapshots = 0;

        if ($params['delete'] == 'selected') {
            foreach ($params['ids'] as $uid) {
                if (array_key_exists($uid, $cloud_snapshots)) {
                    $delete_result = $wp_reset_cloud->cloud_snapshot_delete($uid);
                    if (is_wp_error($delete_result)) {
                        return $delete_result;
                    } else {
                        $deleted_snapshots++;
                    }
                }
            }
        } else {
            foreach ($cloud_snapshots as $uid => $snapshot) {
                if (($params['delete'] == 'auto' && $snapshot['auto'] == true) ||
                    ($params['delete'] == 'user' && (!array_key_exists('auto', $snapshot) ||
                        $snapshot['auto'] == false))
                ) {
                    $delete_result = $wp_reset_cloud->cloud_snapshot_delete($uid);
                    if (is_wp_error($delete_result)) {
                        return $delete_result;
                    } else {
                        $deleted_snapshots++;
                    }
                }
            }
        }

        if ($deleted_snapshots > 0) {
            $wp_reset_cloud->cloud_snapshot_refresh();
        }
        return $deleted_snapshots;
    } // do_delete_snapshots


    /**
     * Exports snapshot as SQL dump; saved in gzipped file in WP_CONTENT folder.
     *
     * @param string  $uid  Snapshot unique 6-char ID.
     *
     * @return string|WP_Error Export base filename, or error object on fail.
     */
    function do_export_snapshot($uid = '', $background = false)
    {
        global $wpdb;

        $snapshots = $this->get_snapshots();
        $options = $this->get_all_options();
        $ajax = false;

        if (!class_exists('ZipArchive')) {
            return new WP_Error(1, 'The PHP ZipArchive Library is missing or disabled! Please contact your host to enable it in order to export snapshots.');
        }

        if ($options['options']['ajax_snapshots_export'] == 1 || $background) {
            $ajax = true;
        }

        if (strlen($uid) != 4 && strlen($uid) != 6) {
            return new WP_Error(1, 'Invalid snapshot ID format.');
        }

        if (!isset($snapshots[$uid])) {
            return new WP_Error(1, 'Unknown snapshot ID.');
        }

        $snapshot = $snapshots[$uid];
        $snapshot['ajax'] = $ajax;
        $snapshot['tbl_names'] = array();

        $htaccess_content = 'AddType application/octet-stream .gz' . PHP_EOL;
        $htaccess_content .= 'Options -Indexes' . PHP_EOL;
        $htaccess_file = fopen($this->export_dir_path('.htaccess'), 'w'); //phpcs:ignore
        if ($htaccess_file) {
            fputs($htaccess_file, $htaccess_content); //phpcs:ignore
            fclose($htaccess_file); //phpcs:ignore
        }

        if ($ajax) {

            $dump_file_path = $this->export_dir_path('wp-reset-snapshot-' . $uid . '.sql');

            if (is_wp_error($dump_file_path)) {
                return $dump_file_path;
            }

            if (file_exists($dump_file_path)) {
                wp_delete_file($dump_file_path);
            }

            $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore

            if (is_array($table_status)) {
                foreach ($table_status as $index => $table) {
                    if (0 !== stripos($table->Name, $uid . '_' . $wpdb->prefix)) {
                        continue;
                    }
                    if (empty($table->Engine)) {
                        continue;
                    }

                    $snapshot['tbl_names'][] = $table->Name;
                } // foreach

                return $snapshot;
            } else {
                return new WP_Error(1, 'Can\'t get table status data.');
            }
        } else {
            require_once $this->plugin_dir . 'libs/wpr_db_dumper.php';

            $dump_file_path = $this->export_dir_path('wp-reset-snapshot-' . $uid . '.sql.gz');
            if (is_wp_error($dump_file_path)) {
                return $dump_file_path;
            }



            try {
                $db_info = $this->get_db_info();

                $world_dumper = WPR_Shuttle_Dumper::create(array(
                    'host' => $db_info['host'],
                    'port' => $db_info['port'],
                    'username' => DB_USER,
                    'password' => DB_PASSWORD,
                    'db_name' =>  DB_NAME,
                ));

                $this->log('success', 'Start full dump for ' . $uid . '_ to ' . $dump_file_path);
                $world_dumper->dump($dump_file_path, $uid . '_');
            } catch (WPR_Shuttle_Exception $e) {
                return new WP_Error(1, 'Couldn\'t create snapshot: ' . $e->getMessage());
            }

            if (!(empty($snapshot['plugins']) && empty($snapshot['themes'])) && !file_exists($this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $uid . '.zip'))) {
                return new WP_Error(1, 'Snapshot files missing: ' . $this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $uid . '.zip'));
            }
        }

        $this->do_export_snapshot_wrap($uid);

        return $this->export_dir_path('wp-reset-snapshot-' . $uid . '.zip', true);
    } // do_export_snapshot

    /**
     * Process a snapshot step
     *
     * @param array  $step_data containing snapshot uid, action and extra data
     *
     * @return bool|WP_Error true on success, WP_Error on error
     */
    function export_snapshot_step($step_data)
    {
        global $wpdb;

        $dump_file_path = $this->export_dir_path('wp-reset-snapshot-' . $step_data['uid'] . '.sql');
        if (is_wp_error($dump_file_path)) {
            return $dump_file_path;
        }

        switch ($step_data['action']) {
            case 'export':
                require_once $this->plugin_dir . 'libs/wpr_db_dumper.php';
                try {
                    $db_info = $this->get_db_info();
                    clearstatcache();

                    $world_dumper = WPR_Shuttle_Dumper::create(array(
                        'host' => $db_info['host'],
                        'port' => $db_info['port'],
                        'username' => DB_USER,
                        'password' => DB_PASSWORD,
                        'db_name' =>  DB_NAME,
                    ));

                    $this->log('success', 'Start individual table dump for ' . $step_data['data'] . ' to ' . $dump_file_path);
                    $world_dumper->dump($dump_file_path, $step_data['data']);
                } catch (WPR_Shuttle_Exception $e) {
                    return new WP_Error(1, 'Couldn\'t create snapshot: ' . $e->getMessage());
                }
                break;
            case 'verify_export':
                WP_Reset_Utility::gzCompressFile($dump_file_path);
                wp_delete_file($dump_file_path);
                $this->do_export_snapshot_wrap($step_data['uid']);

                if ($this->verify_snapshot_integrity($step_data['uid']) === true) {
                    $snapshots = $this->get_snapshots();
                    $snapshots[$step_data['uid']]['partial'] = false;
                    update_option('wp-reset-snapshots', $snapshots);
                } else {
                    $this->do_delete_snapshot($step_data['uid']);
                    return new WP_Error(1, 'Snapshot verification failed and it has been deleted. Contact WP Reset support if this error repeats.');
                }
                return $this->export_dir_path('wp-reset-snapshot-' . $step_data['uid'] . '.zip', true);
                break;
            default:
                return new WP_Error(1, 'Unknown export snapshot step action');
                break;
        }

        return true;
    } // export_snapshot_step

    function do_export_snapshot_wrap($uid)
    {
        global $wpdb;

        $snapshots = $this->get_snapshots();

        $snapshot_json_file_path = $this->export_dir_path('wp-reset-export.json');
        if (is_wp_error($snapshot_json_file_path)) {
            return $snapshot_json_file_path;
        }

        $snapshot_json = @fopen($snapshot_json_file_path, 'w'); //phpcs:ignore
        $snapshots[$uid]['export_timestamp'] = current_time('mysql');
        $snapshots[$uid]['wpr_version'] = $this->version;
        $snapshots[$uid]['table_prefix'] = $wpdb->prefix;
        $snapshots[$uid]['home_url'] = home_url();
        fputs($snapshot_json, json_encode($snapshots[$uid])); //phpcs:ignore
        fclose($snapshot_json); //phpcs:ignore

        $export_zip_filename = $this->export_dir_path('wp-reset-snapshot-' . $uid . '.zip');
        if (is_wp_error($export_zip_filename)) {
            return $export_zip_filename;
        }

        $zip = new ZipArchive();
        if (file_exists($export_zip_filename)) {
            wp_delete_file($export_zip_filename);
        }
        $zip->open($export_zip_filename, ZipArchive::CREATE | ZipArchive::OVERWRITE);
        $zip->addFile($this->export_dir_path('wp-reset-export.json'), 'wp-reset-export.json');
        $zip->addFile($this->export_dir_path('wp-reset-snapshot-' . $uid . '.sql.gz'), 'wp-reset-snapshot-' . $uid . '.sql.gz');

        if (!(empty($snapshots[$uid]['plugins']) && empty($snapshots[$uid]['themes']))) {
            $zip->addFile($this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $uid . '.zip'), 'wp-reset-snapshot-files-' . $uid . '.zip');
        }
        $zip->close();

        @wp_delete_file($this->export_dir_path('wp-reset-export.json'));
        @wp_delete_file($this->export_dir_path('wp-reset-snapshot-' . $uid . '.sql.gz'));

        do_action('wp_reset_export_snapshot', 'wp-reset-snapshot-' . $uid . '.zip', $uid, $snapshots[$uid]);
    } // do_export_snapshot_wrap


    /**
     * Replace current tables with ones in snapshot, unzip attached files if available
     *
     * @param string  $uid  Snapshot unique 6-char ID.
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function do_restore_snapshot($uid = '')
    {
        global $wpdb, $wp_reset_cloud;
        $user_id = get_current_user_id();
        $new_tables = array();
        $snapshots = $this->get_snapshots();
        $wf_licensing_wpr = get_option('wf_licensing_wpr');
        $cloud_snapshots = $wp_reset_cloud->get_cloud_snapshots();

        if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
            return $res;
        }

        if (!empty($snapshots[$uid]['plugins']) || !empty($snapshots[$uid]['themes'])) {
            if (($res = $this->restore_snapshot_files($snapshots[$uid])) !== true) {
                return $res;
            }
        }

        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore
        if (is_array($table_status)) {
            foreach ($table_status as $index => $table) {
                if (0 !== stripos($table->Name, $uid . '_')) {
                    continue;
                }
                if (empty($table->Engine)) {
                    continue;
                }

                $new_tables[] = $table->Name;
            } // foreach
        } else {
            return new WP_Error(1, 'Can\'t get table status data.');
        }

        foreach ($table_status as $index => $table) {
            if (0 !== stripos($table->Name, $wpdb->prefix)) {
                continue;
            }
            if (empty($table->Engine)) {
                continue;
            }

            $wpdb->query('DROP TABLE `' . $table->Name . '`'); //phpcs:ignore
        } // foreach

        // copy snapshot tables to original name
        foreach ($new_tables as $table) {
            $new_name = str_replace($uid . '_', '', $table);

            $wpdb->query('CREATE TABLE `' . $new_name . '` LIKE `' . $table . '`'); //phpcs:ignore
            $wpdb->query('INSERT `' . $new_name . '` SELECT * FROM `' . $table . '`'); //phpcs:ignore
        }

        wp_cache_flush();
        update_option('wp-reset', $this->options);
        update_option('wp-reset-snapshots', $snapshots);
        update_option('wp-reset-cloud-snapshots', $cloud_snapshots);
        update_option('wf_licensing_wpr', $wf_licensing_wpr);


        if (!$this->is_cli_running()) {
            wp_clear_auth_cookie();
            wp_set_auth_cookie($user_id);
        }

        do_action('wp_reset_restore_snapshot', $uid);

        return true;
    } // restore_snapshot

    /**
     * Creates a staging copy of the website
     *
     * @param array  $snapshot  Optional staging meta and options
     *
     * @return array|WP_Error Staging copy details in array on success, or error object on fail.
     */
    function do_deploy_stage($params = array())
    {
        global $wpdb, $wp_filesystem;
        $options = $this->get_options();
        $stages = $this->get_stages();
        $stage = shortcode_atts(array('name' => '', 'folder' => '', 'ajax' => false), (array) $params);
        $tbl_core = $tbl_custom = $tbl_size = $tbl_rows = 0;
        $staging_basedir = $this->wpr_staging_path();
        if (is_wp_error($staging_basedir)) {
            return $staging_basedir;
        }

        $uid = $this->generate_snapshot_uid();

        $si = 0; //Just a safe fallback 
        while (file_exists($staging_basedir . $uid) && $si < 5) {
            $uid = $this->generate_snapshot_uid();
            $si++;
        }

        if (!$uid) {
            return new WP_Error(1, 'Unable to generate a valid stage UID.');
        }

        $stage_folder = wp_mkdir_p($staging_basedir . $uid);
        if (!$stage_folder) {
            return new WP_Error(1, 'Unable to create stage folder ' . $stage_folder);
        }

        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        if ($stage['name']) {
            $stage['name'] = substr(trim($stage['name']), 0, 512);
        }

        $stage['uid'] = $uid;
        $stage['timestamp'] = current_time('mysql');
        $stage['tbl_names'] = array();

        if ($stage['ajax']) {
            $stage['partial'] = true;
        }

        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore
        if (is_array($table_status)) {
            foreach ($table_status as $index => $table) {
                if (0 !== stripos($table->Name, $wpdb->prefix)) {
                    continue;
                }
                if (empty($table->Engine)) {
                    continue;
                }

                $tbl_rows += $table->Rows;
                $tbl_size += $table->Data_length + $table->Index_length;
                if (in_array($table->Name, $this->core_tables)) {
                    $tbl_core++;
                } else {
                    $tbl_custom++;
                }

                $stage['tbl_names'][] = $table->Name;

                if (!$stage['ajax']) {
                    if ($options['optimize_tables'] == true) {
                        $wpdb->query('OPTIMIZE TABLE `' . $table->Name . '`'); //phpcs:ignore
                    }
                    $wpdb->query('CREATE TABLE `' . $uid . '_' . $table->Name . '` LIKE `' . $table->Name . '`'); //phpcs:ignore
                    $wpdb->query('INSERT `' . $uid . '_' . $table->Name . '` SELECT * FROM `' . $table->Name . '`'); //phpcs:ignore
                }
            } // foreach
        } else {
            return new WP_Error(1, 'Can\'t get table status data.');
        }

        $stage['urls_to_replace'] = $this->estimate_quick(site_url('/'));

        $stage['tbl_core']   = $tbl_core;
        $stage['tbl_custom'] = $tbl_custom;
        $stage['tbl_rows']   = $tbl_rows;
        $stage['tbl_size']   = $tbl_size;

        $stages[$uid] = $stage;

        update_option('wp-reset-stages', $stages);
        do_action('wp_reset_deploy_stage', $uid, $stage);

        return $stage;
    } // do_deploy_stage

    private function write_stage_htaccess($fs, $stage_dir, $base_path)
    {
        $htaccess_path = untrailingslashit($stage_dir) . '/.htaccess';

        $wordpress_rules = '# BEGIN WordPress' . PHP_EOL;
        $wordpress_rules .= '<IfModule mod_rewrite.c>' . PHP_EOL;
        $wordpress_rules .= 'RewriteEngine On' . PHP_EOL;
        $wordpress_rules .= 'RewriteBase ' . $base_path . PHP_EOL;
        $wordpress_rules .= 'RewriteRule ^index.php\$ - [L]' . PHP_EOL;
        $wordpress_rules .= 'RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL;
        $wordpress_rules .= 'RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL;
        $wordpress_rules .= 'RewriteRule . ' . $base_path . 'index.php [L]' . PHP_EOL;
        $wordpress_rules .= '</IfModule>' . PHP_EOL;
        $wordpress_rules .= '# END WordPress';

        $header = "Options -Indexes\n";
        $new_htaccess_content = $header . $wordpress_rules . "\n";

        // If .htaccess exists, try to update only the WordPress block
        if ($fs->exists($htaccess_path)) {
            $existing_content = $fs->get_contents($htaccess_path);
            if ($existing_content === false) {
                return new WP_Error('ht_read_failed', "Unable to read existing .htaccess at {$htaccess_path}");
            }

            // Look for an existing WP block
            $wp_block_pattern = '/^\s*#\s*BEGIN\s+WordPress.*?#\s*END\s+WordPress\s*$/ims';

            if (preg_match($wp_block_pattern, $existing_content)) {
                // Replace the existing WordPress block
                $updated_content = preg_replace($wp_block_pattern, $wordpress_rules, $existing_content, 1);

                // Make sure "Options -Indexes" exists
                if (stripos($updated_content, 'Options -Indexes') === false) {
                    $updated_content = $header . $updated_content;
                }

                $write_result = $fs->put_contents($htaccess_path, $updated_content, FS_CHMOD_FILE);
                if (!$write_result) {
                    return new WP_Error('ht_write_failed', "Could not write updated .htaccess at {$htaccess_path}");
                }

                return true;
            }

            // No WordPress block found — write a minimal safe file
            $write_result = $fs->put_contents($htaccess_path, $new_htaccess_content, FS_CHMOD_FILE);
            if (!$write_result) {
                return new WP_Error('ht_write_failed', "Could not write new .htaccess at {$htaccess_path}");
            }

            return true;
        }

        // .htaccess does not exist — create a new one
        $write_result = $fs->put_contents($htaccess_path, $new_htaccess_content, FS_CHMOD_FILE);
        if (!$write_result) {
            return new WP_Error('ht_write_failed', "Could not create .htaccess at {$htaccess_path}");
        }

        return true;
    }

    /**
     * Process a staging step
     *
     * @param array  $step_data containing stage uid, action and extra data
     *
     * @return bool|WP_Error true on success, WP_Error on error
     */
    function deploy_stage_step($step_data)
    {
        global $wpdb, $wp_filesystem;

        $options = $this->get_options();
        ini_set('memory_limit', '1024M');

        if (!isset($step_data['uid'])) {
            return new WP_Error(1, 'Invalid stage ID');
        }

        $stages = $this->get_stages();

        if (!array_key_exists($step_data['uid'], $stages)) {
            return new WP_Error(1, 'Unknown stage ID');
        }

        $stage = $stages[$step_data['uid']];
        $stage['batch_update'] = 0;
        $root_path = untrailingslashit(ABSPATH);
        $staging_basedir = $this->wpr_staging_path();
        $stage_dir = trailingslashit($staging_basedir . $step_data['uid']);
        $old_prefix = $wpdb->prefix;
        $new_prefix = $step_data['uid'] . '_' . $old_prefix;

        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        if (!isset($stage['partial']) || $stage['partial'] != true) {
            return new WP_Error(1, 'stage already created');
        }

        switch ($step_data['action']) {
            case 'copy_files':
                $exclude = [
                    '/wpr_staging',
                    '/node_modules',
                    '/vendor/bin',
                    '/.git',
                    '/.svn',
                    '/.cache',
                    '/wp-content/cache',
                    '/wp-content/uploads/cache',
                    '/wp-content/ai1wm-backups',
                    '/wp-content/updraft'
                ];

                $this->staging_totals = array('files' => 0, 'bytes' => 0);
                $copy_ok = $this->wpfs_copy_folder($wp_filesystem, $root_path, $stage_dir, $exclude);
                if (is_wp_error($copy_ok)) {
                    return $copy_ok;
                }
                $stage['files_count'] = $this->staging_totals['files'];
                $stage['file_size'] = $this->staging_totals['bytes'];
                $stages[$step_data['uid']] = $stage;
                update_option('wp-reset-stages', $stages);

                break;
            case 'copy_table':
                $wpdb->show_errors = false;
                $wpdb->suppress_errors = false;

                if ($options['optimize_tables'] == true) {
                    $wpdb->query('OPTIMIZE TABLE ' . $step_data['data']); //phpcs:ignore
                }

                $wpdb->query('CREATE TABLE `' . $step_data['uid'] . '_' . $step_data['data'] . '` LIKE `' . $step_data['data'] . '`'); //phpcs:ignore
                $wpdb->query('INSERT `' . $step_data['uid'] . '_' . $step_data['data'] . '` SELECT * FROM `' . $step_data['data'] . '`'); //phpcs:ignore
                if ($wpdb->last_error !== '') {
                    return new WP_Error(1, 'An database error occurred: ' . $wpdb->last_error);
                }
                break;
            case 'update_db':
                $paths = $this->compute_stage_paths($step_data['uid']);
                $base_path = $paths['base_path'];
                $old_url = rtrim(home_url(), '/');
                $new_url = rtrim($paths['stage_url'], '/');
                $report = $this->update_stage_urls($wpdb, $new_prefix, $old_url, $new_url, 1);
                $stage['batch_update'] = $report['updated_rows'];
                break;
            case 'setting_up':
                //Update table copies with the new table prefix
                $stage_url = $this->wpr_staging_path(true, $step_data['uid']);

                $update_wp_config = $this->update_config_table_prefix($wp_filesystem, $stage_dir . 'wp-config.php', $new_prefix);
                if (is_wp_error($update_wp_config)) {
                    return $update_wp_config;
                }

                $paths = $this->compute_stage_paths($step_data['uid']);
                $base_path = $paths['base_path'];

                $update_htaccess = $this->write_stage_htaccess($wp_filesystem, $stage_dir, $base_path . $step_data['uid'] . '/');
                if (is_wp_error($update_htaccess)) {
                    return $update_htaccess;
                }

                $wpdb->query($wpdb->prepare("UPDATE `" . $new_prefix . "options` SET option_value=%s WHERE option_name IN ('siteurl','home')", $stage_url)); // Update site and home URLs
                $wpdb->query("UPDATE `" . $new_prefix . "options` SET option_value='' WHERE option_name='rewrite_rules'"); // Force permalink rules to rebuild on first load
                $wpdb->query($wpdb->prepare("UPDATE `" . $new_prefix . "options` SET option_name=%s WHERE option_name=%s", $new_prefix . 'user_roles', $old_prefix . 'user_roles')); // Update user_roles option name
                $wpdb->query($wpdb->prepare("UPDATE `" . $new_prefix . "usermeta` SET meta_key = REPLACE(meta_key, %s, %s) WHERE meta_key LIKE %s", $old_prefix, $new_prefix, $wpdb->esc_like($old_prefix) . '%'));

                break;
            case 'verify_integrity':
                $verify_stage = $this->verify_stage_integrity($step_data['uid']);
                if ($verify_stage === true) {
                    $stage['partial'] = false;
                    $stages[$step_data['uid']] = $stage;
                    update_option('wp-reset-stages', $stages);
                } else {
                    $this->do_delete_stage($step_data['uid']);
                    return $verify_stage;
                }
                break;
            default:
                return new WP_Error(1, 'Unknown stage step action');
                break;
        }

        return $stage;
    } // deploy_stage_step

    /**
    We just get a quick estimate so we don't melt the DB even if it's not accurate we'll at least show some ballpark estimate
     */
    private function estimate_quick($old_url)
    {
        global $wpdb;
        $like = '%' . $wpdb->esc_like(rtrim($old_url, '/')) . '%';

        $table_options     = $wpdb->options;
        $table_posts       = $wpdb->posts;
        $table_postmeta    = $wpdb->postmeta;
        $table_termmeta    = $wpdb->termmeta;
        $table_commentmeta = $wpdb->commentmeta;
        $table_usermeta    = $wpdb->usermeta;
        $table_links       = $wpdb->links;

        $targets = [
            [$table_options,   'option_value', "option_name NOT LIKE '_transient_%' AND option_name NOT LIKE '_site_transient_%'"],
            [$table_posts,     'post_content', ""],
            [$table_posts,     'post_excerpt', ""],
            [$table_postmeta,  'meta_value',   ""],
            [$table_termmeta,  'meta_value',   ""],
            [$table_commentmeta, 'meta_value',  ""],
            [$table_usermeta,  'meta_value',   ""],
            [$table_links,     'link_url',     ""],
        ];

        $total = 0;

        foreach ($targets as $target) {
            $table_name  = $target[0];
            $column_name = $target[1];
            $extra_where = $target[2];

            // Soft-existence check (works even if some tables are missing)
            $table_exists = (int) $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s",
                    $wpdb->dbname,
                    $table_name
                )
            );

            if ($table_exists === 0) {
                continue;
            }

            $where_sql = "`$column_name` LIKE %s";
            if ($extra_where !== '') {
                $where_sql = "(" . $where_sql . ") AND (" . $extra_where . ")";
            }

            $count = (int) $wpdb->get_var(
                $wpdb->prepare("SELECT COUNT(*) FROM `$table_name` WHERE $where_sql", $like)
            );

            if ($count > 0) {
                $total += $count;
            }
        }

        return $total;
    }

    private function replace_url_in_value($value, $old_url, $new_url)
    {
        if (!is_string($value) || $value === '') {
            return $value;
        }

        // if value looks serialized, use WordPress core helper
        if (is_serialized($value)) {
            try {
                $data = unserialize($value, ['allowed_classes' => false]);
            } catch (Throwable $e) {
                return $value;
            }

            $data = $this->replace_url_deep($data, $old_url, $new_url);
            return serialize($data);
        }

        // Plain string path
        if (strpos($value, $old_url) !== false) {
            return str_replace($old_url, $new_url, $value);
        }

        return $value;
    }

    private function replace_url_deep($data, $old_url, $new_url)
    {
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                $data[$k] = $this->replace_url_deep($v, $old_url, $new_url);
            }
            return $data;
        }
        if (is_object($data)) {
            foreach ($data as $p => $v) {
                $data->$p = $this->replace_url_deep($v, $old_url, $new_url);
            }
            return $data;
        }
        if (is_string($data) && $data !== '' && strpos($data, $old_url) !== false) {
            return str_replace($old_url, $new_url, $data);
        }
        return $data;
    }


    private function get_text_columns_with_pk($wpdb, $staged_prefix)
    {
        $database_name = $wpdb->dbname;

        // Text-based column types that might store URLs or serialized data
        $text_types = "'text','mediumtext','longtext','varchar','tinytext'";

        //Find all text-like columns in tables that start with our staged prefix
        $column_info = $wpdb->get_results($wpdb->prepare("
        SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_SCHEMA = %s
          AND TABLE_NAME LIKE %s
          AND DATA_TYPE IN ($text_types)
    ", $database_name, $wpdb->esc_like($staged_prefix) . '%'));

        if (empty($column_info)) {
            return [];
        }

        //Group text columns by table, ignoring things like 'guid'
        $columns_by_table = [];
        foreach ($column_info as $column) {
            $table_name  = $column->TABLE_NAME;
            $column_name = $column->COLUMN_NAME;

            // We never want to rewrite post GUIDs
            if ($column_name === 'guid') {
                continue;
            }

            $columns_by_table[$table_name]['columns'][] = $column_name;
        }

        if (empty($columns_by_table)) {
            return [];
        }

        $results = [];

        //For each table, determine if it has a simple numeric primary key (or single-column unique key)
        foreach (array_keys($columns_by_table) as $table_name) {

            // Try to find the primary key first
            $primary_key_rows = $wpdb->get_results($wpdb->prepare("
            SELECT k.COLUMN_NAME, c.DATA_TYPE
            FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS k
            JOIN INFORMATION_SCHEMA.COLUMNS AS c
              ON c.TABLE_SCHEMA = k.TABLE_SCHEMA
             AND c.TABLE_NAME   = k.TABLE_NAME
             AND c.COLUMN_NAME  = k.COLUMN_NAME
            WHERE k.TABLE_SCHEMA = %s
              AND k.TABLE_NAME   = %s
              AND k.CONSTRAINT_NAME = 'PRIMARY'
        ", $database_name, $table_name));

            $primary_key_column = null;

            // Check if there's exactly one numeric primary key column
            if (count($primary_key_rows) === 1) {
                $data_type = strtolower($primary_key_rows[0]->DATA_TYPE);
                if (in_array($data_type, ['int', 'bigint', 'mediumint', 'smallint', 'tinyint'], true)) {
                    $primary_key_column = $primary_key_rows[0]->COLUMN_NAME;
                }
            }

            // Fallback: look for a single numeric unique key
            if ($primary_key_column === null) {
                $unique_key_rows = $wpdb->get_results($wpdb->prepare("
                SELECT k.COLUMN_NAME, c.DATA_TYPE
                FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
                JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS k
                  ON k.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
                 AND k.TABLE_NAME       = tc.TABLE_NAME
                 AND k.CONSTRAINT_NAME  = tc.CONSTRAINT_NAME
                JOIN INFORMATION_SCHEMA.COLUMNS AS c
                  ON c.TABLE_SCHEMA = k.CONSTRAINT_SCHEMA
                 AND c.TABLE_NAME   = k.TABLE_NAME
                 AND c.COLUMN_NAME  = k.COLUMN_NAME
                WHERE tc.CONSTRAINT_TYPE = 'UNIQUE'
                  AND tc.TABLE_SCHEMA    = %s
                  AND tc.TABLE_NAME      = %s
            ", $database_name, $table_name));

                if (count($unique_key_rows) === 1) {
                    $data_type = strtolower($unique_key_rows[0]->DATA_TYPE);
                    if (in_array($data_type, ['int', 'bigint', 'mediumint', 'smallint', 'tinyint'], true)) {
                        $primary_key_column = $unique_key_rows[0]->COLUMN_NAME;
                    }
                }
            }

            // Skip tables without a simple numeric PK or UK
            if ($primary_key_column === null) {
                continue;
            }

            //Add each text column for this table to the final list
            foreach ($columns_by_table[$table_name]['columns'] as $column_name) {
                $results[] = [
                    'table'        => $table_name,
                    'pk'  => $primary_key_column,
                    'col'       => $column_name,
                ];
            }
        }

        return $results;
    }

    /**
     * Replace URLs in one table/column using a numeric PK, batched.
     * Returns count of updated rows.
     */
    private function fix_urls_in_table_column($wpdb, $table, $pk_col, $val_col, $old_url, $new_url, $batch_size = 1000)
    {
        $updated = 0;
        $last_pk = 0;
        $like    = '%' . $wpdb->esc_like($old_url) . '%';

        while (true) {
            $sql = "
            SELECT {$pk_col} AS pk, {$val_col} AS val
            FROM `{$table}`
            WHERE {$pk_col} > %d
              AND {$val_col} LIKE %s
            ORDER BY {$pk_col} ASC
            LIMIT %d
        ";
            $rows = $wpdb->get_results($wpdb->prepare($sql, $last_pk, $like, $batch_size));

            if (empty($rows)) {
                break;
            }

            foreach ($rows as $row) {
                $last_pk = (int) $row->pk;

                $old_val = $row->val;
                $new_val = $this->replace_url_in_value($old_val, $old_url, $new_url);

                if ($new_val === $old_val) {
                    continue;
                }

                $result = $wpdb->update(
                    $table,
                    [$val_col => $new_val],
                    [$pk_col  => $row->pk],
                    ['%s'],
                    ['%d']
                );

                if ($result !== false) {
                    $updated += (int) $result;
                }
            }
        }

        return $updated;
    }

    /**
     * Update URLs across ALL tables in the staged DB (tables starting with $new_prefix).
     */
    private function update_stage_urls($wpdb, $new_prefix, $old_url, $new_url, $batch_size = 1000)
    {
        $report = [];

        $candidates = $this->get_text_columns_with_pk($wpdb, $new_prefix);
        if (empty($candidates)) {
            $report['updated_rows'] = 0;
            return $report;
        }

        foreach ($candidates as $c) {
            $updated_rows = $this->fix_urls_in_table_column(
                $wpdb,
                $c['table'],
                $c['pk'],
                $c['col'],
                $old_url,
                $new_url,
                $batch_size
            );

            if ($updated_rows > 0) {
                $report[] = [
                    'table'        => $c['table'],
                    'col'          => $c['col'],
                    'updated_rows' => $updated_rows,
                ];
            } else {
                $report['updated_rows'] = 0;
            }
        }

        return $report;
    }

    private function compute_stage_paths($stage_id)
    {
        $home = home_url();
        $parts = wp_parse_url($home);

        $current_path = isset($parts['path']) ? $parts['path'] : '';
        $current_path = '/' . ltrim($current_path, '/');
        $current_path = rtrim($current_path, '/');

        $base_path = $current_path . '/wpr_staging/' . $stage_id . '/';
        $base_path = preg_replace('#/+#', '/', $base_path);

        $home_no_trail = rtrim($home, '/');
        $stage_url = $home_no_trail . '/wpr_staging/' . $stage_id;

        return [
            'base_path' => $base_path,
            'stage_url' => $stage_url,
        ];
    }

    /**
     * Delete stage files and tables from DB
     *
     * @param string  $uid  Stage unique 6-char ID.
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function do_delete_stage($uid = '')
    {
        global $wpdb, $wp_filesystem;

        if (empty($wp_filesystem)) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            WP_Filesystem();
        }

        $stages = $this->get_stages();
        $this->log('info', 'Deleting stage ' . $uid);
        if (strlen($uid) != 4 && strlen($uid) != 6) {
            return new WP_Error(1, 'Invalid UID format.');
        }

        if (!isset($stages[$uid])) {
            return new WP_Error(1, 'Unknown stage ID.');
        }

        $staging_basedir = $this->wpr_staging_path();
        $stage_path = trailingslashit($staging_basedir) . $uid;

        if ($wp_filesystem->exists($stage_path)) {
            if (! $wp_filesystem->rmdir($stage_path, true)) {
                return new WP_Error('delete_failed', 'Could not delete staging folder.');
            }
        }

        $tables = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array($uid . '\_%'))); //phpcs:ignore
        foreach ($tables as $table) {
            $wpdb->query('DROP TABLE IF EXISTS `' . $table . '`'); //phpcs:ignore
            if ($wpdb->last_error !== '') {
                $this->log('error', 'Failed to delete table ' . $table . ': ' . $wpdb->last_error);
            } else {
                $this->log('info', 'Deleted table ' . $table);
            }
        }

        $stage_copy = $stages[$uid];
        unset($stages[$uid]);
        update_option('wp-reset-stages', $stages);

        do_action('wp_reset_delete_stage', $uid, $stage_copy);
        $this->log('info', 'Deleted stage ' . $uid . ' successfully');

        return true;
    } // do_delete_stage

    /**
     * Verifies snapshot zip integrity by comparing metadata folders and size
     *
     * @param string  @param array  $snapshot  Snapshot details
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function verify_zip_integrity($snapshot)
    {
        if (!array_key_exists('file_size', $snapshot) || $snapshot['file_size'] != filesize($this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $snapshot['uid'] . '.zip'))) {
            return new WP_Error(1, 'ZIP file size is not correct!');
        }

        $folders = array();
        if (!empty($snapshot['plugins'])) {
            foreach ($snapshot['plugins'] as $plugin) {
                $folders[] = basename($plugin);
            }
        }

        if (!empty($snapshot['themes'])) {
            foreach ($snapshot['themes'] as $theme) {
                $folders[] = basename($theme);
            }
        }
        sort($folders);

        $zip_folders = array();
        $za = new ZipArchive();
        $za->open($this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $snapshot['uid'] . '.zip'));
        for ($i = 0; $i < $za->numFiles; $i++) {
            $file_path = $za->statIndex($i);
            $file_path_parts = explode('/', $file_path['name']);
            if (!in_array($file_path_parts[0], $zip_folders)) {
                $zip_folders[] = $file_path_parts[0];
            }
        }
        sort($zip_folders);

        if ($folders === $zip_folders) {
            return true;
        } else {
            return new WP_Error(1, 'Snapshot ZIP files have failed integrity validation!');
        }
    } // verify_zip_integrity


    /**
     * Restore snapshot files
     *
     * @param array  $snapshot  Snapshot details
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function restore_snapshot_files($snapshot)
    {
        $temp_dir = $this->autosnapshots_dir_path('_tmp_wp-reset-snapshot-files-' . $snapshot['uid']);

        $archive_files = new ZipArchive;
        $archive_files->open($this->autosnapshots_dir_path('wp-reset-snapshot-files-' . $snapshot['uid'] . '.zip'));
        $archive_files->extractTo($temp_dir);

        if (!empty($snapshot['plugins'])) {
            foreach ($snapshot['plugins'] as $plugin) {
                $folder = basename($plugin);
                $this->delete_folder(WP_PLUGIN_DIR . '/' . $folder, WP_PLUGIN_DIR . '/' . $folder);
                $this->copy_folder($temp_dir . '/' . $folder, WP_PLUGIN_DIR . '/' . $folder);
            }
        }

        if (!empty($snapshot['themes'])) {
            foreach ($snapshot['themes'] as $theme) {
                $folder = basename($theme);
                $this->delete_folder(get_theme_root() . '/' . $folder, get_theme_root() . '/' . $folder);
                $this->copy_folder($temp_dir . '/' . $folder, get_theme_root() . '/' . $folder);
            }
        }

        $archive_files->close();
        $this->delete_folder($temp_dir, $this->autosnapshots_dir_path());

        return true;
    } // restore_snapshot_files


    /**
     * Verifies snapshot integrity by comparing metadata and data in DB
     *
     * @param string  $uid  Snapshot unique 6-char ID.
     *
     * @return bool|WP_Error True on success, or error object on fail.
     */
    function verify_snapshot_integrity($uid)
    {
        global $wpdb;
        $tbl_core = $tbl_custom = 0;
        $snapshots = $this->get_snapshots();

        if (strlen($uid) != 4 && strlen($uid) != 6) {
            return new WP_Error(1, 'Invalid snapshot ID format.');
        }

        if (!isset($snapshots[$uid])) {
            return new WP_Error(1, 'Unknown snapshot ID.');
        }

        $snapshot = $snapshots[$uid];

        if (!empty($snapshot['plugins']) || !empty($snapshot['themes'])) {
            $res = $this->verify_zip_integrity($snapshot);
            if ($res !== true) {
                return $res;
            }
        }

        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore

        if (is_array($table_status)) {
            foreach ($table_status as $table) {
                if (0 !== stripos($table->Name, $uid . '_')) {
                    continue;
                }
                if (empty($table->Engine)) {
                    continue;
                }

                if (in_array(str_replace($uid . '_', '', $table->Name), $this->core_tables)) {
                    $tbl_core++;
                } else {
                    $tbl_custom++;
                }
            } // foreach

            if ($tbl_core != $snapshot['tbl_core'] || $tbl_custom != $snapshot['tbl_custom']) {
                return new WP_Error(1, 'Snapshot data has been compromised. Saved metadata does not match data in the DB (' . $tbl_core . ' / ' . $snapshot['tbl_core'] . ' core, ' . $tbl_custom . ' / ' . $snapshot['tbl_custom'] . ' core). Contact WP Reset support if data is critical, or restore it manualy via SQL.');
            }
        } else {
            return new WP_Error(1, 'Can\'t get table status data.');
        }

        return true;
    } // verify_snapshot_integrity


    /**
     * Compares a selected snapshot with the current table set in DB
     *
     * @param string  $uid  Snapshot unique 6-char ID.
     *
     * @return string|WP_Error Formatted table with details on success, or error object on fail.
     */
    function do_compare_snapshots($uid)
    {
        global $wpdb;
        $current = $snapshot = array();
        $out = $out2 = $out3 = '';

        if (($res = $this->verify_snapshot_integrity($uid)) !== true) {
            return $res;
        }

        $table_status = $wpdb->get_results('SHOW TABLE STATUS'); //phpcs:ignore
        foreach ($table_status as $index => $table) {
            if (empty($table->Engine)) {
                continue;
            }

            if (0 !== stripos($table->Name, $uid . '_') && 0 !== stripos($table->Name, $wpdb->prefix)) {
                continue;
            }

            $info = array();
            $info['rows'] = $table->Rows;
            $info['size_data'] = $table->Data_length;
            $info['size_index'] = $table->Index_length;
            $schema = $wpdb->get_row('SHOW CREATE TABLE ' . $table->Name, ARRAY_N); //phpcs:ignore
            $info['schema'] = $schema[1];
            $info['engine'] = $table->Engine;
            $info['fullname'] = $table->Name;
            $basename = str_replace(array($uid . '_'), array(''), $table->Name);
            $info['basename'] = $basename;
            $info['corename'] = str_replace(array($wpdb->prefix), array(''), $basename);
            $info['uid'] = $uid;

            if (0 === stripos($table->Name, $uid . '_')) {
                $snapshot[$basename] = $info;
            }

            if (0 === stripos($table->Name, $wpdb->prefix)) {
                $info['uid'] = '';
                $current[$basename] = $info;
            }
        } // foreach

        $in_both = array_keys(array_intersect_key($current, $snapshot));
        $in_current_only = array_diff_key($current, $snapshot);
        $in_snapshot_only = array_diff_key($snapshot, $current);

        $out .= '<br><br>';
        foreach ($in_current_only as $table) {
            $out .= '<div class="wpr-table-container in-current-only" data-table="' . $table['basename'] . '">';
            $out .= '<table>';
            $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
            $out .= '<td><b>' . $table['fullname'] . '</b></td>';
            $out .= '<td>table is not present in snapshot<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
            $out .= '</tr>';
            $out .= '<tr class="hidden">';
            $out .= '<td>';
            $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1 ? '' : 's') . ' totaling ' . WP_Reset_Utility::format_size($table['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($table['size_index']) . ' in index.</p>';
            $out .= '<pre>' . $table['schema'] . '</pre>';
            $out .= '</td>';
            $out .= '<td>&nbsp;</td>';
            $out .= '</tr>';
            $out .= '</table>';
            $out .= '</div>';
        } // foreach in current only

        foreach ($in_snapshot_only as $table) {
            $out .= '<div class="wpr-table-container in-snapshot-only" data-table="' . $table['basename'] . '">';
            $out .= '<table>';
            $out .= '<tr title="Click to show/hide more info" class="wpr-table-missing header-row">';
            $out .= '<td>table is not present in current tables</td>';
            $out .= '<td><b>' . $table['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
            $out .= '</tr>';
            $out .= '<tr class="hidden">';
            $out .= '<td>&nbsp;</td>';
            $out .= '<td>';
            $out .= '<p>' . number_format($table['rows']) . ' row' . ($table['rows'] == 1 ? '' : 's') . ' totaling ' . WP_Reset_Utility::format_size($table['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($table['size_index']) . ' in index.</p>';
            $out .= '<pre>' . $table['schema'] . '</pre>';
            $out .= '</td>';
            $out .= '</tr>';
            $out .= '</table>';
            $out .= '</div>';
        } // foreach in snapshot only

        foreach ($in_both as $tablename) {
            $tbl_current = $current[$tablename];
            $tbl_snapshot = $snapshot[$tablename];

            $schema1 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_current['schema'], 1);
            $schema2 = preg_replace('/(auto_increment=)([0-9]*) /i', '${1}1 ', $tbl_snapshot['schema'], 1);
            $tbl_snapshot['tmp_schema'] = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $tbl_snapshot['schema']);
            $schema2 = str_replace($tbl_snapshot['uid'] . '_' . $tablename, $tablename, $schema2);

            if ($tbl_current['rows'] == $tbl_snapshot['rows'] && $tbl_current['schema'] == $tbl_snapshot['tmp_schema']) {
                $out3 .= '<div class="wpr-table-container identical" data-table="' . $tablename . '">';
                $out3 .= '<table>';
                $out3 .= '<tr title="Click to show/hide more info" class="wpr-table-match header-row">';
                $out3 .= '<td><b>' . $tbl_current['fullname'] . '</b></td>';
                $out3 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b><span class="dashicons dashicons-arrow-down-alt2"></span></td>';
                $out3 .= '</tr>';
                $out3 .= '<tr class="hidden">';
                $out3 .= '<td>';
                $out3 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_current['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_current['size_index']) . ' in index.</p>';
                $out3 .= '<pre>' . $tbl_current['schema'] . '</pre>';
                $out3 .= '</td>';
                $out3 .= '<td>';
                $out3 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_snapshot['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_snapshot['size_index']) . ' in index.</p>';
                $out3 .= '<pre>' . $tbl_snapshot['schema'] . '</pre>';
                $out3 .= '</td>';
                $out3 .= '</tr>';
                $out3 .= '</table>';
                $out3 .= '</div>';
            } elseif ($schema1 != $schema2) {
                require_once $this->plugin_dir . 'libs/diff.php';
                require_once $this->plugin_dir . 'libs/diff/Renderer/Html/SideBySide.php';
                $diff = new WPR_Diff(explode("\n", $tbl_current['schema']), explode("\n", $tbl_snapshot['schema']), array('ignoreWhitespace' => false));
                $renderer = new WPR_Diff_Renderer_Html_SideBySide;

                $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
                $out2 .= '<table>';
                $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
                $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> table schemas do not match</td>';
                $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> table schemas do not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
                $out2 .= '</tr>';
                $out2 .= '<tr class="hidden">';
                $out2 .= '<td>';
                $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_current['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_current['size_index']) . ' in index.</p>';
                $out2 .= '</td>';
                $out2 .= '<td>';
                $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_snapshot['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_snapshot['size_index']) . ' in index.</p>';
                $out2 .= '</td>';
                $out2 .= '</tr>';
                $out2 .= '<tr class="hidden">';
                $out2 .= '<td colspan="2" class="no-padding">';
                $out2 .= $diff->Render($renderer);
                $out2 .= '</td>';
                $out2 .= '</tr>';
                $out2 .= '</table>';
                $out2 .= '</div>';
            } else {
                $out2 .= '<div class="wpr-table-container" data-table="' . $tbl_current['basename'] . '">';
                $out2 .= '<table>';
                $out2 .= '<tr title="Click to show/hide more info" class="wpr-table-difference header-row">';
                $out2 .= '<td><b>' . $tbl_current['fullname'] . '</b> data in tables does not match</td>';
                $out2 .= '<td><b>' . $tbl_snapshot['fullname'] . '</b> data in tables does not match<span class="dashicons dashicons-arrow-down-alt2"></span></td>';
                $out2 .= '</tr>';
                $out2 .= '<tr class="hidden">';
                $out2 .= '<td>';
                $out2 .= '<p>' . number_format($tbl_current['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_current['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_current['size_index']) . ' in index.</p>';
                $out2 .= '</td>';
                $out2 .= '<td>';
                $out2 .= '<p>' . number_format($tbl_snapshot['rows']) . ' rows totaling ' . WP_Reset_Utility::format_size($tbl_snapshot['size_data']) . ' in data and ' . WP_Reset_Utility::format_size($tbl_snapshot['size_index']) . ' in index.</p>';
                $out2 .= '</td>';
                $out2 .= '</tr>';

                $out2 .= '<tr class="hidden">';
                $out2 .= '<td colspan="2">';
                if ($tbl_current['corename'] == 'options') {
                    $ss_prefix = $tbl_snapshot['uid'] . '_' . $wpdb->prefix;
                    $diff_rows = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$wpdb->prefix}options.option_value != {$ss_prefix}options.option_value LIMIT 100;"); //phpcs:ignore
                    $only_current = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$ss_prefix}options.option_value IS NULL LIMIT 100;"); //phpcs:ignore
                    $only_snapshot = $wpdb->get_results("SELECT {$wpdb->prefix}options.option_name, {$wpdb->prefix}options.option_value AS current_value, {$ss_prefix}options.option_value AS snapshot_value FROM {$wpdb->prefix}options LEFT JOIN {$ss_prefix}options ON {$ss_prefix}options.option_name = {$wpdb->prefix}options.option_name WHERE {$wpdb->prefix}options.option_value IS NULL LIMIT 100;"); //phpcs:ignore
                    $out2 .= '<table class="table_diff">';
                    $out2 .= '<tr><td style="width: 100px;"><b>Option Name</b></td><td><b>Current Value</b></td><td><b>Snapshot Value</b></td></tr>';
                    foreach ($diff_rows as $row) {
                        $out2 .= '<tr>';
                        $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
                        $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : htmlentities($row->current_value)) . '</td>';
                        $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : htmlentities($row->snapshot_value)) . '</td>';
                        $out2 .= '</tr>';
                    } // foreach
                    foreach ($only_current as $row) {
                        $out2 .= '<tr>';
                        $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
                        $out2 .= '<td>' . (empty($row->current_value) ? '<i>empty</i>' : htmlentities($row->current_value)) . '</td>';
                        $out2 .= '<td><i>not found in snapshot</i></td>';
                        $out2 .= '</tr>';
                    } // foreach
                    foreach ($only_current as $row) {
                        $out2 .= '<tr>';
                        $out2 .= '<td style="width: 100px;">' . $row->option_name . '</td>';
                        $out2 .= '<td><i>not found in current tables</i></td>';
                        $out2 .= '<td>' . (empty($row->snapshot_value) ? '<i>empty</i>' : htmlentities($row->snapshot_value)) . '</td>';
                        $out2 .= '</tr>';
                    } // foreach
                    $out2 .= '</table>';
                } else {
                    $out2 .= '<p class="textcenter">Detailed data diff is not available for this table.</p>';
                }
                $out2 .= '</td>';
                $out2 .= '</tr>';

                $out2 .= '</table>';
                $out2 .= '</div>';
            }
        } // foreach in both

        return $out . $out2 . $out3;
    } // do_compare_snapshots


    /**
     * Generates a unique 6-char snapshot ID; verified non-existing
     *
     * @return string
     */
    function generate_snapshot_uid()
    {
        global $wpdb;
        $snapshots = $this->get_snapshots();
        $cnt = 0;
        $uid = false;
        $uid_length = 6;
        if (strlen($wpdb->prefix) > 9) {
            $uid_length = 4;
        }

        do {
            $cnt++;
            $uid = substr(str_shuffle(str_repeat('abcdefghijklmnopqrstuvwxyz', $uid_length)), 0, $uid_length);

            $verify_db = $wpdb->get_col($wpdb->prepare('SHOW TABLES LIKE %s', array('%' . $uid . '%'))); //phpcs:ignore
        } while (!empty($verify_db) && isset($snapshots[$uid]) && $cnt < 30);

        if ($cnt == 30) {
            $uid = false;
        }

        $uid_tracker_file = false;
        $uid_tracker_contents = '';
        $search = glob(trailingslashit(WP_CONTENT_DIR) . 'wpr_uid_tracker_*');
        if (!empty($search)) {
            $uid_tracker_file = trailingslashit(WP_CONTENT_DIR) . basename($search[0]);
        } else {
            $uid_tracker_file = trailingslashit(WP_CONTENT_DIR) . 'wpr_uid_tracker_' . md5(time());
        }

        if (file_exists($uid_tracker_file)) {
            $uid_tracker_contents = file_get_contents($uid_tracker_file);
        }

        $uid_tracker_contents .= $uid . "\n";
        file_put_contents($uid_tracker_file, $uid_tracker_contents);

        return $uid;
    } // generate_snapshot_uid


    /**
     * Prune autosnapshots
     *
     * @return bool
     */
    function prune_autosnapshots($force = false)
    {
        $options = $this->get_all_options();

        if (!$force && ($options['options']['prune_snapshots'] != 1 || wp_rand(0, 100) < 90)) {
            return false;
        }

        $snapshots = $this->get_snapshots();
        $snapshot_dates = array();
        $snapshot_sizes = array();
        $delete = array();
        foreach ($snapshots as $sid => $snapshot) {
            if (isset($snapshot['auto']) && ($snapshot['auto'] == 'true' || $snapshot['auto'] == '1' || $snapshot['auto'] == true)) {
                $snapshot_dates[$sid] = strtotime($snapshot['timestamp']);
                if (!array_key_exists('file_size', $snapshot)) {
                    $snapshot['file_size'] = 0;
                }
                $snapshot_sizes[$sid] = round(($snapshot['file_size'] + $snapshot['tbl_size']) / 10485.76) / 100; // Size in MB with 2 decimal places
            }
        }

        arsort($snapshot_dates);

        if (substr($options['options']['prune_snapshots_details'], 0, 4) == 'size') {
            $tmp = explode('-', $options['options']['prune_snapshots_details']);
            $max_size = (int) $tmp[1];
            $current_size = 0;
            foreach ($snapshot_dates as $sid => $date) {
                $current_size += $snapshot_sizes[$sid];
                if ($current_size > $max_size) {
                    $delete[$sid] = $snapshot_sizes[$sid];
                }
            }
        } else if (substr($options['options']['prune_snapshots_details'], 0, 3) == 'cnt') {
            $tmp = explode('-', $options['options']['prune_snapshots_details']);
            $tmp = (int) $tmp[1];
            $delete = array_slice($snapshot_dates, $tmp);
        } else {
            $tmp = explode('-', $options['options']['prune_snapshots_details']);
            $tmp = strtotime(current_time('mysql')) - ((int) $tmp[1] * 60 * 60 * 24);

            foreach ($snapshot_dates as $sid => $timestamp) {
                if ($timestamp < $tmp) {
                    $delete[$sid] = $timestamp;
                }
            }
        }

        if (count($delete) > 0) {
            foreach ($delete as $sid => $data) {
                $this->do_delete_snapshot($sid);
            }
        }
        return true;
    } // prune_autosnapshots


    /**
     * Prune autosnapshots
     *
     * @return bool
     */
    function prune_cloud_autosnapshots($force = false)
    {
        global $wp_reset_cloud;
        $options = $this->get_all_options();

        if (!$force && ($options['options']['prune_cloud_snapshots'] != 1 || wp_rand(0, 100) < 90)) {
            return false;
        }

        $snapshots = $wp_reset_cloud->get_cloud_snapshots();
        $snapshot_dates = array();
        $snapshot_sizes = array();
        $delete = array();

        foreach ($snapshots as $sid => $snapshot) {
            if (isset($snapshot['auto']) && ($snapshot['auto'] == 'true' || $snapshot['auto'] == '1' || $snapshot['auto'] === true)) {
                $snapshot_dates[$sid] = strtotime($snapshot['timestamp']);
                if (!isset($snapshot['file_size'])) {
                    $snapshot['file_size'] = 0;
                }
                $snapshot_sizes[$sid] = round(($snapshot['file_size'] + $snapshot['tbl_size']) / 10485.76) / 100; // Size in MB with 2 decimal places
            }
        }
        arsort($snapshot_dates);

        if (substr($options['options']['prune_cloud_snapshots_details'], 0, 4) == 'size') {
            $tmp = explode('-', $options['options']['prune_cloud_snapshots_details']);
            $max_size = (int) $tmp[1];
            $current_size = 0;
            foreach ($snapshot_dates as $sid => $date) {
                $current_size += $snapshot_sizes[$sid];
                if ($current_size > $max_size) {
                    $delete[$sid] = $snapshot_sizes[$sid];
                }
            }
        } else if (substr($options['options']['prune_cloud_snapshots_details'], 0, 3) == 'cnt') {
            $tmp = explode('-', $options['options']['prune_cloud_snapshots_details']);
            $tmp = (int) $tmp[1];
            $delete = array_slice($snapshot_dates, $tmp);
        } else {
            $tmp = explode('-', $options['options']['prune_cloud_snapshots_details']);
            $tmp = strtotime(current_time('mysql')) - ((int) $tmp[1] * 60 * 60 * 24);

            foreach ($snapshot_dates as $sid => $timestamp) {
                if ($timestamp < $tmp) {
                    $delete[$sid] = $timestamp;
                }
            }
        }

        if (count($delete) > 0) {
            foreach ($delete as $sid => $data) {
                $wp_reset_cloud->cloud_snapshot_delete($sid);
            }
        }

        return true;
    } // prune_autosnapshots


    /**
     * Register all hooks and filters for autosnapshots
     *
     * @return null
     */
    function register_autosnapshot_hooks()
    {
        if (!current_user_can('administrator') || !is_admin()) {
            return;
        }

        $options = $this->get_options();

        if (is_multisite() || $options['events_snapshots'] != '1') {
            return;
        }

        add_action('activate_plugin', array($this, 'autosnapshot_actions'));
        add_action('deactivate_plugin', array($this, 'autosnapshot_actions'));
        add_action('delete_plugin', array($this, 'autosnapshot_actions'));
        add_action('delete_theme', array($this, 'autosnapshot_actions'));
        add_filter('upgrader_pre_install', array($this, 'autosnapshot_update'), 10, 2);

        $action = sanitize_text_field(wp_unslash($_REQUEST['action'] ?? '')); //phpcs:ignore
        if (basename(sanitize_text_field(wp_unslash($_SERVER['SCRIPT_FILENAME'] ?? '')), '.php') == 'themes' && isset($_REQUEST['action'])) { //phpcs:ignore
            if ($action == 'activate' || $action == 'delete') {
                $template = get_template_directory();
                $template_data = wp_get_theme(basename($template));
                $themes = array();
                $themes[$template_data['Name']] = $template;
                $stylesheet = get_stylesheet_directory();
                if ($stylesheet != $template) {
                    $stylesheet_data = wp_get_theme(basename($stylesheet));
                    $themes[$stylesheet_data['Name']] = $stylesheet;
                }
                $this->do_autosnapshot(array('action' => $action . '_theme', 'type' => 'theme', 'themes' => $themes));
            }
        }

        if (basename(sanitize_text_field(wp_unslash($_SERVER['SCRIPT_FILENAME'] ?? '')), '.php') == 'admin-ajax' && isset($_REQUEST['action']) && $action == 'delete-theme') { //phpcs:ignore
            global $wp_theme_directories;
            $slug = sanitize_text_field(wp_unslash($_REQUEST['slug'] ?? '')); //phpcs:ignore
            $template_data = wp_get_theme(basename($slug));
            $theme_root = get_raw_theme_root($slug);
            if (empty($theme_root)) {
                $theme_root = get_raw_theme_root($slug);
                if (false === $theme_root) {
                    $theme_root = WP_CONTENT_DIR . '/themes';
                } elseif (!in_array($theme_root, (array) $wp_theme_directories)) {
                    $theme_root = WP_CONTENT_DIR . $theme_root;
                }
            }
            $theme_path = WP_CONTENT_DIR . $theme_root . '/' . $slug;
            $themes = array();
            $themes[$template_data['Name']] = $theme_path;
            $this->do_autosnapshot(array('action' => 'delete_theme', 'type' => 'theme', 'themes' => $themes));
        }

        if (basename(sanitize_text_field(wp_unslash($_SERVER['SCRIPT_FILENAME'] ?? '')), '.php') == 'update-core' && isset($_REQUEST['action']) && $action == 'do-core-reinstall') { //phpcs:ignore
            $this->do_autosnapshot(array('action' => 'reinstall_core', 'type' => 'core'));
        }

        if (basename(sanitize_text_field(wp_unslash($_SERVER['SCRIPT_FILENAME'] ?? '')), '.php') == 'update-core' && isset($_REQUEST['action']) && $action == 'do-core-upgrade') { //phpcs:ignore
            $this->do_autosnapshot(array('action' => 'upgrade_core', 'type' => 'core'));
        }
    } // register_autosnapshot_hooks


    /**
     * Depending on auto snapshots setting for tools, creates a note or link to create snapshot
     *
     * @param string $description Default description for snapshot, if created manually
     *
     * @return string
     */
    function get_autosnapshot_tools_modal($description = '')
    {
        $options = $this->get_options();

        if ($options['tools_snapshots']) {
            return ' An automatic snapshot will be created so you can undo if needed.';
        } else {
            return htmlspecialchars(' Always <a data-snapshot-description="' . trim($description) . '" href="#" class="create-new-snapshot">make a snapshot</a> before running database related tools so you can undo if needed.');
        }
    } // get_autosnapshot_tools_modal


    /**
     * Verify what action is being done and call do_autosnapshot if needed
     *
     * @return null
     */
    function autosnapshot_actions()
    {
        $items = array();
        $items['action'] = current_action();
        $items['args'] = func_get_args();
        $items['type'] = 'plugin';

        $watched_actions = array('activate_plugin', 'deactivate_plugin', 'delete_plugin');

        if (empty($items['args'][0]) || !in_array($items['action'], $watched_actions)) {
            return false;
        }

        $plugin_path = plugin_dir_path(WP_PLUGIN_DIR . '/' . $items['args'][0]);
        $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $items['args'][0]);

        if ($plugin_data['Name'] == 'WP Reset PRO') {
            return false;
        }

        $items['plugins'][$plugin_data['Name'] . ' v' . $plugin_data['Version']] = $plugin_path;

        if (isset($_REQUEST['checked'])) { //phpcs:ignore
            foreach ($_REQUEST['checked'] as $plugin) { //phpcs:ignore
                if (!in_array($plugin, $items['plugins'])) {
                    $plugin_path = plugin_dir_path(WP_PLUGIN_DIR . '/' . $plugin);
                    $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
                    $items['plugins'][$plugin_data['Name'] . ' v' . $plugin_data['Version']] = $plugin_path;
                }
            }
        }

        $this->do_autosnapshot($items);
    } // autosnapshot_actions


    /**
     * Check if plugin or theme is being updated and do_autosnapshot if needed
     *
     * @return null
     */
    function autosnapshot_update($return, $args)
    {
        if (is_wp_error($return)) {
            return $return;
        }

        $options = $this->get_all_options();
        if (is_multisite() || $options['options']['events_snapshots'] != '1') {
            return;
        }

        $items = array();
        $items['args'] = $args;
        $items['type'] = false;

        if (isset($items['args']['plugin'])) {
            if (!(strpos($items['args']['plugin'], 'wp-reset') === 0)) {
                $items['action'] = 'update_plugin';
                $items['type'] = 'plugin';
                $plugin_path = plugin_dir_path(WP_PLUGIN_DIR . '/' . $items['args']['plugin']);
                $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $items['args']['plugin']);
                $items['plugins'][$plugin_data['Name'] . ' v' . $plugin_data['Version']] = $plugin_path;
            }
        } else if (isset($items['args']['theme'])) {
            $items['action'] = 'update_theme';
            $items['type'] = 'themes';
            $template_data = wp_get_theme($items['args']['theme']);
            $items['themes'][$template_data['Name'] . ' v' . $template_data['Version']] = get_theme_root($items['args']['theme']) . '/' . $items['args']['theme'];
        }

        if ($items['type'] !== false) {
            if ($items['type'] == 'plugin' && isset($_REQUEST['plugins'])) { //phpcs:ignore
                $plugins = explode(',', $_REQUEST['plugins']); //phpcs:ignore
                foreach ($plugins as $plugin) {
                    if (!in_array($plugin, $items['plugins'])) {
                        $plugin_path = plugin_dir_path(WP_PLUGIN_DIR . '/' . $plugin);
                        $plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
                        $items['plugins'][$plugin_data['Name'] . ' v' . $plugin_data['Version']] = $plugin_path;
                    }
                }
            }

            if ($items['type'] == 'themes' && isset($_REQUEST['themes'])) { //phpcs:ignore
                $themes = explode(',', $_REQUEST['themes']); //phpcs:ignore
                foreach ($themes as $theme) {
                    if (!in_array($theme, $items['themes'])) {
                        $template_data = wp_get_theme($theme);
                        $items['themes'][$template_data['Name'] . ' v' . $template_data['Version']] = get_theme_root($theme) . '/' . $theme;
                    }
                }
            }
            $this->do_autosnapshot($items);
        }

        return $return;
    } // autosnapshot_update


    /**
     * Get plugin path from slug
     *
     * @return string path
     */
    function get_plugin_path($slug)
    {
        $all_plugins = get_plugins();
        foreach ($all_plugins as $plugin_path => $plugin) {
            if (strpos($plugin_path, $slug . '/') === 0) {
                return $plugin_path;
            }
        }
        return false;
    } // get_plugin_path


    function clean_snapshot_name($name)
    {
        return preg_replace('/[^a-z0-9_s+ \s{}\'\"\:\,\.\/]/i', '', $name);
    }

    function clean_snapshot_items($items)
    {
        $cleaned_items = array();
        foreach ($items as $name => $data) {
            $cleaned_items[$this->clean_snapshot_name($name)] = $data;
        }
        return $cleaned_items;
    }

    /**
     * Crate snapshot and zip files
     *
     * @return null
     */
    function do_autosnapshot($items)
    {
        global $wp_version;
        $options = $this->get_all_options();
        if (isset($GLOBALS['wpr_autosnapshot_done'])) {
            return false;
        }
        $GLOBALS['wpr_autosnapshot_done'] = true;

        wp_cache_flush();

        switch ($items['action']) {
            case 'activate_plugin':
                if (count($items['plugins']) > 1) {
                    $snapshot['name'] = 'Before the following plugins were activated: ' . implode(', ', array_keys($items['plugins']));
                } else {
                    $snapshot['name'] = 'Before ' . implode(', ', array_keys($items['plugins'])) . ' was activated';
                }
                $snapshot['plugins'] = $this->clean_snapshot_items($items['plugins']);
                break;
            case 'deactivate_plugin':
                if (count($items['plugins']) > 1) {
                    $snapshot['name'] = 'Before the following plugins were deactivated: ' . implode(', ', array_keys($items['plugins']));
                } else {
                    $snapshot['name'] = 'Before ' . implode(', ', array_keys($items['plugins'])) . ' was deactivated';
                }
                $snapshot['plugins'] = $this->clean_snapshot_items($items['plugins']);
                break;
            case 'delete_plugin':
                if (count($items['plugins']) > 1) {
                    $snapshot['name'] = 'Before the following plugins were deleted: ' . implode(', ', array_keys($items['plugins']));
                } else {
                    $snapshot['name'] = 'Before ' . implode(', ', array_keys($items['plugins'])) . ' was deleted';
                }
                $snapshot['plugins'] = $this->clean_snapshot_items($items['plugins']);
                break;
            case 'update_plugin':
                if (count($items['plugins']) > 1) {
                    $snapshot['name'] = 'Before the following plugins were updated: ' . implode(', ', array_keys($items['plugins']));
                } else {
                    $update_plugins = get_site_transient('update_plugins');
                    if (isset($update_plugins->response[$items['args']['plugin']])) {
                        $new_version = 'v' . $update_plugins->response[$items['args']['plugin']]->new_version;
                    } else {
                        $new_version = 'an unknown version';
                    }
                    $snapshot['name'] = 'Before ' . implode(', ', array_keys($items['plugins'])) . ' was updated to ' . $new_version;
                }
                $snapshot['plugins'] = $this->clean_snapshot_items($items['plugins']);
                break;
            case 'update_theme':
                if (count($items['themes']) > 1) {
                    $snapshot['name'] = 'Before the following themes were updated: ' . implode(', ', array_keys($items['themes']));
                } else {
                    $update_themes = get_site_transient('update_themes');
                    if (isset($update_themes->response[$items['args']['theme']])) {
                        $new_version = 'v' . $update_themes->response[$items['args']['theme']]['new_version'];
                    } else {
                        $new_version = 'an unknown version';
                    }
                    $snapshot['name'] = 'Before ' . implode(', ', array_keys($items['themes'])) . ' was updated to ' . $new_version;
                }
                $snapshot['themes'] = $this->clean_snapshot_items($items['themes']);
                break;
            case 'activate_theme':
                $current_theme = wp_get_theme();
                $new_theme = wp_get_theme(sanitize_text_field(wp_unslash($_REQUEST['stylesheet'] ?? ''))); //phpcs:ignore
                $snapshot['name'] = 'Before theme ' . $current_theme['Name'] . ' was changed to ' . $new_theme['Name'];
                $snapshot['themes'] = $this->clean_snapshot_items($items['themes']);
                break;
            case 'delete_theme':
                reset($items['themes']);
                $snapshot['name'] = 'Before theme ' . key($items['themes']) . ' was deleted';
                $snapshot['themes'] = $this->clean_snapshot_items($items['themes']);
                break;
            case 'upgrade_core':
                if (!function_exists('get_core_updates')) {
                    require_once ABSPATH . 'wp-admin/includes/update.php';
                }
                $update_core = get_core_updates();
                $snapshot['name'] = 'Before WordPress was upgraded from ' . $wp_version . ' to ' . $update_core[0]->version;
                break;
            case 'reinstall_core':
                $snapshot['name'] = 'Before WordPress ' . $wp_version . ' was reinstalled';
                break;
        }

        $snapshot['name'] = $this->clean_snapshot_name($snapshot['name']);

        $snapshot['auto'] = true;

        $this->do_create_snapshot($snapshot);
    } // do_autosnapshot

    function get_db_info()
    {
        $options = $this->get_options();

        if ($options['alternate_db_connection'] == true) {
            return array('host' => DB_HOST, 'port' => false);
        }

        if (strpos(DB_HOST, ':') > 0) {
            $db_host_parts = parse_url(DB_HOST); //phpcs:ignore
            return array('host' => $db_host_parts['host'], 'port' => $db_host_parts['port']);
        } else {
            return array('host' => DB_HOST, 'port' => 3306);
        }
    }

    function mainwp_child_extra_execution($info, $post_data)
    {
        global $wp_reset_licensing;
        $license = $this->get_license();
        $snapshots = $this->get_all_snapshots();

        if ($wp_reset_licensing->is_active()) {
            $license_key = (empty($license['license_key']) ? '12345678-12345678-12345678-12345678' : substr(esc_attr($license['license_key']), 0, 8) . '-************************');
        } else {
            $license_key = 'inactive';
        }

        $admins = get_users(array(
            'role'    => 'administrator',
            'orderby' => 'user_nicename',
            'order'   => 'ASC'
        ));

        $admin_array = array();
        foreach ($admins as $admin) {
            $admin_array[$admin->ID] = $admin->user_login;
        }

        $theme =  wp_get_theme();
        $theme_name = $theme->get('Name');
        if (empty($theme_name)) {
            $theme_name = '<i>no active theme</i>';
        }

        $active_plugins = get_option('active_plugins');

        $space_usage_total = 0;
        $space_usage_user = 0;
        $space_usage_auto = 0;

        foreach ($snapshots as $snapshot) {
            if (false === $snapshot['local']) {
                continue;
            }
            $space_usage_total += $snapshot['tbl_size'];
            if ($snapshot['auto']) {
                $space_usage_auto += $snapshot['tbl_size'];
            } else {
                $space_usage_user += $snapshot['tbl_size'];
            }
        }

        if (isset($post_data['action'])) {
            switch ($post_data['action']) {
                case 'wp_reset_info':
                    $tests = array(
                        'license' => $license_key,
                        'admins' => $admin_array,
                        'theme' => $theme_name,
                        'post' => $post_data['extra_param'],
                        'snapshots' => array(
                            'total' => WP_Reset_Utility::format_size($space_usage_total),
                            'user' => WP_Reset_Utility::format_size($space_usage_user),
                            'auto' => WP_Reset_Utility::format_size($space_usage_auto)
                        )
                    );

                    return $tests;
                    break;
                case 'wp_reset_reset':
                    $extra_data = array(
                        'mainwp_reset' => 'true',
                        'user_id' => $post_data['user_id'],
                        'user_name' => $post_data['user_name']
                    );

                    if ($post_data['reactivate_theme'] == 'true') {
                        $extra_data['reactivate_theme'] = 'true';
                    }

                    if ($post_data['reactivate_wpreset'] == 'true') {
                        $extra_data['reactivate_wpreset'] = 'true';
                    }

                    if ($post_data['reactivate_plugins'] == 'true') {
                        $extra_data['reactivate_plugins'] = 'true';
                    }

                    $res = $this->do_reinstall($extra_data);
                    if (is_wp_error($res)) {
                        return array(
                            'result' => 'error',
                            'error' => $res->get_error_message()
                        );
                    } else {
                        return array(
                            'result' => 'success',
                            'action' => 'reset'
                        );
                    }
                    break;
                default:
                    break;
            }
        }
        return $info;
    }

    static function wp_kses_wf($html)
    {
        add_filter('safe_style_css', function ($styles) {
            $styles_wf = array(
                'text-align',
                'margin',
                'color',
                'float',
                'border',
                'background',
                'background-color',
                'border-bottom',
                'border-bottom-color',
                'border-bottom-style',
                'border-bottom-width',
                'border-collapse',
                'border-color',
                'border-left',
                'border-left-color',
                'border-left-style',
                'border-left-width',
                'border-right',
                'border-right-color',
                'border-right-style',
                'border-right-width',
                'border-spacing',
                'border-style',
                'border-top',
                'border-top-color',
                'border-top-style',
                'border-top-width',
                'border-width',
                'caption-side',
                'clear',
                'cursor',
                'direction',
                'font',
                'font-family',
                'font-size',
                'font-style',
                'font-variant',
                'font-weight',
                'height',
                'letter-spacing',
                'line-height',
                'margin-bottom',
                'margin-left',
                'margin-right',
                'margin-top',
                'overflow',
                'padding',
                'padding-bottom',
                'padding-left',
                'padding-right',
                'padding-top',
                'text-decoration',
                'text-indent',
                'vertical-align',
                'width',
                'display',
            );

            foreach ($styles_wf as $style_wf) {
                $styles[] = $style_wf;
            }
            return $styles;
        });

        $allowed_tags = wp_kses_allowed_html('post');
        $allowed_tags['input'] = array(
            'type' => true,
            'style' => true,
            'class' => true,
            'id' => true,
            'checked' => true,
            'disabled' => true,
            'name' => true,
            'size' => true,
            'placeholder' => true,
            'value' => true,
            'data-*' => true,
            'size' => true,
            'disabled' => true
        );

        $allowed_tags['textarea'] = array(
            'type' => true,
            'style' => true,
            'class' => true,
            'id' => true,
            'checked' => true,
            'disabled' => true,
            'name' => true,
            'size' => true,
            'placeholder' => true,
            'value' => true,
            'data-*' => true,
            'cols' => true,
            'rows' => true,
            'disabled' => true,
            'autocomplete' => true
        );

        $allowed_tags['select'] = array(
            'type' => true,
            'style' => true,
            'class' => true,
            'id' => true,
            'checked' => true,
            'disabled' => true,
            'name' => true,
            'size' => true,
            'placeholder' => true,
            'value' => true,
            'data-*' => true,
            'multiple' => true,
            'disabled' => true
        );

        $allowed_tags['option'] = array(
            'type' => true,
            'style' => true,
            'class' => true,
            'id' => true,
            'checked' => true,
            'disabled' => true,
            'name' => true,
            'size' => true,
            'placeholder' => true,
            'value' => true,
            'selected' => true,
            'data-*' => true
        );
        $allowed_tags['optgroup'] = array(
            'type' => true,
            'style' => true,
            'class' => true,
            'id' => true,
            'checked' => true,
            'disabled' => true,
            'name' => true,
            'size' => true,
            'placeholder' => true,
            'value' => true,
            'selected' => true,
            'data-*' => true,
            'label' => true
        );

        $allowed_tags['a'] = array(
            'href' => true,
            'data-*' => true,
            'class' => true,
            'style' => true,
            'id' => true,
            'target' => true,
            'data-*' => true,
            'role' => true,
            'aria-controls' => true,
            'aria-selected' => true,
            'disabled' => true,
            'data-btn-confirm' => true,
            'data-text-wait' => true,
            'data-text-confirm' => true,
            'data-btn-confirm' => true,
            'data-ss-uid' => true,
            'data-text-done' => true
        );

        $allowed_tags['div'] = array(
            'style' => true,
            'class' => true,
            'id' => true,
            'data-*' => true,
            'role' => true,
            'aria-labelledby' => true,
            'value' => true,
            'aria-modal' => true,
            'tabindex' => true
        );

        $allowed_tags['li'] = array(
            'style' => true,
            'class' => true,
            'id' => true,
            'data-*' => true,
            'role' => true,
            'aria-labelledby' => true,
            'value' => true,
            'aria-modal' => true,
            'tabindex' => true
        );

        $allowed_tags['span'] = array(
            'style' => true,
            'class' => true,
            'id' => true,
            'data-*' => true,
            'aria-hidden' => true
        );

        $allowed_tags['style'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'style' => true
        );

        $allowed_tags['fieldset'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'style' => true
        );

        $allowed_tags['svg'] = array(
            'aria-hidden' => true,
            'focusable' => true,
            'data-*' => true,
            'role' => true,
            'xmlns' => true,
            'viewBox' => true,
            'viewbox' => true,
            'class' => true
        );

        $allowed_tags['path'] = array(
            'fill' => true,
            'd' => true,
            'data-*' => true,
            'role' => true,
            'xmlns' => true,
            'viewBox' => true,
            'class' => true
        );

        $allowed_tags['link'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'rel' => true,
            'href' => true,
            'media' => true,
            'style' => true
        );

        $allowed_tags['form'] = array(
            'style' => true,
            'class' => true,
            'id' => true,
            'method' => true,
            'action' => true,
            'data-*' => true,
            'style' => true
        );

        $allowed_tags['script'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'src' => true,
            'style' => true
        );

        $allowed_tags['table'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'cellpadding' => true,
            'cellspacing' => true,
            'border' => true,
            'style' => true
        );

        $allowed_tags['tr'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'cellpadding' => true,
            'cellspacing' => true,
            'border' => true,
            'data-*' => true,
            'style' => true
        );

        $allowed_tags['td'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'cellpadding' => true,
            'cellspacing' => true,
            'colspan' => true,
            'border' => true,
            'data-*' => true,
            'style' => true
        );

        $allowed_tags['th'] = array(
            'class' => true,
            'id' => true,
            'type' => true,
            'cellpadding' => true,
            'cellspacing' => true,
            'border' => true,
            'data-*' => true,
            'style' => true
        );

        $allowed_tags['canvas'] = array(
            'class' => true,
            'id' => true,
            'style' => true
        );

        if (!empty($html)) {
            echo wp_kses($html, $allowed_tags);
        }

        add_filter('safe_style_css', function ($styles) {
            $styles_wf = array(
                'text-align',
                'margin',
                'color',
                'float',
                'border',
                'background',
                'background-color',
                'border-bottom',
                'border-bottom-color',
                'border-bottom-style',
                'border-bottom-width',
                'border-collapse',
                'border-color',
                'border-left',
                'border-left-color',
                'border-left-style',
                'border-left-width',
                'border-right',
                'border-right-color',
                'border-right-style',
                'border-right-width',
                'border-spacing',
                'border-style',
                'border-top',
                'border-top-color',
                'border-top-style',
                'border-top-width',
                'border-width',
                'caption-side',
                'clear',
                'cursor',
                'direction',
                'font',
                'font-family',
                'font-size',
                'font-style',
                'font-variant',
                'font-weight',
                'height',
                'letter-spacing',
                'line-height',
                'margin-bottom',
                'margin-left',
                'margin-right',
                'margin-top',
                'overflow',
                'padding',
                'padding-bottom',
                'padding-left',
                'padding-right',
                'padding-top',
                'text-decoration',
                'text-indent',
                'vertical-align',
                'width'
            );

            foreach ($styles_wf as $style_wf) {
                if (($key = array_search($style_wf, $styles)) !== false) {
                    unset($styles[$key]);
                }
            }
            return $styles;
        });
    }

    /**
     * Use when uninstalling (deleting) the plugin to clean up.
     *
     * @return bool
     */
    static function uninstall_plugin()
    {
        $tmp = delete_option('wf_licensing_wpr');

        return $tmp;
    } // uninstall_plugin

    /**
     * Disabled; we use singleton pattern so magic functions need to be disabled
     *
     * @return null
     */
    public function __clone() {}


    /**
     * Disabled; we use singleton pattern so magic functions need to be disabled
     *
     * @return null
     */
    public function __sleep() {}


    /**
     * Disabled; we use singleton pattern so magic functions need to be disabled
     *
     * @return null
     */
    public function __wakeup() {}
} // WP_Reset class


// Create plugin instance and hook things up
global $wp_reset;
$wp_reset = WP_Reset::getInstance();
add_action('plugins_loaded', array($wp_reset, 'load_textdomain'));
add_action('plugins_loaded', array($wp_reset, 'register_autosnapshot_hooks'));
register_uninstall_hook(__FILE__, array('WP_Reset', 'uninstall_plugin'));
add_filter('upgrader_pre_install', array($wp_reset, 'autosnapshot_update'), 1, 2);
