<?php

namespace DeliciousBrains\WPMDB\Pro\MST;

use DeliciousBrains\WPMDB\Common\Filesystem\Filesystem;
use DeliciousBrains\WPMDB\Common\FormData\FormData;
use DeliciousBrains\WPMDB\Common\MigrationPersistence\Persistence;
use DeliciousBrains\WPMDB\Common\MigrationState\MigrationStateManager;
use DeliciousBrains\WPMDB\Common\Multisite\Multisite;
use DeliciousBrains\WPMDB\Common\Profile\ProfileManager;
use DeliciousBrains\WPMDB\Common\Properties\DynamicProperties;
use DeliciousBrains\WPMDB\Common\Properties\Properties;
use DeliciousBrains\WPMDB\Common\Sql\Table;
use DeliciousBrains\WPMDB\Common\Sql\TableHelper;
use DeliciousBrains\WPMDB\Common\Util\Util;
use DeliciousBrains\WPMDB\Common\Addon\Addon;
use DeliciousBrains\WPMDB\Common\Addon\AddonAbstract;
use DeliciousBrains\WPMDB\Pro\UI\Template;
use DeliciousBrains\WPMDB\WPMDBDI;

class MultisiteToolsAddon extends AddonAbstract
{

    protected $accepted_fields;
    /**
     * @var Multisite
     */
    protected $multisite;
    /**
     * @var Util
     */
    protected $util;
    /**
     * @var MigrationStateManager
     */
    protected $migration_state_manager;
    /**
     * @var Table
     */
    protected $table;
    /**
     * @var TableHelper
     */
    protected $table_helper;
    /**
     * @var array
     */
    protected $form_data;

    public $state_data;
    /**
     * @var Template
     */
    protected $template;
    protected $plugin_dir_path;
    protected $plugin_folder_name;
    protected $template_path;
    /**
     * @var ProfileManager
     */
    protected $profile_manager;
    /**
     * @var FormData
     */
    private $form_data_class;

    protected $blog_id;
    private $container;
    /**
     * @var Filesystem
     */
    private $filesystem;
    /**
     * @var MediaFilesCompat
     */
    private $media_files_compat;

    public $selected_subsite_id;

    public function __construct(
        Addon $addon,
        Properties $properties,
        Multisite $multisite,
        Util $util,
        MigrationStateManager $migration_state_manager,
        Table $table,
        TableHelper $table_helper,
        FormData $form_data_class,
        Template $template,
        ProfileManager $profile_manager,
        DynamicProperties $dynamic_properties,
        Filesystem $filesystem,
        MediaFilesCompat $media_files_compat
    ) {
        parent::__construct($addon, $properties);

        $this->plugin_slug        = $properties->plugin_slug;
        $this->plugin_version     = $properties->plugin_version;
        $this->plugin_dir_path    = __DIR__;
        $this->plugin_folder_name = $properties->plugin_folder_name;
        $this->template_path = $this->plugin_dir_path . 'template/';

        $this->accepted_fields = array(
            'multisite_subsite_export', // TODO: Remove backwards compatibility for CLI once Core/MST/CLI dependencies updated.
            'select_subsite', // TODO: Remove backwards compatibility for CLI once Core/MST/CLI dependencies updated.
            'mst_select_subsite',
            'mst_selected_subsite',
            'mst_destination_subsite',
            'mst_subsite_to_subsite',
            'multisite_tools',
            'new_prefix',
            'keep_active_plugins',
        );

        $this->multisite               = $multisite;
        $this->util                    = $util;
        $this->migration_state_manager = $migration_state_manager;
        $this->table                   = $table;
        $this->table_helper            = $table_helper;
        $this->form_data_class         = $form_data_class;
        $this->template                = $template;
        $this->profile_manager         = $profile_manager;
        $this->dynamic_properties      = $dynamic_properties;
        $this->filesystem              = $filesystem;
        $this->media_files_compat      = $media_files_compat;
        $this->selected_subsite_id     = null;
    }

    public function register()
    {

        $this->addon_name = $this->addon->get_plugin_name('wp-migrate-db-pro-multisite-tools/wp-migrate-db-pro-multisite-tools.php');

        add_action('wpmdb_load_assets', array($this, 'load_assets'));
        add_filter('wpmdb_accepted_profile_fields', array($this, 'accepted_profile_fields'));
        add_filter('wpmdb_establish_remote_connection_data', array($this, 'establish_remote_connection_data'));
        add_filter('wpmdb_data', array($this, 'js_variables'));
        add_filter('wpmdb_initiate_key_rules', array($this, 'filter_key_rules'), 10, 2);
        add_filter('wpmdb_verify_connection_key_rules', array($this, 'filter_key_rules'), 10, 2);
        add_filter('wpmdb_respond_to_verify_connection_key_rules', array($this, 'filter_key_rules'), 10, 2);
        add_filter('wpmdb_initiate_push_pull_post', array($this, 'filter_initiate_post_data'), 10, 2);

        add_filter('wpmdb_diagnostic_info', array($this, 'diagnostic_info'));
        add_filter('wpmdb_tables', array($this, 'filter_tables_for_subsite'), 10, 2);
        add_filter('wpmdb_table_sizes', array($this, 'filter_table_sizes_for_subsite'), 10, 2);
        add_filter('wpmdb_target_table_name', array($this, 'filter_target_table_name'), 10, 4);
        add_filter('wpmdb_table_row', array($this, 'filter_table_row'), 10, 5);
        add_filter('wpmdb_find_and_replace', array($this, 'filter_find_and_replace'), 10, 3);
        add_filter('wpmdb_finalize_target_table_name', array($this, 'filter_finalize_target_table_name'), 10, 2);
        add_filter('wpmdb_preserved_options', array($this, 'filter_preserved_options'), 10, 2);
        add_filter('wpmdb_preserved_options_data', array($this, 'filter_preserved_options_data'), 10, 2);
        add_filter('wpmdb_get_alter_queries', array($this, 'filter_get_alter_queries'), 10, 2);
        add_filter('wpmdb_replace_site_urls', array($this, 'filter_replace_site_urls'));
        add_filter('wpmdb_backup_header_url', array($this, 'filter_backup_header_url'));
        add_filter('wpmdb_backup_header_included_tables', array($this, 'filter_backup_header_tables'));
        add_filter('wpmdb_backup_header_is_subsite_export', array($this, 'filter_backup_header_is_subsite_export'));

        /**
         * Media Files Hooks
         */
        add_filter('wpmdb_mf_local_uploads_folder', [$this->media_files_compat, 'filter_uploads_path_local'], 10, 2);
        add_filter('wpmdb_mf_remote_uploads_source_folder', [$this->media_files_compat, 'filter_uploads_path_remote'], 10, 2);
        add_filter('wpmdb_mf_remote_uploads_folder', [$this->media_files_compat, 'filter_uploads_path_remote'], 10, 2);
        add_filter('wpmdb_mf_destination_file', [$this->media_files_compat, 'filter_media_destination'], 10, 2);
        add_filter('wpmdb_mf_destination_uploads', [$this->media_files_compat, 'filter_media_uploads'], 10, 2);
        add_filter('wpmdb_mf_media_upload_path', [$this->media_files_compat, 'filter_media_uploads'], 10, 2);
        add_filter('wpmdb_mf_excludes', [$this->media_files_compat, 'filter_media_excludes'], 10, 2);
        add_filter('wpmdb_export_relative_path', [$this->media_files_compat, 'filter_media_export_destination'], 10, 2);

        $this->container = WPMDBDI::getInstance();
    }

    /**
     * Allow MST params to be passed to certain ajax endpoints.
     *
     * @param array  $rules
     * @param string $context
     *
     * @return mixed
     */
    public function filter_key_rules($rules, $context)
    {
        switch ($context) {
            case 'ajax_verify_connection_to_remote_site':
            case 'wpmdb_verify_connection_key_rules':
            case 'ajax_initiate_migration':
            case 'wpmdb_remote_initiate_migration':
            case 'respond_to_verify_connection_to_remote_site':
            case 'respond_to_remote_initiate_migration':
                $rules['mst_select_subsite']      = 'string';
                $rules['mst_selected_subsite']    = 'int';
                $rules['mst_destination_subsite'] = 'int';
                $rules['new_prefix']              = 'string';
        }

        return $rules;
    }

    public function filter_initiate_post_data($postData, $state_data)
    {
        if (empty($postData) || $postData['action'] !== 'wpmdb_remote_initiate_migration') {
            return $postData;
        }

        if (isset($state_data['mst_select_subsite'])) {
            $postData['mst_select_subsite'] = (string)$state_data['mst_select_subsite'];
        }

        if (isset($state_data['mst_selected_subsite'])) {
            $postData['mst_selected_subsite'] = $state_data['mst_selected_subsite'];
        }

        if (isset($state_data['mst_destination_subsite'])) {
            $postData['mst_destination_subsite'] = $state_data['mst_destination_subsite'];
        }

        if (isset($state_data['new_prefix'])) {
            $postData['new_prefix'] = $state_data['new_prefix'];
        }

        return $postData;
    }

    /**
     * Does the given user need to be migrated?
     *
     * @param int $user_id
     * @param int $blog_id Optional.
     *
     * @return bool
     */
    protected function is_user_required_for_blog($user_id, $blog_id = 0)
    {
        static $users = array();

        if (empty($user_id)) {
            $user_id = 0;
        }

        if (empty($blog_id)) {
            $blog_id = 0;
        }

        if (isset($users[$blog_id][$user_id])) {
            return $users[$blog_id][$user_id];
        }

        if (!is_multisite()) {
            $users[$blog_id][$user_id] = true;

            return $users[$blog_id][$user_id];
        }

        $subsites = $this->util->subsites_list();

        if (empty($subsites) || !array_key_exists($blog_id, $subsites)) {
            $users[$blog_id][$user_id] = false;

            return $users[$blog_id][$user_id];
        }

        if (is_user_member_of_blog($user_id, $blog_id)) {
            $users[$blog_id][$user_id] = true;

            return $users[$blog_id][$user_id];
        }

        // If the user has any posts that are going to be migrated, we need the user regardless of whether they still have access.
        switch_to_blog($blog_id);
        $user_posts = count_user_posts($user_id);
        restore_current_blog();

        if (0 < $user_posts) {
            $users[$blog_id][$user_id] = true;

            return $users[$blog_id][$user_id];
        }

        // If here, user not required.
        $users[$blog_id][$user_id] = false;

        return $users[$blog_id][$user_id];
    }

    /**
     * @param array|false $state_data
     *
     * @return int Will return 0 if not doing MST migration.
     *
     * Will return 0 if not doing MST migration.
     * @return int
     */
    public function selected_subsite($state_data = false)
    {
        $blog_id = 0;

        if ($this->selected_subsite_id) {
            return $this->selected_subsite_id;
        }

        if (!$state_data) {
            $state_data = filter_var_array($_POST, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        }

        if (empty($state_data)) {
            return 0;
        }

        $select_subsite   = isset($state_data['mst_select_subsite']) ? $state_data['mst_select_subsite'] : false;
        $selected_subsite = isset($state_data['mst_selected_subsite']) ? (int)$state_data['mst_selected_subsite'] : false;

        // During a migration, this is where the subsite's id will be derived.
        if (empty($blog_id) &&
            !empty($select_subsite) &&
            !empty($selected_subsite) &&
            is_numeric($selected_subsite)
        ) {
            $blog_id = $selected_subsite;
        }

        // When loading a saved migration profile, this is where the subsite's id will be derived.
        // @TODO refactor this, we're not using $loaded_profile any more
        global $loaded_profile;

        if (empty($blog_id) &&
            !empty($loaded_profile['mst_select_subsite']) &&
            !empty($loaded_profile['mst_selected_subsite']) &&
            is_numeric($loaded_profile['mst_selected_subsite'])
        ) {
            $blog_id = $loaded_profile['mst_selected_subsite'];
        }

        // Early in a push or pull migration selected subsite might just be injected in ajax params.
        if (empty($blog_id) &&
            !empty($this->state_data['mst_select_subsite']) &&
            !empty($this->state_data['mst_selected_subsite'])
        ) {
            $blog_id = $this->multisite->get_subsite_id($this->state_data['mst_selected_subsite']);
        }

        // If on multisite we can check that selected blog exists as all scenarios would require it.
        if (1 < $blog_id && is_multisite() && !$this->subsite_exists($blog_id)) {
            $blog_id = 0;
        }

        if ($blog_id === 0) {
            return 0;
        }

        $this->selected_subsite_id = $blog_id;

        return $blog_id;
    }

    /**
     * Whitelist multisite tools setting fields for use in AJAX save in core
     *
     * @param array $profile_fields
     *
     * @return array
     */
    public function accepted_profile_fields($profile_fields)
    {
        return array_merge($profile_fields, $this->accepted_fields);
    }

    /**
     * Check the remote site has the multisite tools addon setup
     *
     * @param array $data Connection data
     *
     * @return array Updated connection data
     */
    public function establish_remote_connection_data($data)
    {
        $data['mst_available'] = '1';
        $data['mst_version']   = $this->plugin_version;

        return $data;
    }

    /**
     * Add multisite tools related javascript variables to the page
     *
     * @param array $data
     *
     * @return array
     */
    public function js_variables($data)
    {
        global $loaded_profile;

        $data['mst_version']     = $this->plugin_version;
        $data['mst_is_licensed'] = $this->licensed ? '1' : '0';

        // Track originally selected subsite.
        if (empty($loaded_profile) && !empty($data['profile']) && is_numeric($data['profile'])) {
            $loaded_profile = $this->profile_manager->get_profile($data['profile']);
        }

        if (!empty($loaded_profile['mst_select_subsite']) &&
            !empty($loaded_profile['mst_selected_subsite']) &&
            is_numeric($loaded_profile['mst_selected_subsite'])
        ) {
            $data['mst_selected_subsite'] = (int)$loaded_profile['mst_selected_subsite'];
        }

        return $data;
    }

    /**
     * Get translated strings for javascript and other functions.
     *
     * @return array
     */
    public function get_strings()
    {
        static $strings;

        if (!empty($strings)) {
            return $strings;
        }

        $strings = array(
            'new_prefix_contents' => __('Please only enter letters, numbers or underscores for the new table prefix.', 'wp-migrate-db'),
        );

        return $strings;
    }

    /**
     * Retrieve a specific translated string.
     *
     * @param string $key
     *
     * @return string
     */
    public function get_string($key)
    {
        $strings = $this->get_strings();

        return (isset($strings[$key])) ? $strings[$key] : '';
    }

    /**
     * Load multisite tools related assets in core plugin.
     */
    public function load_assets()
    {
        $plugins_url = trailingslashit(plugins_url()) . trailingslashit($this->plugin_folder_name);

        $version     = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? time() : $this->plugin_version;


        $src = $plugins_url . "frontend/public/noop.js";
        wp_enqueue_script(
            'wp-migrate-db-pro-multisite-tools-script',
            $src,
            array(
                'jquery',
                'wp-migrate-db-pro-script-v2',
            ),
            $version,
            true
        );

        wp_localize_script('wp-migrate-db-pro-multisite-tools-script', 'wpmdbmst_strings', $this->get_strings());
        wp_localize_script(
            'wp-migrate-db-pro-multisite-tools-script',
            'wpmdbmst',
            [
                'enabled' => true,
            ]
        );
    }

    /**
     * Adds extra information to the core plugin's diagnostic info
     */
    public function diagnostic_info($diagnostic_info)
    {
        if (is_multisite()) {
            $diagnostic_info['multisite-tools'] = array('Sites' => number_format(get_blog_count()));
        }

        return $diagnostic_info;
    }

    /**
     * Should the given table be excluded from a subsite migration.
     *
     * @param bool   $exclude     Filtered value passed through for non MS globals.
     * @param int    $blog_id     Subsite ID.
     * @param string $table_name  Table name to check.
     * @param string $base_prefix Optional, base prefix override, e.g. when checking remote's tables.
     *
     * @return bool
     */
    public function filter_table_for_subsite($exclude, $blog_id, $table_name, $base_prefix = '')
    {
        if (0 < $blog_id) {
            // wp_users and wp_usermeta are relevant to all sites, shortcut out.
            if ($this->table_helper->table_is('', $table_name, 'non_ms_global')) {
                return $exclude;
            }

            // Following tables are Multisite setup tables and can be excluded from migration.
            if ($this->table_helper->table_is('', $table_name, 'ms_global')) {
                return true;
            }

            global $wpdb;
            $prefix = empty($base_prefix) ? $wpdb->base_prefix : $base_prefix;

            if (1 == $blog_id) {
                $prefix_escaped = preg_quote($prefix);

                // Exclude tables from non-primary subsites.
                if (preg_match('/^' . $prefix_escaped . '([0-9]+)_/', $table_name)) {
                    $exclude = true;
                }
            } else {
                $prefix .= $blog_id . '_';
                if (0 !== stripos($table_name, $prefix)) {
                    $exclude = true;
                }
            }
        }

        return $exclude;
    }

    /**
     * Filter the given tables if doing a subsite migration.
     *
     * @param array  $tables
     * @param string $scope
     *
     * @return array
     */
    public function filter_tables_for_subsite($tables, $scope = 'regular')
    {
        if ( ! is_multisite() || empty($tables)) {
            return $tables;
        }

        // We will not alter backup or temp tables list.
        if (in_array($scope, array('backup', 'temp'))) {
            return $tables;
        }

        $blog_id = $this->selected_subsite();

        return $this->filter_tables_for_subsite_id($blog_id, $tables);
    }

    /**
     * If doing a subsite migration, reduces tables to those relevant for subsite.
     *
     * @param int    $blog_id     Subsite ID.
     * @param array  $tables      Tables to be checked whether belonging to subsite being migrated.
     * @param string $base_prefix Optional, base prefix override, e.g. when checking remote's tables.
     *
     * @return array
     */
    public function filter_tables_for_subsite_id($blog_id, $tables, $base_prefix = '')
    {
        if (0 < $blog_id) {
            $filtered_tables = array();

            foreach ($tables as $key => $value) {
                if (false === $this->filter_table_for_subsite(false, $blog_id, $value, $base_prefix)) {
                    $filtered_tables[$key] = $value;
                }
            }

            return $filtered_tables;
        }

        return $tables;
    }

    /**
     * Filter the given tables with sizes if doing a subsite migration.
     *
     * @param array  $table_sizes
     * @param string $scope
     *
     * @return array
     */
    public function filter_table_sizes_for_subsite($table_sizes, $scope = 'regular')
    {
        if (!is_multisite() || empty($table_sizes)) {
            return $table_sizes;
        }

        $tables = $this->filter_tables_for_subsite(array_keys($table_sizes), $scope);

        return array_intersect_key($table_sizes, array_flip($tables));
    }

    /**
     * Change the name of the given table if subsite selected and migration profile has new prefix.
     *
     * @param string $table_name
     * @param string $action
     * @param string $stage
     * @param array  $site_details
     *
     * @return string
     */
    public function filter_target_table_name($table_name, $state_data, $site_details = array(), $subsite_migration = false)
    {
        if( !$subsite_migration ){
            return $table_name;
        }
        $stage  = $state_data['stage'];
        $action = $state_data['intent'];

        $blog_id = $this->selected_subsite($state_data);

        $destination_subsite = isset($state_data['mst_destination_subsite']) ? $state_data['mst_destination_subsite'] : 0;
        if (0 === $destination_subsite && 1 > $blog_id || 'backup' === $stage) {
            return $table_name;
        }

        $new_prefix = isset($state_data['new_prefix']) ? $state_data['new_prefix'] : Persistence::getFromStateData('new_prefix');

        if (empty($new_prefix)) {
            return $table_name;
        }

        global $wpdb;
        $old_prefix = $wpdb->base_prefix;
        if (is_multisite() && 1 < $blog_id && !$this->table_helper->table_is('', $table_name, 'global', '', $blog_id)) {
            $old_prefix .= $blog_id . '_';
        }

        // We do not want to overwrite the global tables unless exporting or target is a single site install.
        if ('savefile' !== $action &&
            (
                ('pull' === $action && 'true' === $site_details['local']['is_multisite']) ||
                ('push' === $action && 'true' === $site_details['remote']['is_multisite'])
            ) &&
            $this->table_helper->table_is('', $table_name, 'global')
        ) {
            $new_prefix .= 'wpmdbglobal_';
        }

        if (0 === stripos($table_name, $old_prefix)) {
            $table_name = substr_replace($table_name, $new_prefix, 0, strlen($old_prefix));
        }

        return $table_name;
    }

    /**
     * Handler for the wpmdb_table_row filter.
     * The given $row can be modified, but if we return false the row will not be used.
     *
     * @param stdClass $row
     * @param string   $table_name
     * @param string   $action
     * @param string   $stage
     *
     * @return bool
     */
    public function filter_table_row($row, $table_name, $state_data)
    {
        $use       = true;
        $stage     = $state_data['stage'];
        $form_data = $state_data['form_data'];

        if ($this->blog_id) {
            $blog_id = $this->blog_id;
        } else {
            $blog_id = $this->selected_subsite($state_data);
        }

        if (1 > $blog_id || 'backup' == $stage) {
            return $use;
        }
        $new_prefix = $state_data['new_prefix'];

        if (empty($new_prefix)) {
            return $row;
        }

        global $wpdb;

        $old_prefix = $state_data['source_prefix'];
        if (is_multisite() && 1 < $blog_id) {
            $old_prefix .= $blog_id . '_';
        }

        if ($this->table_helper->table_is('options', $table_name)) {
            // Rename options records like wp_X_user_roles to wp_Y_user_roles otherwise no users can do anything in the migrated site.
            if (0 === stripos($row->option_name, $old_prefix)) {
                $row->option_name = substr_replace($row->option_name, $new_prefix, 0, strlen($old_prefix));
            }
        }
        if ($this->table_helper->table_is('usermeta', $table_name)) {
            if (!$this->is_user_required_for_blog($row->user_id, $blog_id)) {
                $use = false;
            } elseif (1 == $blog_id) {
                $prefix_escaped = preg_quote($state_data['source_prefix']);
                if (1 === preg_match('/^' . $prefix_escaped . '([0-9]+)_/', $row->meta_key, $matches)) {
                    // Remove non-primary subsite records from usermeta when migrating primary subsite.
                    $use = false;
                } elseif (0 === stripos($row->meta_key, $old_prefix)) {
                    // Rename prefixed keys.
                    $row->meta_key = substr_replace($row->meta_key, $new_prefix, 0, strlen($old_prefix));
                }
            } else {
                if (0 === stripos($row->meta_key, $old_prefix)) {
                    // Rename prefixed keys.
                    $row->meta_key = substr_replace($row->meta_key, $new_prefix, 0, strlen($old_prefix));
                } elseif (0 === stripos($row->meta_key, $state_data['source_prefix'])) {
                    // Remove wp_* records from usermeta not for extracted subsite.
                    $use = false;
                }
            }
        }

        if ($this->table_helper->table_is('users', $table_name)) {
            if (!$this->is_user_required_for_blog($row->ID, $blog_id)) {
                $use = false;
            }
        }

        return $use;
    }

    /**
     * Handler for the wpmdb_find_and_replace filter.
     *
     * @param array  $tmp_find_replace_pairs
     * @param string $intent
     * @param string $site_url
     *
     * @return array
     */
    public function filter_find_and_replace($tmp_find_replace_pairs, $intent, $site_url)
    {
        $blog_id = $this->selected_subsite();

        $this->state_data = $this->migration_state_manager->set_post_data();

        if (1 > $blog_id) {
            return $tmp_find_replace_pairs;
        }

        $source               = ('pull' === $intent) ? 'remote' : 'local';
        $target               = ('pull' === $intent) ? 'local' : 'remote';

        $is_source_multi      = 'true' === $this->state_data['site_details'][$source]['is_multisite'];
        $is_destination_multi = 'true' === $this->state_data['site_details'][$target]['is_multisite'];
        $destination_id       = $is_source_multi && $is_destination_multi && isset($this->state_data['mst_destination_subsite']) ? $this->state_data['mst_destination_subsite'] : $blog_id;
        if ('true' === $this->state_data['site_details'][$source]['is_multisite']) {
            $source_site_url        = $this->state_data['site_details'][$source]['subsites_info'][$blog_id]['site_url'];
            $source_uploads_baseurl = $this->state_data['site_details'][$source]['subsites_info'][$blog_id]['uploads']['baseurl'];
            $source_short_basedir   = $this->state_data['site_details'][$source]['subsites_info'][$blog_id]['uploads']['short_basedir'];
        } else {
            $source_site_url        = $this->state_data['site_details'][$source]['site_url'];
            $source_uploads_baseurl = $this->state_data['site_details'][$source]['uploads']['baseurl'];
            $source_short_basedir   = '';
        }
        $source_site_url        = '//' . untrailingslashit($this->util->scheme_less_url($source_site_url));
        $source_uploads_baseurl = '//' . untrailingslashit($this->util->scheme_less_url($source_uploads_baseurl));

        if (in_array($intent, array('savefile', 'find_replace'))) {
            $target_site_url        = '';
            $target_uploads_baseurl = '';
            $target_short_basedir   = '';

            foreach ($tmp_find_replace_pairs as $find => $replace) {
                if ($find == $source_site_url) {
                    $target_site_url = $replace;
                    break;
                }
            }

            // Append extra path elements from uploads url, removing unneeded subsite specific elements.
            if (!empty($target_site_url)) {
                $target_uploads_baseurl = $target_site_url . substr($source_uploads_baseurl, strlen($source_site_url));

                if (!empty($source_short_basedir) && 'savefile' === $intent) {
                    $target_uploads_baseurl = substr(untrailingslashit($target_uploads_baseurl), 0, -strlen(untrailingslashit($source_short_basedir)));
                }
            }
        } elseif ('true' === $this->state_data['site_details'][$target]['is_multisite']) {
            $target_site_url        = $this->state_data['site_details'][$target]['subsites_info'][$destination_id]['site_url'];
            $target_uploads_baseurl = $this->state_data['site_details'][$target]['subsites_info'][$destination_id]['uploads']['baseurl'];
            $target_short_basedir   = $this->state_data['site_details'][$target]['subsites_info'][$destination_id]['uploads']['short_basedir'];
        } else {
            $target_site_url        = $this->state_data['site_details'][$target]['site_url'];
            $target_uploads_baseurl = $this->state_data['site_details'][$target]['uploads']['baseurl'];
            $target_short_basedir   = '';
        }

        // If we have a target uploads url, we can add in the find/replace we need.
        if (!empty($target_uploads_baseurl)) {
            $target_site_url        = '//' . untrailingslashit($this->util->scheme_less_url($target_site_url));
            $target_uploads_baseurl = '//' . untrailingslashit($this->util->scheme_less_url($target_uploads_baseurl));

            $target_site_url        = apply_filters('wpmdb_mst_target_site_url', $target_site_url);
            $target_uploads_baseurl = apply_filters('wpmdb_mst_target_uploads_baseurl', $target_uploads_baseurl);

            // As we're appending to the find/replace rows, we need to use the already replaced values for altering uploads url.
            $old_uploads_url                          = substr_replace($source_uploads_baseurl, $target_site_url, 0, strlen($source_site_url));
            $tmp_find_replace_pairs[$old_uploads_url] = $target_uploads_baseurl;
        }

        return $tmp_find_replace_pairs;
    }

    /**
     * Change the name of the given table depending on migration profile settings and source and target site setup.
     *
     * @param string $table_name
     * @param string $intent
     * @param array  $site_details
     *
     * @return string
     *
     * This is run in response to the wpmdb_finalize_target_table_name filter on the target site.
     */
    public function filter_finalize_target_table_name($table_name, $state_data)
    {
        $intent       = $state_data['intent'];
        $site_details = $state_data['site_details'];
        if ('find_replace' === $intent) {
            return $table_name;
        }

        $blog_id = isset($state_data['mst_selected_subsite']) ? $state_data['mst_selected_subsite'] : 0;

        if (1 > $blog_id) {
            return $table_name;
        }

        $new_prefix = $state_data['new_prefix'];
        if (empty($new_prefix)) {
            return $table_name;
        }

        // During a MST migration we add a custom prefix to the global tables so that we can manipulate their data before use.
        $type       = isset($state_data['type']) ? $state_data['type'] : $intent;
        $old_prefix = ('push' === $type) ? $state_data['site_details']['local']['prefix'] : $state_data['site_details']['remote']['prefix'];
        if (is_multisite() && $this->table_helper->table_is('', $table_name, 'global', $new_prefix, $blog_id, $old_prefix)) {
            $new_prefix .= 'wpmdbglobal_';
        }
        $destination_subsite = isset($state_data['mst_destination_subsite']) ? $state_data['mst_destination_subsite'] : 0;
        if ((0 < $destination_subsite || !is_multisite()) && 1 < $blog_id && !$this->table_helper->table_is('', $table_name, 'global', $new_prefix, $blog_id, $old_prefix)) {
            $old_prefix .= $blog_id . '_';
        }

        if (0 === stripos($table_name, $old_prefix)) {
            $table_name = substr_replace($table_name, $new_prefix, 0, strlen($old_prefix));
        }

        return $table_name;
    }

    /**
     * Returns validated and sanitized form data.
     *
     * @param array|string $data
     *
     * @return array|string
     */
    public function parse_migration_form_data($data)
    {
        // ***+=== @TODO - revisit usage of parse_migration_form_data
        $form_data = $this->form_data_class->parse_and_save_migration_form_data($data);

        $form_data = array_intersect_key($form_data, array_flip($this->accepted_fields));

        return $form_data;
    }

    /**
     * Alter file path pulled from remote to local equivalent.
     *
     * @param string  $file
     * @param integer $blog_id
     *
     * @return string TODO: Update for multisite <=> multisite (blog_ids)
     *
     * @throws Exception
     *
     * TODO: Update for multisite <=> multisite (blog_ids)
     */
    protected function alter_pulled_file_path($file, $blog_id)
    {
        if (1 > $blog_id) {
            return $file;
        }

        if ($this->state_data['site_details']['local']['is_multisite'] !== $this->state_data['site_details']['remote']['is_multisite']) {
            if (is_multisite()) {
                if (isset($this->state_data['site_details']['local']['subsites_info'][$blog_id]['uploads']['short_basedir'])) {
                    $file = ltrim(trailingslashit($this->state_data['site_details']['local']['subsites_info'][$blog_id]['uploads']['short_basedir']) . $file, '/');
                } else {
                    throw new \Exception(__('Expected local subsite "short_basedir" missing from `state_data`.', 'wp-migrate-db'));
                }
            } else {
                if (isset($this->state_data['site_details']['remote']['subsites_info'][$blog_id]['uploads']['short_basedir'])) {
                    $file = substr($file, strlen($this->state_data['site_details']['remote']['subsites_info'][$blog_id]['uploads']['short_basedir']));
                } else {
                    throw new \Exception(__('Expected remote subsite "short_basedir" missing from `state_data`.', 'wp-migrate-db'));
                }
            }
        }

        return $file;
    }

    /**
     * Maybe change options keys to be preserved.
     *
     * @param array  $preserved_options
     * @param string $intent
     *
     * @return array
     */
    public function filter_preserved_options($preserved_options, $intent = '')
    {
        $state_data = $intent === 'push' ? Persistence::getRemoteStateData() : Persistence::getStateData();
        $blog_id    = $this->selected_subsite($state_data);

        if (0 < $blog_id && in_array($intent, array('push', 'pull'))) {
            $preserved_options = $this->table->preserve_active_plugins_option($preserved_options);
        }

        return $preserved_options;
    }

    /**
     * Maybe preserve the WPMDB plugins if they aren't already preserved.
     *
     * @param array  $preserved_options_data
     * @param string $intent
     *
     * @return array
     */
    public function filter_preserved_options_data($preserved_options_data, $intent = '')
    {
        $state_data = $intent === 'push' ? Persistence::getRemoteStateData() : Persistence::getStateData();
        $blog_id    = $this->selected_subsite($state_data);

        if (0 < $blog_id && in_array($intent, array('push', 'pull'))) {
            $preserved_options_data = $this->table->preserve_wpmdb_plugins($preserved_options_data);
        }

        return $preserved_options_data;
    }

    /**
     * Append more queries to be run at finalize_migration.
     *
     * @param array $queries
     *
     * @return array
     */
    public function filter_get_alter_queries($queries, $state_data)
    {
        if(empty($state_data)) {
            return $queries;
        }

        $blog_id = isset($state_data['mst_destination_subsite']) ? $state_data['mst_destination_subsite'] : $this->selected_subsite($state_data);

        if (1 > $blog_id) {
            return $queries;
        }

        if (!is_multisite() || 'pull' !== $state_data['intent'] || empty($state_data['tables'])) {
            return $queries;
        }

        global $wpdb;

        $tables = explode(',', $state_data['tables']);

        $target_users_table    = null;
        $source_users_table    = null;
        $target_usermeta_table = null;
        $source_usermeta_table = null;
        $posts_imported        = false;
        $target_posts_table    = null;
        $target_postmeta_table = null;
        $comments_imported     = false;
        $target_comments_table = null;
        $intent = isset($state_data['type']) ? $state_data['type'] : $state_data['intent'];
        $source_prefix = ('pull' === $intent) ? $state_data['site_details']['remote']['prefix'] : $state_data['site_details']['local']['prefix'];
        foreach ($tables as $table) {
            if (empty($source_users_table) && $this->table_helper->table_is('users', $table, 'table', $source_prefix, 0, $source_prefix)) {
                $target_users_table = Util::prefix_updater($table, $state_data['source_prefix'], $state_data['destination_prefix']);
                $source_users_table = $this->filter_finalize_target_table_name($table, $state_data);
                continue;
            }
            if (empty($source_usermeta_table) && $this->table_helper->table_is('usermeta', $table, 'table', $source_prefix, 0, $source_prefix)) {
                $target_usermeta_table = Util::prefix_updater($table, $state_data['source_prefix'], $state_data['destination_prefix']);
                $source_usermeta_table = $this->filter_finalize_target_table_name($table, $state_data);
                continue;
            }
            if (!$posts_imported && $this->table_helper->table_is('posts', $table, 'table', $source_prefix)) {
                $posts_imported     = true;
                $target_posts_table = $this->filter_finalize_target_table_name($table, $state_data);
                continue;
            }
            if ($this->table_helper->table_is('postmeta', $table, 'table', $source_prefix)) {
                $target_postmeta_table = $this->filter_finalize_target_table_name($table, $state_data);
                continue;
            }
            if (!$comments_imported && $this->table_helper->table_is('comments', $table, 'table', $source_prefix)) {
                $comments_imported     = true;
                $target_comments_table = $this->filter_finalize_target_table_name($table, $state_data);
                continue;
            }
        }

        // Find users that already exist and update their content to adopt existing user id and remove from import.
        if (!empty($source_users_table)) {
            $updated_user_ids           = array();
            $temp_prefix                = $state_data['temp_prefix'];
            $temp_source_users_table    = $temp_prefix . $source_users_table;
            $temp_source_usermeta_table = $temp_prefix . $source_usermeta_table;

            $sql = "
					SELECT source.id AS source_id, target.id AS target_id FROM `{$temp_source_users_table}` AS source, `{$target_users_table}` AS target
					WHERE target.user_login = source.user_login
					AND target.user_email = source.user_email
				";

            $user_ids_to_update = $wpdb->get_results($sql, ARRAY_A);

            //If users match from both sites
            if (!empty($user_ids_to_update)) {
                foreach ($user_ids_to_update as $user_ids) {
                    $blogs_of_user = get_blogs_of_user($user_ids['target_id']);

                    // Log user for exclusion from import.
                    $updated_user_ids[] = $user_ids['source_id'];

                    //Add new blog capabilities to imported users
                    if (null !== $source_usermeta_table && !array_key_exists($blog_id, $blogs_of_user)) {
                        $queries = $this->update_usermeta_for_imported_users($queries, $user_ids, $temp_source_usermeta_table, $target_usermeta_table, $blog_id);
                    }

                    if (empty($blogs_of_user) || array_key_exists($blog_id, $blogs_of_user)) {
                        // Only update content ownership if user id has changed.
                        if ($user_ids['source_id'] !== $user_ids['target_id']) {
                            if ($posts_imported) {
                                $queries[]['query'] = "
									UPDATE `{$target_posts_table}`
									SET post_author = {$user_ids['target_id']}
									WHERE post_author = {$user_ids['source_id']}
									;\n
								";
                            }

                            if ($comments_imported) {
                                $queries[]['query'] = "
									UPDATE `{$target_comments_table}`
									SET user_id = {$user_ids['target_id']}
									WHERE user_id = {$user_ids['source_id']}
									;\n
								";
                            }
                        }
                    }
                }
            }

            $queries[]['query'] = "ALTER TABLE `{$target_users_table}` ADD COLUMN wpmdb_user_id BIGINT(20) UNSIGNED;\n";

            $where = '';
            if (!empty($updated_user_ids)) {
                $where = 'WHERE u2.id NOT IN (' . implode(',', $updated_user_ids) . ')';
            }
            $queries[]['query'] = "INSERT INTO `{$target_users_table}` (user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name, wpmdb_user_id)
					SELECT u2.user_login, u2.user_pass, u2.user_nicename, u2.user_email, u2.user_url, u2.user_registered, u2.user_activation_key, u2.user_status, u2.display_name, u2.id
					FROM `{$source_users_table}` AS u2
					{$where};\n";

            if (!empty($source_usermeta_table)) {
                $queries[]['query'] = "INSERT INTO `{$target_usermeta_table}` (user_id, meta_key, meta_value)
						SELECT u.id, m2.meta_key, m2.meta_value
						FROM `{$source_usermeta_table}` AS m2
						JOIN `{$target_users_table}` AS u ON m2.user_id = u.wpmdb_user_id;\n";
            }

            if ($posts_imported) {
                $queries[]['query'] = "
						UPDATE `{$target_posts_table}` AS p, `{$target_users_table}` AS u
						SET p.post_author = u.id
						WHERE p.post_author = u.wpmdb_user_id
						;\n";
            }

            if (!is_null($target_postmeta_table) && !empty($user_ids)) {
                $queries[]['query'] = "
										UPDATE `{$target_postmeta_table}`
										SET {$target_postmeta_table}.meta_value = {$user_ids['target_id']}
										WHERE {$target_postmeta_table}.meta_key = '_edit_last'
										AND {$target_postmeta_table}.meta_value = {$user_ids['source_id']};
										";
            }

            if ($comments_imported) {
                $queries[]['query'] = "
						UPDATE `{$target_comments_table}` AS c, `{$target_users_table}` AS u
						SET c.user_id = u.id
						WHERE c.user_id = u.wpmdb_user_id
						;\n";
            }
            $queries[]['query'] = "DROP TABLE `{$source_users_table}`;\n";

            $queries[]['query'] = "ALTER TABLE `{$target_users_table}` DROP COLUMN wpmdb_user_id;\n";
        }

        // Cleanup imported usermeta table, whether used by above user related queries or not.
        // TODO: Maybe support updating usermeta without imported users table?
        if (!empty($source_usermeta_table)) {
            $queries[]['query'] = "DROP TABLE `{$source_usermeta_table}`;\n";
        }

        return $queries;
    }


    /**
     * Filters the URLs used by WPMDB_Replace.
     *
     * @param array $site_urls
     *
     * @TODO add unit tests for this method
     * @return array
     */
    function filter_replace_site_urls($site_urls)
    {
        if (isset($this->form_data['mst_select_subsite']) && '1' === $this->form_data['mst_select_subsite']) {
            $selected_subsite_id = $this->form_data['mst_selected_subsite'];

            foreach (array('local', 'remote') as $which) {
                if (isset($this->state_data['site_details'][$which]['subsites_info'][$selected_subsite_id])) {
                    $subsite_base = $this->state_data['site_details'][$which]['subsites_info'][$selected_subsite_id];
                    $subsite_url  = $subsite_base['site_url'];

                    // Use home_url if it's set.
                    if (isset($subsite_base['home_url'])) {
                        $subsite_url = $subsite_base['home_url'];
                    }

                    $site_urls[$which] = $subsite_url;
                }
            }
        }

        return $site_urls;
    }

    /**
     * Updates the URL in the export header for MST exports
     *
     * @param string $url
     *
     * @return string
     */
    function filter_backup_header_url($url)
    {
        $selected_subsite = $this->selected_subsite();

        if (0 !== $selected_subsite &&
            'backup' !== $this->state_data['stage'] &&
            isset($this->state_data['site_details']['local']['subsites_info'][$selected_subsite])) {
            $subsite_base = $this->state_data['site_details']['local']['subsites_info'][$selected_subsite];
            $url          = $subsite_base['home_url'];
        }

        return $url;
    }

    /**
     * @param array $tables
     *
     * @return array
     */
    function filter_backup_header_tables($tables)
    {
        if (!is_multisite() || 'backup' === $this->state_data['stage']) {
            return $tables;
        }

        foreach ($tables as $key => $table) {
            $tables[$key] = $this->filter_target_table_name($table, Persistence::getStateData());
        }

        return $tables;
    }

    function filter_backup_header_is_subsite_export()
    {
        return 0 === $this->selected_subsite() ? 'false' : 'true';
    }

    /**
     * Does the passed subsite (ID) exist?
     *
     * @param int $blog_id
     *
     * @return bool
     */
    public function subsite_exists($blog_id)
    {
        if (!is_multisite()) {
            return false;
        }

        if (version_compare($GLOBALS['wp_version'], '4.6', '>=')) {
            $blogs = get_sites(array('number' => false));
        } else {
            $blogs = wp_get_sites(array('limit' => 0));
        }

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

        foreach ($blogs as $blog) {
            $blog = (array)$blog;
            if (!empty($blog['blog_id']) && $blog_id == $blog['blog_id']) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array  $queries
     * @param array  $user_ids
     * @param string $temp_source_usermeta_table
     * @param string $target_usermeta_table
     * @param int    $blog_id
     *
     * @return array
     */
    protected function update_usermeta_for_imported_users($queries, $user_ids, $temp_source_usermeta_table, $target_usermeta_table, $blog_id)
    {
        global $wpdb;
        $prefix = $wpdb->prefix;

        $blog_id_string = 1 === (int)$blog_id ? $prefix : "{$prefix}{$blog_id}_";

        $sql = "SELECT meta_value as value
			FROM `{$temp_source_usermeta_table}`
			WHERE ( meta_key = '{$blog_id_string}user_level' OR meta_key = '{$blog_id_string}capabilities' )
			AND user_id={$user_ids['source_id']}
			ORDER BY meta_key";

        $result = $wpdb->get_results($sql, OBJECT);

        if ($result) {
            //User level
            $queries[]['query'] = "
				INSERT INTO `{$target_usermeta_table}` (user_id, meta_key, meta_value)
				VALUES ({$user_ids['target_id']}, '{$blog_id_string}user_level', {$result[1]->value} );
				\n";

            //Capabilities
            $queries[]['query'] = "
				INSERT INTO `{$target_usermeta_table}`(user_id, meta_key, meta_value)
				VALUES({$user_ids['target_id']}, '{$blog_id_string}capabilities', '{$result[0]->value}')
				;\n";
        }

        return $queries;
    }
}
