<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


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

*/

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

/*
In the software we have cms_merge and we have Resource-fs.

Resource-fs is intended for staging site functionality and backups, mainly.
cms_merge is intended to merge disparate sites in a more complete way.

There is overlap, but intentionally each approach is optimised in a different way.
*/

/*
Our merge code works in one of two ways...
1) Read in of rows, remapping, insertion of rows in destination database
2) Read in of rows, remapping, call of APIs (e.g. add_download)

It's impossible to say which is best in a clear way. Sometimes APIs do extra stuff we want (or do not want), but row copying is simpler.
*/

/**
 * Hook class.
 */
class Hook_import_cms_merge
{
    /**
     * Standard importer hook info function.
     *
     * @return ?array Importer handling details, including lists of all the import types covered (import types are not necessarily the same as actual tables) (null: importer is disabled)
     */
    public function info() : ?array
    {
        require_lang('cns');

        $info = [];
        $info['supports_advanced_import'] = true;
        $info['product'] = do_lang('CMS_SITE_MERGER');
        $info['prefix'] = 'cms_';
        $info['import'] = [
            'attachments',
            'cns_groups',
            'cns_custom_profile_fields',
            'cns_members',
            'authors',
            'banners',
            'calendar',
            'catalogues', // including rating, trackbacks, seo
            'point_transactions', // including daily visits
            'chat_rooms',
            'config',
            'custom_comcode',
            'comcode_pages',
            'staff_checklist_cus_tasks',
            'notifications',
            'downloads_and_categories', // including rating, trackbacks, seo
            'cns_forum_groupings',
            'cns_emoticons',
            'cns_forums', // including intros
            'cns_topics', // including readlogs // including description remapping for comment topics
            'cns_multi_moderations',
            'cns_polls_and_votes',
            'cns_post_templates',
            'cns_posts',
            'cns_warnings',
            'cns_saved_warnings',
            'filedump',
            'images_and_galleries', // including rating, trackbacks, seo
            'match_key_messages',
            'menu_items',
            'news_and_categories', // including rating, trackbacks, seo
            'newsletter_subscriptions',
            'polls', // including rating, trackbacks, seo
            'redirects',
            'staff_links',
            'wiki', // including rating, trackbacks, seo
            'stats',
            'themes',
            'support_tickets',
            'useronline_tracking',
            'ip_bans',
            'wordfilter',
            'zones',
            'attachment_references',
            'ecommerce',
            'cns_welcome_emails',
            'quizzes',
            'awards',
            'feedback',
            'permissions', // including HTTPS
            'leader_boards',
            'site_messages',
        ];
        $info['dependencies'] = [
            // This dependency tree is overdefined, but I wanted to make it clear what depends on what, rather than having a simplified version
            'attachment_references' => ['attachments', 'cns_members', 'cns_posts', 'news_and_categories', 'wiki'],
            'permissions' => array_diff($info['import'], ['feedback', 'attachment_references', 'permissions', 'stats']),
            'feedback' => array_diff($info['import'], ['themes', 'cns_warnings', 'feedback', 'attachment_references', 'permissions', 'quizzes', 'stats']),
            'authors' => ['cns_members', 'catalogues'],
            'banners' => ['cns_members'],
            'catalogues' => ['cns_members'],
            'notifications' => ['cns_members'],
            'chat_rooms' => ['cns_members', 'cns_groups'],
            'downloads_and_categories' => ['cns_members', 'catalogues'],
            'filedump' => ['cns_members'],
            'images_and_galleries' => ['cns_members', 'catalogues'],
            'news_and_categories' => ['cns_members', 'attachments', 'catalogues'],
            'polls' => ['cns_members', 'catalogues'],
            'wiki' => ['cns_members', 'attachments', 'catalogues'],
            'useronline_tracking' => ['cns_members'],
            'ip_bans' => ['cns_members'],
            'point_transactions' => ['cns_members'],
            'calendar' => ['cns_members', 'catalogues'],
            'comcode_pages' => ['cns_members', 'catalogues'],
            'match_key_messages' => [],
            'menu_items' => [],
            'cns_custom_profile_fields' => ['cns_groups'],
            'cns_multi_moderations' => ['cns_forums'],
            // 'cns_groups' => ['catalogues'], Cyclic dependency (catalogues depends on cns_members which depends on cns_groups)
            'cns_members' => ['cns_groups', 'cns_custom_profile_fields', 'attachments'],
            'cns_forums' => ['cns_forum_groupings', 'cns_members', 'cns_groups', 'catalogues'],
            'cns_topics' => ['cns_forums', 'cns_members', 'catalogues'],
            'cns_polls_and_votes' => ['cns_topics', 'cns_posts', 'cns_members'],
            'cns_posts' => ['custom_comcode', 'cns_topics', 'cns_members', 'attachments', 'catalogues'],
            'cns_post_templates' => ['cns_forums', 'attachments'],
            'cns_warnings' => ['cns_members', 'cns_groups', 'cns_topics', 'cns_forums', 'cns_posts'],
            'newsletter_subscriptions' => ['attachments'],
            'support_tickets' => ['cns_forums', 'cns_topics', 'cns_members', 'attachments'],
            'awards' => ['calendar', 'wiki', 'news_and_categories', 'images_and_galleries', 'catalogues', 'authors', 'cns_topics', 'cns_posts', 'cns_forums', 'cns_groups', 'cns_members', 'downloads_and_categories'],
            'ecommerce' => ['cns_groups', 'cns_members'],
            'cns_welcome_emails' => ['cns_members', 'attachments'],
            'quizzes' => ['cns_members', 'catalogues', 'attachments'],
            'aggregate_type_instances' => [],
            'leader_boards' => ['cns_members', 'cns_groups'],
            'site_messages' => ['cns_groups', 'comcode_pages'],
            'stats' => ['cns_members']
        ];

        $_cleanup_url = build_url(['page' => 'admin_cleanup'], get_module_zone('admin_cleanup'));
        $cleanup_url = $_cleanup_url->evaluate();
        $info['final_message'] = do_lang_tempcode('FORUM_CACHE_CLEAR', escape_html($cleanup_url));

        $info['final_tasks'] = [
            ['cns_members_dedup', do_lang('DEDUP_MEMBERS'), 'f_members', 1000000], // Realistically, there would be very few e-mail address duplicates, and the hook uses intelligent SQL filtering so we aren't processing the full member list
            ['cns_topics_recache', do_lang('CACHE_TOPICS'), 'f_topics', 100],
            ['cns_recache', do_lang('CACHE_FORUMS'), 'f_topics', 100],
            ['cns_members_recache', do_lang('CACHE_MEMBERS'), 'f_members', 100],
        ];

        return $info;
    }

    /**
     * Do some tests, to make sure we're happy to continue importing.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     * @return ?Tempcode Error/warning UI (null: no error/warning)
     */
    public function pre_import_tests(object $db, string $table_prefix, string $file_base) : ?object
    {
        $title = get_screen_title('IMPORT');

        $bad = false;

        // Check actually is software DB (ERROR)
        $test = $db->query_select_value('zones', 'zone_name');
        if ($test === null) {
            return warn_screen($title, do_lang_tempcode('ERROR_NOT_CORRECT_DATABASE'));
        }

        // Check version (WARNING)
        $test = $db->query_select_value_if_there('values', 'the_value', ['the_name' => 'version']);
        if (($test === null) || (intval($test) != cms_version())) {
            attach_message(do_lang_tempcode('ERROR_NOT_CORRECT_VERSION'), 'notice');
            $bad = true;
        }

        // Check actually is software file path (ERROR)
        if (((!file_exists($file_base . '/_config.php')) && (!file_exists($file_base . '/info.php'))) || (!file_exists($file_base . '/sources_custom'))) {
            attach_message(do_lang_tempcode('ERROR_NOT_CORRECT_FILES'), 'warn');
            if ((is_on_multi_site_network()) && (!file_exists($file_base . '/_config.php')) && (!file_exists($file_base . '/info.php'))) {
                attach_message(do_lang_tempcode('ERROR_NOT_CORRECT_LINKING_POSSIBLY'), 'warn');
            }
            $bad = true;
        }

        // Check is on same MSN or is Conversr (WARNING)
        if (file_exists($file_base . '/_config.php')) {
            global $SITE_INFO;
            $backup_site_info = $SITE_INFO;
            $SITE_INFO = null;
            @include $file_base . '/_config.php';
            if ($SITE_INFO === null) {
                $SITE_INFO = $backup_site_info;
                attach_message(do_lang_tempcode('ERROR_INACCESSIBLE_DIR'), 'warn');
                if (is_on_multi_site_network()) {
                    attach_message(do_lang_tempcode('ERROR_NOT_CORRECT_LINKING_POSSIBLY'), 'warn');
                }
                $bad = true;
            } else {
                $this_site_info = $SITE_INFO;
                $SITE_INFO = $backup_site_info;
                if (empty($SITE_INFO['db_forums_host'])) {
                    $SITE_INFO['db_forums_host'] = 'localhost';
                }
                $same_forum = (!isset($SITE_INFO['db_forums'])) || ($this_site_info['db_forums'] == $SITE_INFO['db_forums']) && ($this_site_info['db_forums_host'] == $SITE_INFO['db_forums_host']) && ($db->get_table_prefix());
                if (($this_site_info['forum_type'] != 'cns') && (!$same_forum)) {
                    attach_message(do_lang_tempcode('ERROR_NOT_CORRECT_LINKING'), 'warn');
                    $bad = true;
                }
            }
        }

        // Show warning
        if ($bad) {
            return do_template('CONFIRM_SCREEN', [
                '_GUID' => '286928b79830cdff4ac506e4f4f00f3a',
                'TITLE' => $title,
                'PREVIEW' => do_lang_tempcode('IMPORT_WARNINGS_GIVEN'),
                'FIELDS' => build_keep_post_fields(),
                'URL' => get_self_url(false, false, ['happy' => 1]),
            ]);
        }

        return null;
    }

    /**
     * See if the importing site is on the same M.S.N. as the site being imported.
     *
     * @param  PATH $file_base The path to the imported site
     * @return boolean Answer
     */
    public function on_same_msn(string $file_base) : bool
    {
        if (get_param_integer('keep_on_same_msn', null) === 0) {
            return false;
        }
        if (get_param_integer('keep_on_same_msn', null) === 1) {
            return true;
        }

        //return false;
        global $SITE_INFO;
        $backup_site_info = $SITE_INFO;
        $SITE_INFO = null;
        @include $file_base . '/_config.php';
        $sites_site_info = $SITE_INFO;
        $SITE_INFO = $backup_site_info;

        $array_prefix = array_key_exists('db_forums', $sites_site_info) ? 'db_forums' : 'db_site';
        $answer = ($sites_site_info[$array_prefix] == get_db_forums()) && (@$sites_site_info[$array_prefix . '_host'] == get_db_forums_host()) && (@$sites_site_info['table_prefix'] === @$SITE_INFO['table_prefix']);

        return $answer;
    }

    /**
     * Fetch and clean up a content language string.
     *
     * @param  object $db The database connector to import from
     * @param  mixed $id The content language string ID
     * @return string The cleaned-up string
     */
    public function get_lang_string(object $db, $id) : string
    {
        if ($id === null) {
            return '';
        }

        global $HAS_MULTI_LANG_CONTENT;
        $bak = $HAS_MULTI_LANG_CONTENT;
        $HAS_MULTI_LANG_CONTENT = is_integer($id);
        $text = get_translated_text($id, $db);
        $HAS_MULTI_LANG_CONTENT = $bak;

        $text = $this->update_comcode($text);

        return $text;
    }

    /**
     * Import some Comcode, making changes as required.
     *
     * @param  string $comcode Input
     * @param  ?ID_TEXT $referer_id Attachment referer type (null: not setting one now)
     * @return string Output
     */
    public function update_comcode(string $comcode, ?string $referer_id = null) : string
    {
        $matches = [];
        $count = preg_match_all('#\](\d+)\[/attachment\]#', $comcode, $matches);
        if ($count != 0) {
            for ($i = 0; $i < $count; $i++) {
                $from = $matches[1][$i];
                $to = import_id_remap_get('attachment', $from, true);
                if ($to === null) {
                    $to = -1;
                }
                $comcode = str_replace(']' . $from . '[/attachment]', ']' . strval($to) . '[/attachment]', $comcode);

                if ($referer_id !== null) {
                    $GLOBALS['SITE_DB']->query_insert('attachment_refs', ['r_referer_type' => 'comcode_page', 'r_referer_id' => $referer_id, 'a_id' => $to], false, true);
                }
            }
        }

        return $comcode;
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_welcome_emails(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_welcome_emails', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('welcome_email', $row['w_name'])) {
                continue;
            }

            cns_make_welcome_email($row['w_name'], $this->get_lang_string($db, $row['w_subject']), $this->get_lang_string($db, $row['w_text']), $row['w_send_after_hours'], $row['w_newsletter_id'], $row['w_usergroup'], $row['w_usergroup_type']);

            import_id_remap_put('welcome_email', $row['w_name'], 0);
        }
        $this->_import_alternative_ids($db, 'welcome_email', 'welcome_email');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_quizzes(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('quizzes', ['*'], [], 'ORDER BY id', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('quiz', strval($row['id']))) {
                continue;
            }

            unset($row['q_start_text__text_parsed']);
            unset($row['q_start_text__source_user']);
            unset($row['q_end_text__text_parsed']);
            unset($row['q_end_text__source_user']);
            unset($row['q_end_text_fail__text_parsed']);
            unset($row['q_end_text_fail__source_user']);

            $start_text = $this->get_lang_string($db, $row['q_start_text']);
            $end_text = $this->get_lang_string($db, $row['q_end_text']);
            $end_text_fail = $this->get_lang_string($db, $row['q_end_text_fail']);

            $map = [
                'q_timeout' => $row['q_timeout'],
                'q_notes' => $row['q_notes'],
                'q_percentage' => $row['q_percentage'],
                'q_open_time' => $row['q_open_time'],
                'q_close_time' => $row['q_close_time'],
                'q_num_winners' => $row['q_num_winners'],
                'q_reattempt_hours' => $row['q_reattempt_hours'],
                'q_type' => $row['q_type'],
                'q_validated' => $row['q_validated'],
                'q_submitter' => $row['q_submitter'],
                'q_add_date' => $row['q_add_date'],
                'q_points_for_passing' => $row['q_points_for_passing'],
                'q_reveal_answers' => $row['q_reveal_answers'],
                'q_shuffle_questions' => $row['q_shuffle_questions'],
                'q_shuffle_answers' => $row['q_shuffle_answers'],
            ];
            $map += insert_lang('q_name', $this->get_lang_string($db, $row['q_name']), 2);
            $map += insert_lang_comcode('q_start_text', $start_text, 2);
            $map += insert_lang_comcode('q_end_text', $end_text, 2);
            $map += insert_lang_comcode('q_end_text_fail', $end_text_fail, 2);
            if (get_param_integer('keep_preserve_ids', 0) == 1) {
                $map['id'] = $row['id'];
            }
            $id_new = $GLOBALS['SITE_DB']->query_insert('quizzes', $map, true);

            import_id_remap_put('quiz', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'quiz', 'quiz');
        $this->_import_content_reviews($db, $table_prefix, 'quiz', 'quiz');

        $rows = $db->query_select('quiz_questions', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $i => $row) {
            if (import_check_if_imported('quiz_question', strval($row['id']))) {
                continue;
            }

            $quiz = import_id_remap_get('quiz', strval($row['q_quiz_id']), true);
            if ($quiz === null) {
                continue;
            }

            unset($row['q_question_text__text_parsed']);
            unset($row['q_question_text__source_user']);
            unset($row['q_question_extra_text__text_parsed']);
            unset($row['q_question_extra_text__source_user']);

            $map = [
                'q_order' => $row['q_order'],
                'q_type' => $row['q_type'],
                'q_quiz_id' => $quiz,
                'q_required' => $row['q_required'],
                'q_marked' => $row['q_marked'],
            ];
            $map += insert_lang_comcode('q_question_text', $this->get_lang_string($db, $row['q_question_text']), 2);
            $map += insert_lang_comcode('q_question_extra_text', $this->get_lang_string($db, $row['q_question_extra_text']), 2);
            $id_new = $GLOBALS['SITE_DB']->query_insert('quiz_questions', $map, true);

            import_id_remap_put('quiz_question', strval($row['id']), $id_new);
        }

        $rows = $db->query_select('quiz_question_answers', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $i => $row) {
            if (import_check_if_imported('quiz_question_answer', strval($row['id']))) {
                continue;
            }

            $question = import_id_remap_get('quiz_question', strval($row['q_question_id']), true);
            if ($question === null) {
                continue;
            }

            unset($row['q_answer_text__text_parsed']);
            unset($row['q_answer_text__source_user']);

            $map = [
                'q_order' => $row['q_order'],
                'q_question_id' => $question,
                'q_is_correct' => $row['q_is_correct'],
            ];
            $map += insert_lang_comcode('q_answer_text', $this->get_lang_string($db, $row['q_answer_text']), 2);
            $map += insert_lang('q_explanation', $this->get_lang_string($db, $row['q_explanation']), 2);
            $id_new = $GLOBALS['SITE_DB']->query_insert('quiz_question_answers', $map, true);

            import_id_remap_put('quiz_question_answer', strval($row['id']), $id_new);
        }

        $rows = $db->query_select('quiz_entries', ['*']);
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            if (import_check_if_imported('quiz_entry', strval($row['id']))) {
                continue;
            }

            $quiz = import_id_remap_get('quiz', strval($row['q_quiz_id']), true);
            if ($quiz === null) {
                continue;
            }
            $member_id = $on_same_msn ? $row['q_member'] : import_id_remap_get('member', strval($row['q_member']), true);
            if ($member_id === null) {
                $member_id = $GLOBALS['FORUM_DRIVER']->get_guest_id();
            }

            $id_new = $GLOBALS['SITE_DB']->query_insert('quiz_entries', [
                'q_time' => $row['q_time'],
                'q_member' => $member_id,
                'q_quiz_id' => $quiz,
                'q_results' => $row['q_results'],
            ], true);

            import_id_remap_put('quiz_entry', strval($row['id']), $id_new);
        }

        $rows = $db->query_select('quiz_entry_answer', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('quiz_entry_answer', strval($row['id']))) {
                continue;
            }

            $question = import_id_remap_get('quiz_question', strval($row['q_question_id']), true);
            if ($question === null) {
                continue;
            }
            $entry = import_id_remap_get('quiz_entry', strval($row['q_entry_id']));

            $id_new = $GLOBALS['SITE_DB']->query_insert('quiz_entry_answer', ['q_entry_id' => $entry, 'q_question_id' => $question, 'q_answer' => $row['q_answer']], true);

            import_id_remap_put('quiz_entry_answer', strval($row['id']), $id_new);
        }

        $rows = $db->query_select('quiz_winner', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $quiz = import_id_remap_get('quiz', strval($row['q_quiz_id']), true);
            if ($quiz === null) {
                continue;
            }
            $entry = import_id_remap_get('quiz_entry', strval($row['q_entry_id']), true);
            if ($entry === null) {
                continue;
            }

            $GLOBALS['SITE_DB']->query_insert('quiz_winner', ['q_quiz_id' => $quiz, 'q_entry_id' => $entry, 'q_winner_level' => $row['q_winner_level']]);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }

        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'quiz', 'quiz');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_ecommerce(object $db, string $table_prefix, string $file_base)
    {
        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('ecom_transactions', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('transaction', strval($row['id']))) {
                    continue;
                }

                $GLOBALS['SITE_DB']->query_insert('ecom_transactions', $row, false, true); // Allow errors in case the row already exists

                import_id_remap_put('transaction', strval($row['id']), 0);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('ecom_invoices', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('ecom_invoice', strval($row['id']))) {
                    continue;
                }

                $id_old = strval($row['id']);

                $row['i_member_id'] = $on_same_msn ? $row['i_member_id'] : import_id_remap_get('member', strval($row['i_member_id']), true);
                if ($row['i_member_id'] === null) {
                    i_timed_refresh();
                    continue; // Uh oh - someones deleted and they had invoices - lets hope they paid them ;)
                }

                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('ecom_invoices', $row, true);

                import_id_remap_put('ecom_invoice', $id_old, $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('f_usergroup_subs', ['*'], [], '', $max, $start, true);
            if ($rows !== null) {
                $this->_fix_comcode_ownership($rows);
                foreach ($rows as $row) {
                    if (import_check_if_imported('usergroup_sub', strval($row['id']))) {
                        continue;
                    }

                    $group_id = $on_same_msn ? $row['s_group_id'] : import_id_remap_get('group', strval($row['s_group_id']), true);
                    if ($group_id === null) {
                        continue;
                    }

                    $map = [
                        's_price' => $row['s_price'],
                        's_tax_code' => $row['s_tax_code'],
                        's_length' => $row['s_length'],
                        's_length_units' => $row['s_length_units'],
                        's_auto_recur' => $row['s_auto_recur'],
                        's_group_id' => $group_id,
                        's_enabled' => $row['s_enabled'],
                    ];
                    $map += insert_lang('s_title', $this->get_lang_string($db, $row['s_title']), 2, $GLOBALS['FORUM_DB']);
                    $map += insert_lang('s_description', $this->get_lang_string($db, $row['s_description']), 2, $GLOBALS['FORUM_DB']);
                    $map += insert_lang('s_mail_start', $this->get_lang_string($db, $row['s_mail_start']), 2, $GLOBALS['FORUM_DB']);
                    $map += insert_lang('s_mail_end', $this->get_lang_string($db, $row['s_mail_end']), 2, $GLOBALS['FORUM_DB']);
                    $map += insert_lang('s_mail_uhoh', $this->get_lang_string($db, $row['s_mail_uhoh']), 2, $GLOBALS['FORUM_DB']);
                    $id_new = $GLOBALS['FORUM_DB']->query_insert('f_usergroup_subs', $map, true);

                    $mails = $db->query_select('f_usergroup_sub_mails', ['*'], ['m_usergroup_sub_id' => $row['id']]);
                    $this->_fix_comcode_ownership($mails);
                    foreach ($mails as $mail) {
                        $map = [
                            'm_usergroup_sub_id' => $id_new,
                            'm_ref_point' => $mail['m_ref_point'],
                            'm_ref_point_offset' => $mail['m_ref_point_offset'],
                        ];
                        $map += insert_lang('m_subject', $this->get_lang_string($db, $mail['m_subject']), 2, $GLOBALS['FORUM_DB']);
                        $map += insert_lang('m_body', $this->get_lang_string($db, $mail['m_body']), 2, $GLOBALS['FORUM_DB']);
                        $GLOBALS['FORUM_DB']->query_insert('f_usergroup_sub_mails', $map);
                    }

                    import_id_remap_put('usergroup_sub', strval($row['id']), $id_new);
                }
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'usergroup_subscription', 'usergroup_sub');

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('ecom_subscriptions', ['*'], [], 'ORDER BY id', $max, $start, true);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('ecom_subscription', strval($row['id']))) {
                    continue;
                }

                $row['s_member_id'] = $on_same_msn ? $row['s_member_id'] : import_id_remap_get('member', strval($row['s_member_id']), true);
                if ($row['s_member_id'] === null) {
                    continue; // Uh oh - someones deleted and they had invoices - lets hope they paid them ;)
                }

                if (substr($row['s_type_code'], 0, 9) == 'USERGROUP') {
                    $remap_id = import_id_remap_get('usergroup_sub', substr($row['s_type_code'], 9), true);
                    if ($remap_id === null) {
                        continue;
                    }
                    $row['s_type_code'] = 'USERGROUP' . strval($remap_id);
                }

                $id_old = strval($row['id']);

                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('ecom_subscriptions', $row, true);

                import_id_remap_put('ecom_subscription', $id_old, $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('ecom_prods_prices', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                $GLOBALS['SITE_DB']->query_delete('ecom_prods_prices', ['name' => $row['name']], '', 1);
                $GLOBALS['SITE_DB']->query_insert('ecom_prods_prices', $row);

                // We will not mark as did something because there is no check here for repeat actions
                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('ecom_sales', ['*'], [], '', $max, $start, true);
            if ($rows !== null) {
                $this->_fix_comcode_ownership($rows);
                $on_same_msn = ($this->on_same_msn($file_base));
                foreach ($rows as $row) {
                    if (import_check_if_imported('ecom_sale', strval($row['id']))) {
                        continue;
                    }

                    $member_id = $on_same_msn ? $row['member_id'] : import_id_remap_get('member', strval($row['member_id']), true);
                    if ($member_id === null) {
                        continue;
                    }

                    $id_old = strval($row['id']);
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($row['id']);
                    }

                    $id_new = $GLOBALS['SITE_DB']->query_insert('ecom_sales', ['date_and_time' => $row['date_and_time'], 'member_id' => $member_id, 'details' => $row['details'], 'details2' => $row['details2'], 'txn_id' => $row['txn_id']], true);

                    import_id_remap_put('ecom_sale', $id_old, $id_new);
                }
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_ecom_prods_custom($db, $table_prefix, $file_base);
        $this->_import_ecom_prods_permissions($db, $table_prefix);
    }

    /**
     * Imports custom products.
     *
     * @param  object $db The DB connection to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    protected function _import_ecom_prods_custom(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('ecom_prods_custom', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('ecom_prods_custom', strval($row['id']))) {
                continue;
            }

            $import = $this->_import_upload($row['c_image_url'], $file_base);
            if ($import === false) {
                $row['c_image_url'] = '';
                attach_message('Image for custom eCommerce product ID ' . escape_html(strval($row['id'])) . ' (from old site) needs fixed.', 'notice', false, true);
            }

            $id_old = strval($row['id']);
            if (get_param_integer('keep_preserve_ids', 0) == 0) {
                unset($row['id']);
            }

            $row['c_description'] = $this->get_lang_string($db, $row['c_description']);

            $id_new = $GLOBALS['SITE_DB']->query_insert('ecom_prods_custom', $row, true);

            import_id_remap_put('ecom_prods_custom', $id_old, $id_new);
        }
    }

    /**
     * Imports product store permissions.
     *
     * @param  object $db The DB connection to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     */
    protected function _import_ecom_prods_permissions(object $db, string $table_prefix)
    {
        $rows = $db->query_select('ecom_prods_permissions', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('ecom_prods_permission', strval($row['id']))) {
                continue;
            }

            $id_old = strval($row['id']);
            if (get_param_integer('keep_preserve_ids', 0) == 0) {
                unset($row['id']);
            }

            $row['p_description'] = $this->get_lang_string($db, $row['p_description']);

            $id_new = $GLOBALS['SITE_DB']->query_insert('ecom_prods_permissions', $row, true);

            import_id_remap_put('ecom_prods_permission', $id_old, $id_new);
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_attachments(object $db, string $table_prefix, string $file_base)
    {
        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('attachments', ['*'], [], 'ORDER BY id', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('attachment', strval($row['id']))) {
                    continue;
                }

                $row['a_member_id'] = -$row['a_member_id']; // This is resolved when importing members
                $row_copy = $row;
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row_copy['id']);
                }

                $import = $this->_import_upload($row['a_url'], $file_base);
                if ($import === false) {
                    attach_message('Importing of attachment ID ' . escape_html(strval($row['id'])) . ' (from old site) skipped; the file is broken or invalid.', 'warn', false, true);
                    import_id_remap_put('attachment', strval($row['id']), 0);
                    continue;
                }

                $this->_import_upload($row['a_thumb_url'], $file_base);
                if ($import === false) {
                    $row['a_thumb_url'] = '';
                    attach_message('Thumbnail for attachment ID ' . escape_html(strval($row['id'])) . ' (from old site) needs fixed.', 'notice', false, true);
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('attachments', $row_copy, true);

                import_id_remap_put('attachment', strval($row['id']), $id_new);
            }
            $row_start += 200;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_attachment_references(object $db, string $table_prefix, string $file_base)
    {
        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('attachment_refs', ['*'], [], '', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('attachment_ref', strval($row['id']))) {
                    continue;
                }

                $import_type_fixed = $row['r_referer_type'];
                if ($import_type_fixed == 'cns_post') {
                    $import_type_fixed = 'post';
                }
                if ($import_type_fixed == 'cedi_post') {
                    $import_type_fixed = 'wiki_post';
                }
                if ($import_type_fixed == 'cedi_page') {
                    $import_type_fixed = 'wiki_page';
                }

                $id_new = import_id_remap_get($import_type_fixed, $row['r_referer_id'], true);
                if ($id_new === null) {
                    $id_new = $row['r_referer_id'];
                }
                $aid = import_id_remap_get('attachment', strval($row['a_id']), true);
                if ($aid !== null) {
                    $id_new = $GLOBALS['SITE_DB']->query_insert('attachment_refs', ['r_referer_type' => $row['r_referer_type'], 'r_referer_id' => $id_new, 'a_id' => $aid], true, true);
                    if ($id_new !== null) {
                        import_id_remap_put('attachment_ref', strval($row['id']), $id_new);
                    }
                }

                i_timed_refresh(); // We don't always put, so we must check time
            }
            $row_start += 200;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_feedback(object $db, string $table_prefix, string $file_base)
    {
        $type_remap = ['downloads_category' => 'download_category', 'downloads_download' => 'download', 'downloads' => 'download'];

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('rating', ['*'], [], '', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('rating', strval($row['id']))) {
                    continue;
                }

                $remapped = $row['rating_for_type'];
                if (array_key_exists($remapped, $type_remap)) {
                    $remapped = $type_remap[$remapped];
                }

                $id_old = strval($row['id']);
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }
                if (is_numeric($row['rating_for_id'])) {
                    $id_new = import_id_remap_get($remapped, $row['rating_for_id'], true);
                    if ($id_new !== null) {
                        $row['rating_for_id'] = strval($id_new);
                    }
                }
                $row['rating_member'] = $on_same_msn ? $row['rating_member'] : import_id_remap_get('member', strval($row['rating_member']), true);
                if ($row['rating_member'] === null) {
                    $row['rating_member'] = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $id_new = $GLOBALS['SITE_DB']->query_insert('rating', $row, true);

                import_id_remap_put('rating', $id_old, $id_new);
            }
            $row_start += 200;
        } while (!empty($rows));

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('trackbacks', ['*'], [], '', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('trackback', strval($row['id']))) {
                    continue;
                }

                $remapped = $row['trackback_for_type'];
                if (array_key_exists($remapped, $type_remap)) {
                    $remapped = $type_remap[$remapped];
                }

                $id_old = strval($row['id']);
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }
                if (is_numeric($row['trackback_for_id'])) {
                    $id_new = import_id_remap_get($remapped, $row['trackback_for_id'], true);
                    if ($id_new !== null) {
                        $row['trackback_for_id'] = strval($id_new);
                    }
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('trackbacks', $row, true);

                import_id_remap_put('trackback', $id_old, $id_new);
            }
            $row_start += 200;
        } while (!empty($rows));

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('seo_meta', ['*'], [], '', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('seo_meta', strval($row['id']))) {
                    continue;
                }

                $remapped = $row['meta_for_type'];
                if (array_key_exists($remapped, $type_remap)) {
                    $remapped = $type_remap[$remapped];
                }

                $id_old = strval($row['id']);
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }

                if (is_numeric($row['meta_for_id'])) {
                    $id_new = import_id_remap_get($remapped, $row['meta_for_id'], true);
                    if ($id_new !== null) {
                        $row['meta_for_id'] = strval($id_new);
                    }
                }

                $row = insert_lang('meta_description', $this->get_lang_string($db, $row['meta_description']), 2) + $row;
                $id_new = $GLOBALS['SITE_DB']->query_insert('seo_meta', $row, true);

                import_id_remap_put('seo_meta', $id_old, $id_new);
            }
            $row_start += 200;
        } while (!empty($rows));

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('seo_meta_keywords', ['*'], [], '', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('seo_meta_keyword', strval($row['id']))) {
                    continue;
                }

                $remapped = $row['meta_for_type'];
                if (array_key_exists($remapped, $type_remap)) {
                    $remapped = $type_remap[$remapped];
                }

                $id_old = strval($row['id']);
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }

                if (is_numeric($row['meta_for_id'])) {
                    $id_new = import_id_remap_get($remapped, $row['meta_for_id'], true);
                    if ($id_new !== null) {
                        $row['meta_for_id'] = strval($id_new);
                    }
                }

                $row = insert_lang('meta_keyword', $this->get_lang_string($db, $row['meta_keyword']), 2) + $row;
                $id_new = $GLOBALS['SITE_DB']->query_insert('seo_meta_keywords', $row, true);

                import_id_remap_put('seo_meta_keyword', $id_old, $id_new);
            }
            $row_start += 200;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_authors(object $db, string $table_prefix, string $file_base)
    {
        require_code('authors');

        $rows = $db->query_select('authors', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('authors', 'author', ['author' => $row['author']]);
            if ($test === null) {
                add_author($row['author'], $row['url'], $row['member_id'], $this->get_lang_string($db, $row['the_description']), $this->get_lang_string($db, $row['skills']));

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
            }

            i_timed_refresh();
        }
        $this->_import_alternative_ids($db, 'author', null);
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'author', null);
        $this->_import_content_reviews($db, $table_prefix, 'author', null);
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_banners(object $db, string $table_prefix, string $file_base)
    {
        require_code('banners2');

        $rows = $db->query_select('banner_types', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('banner_types', 'id', ['id' => $row['id']]);
            if ($test === null) {
                add_banner_type($row['id'], $row['t_is_textual'], $row['t_image_width'], $row['t_image_height'], $row['t_max_file_size'], $row['t_comcode_inline']);

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
            }

            i_timed_refresh();
        }
        $this->_import_alternative_ids($db, 'banner_type', null);
        $this->_import_content_reviews($db, $table_prefix, 'banner_type', null);

        $rows = $db->query_select('banners', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('banners', 'name', ['name' => $row['name']]);
            if ($test === null) {
                $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $import = $this->_import_upload($row['img_url'], $file_base);
                if ($import === false) {
                    attach_message('Skipped importing banner ' . escape_html($row['name']) . ' because it had a broken image.', 'warn', false, true);
                    i_timed_refresh();
                    continue;
                }

                add_banner(
                    $row['name'],
                    $row['img_url'],
                    $row['title_text'],
                    $this->get_lang_string($db, $row['caption']),
                    $row['direct_code'],
                    $row['campaign_remaining'],
                    $row['site_url'],
                    $row['display_likelihood'],
                    $row['notes'],
                    $row['deployment_agreement'],
                    $row['expiry_date'],
                    $submitter,
                    $row['validated'],
                    $row['b_type'],
                    collapse_1d_complexity('b_type', $db->query_select('banners_types', ['b_type'], ['name' => $row['name']])),
                    collapse_1d_complexity('region', $db->query_select('content_regions', ['region'], ['content_type' => 'banner', 'content_id' => $row['name']])),
                    $row['add_date'],
                    $row['hits_from'],
                    $row['hits_to'],
                    $row['views_from'],
                    $row['views_to'],
                    $row['edit_date']
                );

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
            }

            i_timed_refresh();
        }
        $this->_import_alternative_ids($db, 'banner', null);
        $this->_import_content_reviews($db, $table_prefix, 'banner', null);

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('banner_clicks', ['*'], [], '', 200, $row_start, true);
            if ($rows === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('banner_click', strval($row['id']))) {
                    continue;
                }

                $c_member_id = $on_same_msn ? $row['c_member_id'] : import_id_remap_get('member', $row['c_member_id'], true);
                if ($c_member_id === null) {
                    $c_member_id = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('banner_clicks', [
                    'c_date_and_time' => $row['c_date_and_time'],
                    'c_member_id' => $c_member_id,
                    'c_ip_address' => $row['c_ip_address'],
                    'c_source' => $row['c_source'],
                    'c_banner_id' => $row['c_banner_id'],
                ], true);

                import_id_remap_put('banner_click', $row['id'], $id_new);
            }
            $row_start += 200;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_points_ledger(object $db, string $table_prefix, string $file_base)
    {
        require_code('points2');

        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('points_ledger', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                $rows = [];
            }
            $on_same_msn = ($this->on_same_msn($file_base));
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('points_ledger', strval($row['id']))) {
                    continue;
                }

                unset($row['reason__text_parsed']);
                unset($row['reason__source_user']);

                $viewer_member = $on_same_msn ? $row['sending_member'] : import_id_remap_get('member', strval($row['sending_member']), true);
                $member_id = $on_same_msn ? $row['receiving_member'] : import_id_remap_get('member', strval($row['receiving_member']), true);
                if ($viewer_member === null) {
                    $viewer_member = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                if ($member_id === null) {
                    $member_id = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $map = [
                    'date_and_time' => $row['date_and_time'],
                    'amount_gift_points' => $row['amount_gift_points'],
                    'amount_points' => $row['amount_points'],
                    'sending_member' => $viewer_member,
                    'receiving_member' => $member_id,
                    'anonymous' => $row['anonymous'],
                    'linked_ledger_id' => $row['linked_ledger_id'],

                    't_type' => $row['t_type'],
                    't_subtype' => $row['t_subtype'],
                    't_type_id' => '', // FUDGE: Currently blanking this because it may refer to an improperly mapped content ID

                    'status' => $row['status'],
                    'locked' => $row['locked'],
                    'is_ranked' => $row['is_ranked'],
                ];
                $map += insert_lang_comcode('reason', $this->get_lang_string($db, $row['reason']), 4);
                $id_new = $GLOBALS['SITE_DB']->query_insert('points_ledger', $map, true);

                $GLOBALS['SITE_DB']->query_update('points_ledger', ['linked_ledger_id' => $id_new], ['linked_ledger_id' => $row['id']]);

                import_id_remap_put('points_ledger', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_escrow(object $db, string $table_prefix, string $file_base)
    {
        // Escrows
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('escrow', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                $rows = [];
            }
            $on_same_msn = ($this->on_same_msn($file_base));
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('escrow', strval($row['id']))) {
                    continue;
                }

                $viewer_member = $on_same_msn ? $row['sending_member'] : import_id_remap_get('member', strval($row['sending_member']), true);
                $member_id = $on_same_msn ? $row['receiving_member'] : import_id_remap_get('member', strval($row['receiving_member']), true);
                if ($viewer_member === null) {
                    $viewer_member = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $reason = get_translated_text($row['reason']);
                $agreement = get_translated_text($row['agreement']);

                $ledger_id_new = import_id_remap_get('points_ledger', strval($row['original_points_ledger_id']));
                $map = [
                    'date_and_time' => $row['date_and_time'],
                    'update_date_and_time' => $row['update_date_and_time'],
                    'amount' => $row['amount'],
                    'original_points_ledger_id' => $ledger_id_new,
                    'sending_member' => $viewer_member,
                    'receiving_member' => $member_id,
                    'expiration_time' => $row['expiration_time'],
                    'sender_status' => $row['sender_status'],
                    'recipient_status' => $row['recipient_status'],
                    'status' => $row['status'],
                    'content_type' => $row['content_type'],
                    'content_id' => '', // FUDGE: Currently blanking this because it may refer to an improperly mapped content ID
                ];
                $map += insert_lang_comcode('reason', $reason, 4);
                $map += insert_lang_comcode('agreement', $agreement, 5);
                $id_new = $GLOBALS['SITE_DB']->query_insert('escrow', $map, true);

                import_id_remap_put('escrow', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (!empty($rows));

        // Logs
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('escrow_logs', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                $rows = [];
            }
            $on_same_msn = ($this->on_same_msn($file_base));
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('escrow_log', strval($row['id']))) {
                    continue;
                }

                if ($row['member_id'] !== null) {
                    $member_id = $on_same_msn ? $row['member_id'] : import_id_remap_get('member', strval($row['member_id']), true);
                    if ($member_id === null) {
                        $member_id = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                    }
                } else {
                    $member_id = null;
                }

                $information = get_translated_text($row['information']);
                $escrow_id_new = import_id_remap_get('escrow', strval($row['escrow_id']));
                $map = [
                    'escrow_id' => $escrow_id_new,
                    'date_and_time' => $row['date_and_time'],
                    'log_type' => $row['log_type'],
                    'member_id' => $member_id,
                ];
                $map += insert_lang_comcode('information', $information, 5);
                $id_new = $GLOBALS['SITE_DB']->query_insert('escrow_logs', $map, true);

                import_id_remap_put('escrow_log', strval($row['id']), $id_new);
            }

            $start += $max;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_daily_visits(object $db, string $table_prefix, string $file_base)
    {
        $on_same_msn = ($this->on_same_msn($file_base));

        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('daily_visits', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                $rows = [];
            }
            foreach ($rows as $row) {
                $member_id = $on_same_msn ? $row['d_member'] : import_id_remap_get('member', strval($row['d_member']), true);
                if ($member_id === null) {
                    i_timed_refresh();
                    continue;
                }
                $GLOBALS['SITE_DB']->query_delete('daily_visits', ['d_member' => $row['d_member'], 'd_date_and_time' => $row['d_date_and_time']], '', 1);
                $GLOBALS['SITE_DB']->query_insert('daily_visits', ['d_member' => $row['d_member'], 'd_date_and_time' => $row['d_date_and_time']]);

                // We do not mark as something done because there is no test if we already updated something
                i_timed_refresh();
            }

            $start += $max;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_config(object $db, string $table_prefix, string $file_base)
    {
        $remap = [
        ];

        $rows = $db->query_select('config', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $name = $row['c_name'];
            if (isset($remap[$name])) {
                $name = $remap[$name];
            }

            $value = $row['c_value'];

            if (($value !== null) && ($row['c_set'] == 1)) {
                if ($row['c_needs_dereference'] == 1) {
                    $value = $this->get_lang_string($db, $row['c_value_trans']);
                }

                $test = get_option($name, true);
                if ($test !== null) {
                    set_option($name, $value);

                    global $I_REFRESH_DID_SOMETHING;
                    $I_REFRESH_DID_SOMETHING = true;
                }
            }

            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_polls(object $db, string $table_prefix, string $file_base)
    {
        require_code('polls2');

        $rows = $db->query_select('poll', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            if (import_check_if_imported('poll', strval($row['id']))) {
                continue;
            }

            $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
            if ($submitter === null) {
                $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
            }
            $id_new = add_poll(
                $this->get_lang_string($db, $row['question']),
                $this->get_lang_string($db, $row['option1']),
                $this->get_lang_string($db, $row['option2']),
                $this->get_lang_string($db, $row['option3']),
                $this->get_lang_string($db, $row['option4']),
                $this->get_lang_string($db, $row['option5']),
                $this->get_lang_string($db, $row['option6']),
                $this->get_lang_string($db, $row['option7']),
                $this->get_lang_string($db, $row['option8']),
                $this->get_lang_string($db, $row['option9']),
                $this->get_lang_string($db, $row['option10']),
                $row['num_options'],
                $row['is_current'],
                $row['allow_rating'],
                $row['allow_comments'],
                $row['allow_trackbacks'],
                $row['notes'],
                $row['add_time'],
                $submitter,
                $row['date_and_time'],
                $row['votes1'],
                $row['votes2'],
                $row['votes3'],
                $row['votes4'],
                $row['votes5'],
                $row['votes6'],
                $row['votes7'],
                $row['votes8'],
                $row['votes9'],
                $row['votes10'],
                $row['poll_views'],
                $row['edit_date']
            );

            // Who has voted in the poll?
            $votes = $db->query_select('poll_votes', ['*'], ['v_poll_id' => $row['id']], '', null, 0, true);
            if ($votes === null) { // Old software-style
                $voters = explode('-', $row['ip']);
                $votes = [];
                foreach ($voters as $voter) {
                    $votes[] = [
                        'v_poll_id' => $row['id'],
                        'v_voting_member' => is_numeric($voter) ? intval($voter) : null,
                        'v_voting_ip_address' => is_numeric($voter) ? '' : $voter,
                        'v_vote_for' => null,
                    ];
                }
            }

            foreach ($votes as $vote) {
                $GLOBALS['SITE_DB']->query_insert('poll_votes', [
                    'v_poll_id' => $id_new,
                    'v_voting_member' => $vote['v_voting_member'],
                    'v_voting_ip_address' => $vote['v_voting_ip_address'],
                    'v_vote_for' => $vote['v_vote_for'],
                    'v_vote_time' => $vote['v_vote_time'],
                ]);
            }

            import_id_remap_put('poll', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'poll', 'poll');
        $this->_import_review_supplement($db, $table_prefix, 'polls', 'poll');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'poll', 'poll');
        $this->_import_content_reviews($db, $table_prefix, 'poll', 'poll');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_news_and_categories(object $db, string $table_prefix, string $file_base)
    {
        require_code('news2');

        $rows = $db->query_select('news_categories', ['*'], [], 'ORDER BY id', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            if (import_check_if_imported('news_category', strval($row['id']))) {
                continue;
            }

            $owner = $on_same_msn ? $row['nc_owner'] : import_id_remap_get('member', strval($row['nc_owner']), true);

            $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];
            $id_new = add_news_category($this->get_lang_string($db, $row['nc_title']), $row['nc_img'], $row['notes'], $owner, $id);

            import_id_remap_put('news_category', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'news_category', 'news_category');
        $this->_import_content_reviews($db, $table_prefix, 'news_category', 'news_category');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'news_category', 'news_category');

        $max = 100;
        $start = 0;
        do {
            $rows = $db->query_select('news', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('news', strval($row['id']))) {
                    continue;
                }

                $import = $this->_import_upload($row['news_image_url'], $file_base);
                if ($import === false) {
                    $row['news_image_url'] = '';
                    attach_message('Rep image for news / blog ' . escape_html($this->get_lang_string($db, $row['title'])) . ' needs fixed.', 'notice', false, true);
                }

                $news_category = [];
                $rows2 = $db->query_select('news_category_entries', ['news_entry_category'], ['news_entry' => $row['id']]);
                foreach ($rows2 as $row2) {
                    $next = import_id_remap_get('news_category', strval($row2['news_entry_category']), true);
                    if ($next !== null) {
                        $news_category[] = $next;
                    }
                }
                $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];
                $main_news_category = $row['news_category'];
                $main_news_category = import_id_remap_get('news_category', strval($main_news_category), true);
                if ($main_news_category === null) {
                    $main_news_category = db_get_first_id();
                }

                $regions = collapse_1d_complexity('region', $db->query_select('content_regions', ['region'], ['content_type' => 'news', 'content_id' => strval($row['id'])]));

                require_code('failure');

                // We could get some Comcode errors during the import process. We can't turn that off. So just catch and attach errors.
                set_throw_errors(true);
                try {
                    $id_new = add_news($this->get_lang_string($db, $row['title']), $this->get_lang_string($db, $row['news']), $row['author'], $row['validated'], $row['allow_rating'], $row['allow_comments'], $row['allow_trackbacks'], $row['notes'], $this->get_lang_string($db, $row['news_article']), $main_news_category, $news_category, $row['date_and_time'], $submitter, $row['news_views'], $row['edit_date'], $id, $row['news_image_url'], '', '', $regions);
                } catch (Exception $e) {
                    attach_message('Importing of news / blog ' . escape_html($this->get_lang_string($db, $row['title'])) . ' skipped due to an error: ' . escape_html($e->getMessage()), 'warn', false, true);
                    import_id_remap_put('news', strval($row['id']), 0);
                    continue;
                }
                set_throw_errors(false);

                $this->_import_content_privacy($db, 'news', strval($row['id']), strval($id_new));

                import_id_remap_put('news', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (!empty($rows));
        $this->_import_alternative_ids($db, 'news', 'news');
        $this->_import_review_supplement($db, $table_prefix, 'news', 'news');
        $this->_import_content_reviews($db, $table_prefix, 'news', 'news');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'news', 'news');

        $rows = $db->query_select('news_rss_cloud', ['*'], [], '', null, 0, true);
        if ($rows !== null) {
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('news_rss_cloud', strval($row['id']))) {
                    continue;
                }

                $id_old = strval($row['id']);
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }
                $id_new = $GLOBALS['SITE_DB']->query_insert('news_rss_cloud', $row, true);

                import_id_remap_put('news_rss_cloud', $id_old, $id_new);
            }
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_newsletter_subscriptions(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('newsletters', ['*'], [], '', null, 0, true);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('newsletter', strval($row['id']))) {
                continue;
            }

            $map = [];
            $map += insert_lang('title', $this->get_lang_string($db, $row['title']), 2);
            $map += insert_lang('the_description', $this->get_lang_string($db, $row['the_description']), 2);
            $id_new = $GLOBALS['SITE_DB']->query_insert('newsletters', $map, true);

            import_id_remap_put('newsletter', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'newsletter', 'newsletter');

        $rowsn = $db->query_select('newsletter_subscribers', ['*'], [], '', null, 0, true);
        if ($rowsn === null) {
            return;
        }
        $this->_fix_comcode_ownership($rowsn);
        $rows = [];
        foreach ($rowsn as $row) {
            $GLOBALS['SITE_DB']->query_delete('newsletter_subscribers', ['email' => $row['email'], 'language' => $row['language']], '', 1);
            $GLOBALS['SITE_DB']->query_insert('newsletter_subscribers', ['n_forename' => $row['n_forename'], 'n_surname' => $row['n_surname'], 'join_time' => $row['join_time'], 'email' => $row['email'], 'code_confirm' => $row['code_confirm'], 'pass_salt' => $row['pass_salt'], 'the_password' => $row['the_password'], 'language' => $row['language']]);
            i_timed_refresh();
        }
        $this->_import_alternative_ids($db, 'newsletter_subscriber', null);

        $rows = $db->query_select('newsletter_subscribe', ['*']);
        foreach ($rows as $row) {
            $newsletter_id = import_id_remap_get('newsletter', strval($row['newsletter_id']), true);
            $GLOBALS['SITE_DB']->query_insert('newsletter_subscribe', ['newsletter_id' => $newsletter_id, 'email' => $row['email']]);

            i_timed_refresh();
        }

        $max = 100;
        $start = 0;
        do {
            $rows = $db->query_select('newsletter_archive', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('newsletter_archive', strval($row['id']))) {
                    continue;
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('newsletter_archive', ['date_and_time' => $row['date_and_time'], 'subject' => $row['subject'], 'newsletter' => $row['newsletter'], 'language' => $row['language']], true);

                import_id_remap_put('newsletter_archive', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (!empty($rows));

        $max = 100;
        $start = 0;
        do {
            $rows = $db->query_select('newsletter_periodic', ['*'], [], '', $max, $start, true);
            if ($rows !== null) {
                $this->_fix_comcode_ownership($rows);
                foreach ($rows as $row) {
                    if (import_check_if_imported('newsletter_periodic', strval($row['id']))) {
                        continue;
                    }

                    $id_old = strval($row['id']);
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($row['id']);
                    }
                    $id_new = $GLOBALS['SITE_DB']->query_insert('newsletter_periodic', $row, true);

                    import_id_remap_put('newsletter_periodic', $id_old, $id_new);
                }
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_downloads_and_categories(object $db, string $table_prefix, string $file_base)
    {
        require_code('downloads2');

        $rows = $db->query_select('download_categories', ['*'], [], 'ORDER BY id', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('download_category', strval($row['id']))) {
                continue;
            }

            $import = $this->_import_upload($row['rep_image'], $file_base);
            if ($import === false) {
                $row['rep_image'] = '';
                attach_message('Rep image for download category ' . escape_html($this->get_lang_string($db, $row['category'])) . ' needs fixed.', 'notice', false, true);
            }

            $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];
            $id = add_download_category($this->get_lang_string($db, $row['category']), ($row['parent_id'] === null) ? null : -$row['parent_id'], $this->get_lang_string($db, $row['the_description']), $row['notes'], $row['rep_image'], $id);

            import_id_remap_put('download_category', strval($row['id']), $id);
        }
        $this->_import_alternative_ids($db, 'download_category', 'download_category');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'download_category', 'download_category');
        $this->_import_content_reviews($db, $table_prefix, 'download_category', 'download_category');
        foreach ($rows as $row) {
            $id = import_id_remap_get('download_category', strval($row['id']));
            $parent_id = import_id_remap_get('download_category', strval($row['parent_id']), true);
            if ($parent_id === null) {
                $parent_id = db_get_first_id();
            }

            $GLOBALS['SITE_DB']->query_update('download_categories', ['parent_id' => $parent_id], ['id' => $id], '', 1);
        }

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('download_downloads', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('download', strval($row['id']))) {
                    continue;
                }
                $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $category_id = import_id_remap_get('download_category', strval($row['category_id']), true);
                if ($category_id === null) {
                    $category_id = db_get_first_id();
                }

                $results = $this->_import_upload($row['url'], $file_base);
                if ($results === false) { // Broken download; skip it
                    attach_message('Import of download ' . escape_html($this->get_lang_string($db, $row['name'])) . ' skipped because the file was invalid.', 'warn', false, true);
                    import_id_remap_put('download', strval($row['id']), 0);
                    continue;
                }

                // Null values not allowed in the API, but we use them in the database to indicate an error with checking. Set to 0 to force a re-check.
                if ($row['file_size'] === null) {
                    $row['file_size'] = 0;
                }

                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];
                $id_new = add_download($category_id, $this->get_lang_string($db, $row['name']), $row['url'], $this->get_lang_string($db, $row['the_description']), $row['author'], $this->get_lang_string($db, $row['additional_details']), null, $row['validated'], $row['allow_rating'], $row['allow_comments'], $row['allow_trackbacks'], $row['notes'], $row['original_filename'], $row['file_size'], $row['download_cost'], $row['download_submitter_gets_points'], $row['download_licence_id'], $row['add_date'], $row['num_downloads'], $row['download_views'], $submitter, $row['edit_date'], $id, '', '', $row['default_pic'], $row['url_redirect']);

                $this->_import_content_privacy($db, 'download', strval($row['id']), strval($id_new));

                import_id_remap_put('download', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'download', 'download');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'download', 'download');
        $this->_import_review_supplement($db, $table_prefix, 'downloads', 'download');
        $this->_import_content_reviews($db, $table_prefix, 'download', 'download');

        if (!import_check_if_imported('maintenance_download_downloads', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('download_downloads', ['*'], [], 'ORDER BY id', $max, $start);
                foreach ($rows as $row) {
                    if ($row['out_mode_id'] !== null) {
                        $out_mode_id = import_id_remap_get('download', strval($row['out_mode_id']), true);
                        if ($out_mode_id === null) {
                            $out_mode_id = null;
                        }
                        $id_new = import_id_remap_get('download', strval($row['id']), true);
                        if ($id_new === null) {
                            continue;
                        }
                        $GLOBALS['SITE_DB']->query_update('download_downloads', ['out_mode_id' => $out_mode_id], ['id' => $id_new], '', 1);

                        i_timed_refresh();
                    }
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('maintenance_download_downloads', strval(0), 0);
        }

        $rows = $db->query_select('download_licences', ['*'], [], 'ORDER BY id', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('download_licence', strval($row['id']))) {
                continue;
            }

            $GLOBALS['SITE_DB']->query_insert('download_licences', ['l_title' => $row['l_title'], 'l_text' => $row['l_text']]);

            import_id_remap_put('download_licence', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'download_licence', 'download_licence');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_images_and_galleries(object $db, string $table_prefix, string $file_base)
    {
        require_code('galleries2');

        $rows = $db->query_select('galleries', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('galleries', 'name', ['name' => $row['name']]);
            if ($test === null) {
                $import = $this->_import_upload($row['rep_image'], $file_base);
                if ($import === false) {
                    $row['rep_image'] = '';
                    attach_message('Rep image for gallery ' . escape_html($this->get_lang_string($db, $row['fullname'])) . ' needs fixed.', 'notice', false, true);
                }

                $import = $this->_import_upload($row['watermark_top_left'], $file_base);
                if ($import === false) {
                    $row['watermark_top_left'] = '';
                    attach_message('Top-left watermark for gallery ' . escape_html($this->get_lang_string($db, $row['fullname'])) . ' needs fixed.', 'notice', false, true);
                }

                $import = $this->_import_upload($row['watermark_top_right'], $file_base);
                if ($import === false) {
                    $row['watermark_top_right'] = '';
                    attach_message('Top-right watermark for gallery ' . escape_html($this->get_lang_string($db, $row['fullname'])) . ' needs fixed.', 'notice', false, true);
                }

                $import = $this->_import_upload($row['watermark_bottom_left'], $file_base);
                if ($import === false) {
                    $row['watermark_bottom_left'] = '';
                    attach_message('Bottom-left watermark for gallery ' . escape_html($this->get_lang_string($db, $row['fullname'])) . ' needs fixed.', 'notice', false, true);
                }

                $import = $this->_import_upload($row['watermark_bottom_right'], $file_base);
                if ($import === false) {
                    $row['watermark_bottom_right'] = '';
                    attach_message('Bottom-right watermark for gallery ' . escape_html($this->get_lang_string($db, $row['fullname'])) . ' needs fixed.', 'notice', false, true);
                }

                add_gallery($row['name'], $this->get_lang_string($db, $row['fullname']), $this->get_lang_string($db, $row['the_description']), $row['notes'], $row['parent_id'], $row['accept_images'], $row['accept_videos'], $row['is_member_synched'], $row['layout_mode'], $row['rep_image'], $row['watermark_top_left'], $row['watermark_top_right'], $row['watermark_bottom_left'], $row['watermark_bottom_right'], $row['gallery_sort'], $row['media_sort'], $row['allow_rating'], $row['allow_comments'], false, $row['add_date'], null, '', '', false);
            }
        }
        $this->_import_alternative_ids($db, 'gallery', null);
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'gallery', null);
        $this->_import_review_supplement($db, $table_prefix, 'galleries', null);
        $this->_import_content_reviews($db, $table_prefix, 'gallery', null);

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('images', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('image', strval($row['id']))) {
                    continue;
                }

                $import = $this->_import_upload($row['url'], $file_base);
                if ($import === false) {
                    attach_message('Importing of image ' . escape_html($this->get_lang_string($db, $row['title'])) . ' skipped as it is broken.', 'warn', false, true);
                    import_id_remap_put('image', strval($row['id']), 0);
                    continue;
                }

                $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];

                $regions = collapse_1d_complexity('region', $db->query_select('content_regions', ['region'], ['content_type' => 'image', 'content_id' => strval($row['id'])]));

                $title = $this->get_lang_string($db, $row['title']);
                $id_new = add_image($title, $row['cat'], $this->get_lang_string($db, $row['the_description']), $row['url'], $row['validated'], $row['allow_rating'], $row['allow_comments'], $row['allow_trackbacks'], $row['notes'], $submitter, $row['add_date'], $row['edit_date'], $row['image_views'], $id, '', '', $regions, false); // No watermark in case the imported image already has one

                $this->_import_content_privacy($db, 'image', strval($row['id']), strval($id_new));

                import_id_remap_put('image', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'image', 'image');
        $this->_import_review_supplement($db, $table_prefix, 'images', 'image');
        $this->_import_content_reviews($db, $table_prefix, 'image', 'image');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'image', 'image');

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('videos', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('video', strval($row['id']))) {
                    continue;
                }

                $import = $this->_import_upload($row['url'], $file_base);
                if ($import === false) {
                    attach_message('Importing of video ' . escape_html($this->get_lang_string($db, $row['title'])) . ' skipped as it is broken.', 'warn', false, true);
                    import_id_remap_put('video', strval($row['id']), 0);
                    continue;
                }

                $import = $this->_import_upload($row['thumb_url'], $file_base);
                if ($import === false) {
                    $row['thumb_url'] = '';
                    attach_message('Thumbnail for video ' . escape_html($this->get_lang_string($db, $row['title'])) . ' needs fixed.', 'notice', false, true);
                }

                $import = $this->_import_upload($row['closed_captions_url'], $file_base);
                if ($import === false) {
                    $row['closed_captions_url'] = '';
                    attach_message('Closed captions for video ' . escape_html($this->get_lang_string($db, $row['title'])) . ' needs fixed.', 'notice', false, true);
                }

                $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];

                $regions = collapse_1d_complexity('region', $db->query_select('content_regions', ['region'], ['content_type' => 'video', 'content_id' => strval($row['id'])]));

                $title = $this->get_lang_string($db, $row['title']);
                $id_new = add_video($title, $row['cat'], $this->get_lang_string($db, $row['the_description']), $row['url'], $row['thumb_url'], $row['validated'], $row['allow_rating'], $row['allow_comments'], $row['allow_trackbacks'], $row['notes'], $row['video_length'], $row['video_width'], $row['video_height'], $row['closed_captions_url'], $submitter, $row['add_date'], $row['edit_date'], $row['video_views'], $id, '', '', $regions);

                $this->_import_content_privacy($db, 'video', strval($row['id']), strval($id_new));

                import_id_remap_put('video', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'video', 'video');
        $this->_import_review_supplement($db, $table_prefix, 'videos', 'video');
        $this->_import_content_reviews($db, $table_prefix, 'video', 'video');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'video', 'video');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_wiki(object $db, string $table_prefix, string $file_base)
    {
        if (!import_check_if_imported('wiki_page', strval(db_get_first_id()))) {
            import_id_remap_put('wiki_page', strval(db_get_first_id()), db_get_first_id());
        }

        $max = 200;
        $start = 0;
        do {
            $rows_pages = $db->query_select('wiki_pages', ['*'], [], '', $max, $start, true);
            if ($rows_pages === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows_pages);
            $titlemap = [];
            foreach ($rows_pages as $row) {
                $title = $this->get_lang_string($db, $row['title']);

                if (import_check_if_imported('wiki_page', strval($row['id']))) {
                    $id = import_id_remap_get('wiki_page', strval($row['id']));
                    $titlemap[$id] = $title;
                    continue;
                }

                $on_same_msn = ($this->on_same_msn($file_base));
                $submitter = $on_same_msn ? $row['submitter'] : import_id_remap_get('member', strval($row['submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $map = [
                    'edit_date' => $row['edit_date'],
                    'submitter' => $submitter,
                    'show_posts' => $row['show_posts'],
                    'wiki_views' => $row['wiki_views'],
                    'notes' => $row['notes'],
                    'add_date' => $row['add_date'],
                ];
                $map += insert_lang('title', $title, 2);
                $map += insert_lang_comcode('the_description', $this->get_lang_string($db, $row['the_description']), 2);
                $id = $GLOBALS['SITE_DB']->query_insert('wiki_pages', $map, true);

                $titlemap[$id] = $title;

                import_id_remap_put('wiki_page', strval($row['id']), $id);
            }
            $start += $max;
        } while (($rows_pages !== null) && !empty($rows_pages));
        $this->_import_alternative_ids($db, 'wiki_page', 'wiki_page');
        $this->_import_content_reviews($db, $table_prefix, 'wiki_page', 'wiki_page');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'wiki_page', 'wiki_page');

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('wiki_posts', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('wiki_post', strval($row['id']))) {
                    continue;
                }

                unset($row['the_message__text_parsed']);
                unset($row['the_message__source_user']);

                $page_id = import_id_remap_get('wiki_page', strval($row['page_id']), true);
                if ($page_id === null) {
                    $page_id = db_get_first_id();
                }

                $member_id = $on_same_msn ? $row['member_id'] : import_id_remap_get('member', strval($row['member_id']), true);
                if ($member_id === null) {
                    $member_id = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $map = [
                    'wiki_views' => $row['wiki_views'],
                    'validated' => $row['validated'],
                    'member_id' => $member_id,
                    'date_and_time' => $row['date_and_time'],
                    'page_id' => $page_id,
                    'edit_date' => $row['edit_date'],
                ];
                $map += insert_lang_comcode('the_message', $this->get_lang_string($db, $row['the_message']), 2);
                $id_new = $GLOBALS['SITE_DB']->query_insert('wiki_posts', $map, true);

                import_id_remap_put('wiki_post', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'wiki_post', 'wiki_post');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'wiki_post', 'wiki_post');

        if (addon_installed('actionlog')) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('revisions', ['*'], ['r_resource_type' => 'wiki_page'], '', $max, $start);
                $this->_fix_comcode_ownership($rows);
                foreach ($rows as $row) {
                    if (import_check_if_imported('revision', strval($row['id']))) {
                        continue;
                    }

                    $id_old = strval($row['id']);
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($row['id']);
                    }

                    $actionlog_id = import_id_remap_get('wiki_page', strval($row['r_actionlog_id']), true);
                    if ($actionlog_id === null) {
                        continue;
                    }
                    $row['r_actionlog_id'] = $actionlog_id;

                    $category_id = import_id_remap_get('wiki_page', strval($row['r_category_id']), true);
                    if ($category_id === null) {
                        continue;
                    }
                    $row['r_category_id'] = $category_id;

                    if (strpos($row['log_action'], 'PAGE') !== false) {
                        $row['r_resource_id'] = import_id_remap_get('wiki_page', $row['r_resource_id'], true);
                        if ($row['r_resource_id'] === null) {
                            continue;
                        }
                    } else {
                        $row['r_resource_id'] = import_id_remap_get('wiki_post', $row['r_resource_id'], true);
                        if ($row['r_resource_id'] === null) {
                            continue;
                        }
                    }

                    $row['r_original_content_owner'] = $on_same_msn ? $row['r_original_content_owner'] : import_id_remap_get('member', strval($row['r_original_content_owner']), true);
                    if ($row['r_original_content_owner'] === null) {
                        $row['r_original_content_owner'] = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                    }

                    $id_new = $GLOBALS['SITE_DB']->query_insert('revisions', $row, true);

                    import_id_remap_put('revision', $id_old, $id_new);
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
        }

        $rows = $db->query_select('wiki_children', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $child_id = import_id_remap_get('wiki_page', strval($row['child_id']), true);
            $parent_id = import_id_remap_get('wiki_page', strval($row['parent_id']), true);

            if ($child_id === null) {
                i_timed_refresh();
                continue;
            }
            if ($parent_id === null) {
                i_timed_refresh();
                continue;
            }

            if (array_key_exists($child_id, $titlemap)) {
                $title = $titlemap[$child_id];
            } elseif (array_key_exists($child_id, $rows_pages)) {
                $title = $this->get_lang_string($db, $rows_pages[$child_id]['title']);
            } else {
                i_timed_refresh();
                continue;
            }

            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('wiki_children', 'parent_id', ['parent_id' => $parent_id, 'child_id' => $child_id]);
            if ($test !== null) {
                i_timed_refresh();
                continue;
            }

            $GLOBALS['SITE_DB']->query_insert('wiki_children', ['parent_id' => $parent_id, 'child_id' => $child_id, 'the_order' => $row['the_order'], 'title' => $titlemap[$child_id]]);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_custom_comcode(object $db, string $table_prefix, string $file_base)
    {
        require_code('custom_comcode');

        $rows = $db->query_select('custom_comcode', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('custom_comcode', 'tag_tag', ['tag_tag' => $row['tag_tag']]);
            if ($test !== null) {
                i_timed_refresh();
                continue;
            }

            $tag = $row['tag_tag'];
            $title = $this->get_lang_string($db, $row['tag_title']);
            $description = $this->get_lang_string($db, $row['tag_description']);
            $replace = $row['tag_replace'];
            $example = $row['tag_example'];
            $parameters = $row['tag_parameters'];
            $enabled = $row['tag_enabled'];
            $dangerous_tag = $row['tag_dangerous_tag'];
            $block_tag = $row['tag_block_tag'];
            $textual_tag = $row['tag_textual_tag'];

            add_custom_comcode_tag($tag, $title, $description, $replace, $example, $parameters, $enabled, $dangerous_tag, $block_tag, $textual_tag);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }

        $this->_import_alternative_ids($db, 'custom_comcode_tag', null);
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_comcode_pages(object $db, string $table_prefix, string $file_base)
    {
        $max = 100;
        $start = 0;
        do {
            $rows = $db->query_select('comcode_pages', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('comcode_page', $row['the_zone'] . ':' . $row['the_page'])) {
                    continue;
                }

                $p_submitter = import_id_remap_get('member', strval($row['p_submitter']), true);
                if ($p_submitter === null) {
                    require_code('users_active_actions');
                    $p_submitter = get_first_admin_user();
                }

                $the_zone = $row['the_zone'];
                $the_page = $row['the_page'];
                $p_parent_page = $row['p_parent_page'];
                $p_validated = $row['p_validated'];
                $p_edit_date = $row['p_edit_date'];
                $p_add_date = $row['p_add_date'];
                $p_show_as_edit = $row['p_show_as_edit'];

                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('comcode_pages', 'the_page', [
                    'the_zone' => $the_zone,
                    'the_page' => $the_page,
                ]);
                if ($test !== null) {
                    $the_page .= '_duplicate_' . substr(md5(uniqid('', true)), 0, 5);
                }

                $langs = find_all_langs();
                $found_one = false;
                foreach (array_keys($langs) as $lang) {
                    $file_path_from = $file_base . (($the_zone == '') ? '' : '/') . $the_zone . '/pages/comcode_custom/' . $lang . '/' . $row['the_page'] . '.txt';
                    $file_path_to = get_file_base() . (($the_zone == '') ? '' : '/') . $the_zone . '/pages/comcode_custom/' . $lang . '/' . $row['the_page'] . '.txt';
                    if (is_file($file_path_from)) {
                        $found_one = true;

                        $comcode = cms_file_get_contents_safe($file_path_from, FILE_READ_LOCK | FILE_READ_BOM);
                        $comcode_new = $this->update_comcode($comcode, $the_zone . ':' . $the_page);

                        cms_file_put_contents_safe($file_path_to, $comcode_new, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE | FILE_WRITE_BOM);
                    }
                }

                if (!$found_one) {
                    continue;
                }

                $GLOBALS['SITE_DB']->query_insert('comcode_pages', [
                    'the_zone' => $the_zone,
                    'the_page' => $the_page,
                    'p_parent_page' => $row['p_parent_page'],
                    'p_validated' => $row['p_validated'],
                    'p_edit_date' => $row['p_edit_date'],
                    'p_add_date' => $row['p_add_date'],
                    'p_submitter' => $p_submitter,
                    'p_show_as_edit' => $row['p_show_as_edit'],
                    'p_include_on_sitemap' => $row['p_include_on_sitemap'],
                    'p_order' => $row['p_order'],
                ]);

                import_id_remap_put('comcode_page', $row['the_zone'] . ':' . $row['the_page'], 1);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'comcode_page', null);
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'comcode_page', null);
        $this->_import_content_reviews($db, $table_prefix, 'comcode_page', null);
    }

    /**
     * Import custom tasks.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_staff_checklist_cus_tasks(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('staff_checklist_cus_tasks', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('staff_checklist_cus_task', $row['id'])) {
                continue;
            }

            $id_new = $GLOBALS['SITE_DB']->query_insert('staff_checklist_cus_tasks', [
                'task_title' => $row['task_title'],
                'add_date' => $row['add_date'],
                'recur_interval' => $row['recur_interval'],
                'recur_every' => $row['recur_every'],
                'done_time' => $row['done_time'],
            ], true);

            import_id_remap_put('staff_checklist_cus_task', strval($row['id']), $id_new);
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_wordfilter(object $db, string $table_prefix, string $file_base)
    {
        require_code('wordfilter');

        $rows = $db->query_select('wordfilter', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            add_wordfilter_word($row['word'], $row['w_replacement'], $row['w_match_type']);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_calendar(object $db, string $table_prefix, string $file_base)
    {
        require_code('calendar2');

        $rows = $db->query_select('calendar_types', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('event_type', strval($row['id']))) {
                continue;
            }

            if ($row['id'] == db_get_first_id()) { // System command
                import_id_remap_put('event_type', strval($row['id']), db_get_first_id());
                continue;
            }

            $id_new = add_event_type($this->get_lang_string($db, $row['t_title']), $row['t_logo'], $row['t_external_feed_url']);

            import_id_remap_put('event_type', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'calendar_type', 'event_type');
        $this->_import_content_reviews($db, $table_prefix, 'calendar_type', 'event_type');

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('calendar_interests', ['*'], [], '', $max, $start, true);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                $member_id = $on_same_msn ? $row['i_member_id'] : import_id_remap_get('member', strval($row['i_member_id']), true);
                if ($member_id === null) {
                    continue;
                }
                $type = import_id_remap_get('event_type', strval($row['t_type']), true);
                if ($type === null) {
                    continue;
                }
                $GLOBALS['SITE_DB']->query_delete('calendar_interests', ['i_member_id' => $member_id, 't_type' => $type], '', 1);
                $GLOBALS['SITE_DB']->query_insert('calendar_interests', ['i_member_id' => $member_id, 't_type' => $type]);

                // Cannot mark as anything done as there are no checks if something was updated
                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $event_rows = $db->query_select('calendar_events', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($event_rows);
            foreach ($event_rows as $row) {
                if (import_check_if_imported('event', strval($row['id']))) {
                    continue;
                }

                $submitter = $on_same_msn ? $row['e_submitter'] : import_id_remap_get('member', strval($row['e_submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $member_calendar = $on_same_msn ? $row['e_member_calendar'] : import_id_remap_get('member', strval($row['e_member_calendar']), true);
                if ($member_calendar === null) {
                    $member_calendar = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $type = import_id_remap_get('event_type', strval($row['e_type']), true);
                if ($type === null) {
                    continue;
                }

                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];

                $regions = collapse_1d_complexity('region', $db->query_select('content_regions', ['region'], ['content_type' => 'event', 'content_id' => strval($row['id'])]));

                $id_new = add_calendar_event($type, $row['e_recurrence'], $row['e_recurrences'], $row['e_seg_recurrences'], $this->get_lang_string($db, $row['e_title']), $this->get_lang_string($db, $row['e_content']), $row['e_priority'], $row['e_start_year'], $row['e_start_month'], $row['e_start_day'], $row['e_start_monthly_spec_type'], $row['e_start_hour'], $row['e_start_minute'], $row['e_end_year'], $row['e_end_month'], $row['e_end_day'], $row['e_end_monthly_spec_type'], $row['e_end_hour'], $row['e_end_minute'], $row['e_timezone'], $row['e_do_timezone_conv'], $member_calendar, $row['validated'], $row['allow_rating'], $row['allow_comments'], $row['allow_trackbacks'], $row['notes'], $submitter, $row['e_views'], $row['e_add_date'], $row['e_edit_date'], $id, '', '', $regions);

                $this->_import_content_privacy($db, 'event', strval($row['id']), strval($id_new));

                import_id_remap_put('event', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($event_rows !== null) && !empty($event_rows));
        $this->_import_alternative_ids($db, 'event', 'event');
        $this->_import_review_supplement($db, $table_prefix, 'events', 'event');
        $this->_import_content_reviews($db, $table_prefix, 'event', 'event');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'event', 'event');

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('calendar_reminders', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('calendar_reminder', strval($row['id']))) {
                    continue;
                }

                $event = import_id_remap_get('event', strval($row['e_id']), true);
                if ($event === null) {
                    continue;
                }
                $member_id = $on_same_msn ? $row['n_member_id'] : import_id_remap_get('member', strval($row['n_member_id']), true);
                if ($member_id === null) {
                    continue;
                }
                $id_new = $GLOBALS['SITE_DB']->query_insert('calendar_reminders', ['e_id' => $event, 'n_member_id' => $member_id, 'n_seconds_before' => $row['n_seconds_before']], true);

                import_id_remap_put('calendar_reminder', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        require_code('calendar');

        $max = 200;
        $start = 0;
        do {
            $event_rows = $db->query_select('calendar_events', ['*'], [], 'ORDER BY id', $max, $start);
            foreach ($event_rows as $row) {
                regenerate_event_reminder_jobs(import_id_remap_get('event', strval($row['id'])));

                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_redirects(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('redirects', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $GLOBALS['SITE_DB']->query_insert('redirects', $row, false, true); // Allow failure if row already there

            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_themes(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query('SELECT * FROM ' . $table_prefix . 'theme_images WHERE url LIKE \'' . db_encode_like('%/images\_custom/%') . '\'');
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (!is_dir(get_file_base() . '/themes/' . $row['theme']) && !is_dir(get_custom_file_base() . '/themes/' . $row['theme'])) {
                continue;
            }

            $GLOBALS['SITE_DB']->query_delete('theme_images', $row, '', 1);

            $import = $this->_import_upload($row['url'], $file_base);
            if ($import === false) {
                attach_message('Importing of custom theme image ' . escape_html($row['url']) . ' skipped as it is broken.', 'warn', false, true);
                i_timed_refresh();
                continue;
            }

            $GLOBALS['SITE_DB']->query_insert('theme_images', $row, false, true); // Allow failure if row already there

            i_timed_refresh();
        }
        Self_learning_cache::erase_smart_cache();
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_support_tickets(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('ticket_types', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('ticket_type', strval($row['id']))) {
                continue;
            }

            $map = [
                'search_faq' => $row['search_faq'],
                'guest_emails_mandatory' => $row['guest_emails_mandatory'],
            ];
            $map += insert_lang('ticket_type_name', $this->get_lang_string($db, $row['ticket_type_name']), 1);
            $ticket_type_id = $GLOBALS['SITE_DB']->query_insert('ticket_types', $map, true);
            import_id_remap_put('ticket_type', strval($row['id']), $ticket_type_id);
        }
        $this->_import_alternative_ids($db, 'ticket_type', 'ticket_type');

        $rows = $db->query_select('tickets', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $topic_id = import_id_remap_get('topic', strval($row['topic_id']), true);
            if ($topic_id === null) {
                continue;
            }
            $forum_id = import_id_remap_get('forum', strval($row['forum_id']), true);
            if ($forum_id === null) {
                continue;
            }
            $row['topic_id'] = strval($topic_id);
            $row['forum_id'] = strval($forum_id);

            $GLOBALS['SITE_DB']->query_delete('tickets', ['ticket_id' => $row['ticket_id']]);
            $GLOBALS['SITE_DB']->query_insert('tickets', $row);

            i_timed_refresh();
        }

        $this->_import_ticket_extra_access($db, $table_prefix);
        $this->_import_ticket_known_emailers($db, $table_prefix);
    }

    /**
     * Import ticket extra access.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     */
    protected function _import_ticket_extra_access(object $db, string $table_prefix)
    {
        $rows = $db->query_select('ticket_extra_access', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $member_id = import_id_remap_get('member', strval($row['member_id']), true);
            if ($member_id === null) {
                continue;
            }
            $row['member_id'] = $member_id;

            $GLOBALS['SITE_DB']->query_insert('ticket_extra_access', $row, false, true); // Allow failure if the row already exists

            i_timed_refresh();
        }
    }

    /**
     * Imports ticket known e-mailers.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     */
    protected function _import_ticket_known_emailers(object $db, string $table_prefix)
    {
        $rows = $db->query_select('ticket_known_emailers', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $member_id = import_id_remap_get('member', strval($row['member_id']), true);
            if ($member_id === null) {
                continue;
            }
            $row['member_id'] = $member_id;

            $GLOBALS['SITE_DB']->query_insert('ticket_known_emailers', $row, false, true); // Allow failure if the row already exists

            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_useronline_tracking(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('usersonline_track', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $GLOBALS['SITE_DB']->query_insert('usersonline_track', $row, false, true); // Allow failure if row already there

            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_ip_bans(object $db, string $table_prefix, string $file_base)
    {
        require_code('failure');

        $rows = $db->query_select('banned_ip', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $change = add_ip_ban($row['ip'], $row['i_descrip'], $row['i_ban_until'], $row['i_ban_positive'] == 1, true);

            if ($change) {
                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
            }
            i_timed_refresh();
        }

        $rows = $db->query_select('unbannable_ip', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $GLOBALS['SITE_DB']->query_insert('unbannable_ip', $row, false, true); // Allow failure if row already exists

            i_timed_refresh();
        }

        $rows = $db->query_select('usersubmitban_member', ['*']);
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            $member_id = $on_same_msn ? $row['the_member'] : import_id_remap_get('member', strval($row['the_member']), true);
            if ($member_id === null) {
                continue;
            }
            $GLOBALS['SITE_DB']->query_insert('usersubmitban_member', ['the_member' => $member_id], false, true); // Allow failure if row already exists

            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_zones(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('zones', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('zones', 'zone_name', ['zone_name' => $row['zone_name']]);
            if ($test === null) {
                $old_title = $this->get_lang_string($db, $row['zone_title']);
                $row = insert_lang('zone_title', $old_title, 1) + $row;
                $row = insert_lang('zone_header_text', $this->get_lang_string($db, $row['zone_header_text']), 1) + $row;

                $GLOBALS['SITE_DB']->query_insert('zones', $row);

                require_code('zones2');
                make_zone_directory($row['zone_name']);

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
            }

            i_timed_refresh();
        }
        $this->_import_alternative_ids($db, 'zone', null);
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_catalogues(object $db, string $table_prefix, string $file_base)
    {
        require_code('catalogues2');

        $rows = $db->query_select('catalogues', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            unset($row['c_description__text_parsed']);
            unset($row['c_description__source_user']);

            if (import_check_if_imported('catalogue', $row['c_name'])) {
                continue;
            }

            $name = $row['c_name'];

            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_name', ['c_name' => $name]);
            if ($test === null) {
                $row = insert_lang('c_title', $this->get_lang_string($db, $row['c_title']), 2) + $row;
                $row = insert_lang_comcode('c_description', $this->get_lang_string($db, $row['c_description']), 2) + $row;
                $GLOBALS['SITE_DB']->query_insert('catalogues', ['c_name' => $name] + $row);
                import_id_remap_put('catalogue', $row['c_name'], 1);

                $rows2 = $db->query_select('catalogue_fields', ['*'], ['c_name' => $row['c_name']]);
                foreach ($rows2 as $row2) {
                    if (import_check_if_imported('catalogue_field', strval($row2['id']))) {
                        continue;
                    }

                    $row2 = insert_lang('cf_name', $this->get_lang_string($db, $row2['cf_name']), 2) + $row2;
                    $row2 = insert_lang('cf_description', $this->get_lang_string($db, $row2['cf_description']), 2) + $row2;

                    $old_id = $row2['id'];
                    unset($row2['id']);

                    $id_new = $GLOBALS['SITE_DB']->query_insert('catalogue_fields', ['c_name' => $name] + $row2, true);

                    import_id_remap_put('catalogue_field', $old_id, $id_new);
                }
            } else {
                attach_message(do_lang_tempcode('CANNOT_MERGE_CATALOGUES', escape_html($row['c_name'])), 'warn');
            }
        }
        $this->_import_alternative_ids($db, 'catalogue', null);
        $this->_import_content_reviews($db, $table_prefix, 'catalogue', null);
        $this->_import_review_supplement($db, $table_prefix, 'catalogues', 'catalogue_entry');

        $rows = $db->query_select('catalogue_categories', ['*'], [], 'ORDER BY id');
        $this->_fix_comcode_ownership($rows);
        $id = null;
        foreach ($rows as $row) {
            if (import_check_if_imported('catalogue_category', strval($row['id']))) {
                continue;
            }

            $is_tree = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogues', 'c_is_tree', ['c_name' => $row['c_name']]);
            if ($is_tree === null) {
                i_timed_refresh();
                continue;
            }

            if (($row['cc_parent_id'] === null) && ($is_tree == 1)) {
                $real_root = $GLOBALS['SITE_DB']->query_select_value_if_there('catalogue_categories', 'id', ['cc_parent_id' => null, 'c_name' => $row['c_name']]);
                if ($real_root !== null) {
                    import_id_remap_put('catalogue_category', strval($row['id']), $real_root);
                    continue;
                }
            }

            $import = $this->_import_upload($row['rep_image'], $file_base);
            if ($import === false) {
                $row['rep_image'] = '';
                attach_message('Rep image for catalogue category ' . escape_html($this->get_lang_string($db, $row['cc_title'])) . ' in catalogue ' . escape_html($row['c_name']) . ' needs fixed.', 'notice', false, true);
            }

            $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];

            $rep_image = $row['rep_image'];

            $id_new = actual_add_catalogue_category($row['c_name'], $this->get_lang_string($db, $row['cc_title']), $this->get_lang_string($db, $row['cc_description']), $row['cc_notes'], ($row['cc_parent_id'] === null) ? null : -$row['cc_parent_id'], $rep_image, $row['cc_move_days_lower'], $row['cc_move_days_higher'], $row['cc_move_target_id'], $row['cc_add_date'], $id);

            import_id_remap_put('catalogue_category', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'catalogue_category', 'catalogue_category');
        $this->_import_content_reviews($db, $table_prefix, 'catalogue_category', 'catalogue_category');

        $rows = $GLOBALS['SITE_DB']->query('SELECT id,cc_parent_id FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'catalogue_categories WHERE cc_parent_id<0');
        foreach ($rows as $row) {
            if ($row['cc_parent_id'] !== null) {
                $parent_id = import_id_remap_get('catalogue_category', strval(-$row['cc_parent_id']), true);
            } else {
                $parent_id = null;
            }
            $GLOBALS['SITE_DB']->query_update('catalogue_categories', ['cc_parent_id' => $parent_id], ['id' => $row['id']], '', 1);

            i_timed_refresh();
        }

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('catalogue_entries', ['*'], [], 'ORDER BY id', $max, $start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_id_remap_get('catalogue_entry', strval($row['id']), true) !== null) {
                    continue;
                }
                $category_id = import_id_remap_get('catalogue_category', strval($row['cc_id']), true);
                if ($category_id === null) {
                    continue;
                }
                $map = [];

                // Tedious...
                foreach (['long', 'short', 'float', 'integer'] as $table) {
                    $rows2 = $db->query_select('catalogue_efv_' . $table, ['*'], ['ce_id' => $row['id']], '', null, 0, true);
                    foreach ($rows2 as $row2) {
                        $remapped = import_id_remap_get('catalogue_field', strval($row2['cf_id']), true);
                        if ($remapped === null) {
                            continue;
                        }

                        $value = $row2['cv_value'];
                        if (is_integer($value)) {
                            $value = strval($value);
                        } elseif (is_float($value)) {
                            $value = float_to_raw_string($value);
                        } else {
                            // Determine if this is a field for which we may need to potentially import uploads
                            $field_type = $db->query_select_value('catalogue_fields', 'cf_type', ['id' => $row2['cf_id']]);

                            if (in_array($field_type, ['video', 'picture', 'upload', 'video_multi', 'picture_multi', 'upload_multi'])) { // FUDGE
                                $lines = explode("\n", $value); // Handle multi-upload fields
                                $new_value = []; // We are going to try pruning out bad files from the field value
                                foreach ($lines as $path) {
                                    if (strpos($path, '::') !== false) { // Could have special metadata (e.g. videos)
                                        $parts = explode('::', $path, 3); // Could have up to 5 parts, but we only need the first 2 (we dump the remaining into the third element).
                                        if (count($parts) >= 2) {
                                            $import = $this->_import_upload($parts[0], $file_base); // URL
                                            if ($import === false) {
                                                attach_message('Catalogue entry ID ' . escape_html(strval($row['id'])) . ' (from old site) needs fixed; field ID ' . strval($row2['cf_id']) . ' (from old site) has an invalid file.', 'warn', false, true);
                                            } else {
                                                $import = $this->_import_upload($parts[1], $file_base); // Thumb URL
                                                if ($import === false) {
                                                    attach_message('Catalogue entry ID ' . escape_html(strval($row['id'])) . ' (from old site) needs fixed; field ID ' . strval($row2['cf_id']) . ' (from old site) has an invalid thumbnail.', 'warn', false, true);
                                                    $new_value[] = $parts[0];
                                                } else {
                                                    $new_value[] = $parts[0] . '::' . $parts[1];
                                                }
                                            }
                                        } else {
                                            $import = $this->_import_upload($path, $file_base);
                                            if ($import === false) {
                                                attach_message('Catalogue entry ID ' . escape_html(strval($row['id'])) . ' (from old site) needs fixed; field ID ' . strval($row2['cf_id']) . ' (from old site) has an invalid file.', 'warn', false, true);
                                            } else {
                                                $new_value[] = $path;
                                            }
                                        }
                                    } else {
                                        $import = $this->_import_upload($path, $file_base);
                                        if ($import === false) {
                                            attach_message('Catalogue entry ID ' . escape_html(strval($row['id'])) . ' (from old site) needs fixed; field ID ' . strval($row2['cf_id']) . ' (from old site) has an invalid file.', 'warn', false, true);
                                        } else {
                                            $new_value[] = $path;
                                        }
                                    }
                                }
                                $value = implode("\n", $new_value);
                            }
                        }

                        $map[$remapped] = $value;
                    }
                }
                $rows2 = $db->query_select('catalogue_efv_long_trans', ['*'], ['ce_id' => $row['id']]);
                foreach ($rows2 as $row2) {
                    $remapped = import_id_remap_get('catalogue_field', strval($row2['cf_id']), true);
                    if ($remapped === null) {
                        continue;
                    }
                    $map[$remapped] = $this->get_lang_string($db, $row2['cv_value']);
                }
                $rows2 = $db->query_select('catalogue_efv_short_trans', ['*'], ['ce_id' => $row['id']]);
                foreach ($rows2 as $row2) {
                    $remapped = import_id_remap_get('catalogue_field', strval($row2['cf_id']), true);
                    if ($remapped === null) {
                        continue;
                    }
                    $map[$remapped] = $this->get_lang_string($db, $row2['cv_value']);
                }
                $submitter = $on_same_msn ? $row['ce_submitter'] : import_id_remap_get('member', strval($row['ce_submitter']), true);
                if ($submitter === null) {
                    $submitter = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];
                $id_new = actual_add_catalogue_entry($category_id, $row['ce_validated'], $row['notes'], $row['allow_rating'], $row['allow_comments'], $row['allow_trackbacks'], $map, $row['ce_add_date'], $submitter, $row['ce_edit_date'], $row['ce_views'], $id);

                $this->_import_content_privacy($db, 'catalogue_entry', strval($row['id']), strval($id_new));

                import_id_remap_put('catalogue_entry', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'catalogue_entry', 'catalogue_entry');
        $this->_import_content_reviews($db, $table_prefix, 'catalogue_entry', 'catalogue_entry');

        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'catalogue', null);
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'catalogue_category', 'catalogue_category');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_chat_rooms(object $db, string $table_prefix, string $file_base)
    {
        require_code('chat2');

        $rows = $db->query_select('chat_rooms', ['*'], ['is_im' => 0], 'ORDER BY id', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            if (import_check_if_imported('chat', strval($row['id']))) {
                continue;
            }
            $id_old = $row['id'];

            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_rooms', 'id', ['room_name' => $row['room_name']]);
            if ($test !== null) {
                continue;
            }

            $row['room_owner'] = $on_same_msn ? $row['room_owner'] : import_id_remap_get('member', strval($row['room_owner']), true);
            $row = insert_lang('c_welcome', $this->get_lang_string($db, $row['c_welcome']), 2) + $row;

            $_disallow_list_groups = explode(',', $row['disallow_list_groups']);
            $row['disallow_list_groups'] = '';
            foreach ($_disallow_list_groups as $x) {
                $row['disallow_list_groups'] .= ($on_same_msn ? $x : strval(import_id_remap_get('group', $x, true))) . ',';
            }
            $_allow_list_groups = explode(',', $row['allow_list']);
            $row['allow_list_groups'] = '';
            foreach ($_allow_list_groups as $x) {
                $row['allow_list_groups'] .= ($on_same_msn ? $x : strval(import_id_remap_get('group', $x, true))) . ',';
            }
            $_disallow_list = explode(',', $row['disallow_list']);
            $row['disallow_list'] = '';
            foreach ($_disallow_list as $x) {
                $row['disallow_list'] .= ($on_same_msn ? $x : strval(import_id_remap_get('member', $x, true))) . ',';
            }
            $_allow_list = explode(',', $row['allow_list']);
            $row['allow_list'] = '';
            foreach ($_allow_list as $x) {
                $row['allow_list'] .= ($on_same_msn ? $x : strval(import_id_remap_get('member', $x, true))) . ',';
            }

            if (get_param_integer('keep_preserve_ids', 0) == 0) {
                unset($row['id']);
            }
            $id_new = $GLOBALS['SITE_DB']->query_insert('chat_rooms', $row, true);

            import_id_remap_put('chat', strval($id_old), $id_new);
        }
        $this->_import_alternative_ids($db, 'chat', 'chat');
        $this->_import_content_reviews($db, $table_prefix, 'chat', 'chat');

        $rows = $db->query_select('chat_blocking', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            $member_blocker = import_id_remap_get('member', strval($row['member_blocker']), true);
            $member_blocked = import_id_remap_get('member', strval($row['member_blocked']), true);
            if ($member_blocker === null) {
                continue;
            }
            if ($member_blocked === null) {
                continue;
            }
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_blocking', 'member_blocker', ['member_blocker' => $member_blocker, 'member_blocked' => $member_blocked]);
            if ($test !== null) {
                i_timed_refresh();
                continue;
            }
            $GLOBALS['SITE_DB']->query_insert('chat_blocking', ['member_blocker' => $member_blocker, 'member_blocked' => $member_blocked, 'date_and_time' => $row['date_and_time']]);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }

        $rows = $db->query_select('chat_friends', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $member_likes = import_id_remap_get('member', strval($row['member_likes']), true);
            $member_liked = import_id_remap_get('member', strval($row['member_liked']), true);
            if ($member_likes === null) {
                continue;
            }
            if ($member_liked === null) {
                continue;
            }
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_friends', 'member_likes', ['member_likes' => $member_likes, 'member_liked' => $member_liked]);
            if ($test !== null) {
                i_timed_refresh();
                continue;
            }
            $GLOBALS['SITE_DB']->query_insert('chat_friends', ['member_likes' => $member_likes, 'member_liked' => $member_liked, 'date_and_time' => $row['date_and_time']]);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }

        $rows = $db->query_select('chat_sound_effects', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        $on_same_msn = ($this->on_same_msn($file_base));
        foreach ($rows as $row) {
            $s_member = import_id_remap_get('member', strval($row['s_member']), true);
            if ($s_member === null) {
                continue;
            }
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('chat_sound_effects', 's_member', ['s_member' => $s_member, 's_effect_id' => $row['s_effect_id']]);
            if ($test !== null) {
                i_timed_refresh();
                continue;
            }

            $import = $this->_import_upload($row['s_url'], $file_base);
            if ($import === false) {
                attach_message('Importing of chat sound ' . escape_html(strval($row['s_effect_id'])) . ' for member ' . escape_html(strval($s_member)) . ' skipped; the file is broken / invalid.', 'warn', false, true);
                i_timed_refresh();
                continue;
            }

            $GLOBALS['SITE_DB']->query_insert('chat_sound_effects', ['s_member' => $s_member, 's_effect_id' => $row['s_effect_id'], 's_url' => $row['s_url']]);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_awards(object $db, string $table_prefix, string $file_base)
    {
        $content_types = [];

        $rows = $db->query_select('award_types', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            unset($row['a_description__text_parsed']);
            unset($row['a_description__source_user']);

            $content_types[$row['id']] = $row['a_content_type'];

            if (import_check_if_imported('award_type', strval($row['id']))) {
                continue;
            }

            $row = insert_lang('a_title', $this->get_lang_string($db, $row['a_title']), 2) + $row;
            $row = insert_lang_comcode('a_description', $this->get_lang_string($db, $row['a_description']), 2) + $row;

            $id_old = $row['id'];
            if (get_param_integer('keep_preserve_ids', 0) == 0) {
                unset($row['id']);
            }
            $id_new = $GLOBALS['SITE_DB']->query_insert('award_types', $row, true);

            import_id_remap_put('award_type', strval($id_old), $id_new);
        }
        $this->_import_alternative_ids($db, 'award_type', 'award_type');

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('award_archive', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('awarded', strval($row['date_and_time']))) {
                    continue;
                }

                $row['member_id'] = $on_same_msn ? $row['member_id'] : import_id_remap_get('member', strval($row['member_id']), true);
                if ($row['member_id'] === null) {
                    $row['member_id'] = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $row['content_id'] = import_id_remap_get($content_types[$row['a_type_id']], $row['content_id'], true);
                if ($row['content_id'] === null) {
                    continue;
                }
                $row['a_type_id'] = import_id_remap_get('award_type', strval($row['a_type_id']));
                $GLOBALS['SITE_DB']->query_insert('award_archive', $row);

                import_id_remap_put('awarded', strval($row['date_and_time']), $row['date_and_time']);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_filedump(object $db, string $table_prefix, string $file_base)
    {
        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('filedump', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows);
            $on_same_msn = ($this->on_same_msn($file_base));
            foreach ($rows as $row) {
                if (import_check_if_imported('filedump', strval($row['id']))) {
                    continue;
                }

                $import = $this->_import_upload('uploads/filedump' . $row['subpath'] . $row['name'], $file_base);
                if ($import === false) {
                    attach_message('Importing of filedump item (uploads/filedump) ' . escape_html($row['subpath'] . $row['name']) . ' skipped; the file is broken / invalid.', 'warn', false, true);
                    import_id_remap_put('filedump', strval($row['id']), 0);
                    continue;
                }

                $row = insert_lang('the_description', $this->get_lang_string($db, $row['the_description']), 2) + $row;
                $row['the_member'] = $on_same_msn ? $row['the_member'] : import_id_remap_get('member', strval($row['the_member']), true);
                if ($row['the_member'] === null) {
                    $row['the_member'] = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }
                $id_new = $GLOBALS['SITE_DB']->query_insert('filedump', $row, true);

                import_id_remap_put('filedump', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_permissions(object $db, string $table_prefix, string $file_base)
    {
        $remap = [
        ];

        // NB: this import is very resource intensive but we don't use remapping, therefore we have to use pseudo-remaps on each stage for the refreshing.
        if (!import_check_if_imported('group_privileges', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('group_privileges', ['*'], [], '', $max, $start);
                $on_same_msn = ($this->on_same_msn($file_base));
                foreach ($rows as $row) {
                    if ($row['the_page'] == 'cedi') {
                        $row['the_page'] = 'wiki';
                    }

                    $row['group_id'] = $on_same_msn ? $row['group_id'] : import_id_remap_get('group', strval($row['group_id']), true);
                    if ($row['group_id'] === null) {
                        continue;
                    }

                    if (isset($remap[$row['privilege']])) {
                        $row['privilege'] = $remap[$row['privilege']];
                    }

                    $row_temp = $row;

                    $_db = $GLOBALS[((($row['module_the_name'] == 'forums') || ($row['module_the_name'] == 'topics')) && (get_forum_type() == 'cns')) ? 'FORUM_DB' : 'SITE_DB'];

                    $_db->query_delete('group_privileges', $row_temp, '', 1);

                    if ($row['category_name'] != '') {
                        $import_type = $row['module_the_name'];
                        $str = false;
                        switch ($import_type) {
                            case 'galleries':
                                $import_type = 'gallery';
                                $str = true;
                                break;
                            case 'downloads':
                                $import_type = 'download_category';
                                break;
                            case 'calendar':
                                $import_type = 'event_type';
                                break;
                            case 'catalogues_catalogue':
                                $import_type = 'catalogue';
                                $str = true;
                                break;
                            case 'catalogues_category':
                                $import_type = 'catalogue_category';
                                break;
                            case 'forums':
                                $import_type = 'forum';
                                break;
                            case 'topics':
                                $import_type = 'topic';
                                break;
                            case 'wiki_page':
                                $import_type = 'wiki_page';
                                break;
                            case 'award':
                                $import_type = 'award_type';
                                break;
                            case 'news':
                                $import_type = 'news_category';
                                break;
                            case 'tickets':
                                $import_type = 'ticket_type';
                                break;
                        }
                        if ($str) {
                            $id_new = import_id_remap_get($import_type, $row['category_name'], true);
                            if ($id_new === null) {
                                continue;
                            }
                            $row['category_name'] = strval($id_new);
                        }
                    }
                    $_db->query_insert('group_privileges', $row);

                    // Cannot mark something done because we do not test if something was already updated
                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('group_privileges', strval(0), 0);
        }

        if (!import_check_if_imported('group_page_access', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('group_page_access', ['*'], [], '', $max, $start);
                foreach ($rows as $row) {
                    if ($row['page_name'] == 'cedi') {
                        $row['page_name'] = 'wiki';
                    }
                    if ($row['page_name'] == 'cms_cedi') {
                        $row['page_name'] = 'cms_wiki';
                    }

                    $row['group_id'] = $on_same_msn ? $row['group_id'] : import_id_remap_get('group', strval($row['group_id']), true);
                    if ($row['group_id'] === null) {
                        continue;
                    }
                    $GLOBALS['SITE_DB']->query_delete('group_page_access', $row, '', 1);
                    $GLOBALS['SITE_DB']->query_insert('group_page_access', $row);

                    // Cannot mark something done because we do not test if something was already updated
                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('group_page_access', strval(0), 0);
        }

        if (!import_check_if_imported('group_zone_access', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('group_zone_access', ['*'], [], '', $max, $start);
                foreach ($rows as $row) {
                    $row['group_id'] = $on_same_msn ? $row['group_id'] : import_id_remap_get('group', strval($row['group_id']), true);
                    if ($row['group_id'] === null) {
                        continue;
                    }
                    $GLOBALS['SITE_DB']->query_delete('group_zone_access', $row, '', 1);
                    $GLOBALS['SITE_DB']->query_insert('group_zone_access', $row);

                    // Cannot mark something done because we do not test if something was already updated
                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('group_zone_access', strval(0), 0);
        }

        if (!import_check_if_imported('group_category_access', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('group_category_access', ['*'], [], '', $max, $start);
                foreach ($rows as $row) {
                    $row['group_id'] = $on_same_msn ? $row['group_id'] : import_id_remap_get('group', strval($row['group_id']), true);
                    if ($row['group_id'] === null) {
                        continue;
                    }
                    if (is_numeric($row['category_name'])) {
                        $import_type = $row['module_the_name'];
                        $str = false;
                        switch ($import_type) {
                            case 'galleries':
                                $import_type = 'gallery';
                                $str = true;
                                break;
                            case 'downloads':
                                $import_type = 'download_category';
                                break;
                            case 'calendar':
                                $import_type = 'event_type';
                                break;
                            case 'catalogues_catalogue':
                                $import_type = 'catalogue';
                                $str = true;
                                break;
                            case 'catalogues_category':
                                $import_type = 'catalogue_category';
                                break;
                            case 'forums':
                                $import_type = 'forum';
                                break;
                            case 'topics':
                                $import_type = 'topic';
                                break;
                            case 'wiki_page':
                                $import_type = 'wiki_page';
                                break;
                            case 'award':
                                $import_type = 'award_type';
                                break;
                            case 'news':
                                $import_type = 'news_category';
                                break;
                            case 'tickets':
                                $import_type = 'ticket_type';
                                break;
                        }
                        if (!$str) {
                            $id_new = import_id_remap_get($import_type, $row['category_name'], true);
                            if ($id_new === null) {
                                continue;
                            }
                            $row['category_name'] = strval($id_new);
                        }
                    }

                    $_db = $GLOBALS[((($row['module_the_name'] == 'forums') || ($row['module_the_name'] == 'topics')) && (get_forum_type() == 'cns')) ? 'FORUM_DB' : 'SITE_DB'];

                    $_db->query_delete('group_category_access', $row, '', 1);
                    $_db->query_insert('group_category_access', $row);

                    // Cannot mark something done because we do not test if something was already updated
                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('group_category_access', strval(0), 0);
        }

        if (!import_check_if_imported('member_zone_access', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('member_zone_access', ['*'], [], '', $max, $start, true);
                if ($rows !== null) {
                    foreach ($rows as $row) {
                        $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                        if ($member_id === null) {
                            continue;
                        }
                        $test = $GLOBALS['SITE_DB']->query_select_value_if_there('member_zone_access', 'zone_name', ['member_id' => $member_id, 'zone_name' => $row['zone_name']]);
                        if ($test !== null) {
                            continue;
                        }

                        $GLOBALS['SITE_DB']->query_insert('member_zone_access', ['zone_name' => $row['zone_name'], 'member_id' => $member_id, 'active_until' => $row['active_until']], false, true); // Allow errors in case row already exists

                        // Cannot mark something done because we do not test if something was already updated
                        i_timed_refresh();
                    }
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('member_zone_access', strval(0), 0);
        }

        if (!import_check_if_imported('member_category_access', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('member_category_access', ['*'], [], '', $max, $start, true);
                if ($rows !== null) {
                    foreach ($rows as $row) {
                        $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                        if ($member_id === null) {
                            continue;
                        }

                        $_db = $GLOBALS[((($row['module_the_name'] == 'forums') || ($row['module_the_name'] == 'topics')) && (get_forum_type() == 'cns')) ? 'FORUM_DB' : 'SITE_DB'];

                        $test = $_db->query_select_value_if_there('member_category_access', 'module_the_name', ['member_id' => $member_id, 'module_the_name' => $row['module_the_name'], 'category_name' => $row['category_name']]);
                        if ($test !== null) {
                            continue;
                        }

                        $_db->query_insert('member_category_access', ['module_the_name' => $row['module_the_name'], 'category_name' => $row['category_name'], 'member_id' => $member_id, 'active_until' => $row['active_until']], false, true); // Allow errors in case row already exists

                        // Cannot mark something done because we do not test if something was already updated
                        i_timed_refresh();
                    }
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('member_category_access', strval(0), 0);
        }

        if (!import_check_if_imported('member_page_access', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query('SELECT * FROM ' . $table_prefix . 'member_page_access', $max, $start);
                foreach ($rows as $row) {
                    $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                    if ($member_id === null) {
                        continue;
                    }
                    $test = $GLOBALS['SITE_DB']->query_select_value_if_there('member_page_access', 'page_name', ['member_id' => $member_id, 'page_name' => $row['page_name']]);
                    if ($test !== null) {
                        continue;
                    }
                    $zone_name = $row['zone_name'];
                    $page_name = $row['page_name'];

                    $GLOBALS['SITE_DB']->query_insert('member_page_access', ['page_name' => $page_name, 'zone_name' => $zone_name, 'member_id' => $member_id, 'active_until' => $row['active_until']], false, true); // Allow errors in case row already exists

                    // Cannot mark something done because we do not test if something was already updated
                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('member_page_access', strval(0), 0);
        }

        if (!import_check_if_imported('member_privileges', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('member_privileges', ['*'], [], '', $max, $start, true);
                if ($rows !== null) {
                    foreach ($rows as $row) {
                        $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                        if ($member_id === null) {
                            continue;
                        }
                        if (is_numeric($row['category_name'])) {
                            $import_type = $row['module_the_name'];
                            $is_str = false;
                            switch ($import_type) {
                                case 'galleries':
                                    $import_type = 'gallery';
                                    $str = true;
                                    break;
                                case 'downloads':
                                    $import_type = 'download_category';
                                    break;
                                case 'calendar':
                                    $import_type = 'event_type';
                                    break;
                                case 'catalogues_catalogue':
                                    $import_type = 'catalogue';
                                    $str = true;
                                    break;
                                case 'catalogues_category':
                                    $import_type = 'catalogue_category';
                                    break;
                                case 'forums':
                                    $import_type = 'forum';
                                    break;
                                case 'topics':
                                    $import_type = 'topic';
                                    break;
                                case 'wiki_page':
                                    $import_type = 'wiki_page';
                                    break;
                                case 'award':
                                    $import_type = 'award_type';
                                    break;
                                case 'news':
                                    $import_type = 'news_category';
                                    break;
                                case 'tickets':
                                    $import_type = 'ticket_type';
                                    break;
                            }
                            if (!$str) {
                                $id_new = import_id_remap_get($import_type, $row['category_name'], true);
                                if ($id_new === null) {
                                    continue;
                                }
                                $row['category_name'] = strval($id_new);
                            }
                        }

                        $_db = $GLOBALS[((($row['module_the_name'] == 'forums') || ($row['module_the_name'] == 'topics')) && (get_forum_type() == 'cns')) ? 'FORUM_DB' : 'SITE_DB'];

                        $row['member_id'] = $member_id;
                        $_db->query_delete('member_privileges', $row);
                        $_db->query_insert('member_privileges', $row);

                        // Cannot mark something done because we do not test if something was already updated
                        i_timed_refresh();
                    }
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('member_privileges', strval(0), 0);
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_notifications(object $db, string $table_prefix, string $file_base)
    {
        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('notifications_enabled', ['*'], [], '', $max, $start, true);
            if ($rows !== null) {
                foreach ($rows as $row) {
                    if (import_check_if_imported('notification_enabled', strval($row['id']))) {
                        continue;
                    }

                    $member_id = import_id_remap_get('member', strval($row['l_member_id']), true);
                    if ($member_id === null) {
                        continue;
                    }

                    if ($row['l_notification_code'] == 'cedi') {
                        $row['l_notification_code'] = 'wiki';
                    }

                    $_db = $GLOBALS[((substr($row['l_notification_code'], 0, 4) == 'cns_') && (get_forum_type() == 'cns')) ? 'FORUM_DB' : 'SITE_DB'];

                    $test = $_db->query_select_value_if_there('notifications_enabled', 'id', ['l_member_id' => $member_id, 'l_notification_code' => $row['l_notification_code']]);
                    if ($test !== null) {
                        import_id_remap_put('notification_enabled', $row['id'], $test);
                        continue;
                    }

                    $id_old = strval($row['id']);
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($row['id']);
                    }

                    $row['l_member_id'] = $member_id;

                    $id_new = $_db->query_insert('notifications_enabled', $row, true);
                    import_id_remap_put('notification_enabled', $id_old, $id_new);
                }
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('device_token_details', ['*'], [], '', $max, $start, true);
            if ($rows !== null) {
                foreach ($rows as $row) {
                    if (import_check_if_imported('device_token_details', strval($row['id']))) {
                        continue;
                    }

                    $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                    if ($member_id === null) {
                        continue;
                    }

                    $id_old = strval($row['id']);
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($row['id']);
                    }

                    $row['member_id'] = $member_id;

                    $id_new = $GLOBALS['SITE_DB']->query_insert('device_token_details', $row, true);
                    import_id_remap_put('device_token_details', $id_old, $id_new);
                }
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $rows = $db->query_select('notification_lockdown', ['*'], [], '', null, 0, true);
        if ($rows !== null) {
            foreach ($rows as $row) {
                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('notification_lockdown', 'l_notification_code', ['l_notification_code' => $row['l_notification_code']]);
                if ($test !== null) {
                    continue;
                }

                $id_old = strval($row['id']);
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row['id']);
                }

                $GLOBALS['SITE_DB']->query_insert('notification_lockdown', $row, false, true); // Allow errors in case row already exists

                i_timed_refresh();
            }
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_groups(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_groups', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('group', strval($row['id']))) {
                continue;
            }

            $name = $this->get_lang_string($db, $row['g_name']);
            $id_new = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_groups', 'id', [$GLOBALS['FORUM_DB']->translate_field_ref('g_name') => $name]);
            if ($id_new === null) {
                $title = $this->get_lang_string($db, $row['g_title']);

                $id_new = cns_make_group($name, $row['g_is_default'], $row['g_is_super_admin'], $row['g_is_super_moderator'], $title, '', $row['g_promotion_target_group'], $row['g_promotion_threshold'], $row['g_promotion_approval'], ($row['g_group_lead_member'] !== null) ? -$row['g_group_lead_member'] : null, $row['g_flood_control_submit_secs'], $row['g_flood_control_access_secs'], $row['g_max_daily_upload_mb'], $row['g_max_attachments_per_post'], $row['g_max_avatar_width'], $row['g_max_avatar_height'], $row['g_max_post_length_comcode'], $row['g_max_sig_length_comcode'], $row['g_gift_points_base'], $row['g_gift_points_per_day'], $row['g_enquire_on_new_ips'], $row['g_is_presented_at_install'], $row['g_hidden'], $row['g_order'], $row['g_rank_image_pri_only'], $row['g_open_membership'], $row['g_is_private_club']);
            }

            import_id_remap_put('group', strval($row['id']), $id_new);
        }

        // Now we must fix promotion
        foreach ($rows as $row) {
            if ($row['g_promotion_target_group'] !== null) {
                $row_promotion_target = import_id_remap_get('group', strval($row['g_promotion_target_group']), true);
                if ($row_promotion_target === null) {
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_update('f_groups', ['g_promotion_target_group' => $row_promotion_target], ['id' => import_id_remap_get('group', strval($row['id']))], '', 1);

                i_timed_refresh();
            }
        }
        $this->_import_alternative_ids($db, 'group', 'group');
        $this->_import_content_reviews($db, $table_prefix, 'group', 'group');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_members(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $cpf_types = collapse_2d_complexity('id', 'cf_type', $GLOBALS['FORUM_DB']->query_select('f_custom_fields', ['id', 'cf_type']));

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('f_members', ['*'], [], 'ORDER BY id', 100, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('member', strval($row['id']))) {
                    continue;
                }

                $id_new = $GLOBALS['CNS_DRIVER']->get_member_from_username($row['m_username']);
                if ($id_new === null) {
                    $primary_group = import_id_remap_get('group', strval($row['m_primary_group']));

                    $custom_fields = [];

                    $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];

                    $timezone = $row['m_timezone_offset'];

                    $import = $this->_import_upload($row['m_photo_url'], $file_base);
                    if ($import === false) {
                        $row['m_photo_url'] = '';
                        attach_message('Photo of member ' . escape_html($row['m_username']) . ' needs fixed.', 'notice', false, true);
                    }

                    $import = $this->_import_upload($row['m_avatar_url'], $file_base);
                    if ($import === false) {
                        $row['m_avatar_url'] = '';
                        attach_message('Avatar of member ' . escape_html($row['m_username']) . ' needs fixed.', 'notice', false, true);
                    }

                    $id_new = cns_make_member(
                        $row['m_username'], // username
                        $row['m_pass_hash_salted'], // password
                        $row['m_email_address'], // email_address
                        $primary_group, // primary_group
                        null, // secondary_groups
                        $row['m_dob_day'], // dob_day
                        $row['m_dob_month'], // dob_month
                        $row['m_dob_year'], // dob_year
                        $custom_fields, // custom_fields
                        $timezone, // timezone
                        $row['m_region'], // region
                        $row['m_language'], // language
                        $row['m_theme'], // theme
                        $row['m_title'], // title
                        $row['m_photo_url'], // photo_url
                        $row['m_avatar_url'], // avatar_url
                        $this->get_lang_string($db, $row['m_signature']), // signature
                        $row['m_preview_posts'], // preview_posts
                        $row['m_reveal_age'], // reveal_age
                        $row['m_views_signatures'], // views_signatures
                        $row['m_auto_monitor_contrib_content'], // auto_monitor_contrib_content
                        $row['m_smart_topic_notification'], // smart_topic_notification
                        $row['m_mailing_list_style'], // mailing_list_style
                        $row['m_auto_mark_read'], // auto_mark_read
                        $row['m_sound_enabled'], // sound_enabled
                        $row['m_allow_emails'], // allow_emails
                        $row['m_allow_emails_from_staff'], // allow_emails_from_staff
                        $row['m_highlighted_name'], // highlighted_name
                        $row['m_pt_allow'], // pt_allow
                        $this->get_lang_string($db, $row['m_pt_rules_text']), // pt_rules_text
                        $row['m_validated'], // validated
                        $row['m_validated_email_confirm_code'], // validated_email_confirm_code
                        $row['m_probation_expiration_time'], // probation_expiration_time
                        $row['m_is_perm_banned'], // is_perm_banned
                        false, // check_correctness
                        $row['m_ip_address'], // ip_address
                        $row['m_password_compat_scheme'], // password_compatibility_scheme
                        $row['m_pass_salt'], // salt
                        $row['m_join_time'], // join_time
                        $row['m_last_visit_time'], // last_visit_time
                        $row['m_last_submit_time'], // last_submit_time
                        $row['m_profile_views'], // profile_views
                        $row['m_total_sessions'], // total_sessions
                        $id // id
                    );
                    $rows2 = $db->query_select('f_member_custom_fields', ['*'], ['mf_member_id' => $row['id']], '', 1);
                    $this->_fix_comcode_ownership($rows2);
                    if (array_key_exists(0, $rows2)) {
                        $row2 = [];
                        foreach ($rows2[0] as $key => $val) {
                            if (preg_match('#^field_\d+$#', $key) != 0) {
                                $cpf_id = import_id_remap_get('cpf', substr($key, 6), true);
                                if ($cpf_id === null) {
                                    continue;
                                }
                                $cpf_type = $cpf_types[$cpf_id];
                                if ((($cpf_type == 'short_trans') || ($cpf_type == 'long_trans'))) {
                                    unset($row2['field_' . strval($cpf_id) . '__text_parsed']);
                                    unset($row2['field_' . strval($cpf_id) . '__source_user']);
                                    $row2 = insert_lang_comcode('field_' . strval($cpf_id), $this->get_lang_string($db, $val), 3) + $row2;
                                } else {
                                    $row2['field_' . strval($cpf_id)] = $val;
                                }

                                // Determine if this is a field for which we may need to potentially import uploads
                                if (in_array($cpf_type, ['video', 'picture', 'upload', 'video_multi', 'picture_multi', 'upload_multi'])) { // FUDGE
                                    $lines = explode("\n", $row2['field_' . strval($cpf_id)]); // Handle multi-upload fields
                                    $new_value = []; // We are going to try pruning out bad files from the field value
                                    foreach ($lines as $path) {
                                        if (strpos($path, '::') !== false) { // Could have special metadata (e.g. videos)
                                            $parts = explode('::', $path, 3); // Could have up to 5 parts, but we only need the first 2 (we dump the remaining into the third element).
                                            if (count($parts) >= 2) {
                                                $import = $this->_import_upload($parts[0], $file_base); // URL
                                                if ($import === false) {
                                                    attach_message('Custom profile field ' . escape_html(strval($cpf_id)) . ' (from new site) for member ' . escape_html($row['m_username']) . ' has an invalid file.', 'warn', false, true);
                                                } else {
                                                    $import = $this->_import_upload($parts[1], $file_base); // Thumb URL
                                                    if ($import === false) {
                                                        attach_message('Custom profile field ' . escape_html(strval($cpf_id)) . ' (from new site) for member ' . escape_html($row['m_username']) . ' has an invalid thumbnail.', 'warn', false, true);
                                                        $new_value[] = $parts[0];
                                                    } else {
                                                        $new_value[] = $parts[0] . '::' . $parts[1];
                                                    }
                                                }
                                            } else {
                                                $import = $this->_import_upload($path, $file_base);
                                                if ($import === false) {
                                                    attach_message('Custom profile field ' . escape_html(strval($cpf_id)) . ' (from new site) for member ' . escape_html($row['m_username']) . ' has an invalid file.', 'warn', false, true);
                                                } else {
                                                    $new_value[] = $path;
                                                }
                                            }
                                        } else {
                                            $import = $this->_import_upload($path, $file_base);
                                            if ($import === false) {
                                                attach_message('Custom profile field ' . escape_html(strval($cpf_id)) . ' (from new site) for member ' . escape_html($row['m_username']) . ' has an invalid file.', 'warn', false, true);
                                            } else {
                                                $new_value[] = $path;
                                            }
                                        }
                                    }
                                    $row2['field_' . strval($cpf_id)] = implode("\n", $new_value);
                                }
                            }
                        }
                        $GLOBALS['FORUM_DB']->query_update('f_member_custom_fields', $row2, ['mf_member_id' => $id_new], '', 1);
                    }

                    // Fix some tricky dependencies that we shoved to one side
                    $GLOBALS['FORUM_DB']->query_update('f_groups', ['g_group_lead_member' => $id_new], ['g_group_lead_member' => -$row['id']]);
                    $GLOBALS['SITE_DB']->query_update('attachments', ['a_member_id' => $id_new], ['a_member_id' => -$row['id']]);
                }

                import_id_remap_put('member', strval($row['id']), $id_new);
            }

            $row_start += 200;
        } while (!empty($rows));
        $this->_import_alternative_ids($db, 'member', 'member');
        $this->_import_content_reviews($db, $table_prefix, 'member', 'member');

        // Group membership
        if (!import_check_if_imported('group_members', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('f_group_members', ['*'], [], '', $max, $start);
                $this->_fix_comcode_ownership($rows);
                foreach ($rows as $row) {
                    $row['gm_group_id'] = import_id_remap_get('group', strval($row['gm_group_id']), true);
                    if ($row['gm_group_id'] === null) {
                        continue;
                    }
                    $row['gm_member_id'] = import_id_remap_get('member', strval($row['gm_member_id']), true);
                    if ($row['gm_member_id'] === null) {
                        continue;
                    }
                    $GLOBALS['FORUM_DB']->query_insert('f_group_members', $row, false, true); // errors suppressed in case already there

                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('group_members', strval(0), 0);
        }

        // Known login IPs
        if (!import_check_if_imported('member_known_login_ips', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('f_member_known_login_ips', ['*'], [], '', $max, $start);
                foreach ($rows as $row) {
                    $row['i_member_id'] = import_id_remap_get('member', strval($row['i_member_id']), true);
                    if ($row['i_member_id'] === null) {
                        continue;
                    }
                    $GLOBALS['FORUM_DB']->query_insert('f_member_known_login_ips', $row, false, true); // In case the row already exists
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('member_known_login_ips', strval(0), 0);
        }

        // Group member timeouts
        if (!import_check_if_imported('group_member_timeouts', strval(0))) {
            $rows = $db->query_select('f_group_member_timeouts', ['*']);
            foreach ($rows as $row) {
                $member_id = import_id_remap_get('member', strval($row['member_id']));
                if ($member_id === null) {
                    continue;
                }
                $group_id = import_id_remap_get('group', strval($row['group_id']));
                if ($group_id === null) {
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_insert('f_group_member_timeouts', ['member_id' => $member_id, 'group_id' => $group_id, 'timeout' => $row['timeout']], false, true); // Allow errors in case row exists
            }
            import_id_remap_put('group_member_timeouts', strval(0), 0);
        }

        // Invites
        $rows = $db->query_select('f_invites', ['*'], [], 'ORDER BY id', null, 0, true);
        if ($rows !== null) {
            foreach ($rows as $row) {
                if (import_check_if_imported('invite', strval($row['id']))) {
                    continue;
                }

                $i_invite_member = import_id_remap_get('member', strval($row['i_invite_member']), true);
                if ($i_invite_member === null) {
                    continue;
                }

                $id_new = $GLOBALS['FORUM_DB']->query_insert('f_invites', ['i_invite_member' => $i_invite_member, 'i_email_address' => $row['i_email_address'], 'i_time' => $row['i_time'], 'i_taken' => $row['i_taken']], true);

                import_id_remap_put('invite', strval($row['id']), $id_new);
            }
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_custom_profile_fields(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_custom_fields', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('cpf', strval($row['id']))) {
                continue;
            }

            $name = $this->get_lang_string($db, $row['cf_name']);

            $existing = $GLOBALS['FORUM_DB']->query_select('f_custom_fields', ['id', 'cf_type'], [$GLOBALS['FORUM_DB']->translate_field_ref('cf_name') => $name], '', 1);
            if ((!array_key_exists(0, $existing)) || ($existing[0]['cf_type'] != $row['cf_type']) && (substr($name, 0, 4) != 'cms_')) {
                $only_group = $row['cf_only_group'];
                if ($only_group != '') {
                    $only_group2 = '';
                    foreach (explode(',', $only_group) as $_only_group) {
                        if ($only_group2 != '') {
                            $only_group2 .= ',';
                        }
                        $group = import_id_remap_get('group', $_only_group, true);
                        if ($group === null) {
                            continue;
                        }
                        $only_group2 .= strval($group);
                    }
                    $only_group2 = $only_group;
                }
                $id_new = cns_make_custom_field($name, $row['cf_locked'], $this->get_lang_string($db, $row['cf_description']), $row['cf_default'], $row['cf_public_view'], $row['cf_owner_view'], $row['cf_owner_set'], $row['cf_encrypted'], $row['cf_type'], $row['cf_required'], $row['cf_show_in_posts'], $row['cf_show_in_post_previews'], $row['cf_order'], $only_group, $row['cf_show_on_join_form'], $row['cf_options'], $row['cf_include_in_main_search'], $row['cf_allow_template_search'], $row['cf_icon'], $row['cf_section'], $row['cf_tempcode']);
            } else {
                $id_new = $existing[0]['id'];
            }

            import_id_remap_put('cpf', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'cpf', 'cpf');

        // Import member cpf_perms
        $this->_import_f_member_cpf_perms($db, $table_prefix);
    }

    /**
     * Import CPF permissions.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     */
    protected function _import_f_member_cpf_perms(object $db, string $table_prefix)
    {
        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('f_member_cpf_perms', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                return;
            }
            foreach ($rows as $row) {
                $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                if ($member_id === null) {
                    continue;
                }
                $field_id = import_id_remap_get('cpf', strval($row['field_id']), true);
                if ($field_id === null) {
                    continue;
                }
                $test = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_member_cpf_perms', 'member_id', ['member_id' => $member_id, 'field_id' => $field_id]);
                if ($test === null) {
                    i_timed_refresh();
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_insert('f_member_cpf_perms', ['member_id' => $member_id, 'field_id' => $field_id, 'guest_view' => $row['guest_view'], 'member_view' => $row['member_view'], 'friend_view' => $row['friend_view'], 'group_view' => $row['group_view']]);

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_forum_groupings(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_forum_groupings', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('forum_groupings', strval($row['id']))) {
                continue;
            }

            $title = $row['c_title'];

            $test = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_forum_groupings', 'id', ['c_title' => $title]);
            if ($test !== null) {
                import_id_remap_put('forum_groupings', strval($row['id']), $test);
                continue;
            }

            $id_new = cns_make_forum_grouping($title, $row['c_description'], $row['c_expanded_by_default']);

            import_id_remap_put('forum_groupings', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'forum_grouping', 'forum_groupings');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_forums(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        require_code('cns_forums_action2');

        $comments_forum = get_option('comments_forum_name');
        if (!is_numeric($comments_forum)) {
            $comments_forum_id = $GLOBALS['FORUM_DRIVER']->forum_id_from_name($comments_forum);
        } else {
            $comments_forum_id = intval($comments_forum);
            $comments_forum = $GLOBALS['FORUM_DB']->query_select_value('f_forums', 'f_name', ['id' => $comments_forum_id]);
        }

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('f_forums', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('forum', strval($row['id']))) {
                    continue;
                }
                if ($row['id'] == db_get_first_id()) { // PTs
                    import_id_remap_put('forum', strval(db_get_first_id()), db_get_first_id());
                    continue;
                }
                if (($row['f_name'] == $comments_forum) && ($comments_forum_id !== null)) {
                    import_id_remap_put('forum', strval($row['id']), $comments_forum_id);
                    continue;
                }

                $forum_groupings_id = import_id_remap_get('forum_groupings', strval($row['f_forum_grouping_id']), true);

                $id_new = cns_make_forum($row['f_name'], $this->get_lang_string($db, $row['f_description']), $forum_groupings_id, [], db_get_first_id(), $row['f_position'], $row['f_post_count_increment'], $row['f_order_sub_alpha'], $this->get_lang_string($db, $row['f_intro_question']), $row['f_intro_answer'], $row['f_redirection'], $row['f_order'], $row['f_is_threaded'], $row['f_allows_anonymous_posts'], $row['f_mail_email_address'], $row['f_mail_server_type'], $row['f_mail_server_host'], $row['f_mail_server_port'], $row['f_mail_folder'], $row['f_mail_username'], $row['f_mail_password'], $row['f_mail_nonmatch_policy'], $row['f_mail_unconfirmed_notice'], $row['f_poll_default_options_xml']);
                import_id_remap_put('forum', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        // Now we must fix parenting
        if (!import_check_if_imported('forum_parenting', strval(0))) {
            $max = 200;
            $start = 0;
            do {
                $rows = $db->query_select('f_forums', ['*'], [], '', $max, $start);
                foreach ($rows as $row) {
                    if ($row['f_parent_forum_id'] === null) {
                        continue;
                    }
                    $parent_id = import_id_remap_get('forum', strval($row['f_parent_forum_id']), true);
                    if ($parent_id === null) {
                        $parent_id = db_get_first_id();
                    }
                    $GLOBALS['FORUM_DB']->query_update('f_forums', ['f_parent_forum_id' => $parent_id], ['id' => import_id_remap_get('forum', strval($row['id']))], '', 1);

                    i_timed_refresh();
                }
                $start += $max;
            } while (($rows !== null) && !empty($rows));
            import_id_remap_put('forum_parenting', strval(0), 0);
        }

        $this->_import_alternative_ids($db, 'forum', 'forum');
        $this->_import_content_reviews($db, $table_prefix, 'forum', 'forum');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'forum', 'forum');

        // Intros
        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('f_forum_intro_ip', ['*'], [], '', $max, $start);
            foreach ($rows as $row) {
                $row['i_forum_id'] = import_id_remap_get('forum', strval($row['i_forum_id']), true);
                if ($row['i_forum_id'] === null) {
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_delete('f_forum_intro_ip', $row, '', 1);
                $GLOBALS['FORUM_DB']->query_insert('f_forum_intro_ip', $row);

                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('f_forum_intro_member', ['*'], [], '', $max, $start);
            foreach ($rows as $row) {
                $row['i_forum_id'] = import_id_remap_get('forum', strval($row['i_forum_id']), true);
                if ($row['i_forum_id'] === null) {
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_delete('f_forum_intro_member', $row, '', 1);
                $GLOBALS['FORUM_DB']->query_insert('f_forum_intro_member', $row);

                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_topics(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('f_topics', ['*'], [], 'ORDER BY id', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('topic', strval($row['id']))) {
                    continue;
                }

                if ($row['t_forum_id'] === null) {
                    $forum_id = null;
                } else {
                    $forum_id = import_id_remap_get('forum', strval($row['t_forum_id']), true);
                    if ($forum_id === null) {
                        continue;
                    }
                }

                // Comment topic remapping
                $matches = [];
                if (preg_match('#: \#(\w+)s_(\d+)$#', $row['t_description'], $matches) != 0) {
                    $import_type = $matches[1];
                    if ($import_type == 'new') {
                        $import_type = 'news';
                    }
                    $c_id = import_id_remap_get($import_type, $matches[2], true);
                    if ($c_id !== null) {
                        $row['t_description'] = str_replace($matches[0], ': #' . $matches[1] . 's_' . strval($c_id), $row['t_description']);
                    }
                }
                // NB: Spacer post not fixed up

                $t_pt_to_member = $row['t_pt_to_member'];
                $t_pt_from_member = $row['t_pt_from_member'];
                if ($t_pt_to_member !== null) {
                    $t_pt_to_member = import_id_remap_get('member', strval($t_pt_to_member), true);
                    if ($t_pt_to_member === null) {
                        $t_pt_to_member = db_get_first_id();
                    }
                }
                if ($t_pt_from_member !== null) {
                    $t_pt_from_member = import_id_remap_get('member', strval($t_pt_from_member), true);
                    if ($t_pt_from_member === null) {
                        $t_pt_from_member = db_get_first_id();
                    }
                }

                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];

                $id_new = cns_make_topic($forum_id, $row['t_description'], $row['t_emoticon'], $row['t_validated'], $row['t_is_open'], $row['t_pinned'], $row['t_cascading'], $t_pt_from_member, $t_pt_to_member, false, $row['t_num_views'], $id);

                import_id_remap_put('topic', strval($row['id']), $id_new);
            }

            $row_start += 200;
        } while (!empty($rows));

        $this->_import_alternative_ids($db, 'topic', 'topic');
        $this->_import_content_reviews($db, $table_prefix, 'topic', 'topic');

        // Read logs
        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('f_read_logs', ['*'], [], '', 200, $row_start);
            foreach ($rows as $row) {
                $row['l_member_id'] = import_id_remap_get('member', strval($row['l_member_id']), true);
                $row['l_topic_id'] = import_id_remap_get('topic', strval($row['l_topic_id']), true);
                if ($row['l_member_id'] === null) {
                    continue;
                }
                if ($row['l_topic_id'] === null) {
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_delete('f_read_logs', $row, '', 1);
                $GLOBALS['FORUM_DB']->query_insert('f_read_logs', $row);

                i_timed_refresh();
            }
            $row_start += 200;
        } while (($rows !== null) && !empty($rows));

        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'topic', 'topic');

        // Special pt access
        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('f_special_pt_access', ['*'], [], 'ORDER BY s_topic_id,s_member_id', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                $row['s_member_id'] = import_id_remap_get('member', strval($row['s_member_id']), true);
                $row['s_topic_id'] = import_id_remap_get('topic', strval($row['s_topic_id']), true);
                if ($row['s_member_id'] === null) {
                    continue;
                }
                if ($row['s_topic_id'] === null) {
                    continue;
                }
                $GLOBALS['FORUM_DB']->query_delete('f_special_pt_access', $row);
                $GLOBALS['FORUM_DB']->query_insert('f_special_pt_access', $row);

                i_timed_refresh();
            }

            $row_start += 200;
        } while (!empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_posts(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        global $TOPIC_FORUM_CACHE;
        $TOPIC_FORUM_CACHE = [];

        $row_start = 0;

        // Optimisation to speed through quickly, as can be slow scrolling through so many posts we may have already imported!
        do {
            $rows = $db->query('SELECT id FROM ' . $table_prefix . 'f_posts ORDER BY id', 1, $row_start + 200 - 1);
            if ((!array_key_exists(0, $rows)) || (!import_check_if_imported('post', strval($rows[0]['id'])))) {
                break;
            }

            $row_start += 200;
        } while (true);

        $rows = [];
        do {
            $rows = $db->query_select('f_posts', ['*'], [], 'ORDER BY id', 200, $row_start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('post', strval($row['id']))) {
                    continue;
                }

                $member_id = import_id_remap_get('member', strval($row['p_posting_member']), true);
                if ($member_id === null) {
                    $member_id = db_get_first_id();
                }

                $topic_id = import_id_remap_get('topic', strval($row['p_topic_id']), true);
                if ($topic_id === null) {
                    continue;
                }

                // This speeds up addition... using the cache can reduce about 7/8 of a query per post on average
                if (array_key_exists($topic_id, $TOPIC_FORUM_CACHE)) {
                    $forum_id = $TOPIC_FORUM_CACHE[$topic_id];
                } else {
                    $forum_id = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_topics', 't_forum_id', ['id' => $topic_id]);
                    $TOPIC_FORUM_CACHE[$topic_id] = $forum_id;
                }

                $topic_is_pt = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_topics', 't_pt_from_member', ['id' => $topic_id]);

                $last_edit_member = $row['p_last_edit_member'];
                if ($last_edit_member !== null) {
                    $last_edit_member = import_id_remap_get('member', strval($last_edit_member), true);
                }
                $whisper_to_member = $row['p_whisper_to_member'];
                if ($whisper_to_member !== null) {
                    $whisper_to_member = import_id_remap_get('member', strval($whisper_to_member), true);
                    if ($whisper_to_member === null) {
                        $whisper_to_member = -1;
                    }
                }
                $id = (get_param_integer('keep_preserve_ids', 0) == 0) ? null : $row['id'];
                $id_new = cns_make_post($topic_id, $row['p_title'], $this->get_lang_string($db, $row['p_post']), 0, false, $row['p_validated'], $row['p_is_emphasised'], $row['p_poster_name_if_guest'], $row['p_ip_address'], $row['p_time'], $member_id, $whisper_to_member, $row['p_last_edit_time'], $last_edit_member, false, false, $forum_id, false, '', $id, false, false, ($topic_is_pt !== null), false, $row['p_parent_id'], false);

                import_id_remap_put('post', strval($row['id']), $id_new);
            }

            $row_start += 200;
        } while (!empty($rows));

        $this->_import_alternative_ids($db, 'post', 'post');
        $this->_import_review_supplement($db, $table_prefix, 'post', 'post');
        $this->_import_catalogue_entry_linkage($db, $table_prefix, 'post', 'post');
        $this->_import_content_reviews($db, $table_prefix, 'post', 'post');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_polls_and_votes(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $max = 200;
        $start = 0;
        do {
            $rows = $db->query_select('f_polls p LEFT JOIN ' . $table_prefix . 'f_topics t ON p.id=t.t_poll_id', ['p.*', 't.id AS tid'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('poll', strval($row['id']))) {
                    continue;
                }

                $topic_id = import_id_remap_get('topic', strval($row['tid']), true);
                if ($topic_id === null) {
                    continue;
                }

                $rows2 = $db->query_select('f_poll_votes', ['*'], ['pv_poll_id' => $row['id']]);
                foreach ($rows2 as $i => $row2) {
                    $rows2[$i]['pv_member_id'] = import_id_remap_get('member', strval($row2['pv_member_id']), true);
                }
                $rows3 = $db->query_select('f_poll_answers', ['*'], ['pa_poll_id' => $row['id']], 'ORDER BY pa_order');
                $answers = [];
                $id_ordinal_map = [];
                foreach ($rows3 as $i => $row3) {
                    $answers[] = $row3['pa_answer'];
                    $id_ordinal_map[$row3['id']] = $i;
                }

                $id_new = cns_make_poll($topic_id, $row['po_question'], $row['po_is_private'], $row['po_is_open'], $row['po_minimum_selections'], $row['po_maximum_selections'], $row['po_requires_reply'], $answers, $row['po_view_member_votes'], $row['po_vote_revocation'], $row['po_guests_can_vote'], $row['po_point_weighting'], false);

                $answers = collapse_1d_complexity('id', $GLOBALS['FORUM_DB']->query_select('f_poll_answers', ['id'], ['pa_poll_id' => $id_new])); // Effectively, a remapping from vote number ordinal to new vote number
                foreach ($rows2 as $row2) {
                    $vote = $row2['pv_answer_id'];
                    if (!array_key_exists($vote, $id_ordinal_map)) {
                        continue;
                    }
                    if (!array_key_exists($id_ordinal_map[$vote], $answers)) {
                        continue;
                    }
                    $answer = $answers[$id_ordinal_map[$vote]];
                    if ($row2['pv_member_id'] === null) {
                        continue;
                    }
                    $GLOBALS['FORUM_DB']->query_insert('f_poll_votes', ['pv_poll_id' => $id_new, 'pv_member_id' => $row2['pv_member_id'], 'pv_answer_id' => $answer, 'pv_ip_address' => $row2['pv_ip_address'], 'pv_revoked' => $row2['pv_revoked'], 'pv_date_time' => $row2['pv_date_time'], 'pv_points_when_voted' => $row2['pv_points_when_voted']]);
                }

                import_id_remap_put('f_poll', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_emoticons(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_emoticons', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_emoticons', 'e_code', ['e_code' => $row['e_code']]);
            if ($test === null) {
                $GLOBALS['FORUM_DB']->query_insert('f_emoticons', $row);

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
            }

            i_timed_refresh();
        }

        $this->_import_alternative_ids($db, 'emoticon', null);
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_leader_boards(object $db, string $table_prefix, string $file_base)
    {
        require_code('leader_board2');

        $on_same_msn = $this->on_same_msn($file_base);

        // Leader-boards and usergroup maps
        $rows = $db->query_select('leader_boards', ['*'], [], '', null, 0, true);
        if ($rows === null || $on_same_msn) {
            $rows = [];
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('leader_boards', strval($row['id']))) {
                continue;
            }

            $map = [
                'lb_title' => $row['lb_title'],
                'lb_type' => $row['lb_type'],
                'lb_member_count' => $row['lb_member_count'],
                'lb_timeframe' => $row['lb_timeframe'],
                'lb_rolling' => $row['lb_rolling'],
                'lb_include_staff' => $row['lb_include_staff'],
            ];

            // Prepare usergroups
            $groups = $db->query_select('leader_boards_groups', ['*'], ['lb_leader_board_id' => $row['id']], '', null, 0, true);
            if ($groups === null || $on_same_msn) {
                $groups = [];
            }
            $groups = collapse_1d_complexity('lb_group', $groups);

            // Re-map group IDs
            foreach ($groups as $key => $group) {
                if ($group !== null && $group !== '') {
                    $groups[$key] = $on_same_msn ? $group : import_id_remap_get('group', strval($group), true);
                }
            }

            $id_new = add_leader_board($row['lb_title'], $row['lb_type'], $row['lb_member_count'], $row['lb_timeframe'], $row['lb_rolling'], $row['lb_include_staff'], $groups, $row['lb_calculate_voting_power']);

            import_id_remap_put('leader_board', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'leader_board', 'leader_board');

        // Result sets
        if (!import_check_if_imported('leader_board_results', strval(0))) {
            $max = 300;
            $start = 0;
            do {
                $rows = $db->query_select('leader_board', ['*'], [], '', $max, $start, true);
                if ($rows === null) {
                    $rows = [];
                }
                $this->_fix_comcode_ownership($rows);
                foreach ($rows as $row) {
                    $member_id = $on_same_msn ? $row['lb_member'] : import_id_remap_get('member', strval($row['lb_member']), true);
                    if ($member_id === null) {
                        continue;
                    }
                    $GLOBALS['SITE_DB']->query_delete('leader_board', ['lb_member' => $member_id, 'lb_points' => $row['lb_points'], 'lb_date_and_time' => $row['lb_date_and_time'], 'lb_rank' => $row['lb_rank'], 'lb_leader_board_id' => $row['lb_leader_board_id']], '', 1);
                    $GLOBALS['SITE_DB']->query_insert('leader_board', ['lb_member' => $member_id, 'lb_points' => $row['lb_points'], 'lb_date_and_time' => $row['lb_date_and_time'], 'lb_rank' => $row['lb_rank'], 'lb_leader_board_id' => $row['lb_leader_board_id']]);

                    i_timed_refresh();
                }

                $start += $max;
            } while (!empty($rows));
            import_id_remap_put('leader_board_results', strval(0), 0);
        }
    }

    /**
     * Pass a multi-code through a forum remap.
     *
     * @param  SHORT_TEXT $multi_code Multi code
     * @return SHORT_TEXT New multi code
     */
    public function convert_multi_code(string $multi_code) : string
    {
        if ($multi_code == '*') {
            return $multi_code;
        }
        if ($multi_code == '') {
            return $multi_code;
        }

        $new_multi_code = '';
        if ($multi_code[0] == '+') {
            $parts = explode(',', substr($multi_code, 1));
            $new_multi_code = '+';
            foreach ($parts as $_part) {
                $part = import_id_remap_get('forum', trim($_part), true);
                if ($part !== null) {
                    if ($new_multi_code != '') {
                        $new_multi_code .= ',';
                    }
                    $new_multi_code .= strval($part);
                }
            }
        } else {
            $parts = explode(',', $multi_code);
            foreach ($parts as $_part) {
                $part = import_id_remap_get('forum', trim($_part), true);
                if ($part !== null) {
                    if ($new_multi_code != '') {
                        $new_multi_code .= ',';
                    }
                    $new_multi_code .= strval($part);
                }
            }
        }

        return $new_multi_code;
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_multi_moderations(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_multi_moderations', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('multi_moderation', strval($row['id']))) {
                continue;
            }

            $name = $this->get_lang_string($db, $row['mm_name']);
            $move_to = ($row['mm_move_to_forum_id'] === null) ? null : import_id_remap_get('forum', strval($row['mm_move_to_forum_id']), true);
            $multi_code = $this->convert_multi_code($row['mm_forum_multi_code']);
            $id_new = cns_make_multi_moderation($name, $row['mm_post_text'], $move_to, $row['mm_pin_state'], $row['mm_open_state'], $multi_code, $row['mm_title_suffix']);

            import_id_remap_put('multi_moderation', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'multi_moderation', 'multi_moderation');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_post_templates(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        $rows = $db->query_select('f_post_templates', ['*']);
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('post_template', strval($row['id']))) {
                continue;
            }

            $multi_code = $this->convert_multi_code($row['t_forum_multi_code']);
            $id_new = cns_make_post_template($row['t_title'], $row['t_text'], $multi_code, $row['t_use_default_forums']);

            import_id_remap_put('post_template', strval($row['id']), $id_new);
        }
        $this->_import_alternative_ids($db, 'post_template', 'post_template');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_warnings(object $db, string $table_prefix, string $file_base)
    {
        if ($this->on_same_msn($file_base)) {
            return;
        }

        // Start with the warnings themselves
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('f_warnings', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('warning', strval($row['id']))) {
                    continue;
                }

                $member_id = import_id_remap_get('member', strval($row['w_member_id']), true);
                $by = import_id_remap_get('member', $row['w_issuing_member'], true);
                if ($member_id === null) {
                    continue;
                }
                if ($by === null) {
                    $by = $GLOBALS['FORUM_DRIVER']->get_guest_id();
                }

                $warning_id = cns_make_warning($member_id, $row['w_explanation'], $by, $row['w_time'], $row['w_is_warning']);
                import_id_remap_put('warning', strval($row['id']), $warning_id);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'warning', 'warning');

        // Now migrate the punitive action logs
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('f_warnings_punitive', ['*'], [], '', $max, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('warning_punitive', strval($row['id']))) {
                    continue;
                }

                $warning_id = import_id_remap_get('warning', strval($row['p_warning_id']), true);
                if ($warning_id === null) {
                    continue;
                }

                // Special considerations for ID remapping
                $p_param_a = $row['p_param_a'];
                if ($p_param_a !== '') {
                    switch ($row['p_action']) {
                        case '_PUNITIVE_SILENCE_FROM_FORUM':
                            $p_param_a = import_id_remap_get('forum', $p_param_a, true);
                            break;
                        case '_PUNITIVE_SILENCE_FROM_TOPIC':
                            $p_param_a = import_id_remap_get('topic', $p_param_a, true);
                            break;
                        case '_PUNITIVE_CHANGE_USERGROUP':
                            $p_param_a = import_id_remap_get('group', $p_param_a, true);
                            break;
                        case '_PUNITIVE_DELETE_POST':
                        case '_PUNITIVE_DELETE_POST_AND_FOLLOWING':
                            $p_param_a = import_id_remap_get('post', $p_param_a, true);
                            break;
                    }
                    if ($p_param_a === null) {
                        continue;
                    }
                }
                $p_param_b = $row['p_param_b'];
                if ($p_param_b !== '') {
                    switch ($row['p_action']) {
                        case '_PUNITIVE_CHANGE_USERGROUP':
                            $p_param_b = import_id_remap_get('group', $p_param_b, true);
                            break;
                    }
                    if ($p_param_b === null) {
                        continue;
                    }
                }

                $id_new = $GLOBALS['FORUM_DB']->query_insert('f_warnings_punitive', [
                    'p_warning_id' => $warning_id,
                    'p_member_id' => $row['p_member_id'],
                    'p_ip_address' => $row['p_ip_address'],
                    'p_email_address' => $row['p_email_address'],
                    'p_hook' => $row['p_hook'],
                    'p_action' => $row['p_action'],
                    'p_param_a' => ($p_param_a === null) ? '' : strval($p_param_a),
                    'p_param_b' => ($p_param_b === null) ? '' : strval($p_param_b),
                    'p_reversed' => $row['p_reversed'],
                ], true);

                import_id_remap_put('warning_punitive', strval($row['id']), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'warning_punitive', 'warning_punitive');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_aggregate_type_instances(object $db, string $table_prefix, string $file_base)
    {
        require_code('aggregate_types');

        $start = 0;
        do {
            $rows = $db->query_select('aggregate_type_instances', ['*'], [], '', 100, $start);
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                if (import_check_if_imported('aggregate_type_instance', strval($row['id']))) {
                    continue;
                }

                $id_new = add_aggregate_type_instance($row['aggregate_label'], $row['aggregate_type'], $row['other_parameters'], $row['add_time'], $row['edit_time'], false);

                import_id_remap_put('aggregate_type_instance', strval($row['id']), $id_new);
            }
            $start += 100;
        } while (!empty($rows));
        $this->_import_alternative_ids($db, 'aggregate_type_instance', 'aggregate_type_instance');
    }

    /**
     * Import privacy for a particular record.
     *
     * @param  object $db The database connector to import from
     * @param  ID_TEXT $content_type The content type
     * @param  ID_TEXT $old_id The old ID
     * @param  ID_TEXT $id_new The new ID
     */
    protected function _import_content_privacy(object $db, string $content_type, string $old_id, string $id_new)
    {
        if (addon_installed('content_privacy')) {
            $rows = $db->query_select('content_privacy', ['*'], ['content_type' => $content_type, 'content_id' => $old_id], '', 1);
            foreach ($rows as $row) {
                $GLOBALS['SITE_DB']->query_insert('content_privacy', [
                    'content_id' => $id_new,
                ] + $row, false, true); // Allow errors in case row already exists
            }

            $rows = $db->query_select('content_privacy__members', ['*'], ['content_type' => $content_type, 'content_id' => $old_id]);
            foreach ($rows as $row) {
                $member_id = import_id_remap_get('member', strval($row['member_id']), true);
                if ($member_id !== null) {
                    $GLOBALS['SITE_DB']->query_insert('content_privacy__members', [
                        'content_id' => $id_new,
                        'member_id' => $member_id,
                    ] + $row, false, true); // Allow errors in case row already exists
                }
            }
        }

        i_timed_refresh();
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_cns_saved_warnings(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('f_saved_warnings', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            $test = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_saved_warnings', 's_title', ['s_title' => $row['s_title']]);
            if ($test !== null) {
                i_timed_refresh();
                continue;
            }

            $GLOBALS['FORUM_DB']->query_insert('f_saved_warnings', ['s_title' => $row['s_title'], 's_explanation' => $row['s_explanation'], 's_message' => $row['s_message']]);

            global $I_REFRESH_DID_SOMETHING;
            $I_REFRESH_DID_SOMETHING = true;
            i_timed_refresh();
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_match_key_messages(object $db, string $table_prefix, string $file_base)
    {
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('match_key_messages', ['*'], [], '', $max, $start, true);
            if ($rows === null) {
                return;
            }
            $this->_fix_comcode_ownership($rows);
            foreach ($rows as $row) {
                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('match_key_messages', 'id', ['k_match_key' => $row['k_match_key']]);
                if ($test !== null) {
                    i_timed_refresh();
                    continue;
                }

                $row['k_message'] = $this->get_lang_string($db, $row['k_message']);

                $GLOBALS['SITE_DB']->query_insert('match_key_messages', $row);

                global $I_REFRESH_DID_SOMETHING;
                $I_REFRESH_DID_SOMETHING = true;
                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_menu_items(object $db, string $table_prefix, string $file_base)
    {
        $parent_rows = $db->query_select('menu_items', ['*']);
        if ($parent_rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($parent_rows);
        foreach ($parent_rows as $row) {
            $id_old = strval($row['id']);
            if (get_param_integer('keep_preserve_ids', 0) == 0) {
                unset($row['id']);
            }

            if (import_check_if_imported('menu_item', $id_old)) {
                continue;
            }

            $row['i_parent_id'] = ($row['i_parent_id'] === null) ? null : (-$row['i_parent_id']);

            unset($row['i_caption__text_parsed']);
            unset($row['i_caption__source_user']);
            unset($row['i_caption_long__text_parsed']);
            unset($row['i_caption_long__source_user']);

            $row = insert_lang_comcode('i_caption', $this->get_lang_string($db, $row['i_caption']), 1) + $row;
            $row = insert_lang_comcode('i_caption_long', $this->get_lang_string($db, $row['i_caption_long']), 1) + $row;

            $id_new = $GLOBALS['SITE_DB']->query_insert('menu_items', $row, true);
            import_id_remap_put('menu_item', $id_old, $id_new);
        }

        $child_rows = $GLOBALS['SITE_DB']->query('SELECT * FROM ' . get_table_prefix() . 'menu_items WHERE i_parent_id<0');
        foreach ($child_rows as $row) {
            $row['i_parent_id'] = import_id_remap_get('menu_item', strval($row['i_parent_id']), true);
            $GLOBALS['SITE_DB']->query_update('menu_items', $row, ['id' => $row['id']], '', 1);

            i_timed_refresh();
        }
        $this->_import_alternative_ids($db, 'menu_item', 'menu_item');
    }

    /**
     * Imports staff-links.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_staff_links(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('staff_links', ['*'], [], '', null, 0, true);
        if ($rows === null) {
            return;
        }
        $this->_fix_comcode_ownership($rows);
        foreach ($rows as $row) {
            if (import_check_if_imported('staff_link', $row['id'])) {
                continue;
            }

            $id_old = strval($row['id']);
            if (get_param_integer('keep_preserve_ids', 0) == 0) {
                unset($row['id']);
            }
            $id_new = $GLOBALS['SITE_DB']->query_insert('staff_links', $row, true);

            import_id_remap_put('staff_link', $id_old, $id_new);
        }
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_site_messages(object $db, string $table_prefix, string $file_base)
    {
        $row_start = 0;
        $rows = [];
        do {
            $rows = $db->query_select('site_messages', ['*'], [], 'ORDER BY id', 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('site_message', strval($row['id']))) {
                    continue;
                }

                $row_copy = $row;
                if (get_param_integer('keep_preserve_ids', 0) == 0) {
                    unset($row_copy['id']);
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('site_messages', $row_copy, true);
                import_id_remap_put('site_message', strval($row['id']), $id_new);

                $pages = $db->query_select('site_messages_pages', ['*'], ['message_id' => $row['id']]);
                foreach ($pages as $page) {
                    if (import_check_if_imported('site_message_page', strval($page['id']))) {
                        continue;
                    }

                    $page_copy = $page;
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($page_copy['id']);
                    }
                    $page_copy['message_id'] = $id_new;

                    $page_bits = explode(':', $page_copy['page_link']);
                    if (!array_key_exists(1, $page_bits)) {
                        continue;
                    }
                    $new_page = import_id_remap_get('comcode_page', $page_bits[0] . ':' . $page_bits[1], true);
                    if ($new_page !== null) {
                        $new_page_bits = explode(':', strval($new_page));
                        $page_bits[0] = $new_page_bits[0];
                        $page_bits[1] = $new_page_bits[1];
                        $page_copy['page_link'] = implode(':', $page_bits);
                    }

                    $id_new_page = $GLOBALS['SITE_DB']->query_insert('site_messages_pages', $page_copy, true);
                    import_id_remap_put('site_message_page', strval($page['id']), $id_new_page);
                }

                $groups = $db->query_select('site_messages_groups', ['*'], ['message_id' => $row['id']]);
                foreach ($groups as $group) {
                    if (import_check_if_imported('site_message_group', strval($group['id']))) {
                        continue;
                    }

                    $group_copy = $group;
                    if (get_param_integer('keep_preserve_ids', 0) == 0) {
                        unset($group_copy['id']);
                    }
                    $group_copy['message_id'] = $id_new;

                    $new_group = import_id_remap_get('group', strval($group['group_id']), true);
                    if ($new_group !== null) {
                        $page_copy['group_id'] = $new_group;
                    }

                    $id_new_group = $GLOBALS['SITE_DB']->query_insert('site_messages_groups', $group_copy, true);
                    import_id_remap_put('site_message_group', strval($group['id']), $id_new_group);
                }
            }
            $row_start += 200;
        } while (!empty($rows));
        $this->_import_alternative_ids($db, 'site_message', 'site_message');
    }

    /**
     * Standard import function.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  PATH $file_base The base directory we are importing from
     */
    public function import_stats(object $db, string $table_prefix, string $file_base)
    {
        $max = 300;

        $start = 0;
        do {
            $rows = $db->query_select('stats_known_events', ['*'], [], '', $max, $start);
            foreach ($rows as $row) {
                if (import_check_if_imported('stats_known_event', $row['e_event'])) {
                    continue;
                }

                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('stats_known_events', 'e_count_logged', ['e_event' => $row['e_event']]);
                if ($test !== null) {
                    $GLOBALS['SITE_DB']->query_insert('stats_known_events', ['e_event' => $row['e_event'], 'e_count_logged' => $test]);
                } else { // Merge counts
                    $GLOBALS['SITE_DB']->query_update('stats_known_events', ['e_count_logged' => $test + $row['e_count_logged']], ['e_event' => $row['e_event']]);
                }

                import_id_remap_put('stats_known_event', $row['e_event'], 0);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $start = 0;
        do {
            $rows = $db->query_select('stats_known_links', ['*'], [], '', $max, $start);
            foreach ($rows as $row) {
                if (import_check_if_imported('stats_known_link', $row['l_url'])) {
                    continue;
                }

                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('stats_known_links', 'l_count_logged', ['l_url' => $row['l_url']]);
                if ($test !== null) {
                    $GLOBALS['SITE_DB']->query_insert('stats_known_links', ['l_url' => $row['l_url'], 'l_count_logged' => $test]);
                } else { // Merge counts
                    $GLOBALS['SITE_DB']->query_update('stats_known_links', ['l_count_logged' => $test + $row['l_count_logged']], ['l_url' => $row['l_url']]);
                }

                import_id_remap_put('stats_known_link', $row['l_url'], 0);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $start = 0;
        do {
            $rows = $db->query_select('stats_known_tracking', ['*'], [], '', $max, $start);
            foreach ($rows as $row) {
                if (import_check_if_imported('stats_known_tracking', $row['t_tracking_code'])) {
                    continue;
                }

                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('stats_known_tracking', 't_count_logged', ['t_tracking_code' => $row['t_tracking_code']]);
                if ($test !== null) {
                    $GLOBALS['SITE_DB']->query_insert('stats_known_tracking', ['t_tracking_code' => $row['t_tracking_code'], 't_count_logged' => $test]);
                } else { // Merge counts
                    $GLOBALS['SITE_DB']->query_update('stats_known_tracking', ['t_count_logged' => $test + $row['t_count_logged']], ['t_tracking_code' => $row['t_tracking_code']]);
                }

                import_id_remap_put('stats_known_tracking', $row['t_tracking_code'], 0);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));

        $start = 0;
        do {
            $rows = $db->query_select('stats_kpis', ['*'], [], '', $max, $start);
            foreach ($rows as $row) {
                if (import_check_if_imported('kpi', $row['id'])) {
                    continue;
                }

                $id_old = $row['id'];
                unset($row['id']);

                $submitter = import_id_remap_get('member', $id_old, true);
                if ($submitter === null) {
                    i_timed_refresh();
                    continue;
                }

                $id_new = $GLOBALS['SITE_DB']->query_insert('stats_kpis', array_merge($row, ['k_submitter' => $submitter]), true);

                import_id_remap_put('kpi', strval($id_old), $id_new);
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
        $this->_import_alternative_ids($db, 'kpi', 'kpi');
    }

    /**
     * Import reviews.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  ID_TEXT $rating_type The rating type
     * @param  ?ID_TEXT $import_type The import type to get remapping from (null: no remapping)
     */
    protected function _import_review_supplement(object $db, string $table_prefix, string $rating_type, ?string $import_type)
    {
        $rows = $db->query_select('review_supplement', ['*'], ['r_rating_type' => $rating_type]);
        foreach ($rows as $row) {
            $rating_for_id = ($import_type === null) ? $row['r_rating_for_id'] : import_id_remap_get($import_type, @strval($row['r_rating_for_id']), true);
            if ($rating_for_id === null) {
                continue;
            }

            $r_post_id = import_id_remap_get('post', strval($row['r_post_id']), true);
            if ($r_post_id === null) {
                continue;
            }
            $r_topic_id = import_id_remap_get('topic', strval($row['r_topic_id']), true);
            if ($r_topic_id === null) {
                continue;
            }
            $row['r_post_id'] = $r_post_id;
            $row['r_topic_id'] = $r_topic_id;
            $row['r_rating_for_id'] = $rating_for_id;

            $GLOBALS['SITE_DB']->query_insert('review_supplement', $row, false, true); // Allow errors in case row already exists

            i_timed_refresh();
        }
    }

    /**
     * Import content review schedules.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  ID_TEXT $content_type The content type
     * @param  ?ID_TEXT $import_type The import type to get remapping from (null: no remapping)
     */
    protected function _import_content_reviews(object $db, string $table_prefix, string $content_type, ?string $import_type)
    {
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('content_reviews', ['*'], ['content_type' => $content_type], '', $max, $start);
            if ($rows === null) {
                return;
            }
            foreach ($rows as $row) {
                $content_id = ($import_type === null) ? $row['content_id'] : import_id_remap_get($import_type, @strval($row['content_id']), true);
                if ($content_id === null) {
                    continue;
                }

                $row['content_type'] = $content_type;
                $row['content_id'] = $content_id;

                $GLOBALS['SITE_DB']->query_insert('content_reviews', $row, false, true); // Allow errors in case record already exists

                i_timed_refresh();
            }
            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }

    /**
     * Import custom fields for a particular record.
     *
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @param  ID_TEXT $content_type The content type
     * @param  ?ID_TEXT $import_type The import type to get remapping from (null: no remapping)
     */
    protected function _import_catalogue_entry_linkage(object $db, string $table_prefix, string $content_type, ?string $import_type)
    {
        $sql = 'SELECT * FROM ' . $table_prefix . 'catalogue_entry_linkage WHERE ' . db_string_equal_to('content_type', $content_type);
        if ($content_type == 'wiki_page') {
            $sql .= ' OR ' . db_string_equal_to('content_type', 'cedi_page');
        }
        if ($content_type == 'wiki_post') {
            $sql .= ' OR ' . db_string_equal_to('content_type', 'cedi_post');
        }
        $rows = $db->query($sql);
        foreach ($rows as $row) {
            $catalogue_entry_id = import_id_remap_get('catalogue_entry', strval($row['catalogue_entry_id']), true);
            if ($catalogue_entry_id === null) {
                continue;
            }

            $content_id = ($import_type === null) ? $row['content_id'] : import_id_remap_get($import_type, @strval($row['content_id']), true);
            if ($content_id === null) {
                continue;
            }

            $row['catalogue_entry_id'] = $catalogue_entry_id;
            $row['content_id'] = $content_id;

            $GLOBALS['SITE_DB']->query_insert('catalogue_entry_linkage', $row, false, true); // Allow errors in case record already exists

            i_timed_refresh();
        }
    }

    /**
     * Remap any Comcode ownership fields.
     *
     * @param  array $rows Rows with potential fields to fix
     */
    protected function _fix_comcode_ownership(array &$rows)
    {
        foreach ($rows as &$row) {
            foreach ($row as $field => $value) {
                if (substr($field, 0, 13) == '__source_user') {
                    $member_id = import_id_remap_get('member', strval($value), true);
                    if ($member_id === null) {
                        $member_id = db_get_first_id();
                    }

                    $row[$field] = $member_id;
                }
            }
        }
    }

    /**
     * Import an uploaded file from the old site to the new one.
     * This should be called on every URLPATH db field which could contain an uploaded file. For multi fields, this should be called on every line of the field.
     *
     * @param  ?URLPATH $path The contents of one line of the URLPATH db field / a path relative to the upload file (null: none)
     * @param  PATH $file_base The base directory we are importing from
     * @return boolean Whether to treat as successful
     */
    protected function _import_upload(?string $path, string $file_base) : bool
    {
        if ($path === null) { // Nothing to do, and not an error
            return true;
        }

        if ($path == '') { // Nothing to do, and not an error
            return true;
        }

        $path = rawurldecode($path);

        require_code('files2');
        require_code('urls');

        if (!url_is_local($path)) { // Nothing to import if a remote URL; skip and treat as success
            return true;
        }

        $old_path = $file_base . '/' . $path;
        if (!is_file($old_path)) {
            return false; // Not a valid file; treat as a failure
        }

        $contents = cms_file_get_contents_safe($old_path);
        if ($contents === false) {
            return false; // Not a valid file or could not read it; treat as a failure
        }

        $new_path = get_file_base() . '/' . $path;

        // NB: We should overwrite what is already on disk if the file already exists; import uploads take priority over current ones
        return cms_file_put_contents_safe($new_path, $contents);
    }

    /**
     * Import alternative IDs of a given resource type.
     * This must be called *after* importing the resources into the database and making note of their new IDs to map.
     *
     * @param  object $db The database connector to import from
     * @param  ID_TEXT $resource_type The value of resource_type in the db to import
     * @param  ?ID_TEXT $import_type The import_id_remap key applicable for this resource (null: not applicable; use the same resource_id from old site)
     */
    protected function _import_alternative_ids(object $db, string $resource_type, ?string $import_type)
    {
        $max = 300;
        $start = 0;
        do {
            $rows = $db->query_select('alternative_ids', ['*'], ['resource_type' => $resource_type], '', $max, $start);
            if ($rows === null) {
                return;
            }

            foreach ($rows as $row) {
                if (import_check_if_imported('alternative_id__' . $resource_type, $row['resource_type'] . '__' . $row['resource_id'])) {
                    continue;
                }

                if ($import_type !== null) {
                    // Ignore resources which we did not import
                    $new_id = import_id_remap_get($import_type, $row['resource_id'], true);
                    if ($new_id === null) {
                        import_id_remap_put('alternative_id__' . $resource_type, $row['resource_id'], 0);
                        continue;
                    }

                    $GLOBALS['SITE_DB']->query_insert('alternative_ids', array_merge($row, ['resource_id' => $new_id]), false, true); // Suppress errors in case an alternative ID already exists for a resource
                } else {
                    $GLOBALS['SITE_DB']->query_insert('alternative_ids', $row, false, true); // Suppress errors in case an alternative ID already exists for a resource
                }

                import_id_remap_put('alternative_id__' . $resource_type, $row['resource_id'], 0);
            }

            $start += $max;
        } while (($rows !== null) && !empty($rows));
    }
}
