<?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
 */

/**
 * Standard code module initialisation function.
 *
 * @ignore
 */
function init__hooks__modules__admin_import__vb3()
{
    global $TOPIC_FORUM_CACHE;
    $TOPIC_FORUM_CACHE = [];

    global $STRICT_FILE;
    $STRICT_FILE = false; // Disable this for a quicker import that is quite liable to go wrong if you don't have the files in the right place

    global $OLD_BASE_URL;
    $OLD_BASE_URL = null;
}

/**
 * Forum Driver.
 */
class Hook_import_vb3
{
    /**
     * 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'] = false;
        $info['product'] = 'vBulletin 3.0.x / 3.5.x';
        $info['prefix'] = 'vb3_';
        $info['import'] = [
            'cns_groups',
            'custom_comcode',
            'cns_members',
            'cns_member_files',
            'point_transactions',
            'cns_custom_profile_fields',
            'cns_forum_groupings',
            'cns_forums',
            'cns_topics',
            'cns_posts',
            'cns_post_files',
            'cns_polls_and_votes',
            'notifications',
            'cns_private_topics',
            'wordfilter',
            'config',
            'calendar',
            //'logs',
        ];
        $info['dependencies'] = [
            // This dependency tree is overdefined, but I wanted to make it clear what depends on what, rather than having a simplified version
           'cns_members' => ['cns_groups'],
           'cns_member_files' => ['cns_members'],
           'cns_forums' => ['cns_forum_groupings', 'cns_members', 'cns_groups'],
           'cns_topics' => ['cns_forums', 'cns_members'],
           'cns_polls_and_votes' => ['cns_topics', 'cns_posts', 'cns_members'],
           'cns_posts' => ['custom_comcode', 'cns_topics', 'cns_members'],
           'cns_post_files' => ['cns_posts'],
           'notifications' => ['cns_topics', 'cns_members'],
           'cns_private_topics' => ['custom_comcode', 'cns_members'],
           'point_transactions' => ['cns_members'],
           'logs' => ['cns_members', 'cns_posts'],
           'calendar' => ['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_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;
    }

    /**
     * Probe a file path for DB access details.
     *
     * @param  string $file_base The probe path
     * @return array A quartet of the details (db_name, db_user, db_pass, table_prefix)
     */
    public function probe_db_access(string $file_base) : array
    {
        $dbname = null;
        $dbusername = '';
        $dbpassword = '';
        $tableprefix = '';
        $servername = '';
        $config = [];
        if (!file_exists($file_base . '/includes/config.php')) {
            warn_exit(do_lang_tempcode('BAD_IMPORT_PATH', escape_html('includes/config.php')));
        }
        require($file_base . '/includes/config.php');

        if ($dbname !== null) {
            $sql_database = $dbname;
            $sql_user = $dbusername;
            $sql_pass = $dbpassword;
            $sql_tbl_prefix = $tableprefix;
            $sql_host = $servername;
        } else {
            $sql_database = $config['Database']['dbname'];
            $sql_user = $config['MasterServer']['username'];
            $sql_pass = $config['MasterServer']['password'];
            $sql_tbl_prefix = $config['Database']['tableprefix'];
            $sql_host = $config['MasterServer']['servername'];
        }

        return [$sql_database, $sql_user, $sql_pass, $sql_tbl_prefix, $sql_host];
    }

    /**
     * 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)
    {
        $config_remapping = [
            'bbactive' => '!site_closed',
            'bbclosedreason' => 'closed',
            'webmasteremail' => 'staff_address',
            'hometitle' => 'site_name',
            'copyrighttext' => 'copyright',
            'maxposts' => 'forum_posts_per_page',
            'maxthreads' => 'forum_topics_per_page',
            //'reputationenable' => 'is_on_points',
            'gzipoutput' => 'output_compression',
            'regimagecheck' => 'use_captchas',
            'moderatenewmembers' => 'require_new_member_validation',
            'timeoffset' => 'timezone',
        ];

        $rows = $db->query_select('setting', ['*']);
        $PROBED_FORUM_CONFIG = [];
        foreach ($rows as $row) {
            if ($row['value'] == '') {
                $row['value'] = $row['defaultvalue'];
            }
            if (array_key_exists($row['varname'], $config_remapping)) {
                $value = $row['value'];
                $remapping = $config_remapping[$row['varname']];
                if ($remapping[0] == '!') {
                    $remapping = substr($remapping, 1);
                    $value = strval(1 - intval($value));
                }
                set_option($remapping, $value);
            }
            $PROBED_FORUM_CONFIG[$row['varname']] = $row['value'];
        }

        // Now some usergroup options
        $groups = $GLOBALS['CNS_DRIVER']->get_usergroup_list();
        $page_remap = [
            'allowregistration' => 'join',
            'enablesearches' => 'search',
            'enablememberlist' => 'members',
        ];
        foreach ($page_remap as $to) {
            $GLOBALS['SITE_DB']->query_delete('group_page_access', ['page_name' => $to, 'zone_name' => get_module_zone($to)]);
        }
        foreach (array_keys($groups) as $id) {
            if ($GLOBALS['CNS_DRIVER']->is_super_admin($id)) {
                continue;
            }

            foreach ($page_remap as $from => $to) {
                if ($PROBED_FORUM_CONFIG[$from] == '1') {
                    $GLOBALS['SITE_DB']->query_insert('group_page_access', ['page_name' => $to, 'zone_name' => get_module_zone($to), 'group_id' => $id]);
                }
            }

            $map = [];
            $map['g_max_attachments_per_post'] = $PROBED_FORUM_CONFIG['attachlimit'];
            if ($PROBED_FORUM_CONFIG['postmaxchars'] > 0) {
                $map['g_max_post_length_comcode'] = $PROBED_FORUM_CONFIG['postmaxchars'];
            } else {
                $map['g_max_post_length_comcode'] = 1000000;
            }
            if (array_key_exists('sigmax', $map)) {
                if ($PROBED_FORUM_CONFIG['sigmax'] > 0) {
                    $map['g_max_sig_length_comcode'] = $PROBED_FORUM_CONFIG['sigmax'];
                } else {
                    $map['g_max_sig_length_comcode'] = 1000000;
                }
            }
            $GLOBALS['FORUM_DB']->query_update('f_groups', $map, ['id' => $id], '', 1);

            set_privilege($id, 'use_quick_reply', $PROBED_FORUM_CONFIG['quickreply']);
            set_privilege($id, 'comcode_dangerous', $PROBED_FORUM_CONFIG['allowhtml']);
        }
    }

    /**
     * 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)
    {
        $rows = $db->query_select('setting', ['*']);
        $PROBED_FORUM_CONFIG = [];
        foreach ($rows as $row) {
            $key = $row['varname'];
            $val = $row['value'];
            if ($val == '') {
                $val = $row['defaultvalue'];
            }
            $PROBED_FORUM_CONFIG[$key] = $val;
        }

        $rows = $db->query_select('usergroup g LEFT JOIN ' . $table_prefix . 'usergroupleader l ON g.usergroupid=l.usergroupid LEFT JOIN ' . $table_prefix . 'userpromotion p ON g.usergroupid=p.usergroupid', ['*', 'g.usergroupid AS usergroupid']);
        $remap_id = [];
        foreach ($rows as $row) {
            if (import_check_if_imported('group', strval($row['usergroupid']))) {
                continue;
            }

            $row_group_leader = null;
            if ($row['userid'] !== null) {
                $row_group_leader = -$row['userid']; // This will be fixed when we import members
            }

            $row_promotion_target = $row['joinusergroupid']; // This will be fixed when all the usergroups are added

            $is_super_admin = (($row['adminpermissions'] & 2) != 0) ? 1 : 0;
            $is_super_moderator = (($row['adminpermissions'] & 1) != 0) ? 1 : 0;

            $id_new = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_groups', 'id', [$GLOBALS['FORUM_DB']->translate_field_ref('g_name') => $row['title']]);
            if ($id_new === null) {
                $id_new = cns_make_group($row['title'], 0, $is_super_admin, $is_super_moderator, $row['usertitle'], '', $row_promotion_target, $row['reputation'], 0, $row_group_leader, 5, 0, 5, $row['attachlimit'], $row['avatarmaxwidth'], $row['avatarmaxheight'], $PROBED_FORUM_CONFIG['postmaxchars'], array_key_exists('sigmax', $PROBED_FORUM_CONFIG) ? $PROBED_FORUM_CONFIG['sigmax'] : 700);
            }

            // Zone permissions
            if (!(($row['forumpermissions'] & 1) != 0) || (($row['genericoptions'] & 32) != 0)) {
                $GLOBALS['SITE_DB']->query_delete('group_zone_access', ['group_id' => $id_new]);
            }

            // Page permissions
            $denies = [];
            if (($row['forumpermissions'] & 4) == 0) {
                $denies[] = ['search', get_module_zone('search')];
            }
            if (($row['forumpermissions'] & 8) == 0) {
                $denies[] = ['contact_member', get_module_zone('contact_member')];
            }
            if (($row['genericpermissions'] & 1) == 0) {
                $denies[] = ['members', get_module_zone('members')];
            }
            foreach ($denies as $deny) {
                list($page, $zone) = $deny;
                $test = $GLOBALS['SITE_DB']->query_select_value_if_there('group_page_access', 'group_id', ['group_id' => $id_new, 'zone_name' => $zone, 'page_name' => $page]);
                if ($test === null) {
                    $GLOBALS['SITE_DB']->query_insert('group_page_access', ['group_id' => $id_new, 'zone_name' => $zone, 'page_name' => $page]);
                }
            }

            // Privileges
            set_privilege($id_new, 'comcode_dangerous', $PROBED_FORUM_CONFIG['allowhtml'] == 1);
            set_privilege($id_new, 'bypass_wordfilter', $PROBED_FORUM_CONFIG['censorwords'] == 0);
            set_privilege($id_new, 'use_quick_reply', $PROBED_FORUM_CONFIG['quickreply'] == 1);
            set_privilege($id_new, 'vote_in_polls', ($row['forumpermissions'] & 32768) != 0);
            set_privilege($id_new, 'view_member_photos', ($row['genericpermissions'] & 4096) != 0);
            set_privilege($id_new, 'view_any_profile_field', ($row['genericpermissions'] & 262144) != 0);
            set_privilege($id_new, 'see_warnings', ($row['genericpermissions'] & 8) != 0);
            set_privilege($id_new, 'warn_members', ($row['genericpermissions'] & 16384) != 0);
            set_privilege($id_new, 'may_choose_custom_title', ($row['genericpermissions'] & 2048) != 0);

            $remap_id[$row['usergroupid']] = $id_new;
            import_id_remap_put('group', strval($row['usergroupid']), $id_new);
        }

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

    /**
     * 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)
    {
        $row_start = get_param_integer('JUMPSTART_import_cns_members', 0);
        $rows = [];
        do {
            $rows = $db->query('user u LEFT JOIN ' . $table_prefix . 'administrator a ON u.userid=a.userid LEFT JOIN ' . $table_prefix . 'usertextfield t ON u.userid=t.userid', ['*'], 'ORDER BY u.userid', 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('member', strval($row['userid']))) {
                    continue;
                }

                $test = $GLOBALS['CNS_DRIVER']->get_member_from_username($row['username']);
                if ($test !== null) {
                    import_id_remap_put('member', strval($row['userid']), $test);
                    continue;
                }

                $language = '';
                if (!empty($row['languageid'])) {
                    $rows2 = $db->query_select('language', ['languagecode'], ['languageid' => $row['languageid']]);
                    if (array_key_exists(0, $rows2)) {
                        $language = cms_strtoupper_ascii($rows2[0]['languagecode']);
                        if ((!file_exists(get_custom_file_base() . '/lang_custom/' . $language)) && (!file_exists(get_file_base() . '/lang/' . $language))) {
                            $language = '';
                        }
                    }
                }

                $primary_group = import_id_remap_get('group', strval($row['usergroupid']));
                $secondary_groups = [];
                if ($row['membergroupids'] != '') {
                    $secondary_groups = explode(',', $row['membergroupids']);
                }

                $custom_fields = [];
                if ($row['homepage'] != '') {
                    $custom_fields[cns_make_predefined_content_field('website')] = (strlen($row['homepage']) > 0) ? ('[url]' . $row['homepage'] . '[/url]') : '';
                }

                $signature = '[semihtml]' . $row['signature'] . '[/semihtml]';
                $validated = 1;
                $reveal_age = ($row['birthday'] != '') ? 1 : 0;
                $bits = explode('-', $row['birthday']);
                if (($reveal_age == 1) && (count($bits) == 3)) {
                    list($bday_month, $bday_day, $bday_year) = $bits;
                } else {
                    list($bday_month, $bday_day, $bday_year) = [0, 0, 0];
                }
                $title = ($row['customtitle'] == 1) ? $row['usertitle'] : '';
                $views_signatures = $row['options'] & 1;
                $preview_posts = 1;

                // These are done in the members-files stage
                $avatar_url = '';
                $photo_url = '';

                $password = $row['password'];
                $type = 'vb3';
                $salt = $row['salt'];

                $requests = $db->query_select('usergrouprequest', ['*'], ['userid' => $row['userid']]);
                foreach ($requests as $i => $request) {
                    $requests[$i]['usergroupid'] = import_id_remap_get('group', strval($request['usergroupid']));
                }

                $id_new = cns_make_member(
                    $row['username'], // username
                    $password, // password
                    $row['email'], // email_address
                    $primary_group, // primary_group
                    null, // secondary_groups
                    $bday_day, // dob_day
                    $bday_month, // dob_month
                    $bday_year, // dob_year
                    $custom_fields, // custom_fields
                    strval($row['timezoneoffset']), // timezone
                    $language, // language
                    '', // theme
                    $title, // title
                    $photo_url, // photo_url
                    $avatar_url, // avatar_url
                    $signature, // signature
                    $preview_posts, // preview_posts
                    $reveal_age, // reveal_age
                    $views_signatures, // views_signatures
                    $row['autosubscribe'], // auto_monitor_contrib_content
                    null, // smart_topic_notification
                    null, // mailing_list_style
                    1, // auto_mark_read
                    null, // sound_enabled
                    $row['options'] & 16, // allow_emails
                    $row['options'] & 16, // allow_emails_from_staff
                    0, // highlighted_name
                    '*', // pt_allow
                    '', // pt_rules_text
                    $validated, // validated
                    '', // validated_email_confirm_code
                    null, // probation_expiration_time
                    '0', // is_perm_banned
                    false, // check_correctness
                    null, // ip_address
                    $type, // password_compatibility_scheme
                    $salt, // salt
                    $row['joindate'], // join_time
                    $row['lastvisit'], // last_visit_time
                    ($row['lastpost'] == 0) ? $row['lastpost'] : null // last_submit_time
                );

                // Fix usergroup leadership
                $GLOBALS['FORUM_DB']->query_update('f_groups', ['g_group_lead_member' => $id_new], ['g_group_lead_member' => -$row['userid']]);

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

                // Set up usergroup membership
                foreach ($secondary_groups as $group) {
                    $group = import_id_remap_get('group', strval($group['usergroupid']));
                    cns_add_member_to_secondary_group($id_new, $group, 1);
                }
                foreach ($requests as $request) {
                    cns_add_member_to_secondary_group($id_new, $request['usergroupid'], 0);
                }
                cns_update_group_approvals($id_new, get_member());
            }

            $row_start += 200;
            $GLOBALS['JUMPSTART_import_cns_members'] = $row_start;
        } 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_member_files(object $db, string $table_prefix, string $file_base)
    {
        global $STRICT_FILE;

        $row_start = get_param_integer('JUMPSTART_import_cns_member_files', 0);
        $rows = [];
        do {
            $extra = '';
            $is_vb35 = ($db->query_select_value('customprofilepic', 'COUNT(*)', ['filedata' => ''], '', true) !== null); // i.e. if it doesn't fail

            if ($is_vb35) {
                $extra = ',a.filedata AS avatardata,p.filedata AS profilepicdata';
            }
            $query = 'SELECT *,p.filename AS p_filename,a.filename AS a_filename,u.userid AS userid' . $extra . ' FROM ' . $table_prefix . 'user u LEFT JOIN ' . $table_prefix . 'customavatar a ON a.userid=u.userid LEFT JOIN ' . $table_prefix . 'customprofilepic p ON p.userid=u.userid ORDER BY u.userid';
            $rows = $db->query($query, 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('member_files', strval($row['userid']))) {
                    continue;
                }

                $member_id = import_id_remap_get('member', strval($row['userid']));

                $photo_url = '';
                if ($row['profilepicdata'] !== null) {
                    list($photo_url) = $this->data_to_disk($row['profilepicdata'], $row['p_filename'], 'cns_photos');
                }

                $avatar_url = '';
                if ($row['avatardata'] !== null) {
                    list($avatar_url) = $this->data_to_disk($row['avatardata'], $row['a_filename'], 'cns_avatars');
                } else {
                    if (!empty($row['avatarid'])) {
                        $avatar_rows = $db->query_select('avatar', ['*'], ['avatarid' => $row['avatarid']]);
                        $setting_row = $db->query_select('setting', ['value', 'defaultvalue'], ['varname' => 'avatarpath']);
                        $setting = ($setting_row[0]['value'] == '') ? $setting_row[0]['defaultvalue'] : $setting_row[0]['value'];
                        if (array_key_exists(0, $avatar_rows)) {
                            $avatar_row = $avatar_rows[0];
                            $relpath = rawurldecode($avatar_row['avatarpath']);
                            if ((file_exists(get_custom_file_base() . '/uploads/cns_avatars/' . basename($relpath))) || (@copy($file_base . '/uploads/' . $setting . $relpath, get_custom_file_base() . '/uploads/cns_avatars/' . basename($relpath)))) {
                                $avatar_url = 'uploads/cns_avatars/' . basename($relpath);
                                sync_file(get_custom_file_base() . '/' . $avatar_url);
                            }
                        }
                    }
                }

                $GLOBALS['FORUM_DB']->query_update('f_members', ['m_avatar_url' => $avatar_url, 'm_photo_url' => $photo_url], ['id' => $member_id], '', 1);

                import_id_remap_put('member_files', strval($row['userid']), 1);
            }

            $row_start += 200;
            $GLOBALS['JUMPSTART_import_cns_member_files'] = $row_start;
        } 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_custom_profile_fields(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('profilefield', ['*']);
        foreach ($rows as $row) {
            if (import_check_if_imported('cpf', strval($row['profilefieldid']))) {
                continue;
            }

            $type = 'short_text';
            if ($row['type'] == 'input') {
                $type = 'short_text';
            } elseif ($row['type'] == 'textarea') {
                $type = 'long_text';
            }

            if (!array_key_exists('title', $row)) {
                $row['title'] = $db->query_value_if_there('SELECT text FROM ' . $table_prefix . 'phrase WHERE ' . db_string_equal_to('product', 'vbulletin') . ' AND ' . db_string_equal_to('varname', 'field' . strval($row['profilefieldid']) . '_title'));
                $row['description'] = $db->query_value_if_there('SELECT text FROM ' . $table_prefix . 'phrase WHERE ' . db_string_equal_to('product', 'vbulletin') . ' AND ' . db_string_equal_to('varname', 'field' . strval($row['profilefieldid']) . '_desc'));
            }
            $id_new = $GLOBALS['FORUM_DB']->query_select_value_if_there('f_custom_fields', 'id', [$GLOBALS['FORUM_DB']->translate_field_ref('cf_name') => $row['title']]);
            if ($id_new === null) {
                $id_new = cns_make_custom_field($row['title'], 0, $row['description'], '', 1 - $row['hidden'], 1 - $row['hidden'], $row['editable'], 0, $type, $row['required'], $row['memberlist'], $row['memberlist'], $row['displayorder'], '', 0, '', 0, 0, '', '', '', true);
            }

            import_id_remap_put('cpf', strval($row['profilefieldid']), $id_new);
        }

        $row_start = get_param_integer('JUMPSTART_import_cns_custom_profile_fields', 0);
        $members = [];
        do {
            $members = $db->query_select('userfield', ['*'], [], '', 200, $row_start);
            foreach ($members as $member) {
                if (import_check_if_imported('cpf_member', strval($member['userid']))) {
                    continue;
                }

                foreach ($rows as $row) {
                    $id_new = import_id_remap_get('cpf', strval($row['profilefieldid']));

                    $v = $member['field' . strval($row['profilefieldid'])];
                    $member_id = import_id_remap_get('member', strval($member['userid']), true);
                    if (($v != '') && ($member_id !== null)) {
                        cns_set_custom_field($member_id, intval($id_new), $v);
                    }
                }

                import_id_remap_put('cpf_member', strval($member['userid']), 1);
            }

            $row_start += 200;
            $GLOBALS['JUMPSTART_import_cns_custom_profile_fields'] = $row_start;
        } while (!empty($members));
    }

    /**
     * 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 $old_base_dir The base directory we are importing from
     */
    public function import_cns_forum_groupings(object $db, string $table_prefix, string $old_base_dir)
    {
        $rows = $db->query_select('forum', ['*'], ['parentid' => -1]);
        foreach ($rows as $row) {
            if (import_check_if_imported('category', strval($row['forumid']))) {
                continue;
            }

            $title = $row['title'];

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

            $id_new = cns_make_forum_grouping($title, $row['description'], 1);

            import_id_remap_put('category', strval($row['forumid']), $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 $old_base_dir The base directory we are importing from
     */
    public function import_cns_forums(object $db, string $table_prefix, string $old_base_dir)
    {
        require_code('cns_forums_action2');

        $remap_id = [];
        $rows = $db->query('SELECT * FROM ' . $table_prefix . 'forum WHERE parentid<>-1');
        foreach ($rows as $row) {
            $remapped = import_id_remap_get('forum', strval($row['forumid']), true);
            if ($remapped !== null) {
                $remap_id[$row['forumid']] = $remapped;
                continue;
            }

            $name = html_entity_decode($row['title'], ENT_QUOTES);
            $description = html_to_comcode($row['description']);
            $position = $row['displayorder'];
            $post_count_increment = 1;//$row['options']&4096; This didn't work, not important though

            $category_id = import_id_remap_get('category', strval($row['parentid']), true);
            if ($category_id === null) {
                $category_id = db_get_first_id();
            }
            $parent_forum = db_get_first_id();

            $permissions = $db->query_select('forumpermission', ['usergroupid', 'forumpermissions'], ['forumid' => $row['forumid']]);
            $access_mapping = [];
            foreach ($permissions as $p) {
                $x = $p['forumpermissions'];
                $v = 0;
                if (($x & 2) != 0) {
                    $v = 1;
                }
                if (($x & 16) != 0) {
                    $v = 2;
                }
                if ((($x & 16) != 0) && (!(($x & 131072) != 0))) {
                    $v = 3;
                }
                if ((($x & 16) != 0) && (!(($x & 131072) != 0)) && (($x & 64) != 0)) {
                    $v = 4;
                }
                if ((($x & 16) != 0) && (($x & 512) != 0)) {
                    $v = 5;
                }
                $group_id = import_id_remap_get('group', strval($p['usergroupid']));
                $access_mapping[$group_id] = $v;
            }

            $id_new = cns_make_forum($name, $description, $category_id, $access_mapping, $parent_forum, $position, $post_count_increment, 0, '', $row['password'], $row['link']);

            $remap_id[$row['forumid']] = $id_new;
            import_id_remap_put('forum', strval($row['forumid']), $id_new);
        }

        // Now we must fix parenting
        foreach ($rows as $row) {
            $parents = explode(',', $row['parentlist']);
            if (array_key_exists(2, $parents)) {
                $category_id = import_id_remap_get('category', strval($parents[1]), true);
                $r = $parents[($category_id === null) ? 1 : 2];
                if ($r != -1) {
                    $parent_id = $remap_id[$r];
                    $GLOBALS['FORUM_DB']->query_update('f_forums', ['f_parent_forum_id' => $parent_id], ['id' => $remap_id[$row['forumid']]], '', 1);
                }
            }
        }
    }

    /**
     * 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)
    {
        $row_start = get_param_integer('JUMPSTART_import_cns_topics', 0);
        $rows = [];
        do {
            $rows = $db->query_select('thread', ['*'], ['visible' => 1], 'ORDER BY threadid', 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('topic', strval($row['threadid']))) {
                    continue;
                }

                $forum_id = import_id_remap_get('forum', strval($row['forumid']));

                $emoticon = $this->convert_topic_emoticon($row['iconid']);

                $id_new = cns_make_topic($forum_id, '', $emoticon, $row['visible'], $row['open'], $row['sticky'], 0, null, null, false, $row['views']);

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

            $row_start += 200;
            $GLOBALS['JUMPSTART_import_cns_topics'] = $row_start;
        } while (!empty($rows));

        $rows = $db->query_select('announcement', ['*'], 'ORDER BY announcementid', 200, $row_start);
        foreach ($rows as $row) {
            if (import_check_if_imported('announcement', strval($row['announcementid']))) {
                continue;
            }

            $forum_id = import_id_remap_get('forum', strval($row['forumid']));

            $emoticon = '';

            $sticky = ($row['enddate'] < time()) ? 1 : 0;
            $cascading = $sticky;

            $id_new = cns_make_topic($forum_id, '', $emoticon, 1, 0, $sticky, $cascading, null, null, false, $row['views']);
            cns_make_post($id_new, @html_entity_decode($row['title'], ENT_QUOTES), $this->fix_links($row['pagetext'], $db, $table_prefix), 0, true, 1, 1, null, null, $row['startdate'], $row['userid'], null, null, null, false, false, $forum_id, false, $row['title']);

            import_id_remap_put('announcement', strval($row['announcementid']), $id_new);
        }

        // Read logs
        $rows = $db->query_select('threadread', ['*'], [], '', null, 0, true);
        if ($rows !== null) {
            foreach ($rows as $row) {
                send_http_output_ping();

                $member_id = import_id_remap_get('member', strval($row['userid']), true);
                $topic_id = import_id_remap_get('topic', strval($row['threadid']), true);
                if ($member_id === null) {
                    continue;
                }
                if ($topic_id === null) {
                    continue;
                }
                $new_row = ['l_member_id' => $member_id, 'l_topic_id' => $topic_id, 'l_time' => $row['readtime']];
                $GLOBALS['FORUM_DB']->query_delete('f_read_logs', $new_row, '', 1);
                $GLOBALS['FORUM_DB']->query_insert('f_read_logs', $new_row);
            }
        }
    }

    /**
     * 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)
    {
        global $STRICT_FILE;

        $row_start = get_param_integer('JUMPSTART_import_cns_posts', 0);

        $max_per_cycle = 5;

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

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

        $min_id = null;

        $rows = [];
        do {
            if ($min_id !== null) { // Key-set pagination, much faster
                $rows = $db->query('SELECT * FROM ' . $table_prefix . 'post WHERE postid>' . strval($min_id) . ' ORDER BY postid', $max_per_cycle);
            } else {
                $rows = $db->query('SELECT * FROM ' . $table_prefix . 'post ORDER BY postid', $max_per_cycle, $row_start);
            }
            foreach ($rows as $row) {
                $min_id = $row['postid'];

                if ($row['visible'] == 0) { // We don't have in WHERE query as there's no index for it
                    import_id_remap_put('post', strval($row['postid']), -1);
                    continue;
                }

                if (import_check_if_imported('post', strval($row['postid']))) {
                    continue;
                }

                $topic_id = import_id_remap_get('topic', strval($row['threadid']), true);
                if ($topic_id === null) {
                    import_id_remap_put('post', strval($row['postid']), -1);
                    continue;
                }
                $member_id = import_id_remap_get('member', strval($row['userid']), true);
                if ($member_id === null) {
                    $member_id = db_get_first_id();
                }

                // This speeds up addition... using the cache can reduce about 7/8 of a query per post on average
                global $TOPIC_FORUM_CACHE;
                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]);
                    if ($forum_id === null) {
                        continue;
                    }
                    $TOPIC_FORUM_CACHE[$topic_id] = $forum_id;
                }

                $title = '';
                if ($row['parentid'] == 0) {
                    $topics = $db->query_select('thread', ['title'], ['threadid' => $row['threadid']]);
                    $title = $topics[0]['title'];
                } elseif ($row['title'] !== null) {
                    $title = $row['title'];
                }

                $post = $this->fix_links($row['pagetext'], $db, $table_prefix);

                $last_edit_member = null;

                $id_new = cns_make_post($topic_id, @html_entity_decode($title, ENT_QUOTES), $post, 0, $row['parentid'] == 0, $row['visible'], 0, $row['username'], $row['ipaddress'], $row['dateline'], $member_id, null, null, $last_edit_member, false, false, $forum_id, false);

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

            $row_start += $max_per_cycle;
            $GLOBALS['JUMPSTART_import_cns_posts'] = $row_start;
        } while (!empty($rows));
    }

    /**
     * Substitution callback for 'fix_links'.
     *
     * @param  array $m The match
     * @return string The substitution string
     */
    protected function _fix_links_callback_topic(array $m) : string
    {
        return 'index.php?page=topicview&id=' . strval(import_id_remap_get('topic', strval($m[2]), true));
    }

    /**
     * Substitution callback for 'fix_links'.
     *
     * @param  array $m The match
     * @return string The substitution string
     */
    protected function _fix_links_callback_post(array $m) : string
    {
        return 'index.php?page=topicview&type=findpost&id=' . strval(import_id_remap_get('post', strval($m[2]), true));
    }

    /**
     * Substitution callback for 'fix_links'.
     *
     * @param  array $m The match
     * @return string The substitution string
     */
    protected function _fix_links_callback_forum(array $m) : string
    {
        return 'index.php?page=forumview&id=' . strval(import_id_remap_get('forum', strval($m[2]), true));
    }

    /**
     * Substitution callback for 'fix_links'.
     *
     * @param  array $m The match
     * @return string The substitution string
     */
    protected function _fix_links_callback_member(array $m) : string
    {
        return 'index.php?page=members&type=view&id=' . strval(import_id_remap_get('member', strval($m[2]), true));
    }

    /**
     * Convert vBulletin URLs pasted in text fields into software ones.
     *
     * @param  string $post The text field text (e.g. a post)
     * @param  object $db The database connector to import from
     * @param  string $table_prefix The table prefix the target prefix is using
     * @return string The new text field text
     */
    public function fix_links(string $post, object $db, string $table_prefix) : string
    {
        global $OLD_BASE_URL;
        if ($OLD_BASE_URL === null) {
            $rows = $db->query_select('setting', ['value'], ['varname' => 'bburl']);
            $OLD_BASE_URL = $rows[0]['value'];
        }
        $post = preg_replace_callback('#' . preg_quote($OLD_BASE_URL) . '/(showthread\.php\?t=)(\d*)(&page=\d+)?#', [$this, '_fix_links_callback_topic'], $post);
        $post = preg_replace_callback('#' . preg_quote($OLD_BASE_URL) . '/(showpost\.php\?p=)(\d*)#', [$this, '_fix_links_callback_post'], $post);
        $post = preg_replace_callback('#' . preg_quote($OLD_BASE_URL) . '/(forumdisplay\.php\?f=)(\d*)#', [$this, '_fix_links_callback_forum'], $post);
        $post = preg_replace_callback('#' . preg_quote($OLD_BASE_URL) . '/(member\.php\?user=)(\d*)#', [$this, '_fix_links_callback_member'], $post);
        $post = preg_replace('#\[size="?(\d+)"?\]#', '[size="${1}of"]', $post);
        return $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_post_files(object $db, string $table_prefix, string $file_base)
    {
        global $STRICT_FILE;
        require_code('attachments2');
        require_code('attachments3');

        $row_start = get_param_integer('JUMPSTART_import_cns_post_files', 0);
        $rows = [];
        do {
            $rows = $db->query_select('attachment', ['*'], 'ORDER BY attachmentid', 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('post_files', strval($row['attachmentid']))) {
                    continue;
                }

                $post_id = import_id_remap_get('post', strval($row['postid']), true);
                if (($post_id === null) || ($post_id == -1)) {
                    continue;
                }

                $post_row = $GLOBALS['FORUM_DB']->query_select('f_posts', ['p_time', 'p_posting_member', 'p_post'], ['id' => $post_id], '', 1);
                if (!array_key_exists(0, $post_row)) {
                    import_id_remap_put('post_files', strval($row['attachmentid']), 1);
                    continue; // Orphaned post
                }
                $post = get_translated_text($post_row[0]['p_post']);
                $member_id = $post_row[0]['p_posting_member'];

                list($url, $thumb_url) = $this->data_to_disk($row['filedata'], $row['filename'], 'attachments', $row['thumbnail'], true);
                $a_id = $GLOBALS['FORUM_DB']->query_insert('attachments', ['a_member_id' => $member_id, 'a_file_size' => $row['filesize'], 'a_url' => $url, 'a_thumb_url' => $thumb_url, 'a_original_filename' => $row['filename'], 'a_num_downloads' => $row['counter'], 'a_last_downloaded_time' => null, 'a_add_time' => $row['dateline'], 'a_description' => ''], true);

                $GLOBALS['FORUM_DB']->query_insert('attachment_refs', ['r_referer_type' => 'cns_post', 'r_referer_id' => strval($post_id), 'a_id' => $a_id]);
                $post .= "\n\n" . '[attachment]' . strval($a_id) . '[/attachment]';

                cns_over_msn();
                $GLOBALS['FORUM_DB']->query_update('f_posts', update_lang_comcode_attachments('p_post', $post_row[0]['p_post'], $post, 'cns_post', strval($post_id)), ['id' => $post_id], '', 1);
                cns_over_local();

                import_id_remap_put('post_files', strval($row['attachmentid']), 1);
            }

            $row_start += 200;
            $GLOBALS['JUMPSTART_import_cns_post_files'] = $row_start;
        } while (!empty($rows));
    }

    /**
     * Convert a VB database file to a software uploaded file (stored on disk).
     *
     * @param  string $data The file data
     * @param  string $filename The optimal filename
     * @param  ID_TEXT $sections The upload type (e.g. cns_photos)
     * @param  string $thumbnail_data Thumbnail data (blank: no thumbnail / generate one if asked)
     * @param  boolean $obfuscate Whether to obfuscate the file type
     * @return array A tuple containing the URL, and if requested, the thumbnail
     */
    public function data_to_disk(string $data, string $filename, string $sections, string $thumbnail_data = '', bool $obfuscate = false) : array
    {
        if ($filename == '') {
            $filetype = '';
            if (substr($data, 4, 4) == 'JFIF') {
                $filetype = 'jpg';
            } elseif (substr($data, 0, 3) == 'GIF') {
                $filetype = 'gif';
            } elseif (substr($data, 1, 3) == 'PNG') {
                $filetype = 'png';
            }

            if ($filetype != '') {
                $filename = uniqid('', true) . '.' . $filetype;
            }
        }
        if ($filename != '') {
            require_code('files');

            // Save main file
            list($path, $url) = find_unique_path('uploads/' . $sections, $filename . ($obfuscate ? '.bin' : ''));
            cms_file_put_contents_safe($path, $data, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

            // Save thumbnail
            if ($thumbnail_data == '') {
                return [$url, ''];
            } else {
                // Given directly, so save
                list($path, $thumb_url) = find_unique_path('uploads/' . $sections . '_thumbs', $filename);
                cms_file_put_contents_safe($path, $thumbnail_data, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

                return [$url, $thumb_url];
            }
        }
        return ['', ''];
    }

    /**
     * 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)
    {
        $rows = $db->query_select('poll p LEFT JOIN ' . $table_prefix . 'thread t ON p.pollid=t.pollid', ['*', 'p.pollid AS pollid']);
        foreach ($rows as $row) {
            if (import_check_if_imported('poll', strval($row['pollid']))) {
                continue;
            }

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

            $is_open = $row['active'];

            $answers = explode('|||', $row['options']);
            $maximum = ($row['multiple'] == 1) ? count($answers) : 1;

            $rows2 = $db->query_select('pollvote', ['*'], ['pollid' => $row['pollid']]);
            foreach ($rows2 as $i => $row2) {
                $rows2[$i]['userid'] = import_id_remap_get('member', strval($row2['userid']), true);
            }

            $id_new = cns_make_poll($topic_id, $row['question'], 0, $is_open, 1, $maximum, 0, $answers, 0, 0, 1, 0, false);

            $answers = collapse_1d_complexity('id', $GLOBALS['FORUM_DB']->query_select('f_poll_answers', ['id'], ['pa_poll_id' => $id_new])); // Effectively, a remapping from VB vote number to software vote number

            foreach ($rows2 as $row2) {
                $member_id = $row2['userid'];
                if (!empty($member_id)) {
                    $answer = array_key_exists($row2['voteoption'] - 1, $answers) ? $answers[$row2['voteoption'] - 1] : -1;
                    $GLOBALS['FORUM_DB']->query_insert('f_poll_votes', ['pv_poll_id' => $id_new, 'pv_member_id' => $member_id, 'pv_answer_id' => $answer, 'pv_ip_address' => '', 'pv_revoked' => 0, 'pv_date_time' => $row2['votedate'], 'pv_points_when_voted' => 0]);
                }
            }

            import_id_remap_put('poll', strval($row['pollid']), $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_calendar(object $db, string $table_prefix, string $file_base)
    {
        require_code('calendar2');

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

            $submitter = import_id_remap_get('member', strval($row['userid']), true);
            if ($submitter === null) {
                $submitter = $GLOBALS['CNS_DRIVER']->get_guest_id();
            }

            $recurrence = 'none';
            $recurrences = null;
            $bits = explode('|', $row['recuroption']);
            switch ($row['recurring']) {
                case 1:
                    $recurrence = 'daily ' . str_repeat('0', intval($bits[0]) - 1) . '1';
                    break;
                case 2:
                    $recurrence = 'daily 1111100';
                    break;
                case 3:
                    $recurrence = 'daily 0000000';
                    if (($bits[1] & 1) != 0) {
                        $recurrence[6] = '1';
                    }
                    if (($bits[1] & 2) != 0) {
                        $recurrence[7] = '1';
                    }
                    if (($bits[1] & 4) != 0) {
                        $recurrence[8] = '1';
                    }
                    if (($bits[1] & 8) != 0) {
                        $recurrence[9] = '1';
                    }
                    if (($bits[1] & 16) != 0) {
                        $recurrence[10] = '1';
                    }
                    if (($bits[1] & 32) != 0) {
                        $recurrence[11] = '1';
                    }
                    if (($bits[1] & 64) != 0) {
                        $recurrence[12] = '1';
                    }
                    $recurrences = $bits[0]; // Guess
                    break;
                case 4:
                    $recurrence = 'monthly ' . str_repeat('0', intval($bits[1]) - 1) . '1';
                    break;
                case 5:
                    $recurrence = 'monthly ' . str_repeat('0', intval($bits[0]) - 1) . '1';
                    break;
                case 6:
                    $recurrence = 'yearly ' . str_repeat('0', intval($bits[0]) - 1) . '1';
                    break;
                case 7:
                    $recurrence = 'yearly ' . str_repeat('0', intval($bits[0]) - 1) . '1';
                    break;
            }
            list($start_year, $start_month, $start_day, $start_hour, $start_minute) = explode('-', date('Y-m-d-h-i', $row['dateline']));
            list($end_year, $end_month, $end_day, $end_hour, $end_minute) = [null, null, null, null, null];
            cns_over_msn();
            $id_new = add_calendar_event(db_get_first_id() + 1, $recurrence, $recurrences, 0, $row['title'], $row['event'], 3, $start_year, $start_month, $start_day, 'day_of_month', $start_hour, $start_minute, $end_year, $end_month, $end_day, 'day_of_month', $end_hour, $end_minute, null, 1, null, 1, 1, 1, 1, '', $submitter, 0, $row['dateline']);
            cns_over_local();
            if ($row['visible'] == 0) {
                if (addon_installed('content_privacy')) {
                    $GLOBALS['SITE_DB']->query_insert('content_privacy', [
                        'content_type' => 'event',
                        'content_id' => strval($id_new),
                        'guest_view' => 0,
                        'member_view' => 0,
                        'friend_view' => 0,
                    ]);
                }
            }

            import_id_remap_put('event', strval($row['eventid']), $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 $old_base_dir The base directory we are importing from
     */
    public function import_cns_private_topics(object $db, string $table_prefix, string $old_base_dir)
    {
        $sql = 'SELECT pmid,fromuserid,userid,title FROM ' . $table_prefix . 'pm p LEFT JOIN ' . $table_prefix . 'pmtext t ON p.pmtextid=t.pmtextid WHERE folderid<>-1 AND title NOT LIKE \'' . db_encode_like('Sent:  %') . '\' ORDER BY dateline';
        $rows = $db->query($sql);

        // Group them up into what will become topics
        $groups = [];
        foreach ($rows as $row) {
            // Do some fiddling around for duplication
            if ($row['fromuserid'] > $row['userid']) {
                $a = $row['userid'];
                $b = $row['fromuserid'];
            } else {
                $a = $row['fromuserid'];
                $b = $row['userid'];
            }
            $row['title'] = str_replace('Re: ', '', $row['title']);
            $groups[strval($a) . ':' . strval($b) . ':' . $row['title']][] = $row;
        }

        // Import topics
        foreach ($groups as $group) {
            $row = $group[0];

            if (import_check_if_imported('pt', strval($row['pmid']))) {
                continue;
            }

            $row_sup = $db->query_select('pm p LEFT JOIN ' . $table_prefix . 'pmtext t ON p.pmtextid=t.pmtextid', ['iconid', 'dateline', 'fromusername', 'message'], ['pmid' => $row['pmid']], '', 1);
            $row += $row_sup[0];

            // Create topic
            $from_id = import_id_remap_get('member', strval($row['fromuserid']), true);
            if ($from_id === null) {
                $from_id = $GLOBALS['CNS_DRIVER']->get_guest_id();
            }
            $to_id = import_id_remap_get('member', strval($row['userid']), true);
            if ($to_id === null) {
                $to_id = $GLOBALS['CNS_DRIVER']->get_guest_id();
            }
            $topic_id = cns_make_topic(null, '', $this->convert_topic_emoticon($row['iconid']), 1, 1, 0, 0, $from_id, $to_id, false);

            $first_post = true;
            foreach ($group as $_postdetails) {
                $_postdetails_sup = $db->query_select('pm p LEFT JOIN ' . $table_prefix . 'pmtext t ON p.pmtextid=t.pmtextid', ['iconid', 'dateline', 'fromusername', 'message'], ['pmid' => $_postdetails['pmid']], '', 1);
                $_postdetails += $_postdetails_sup[0];

                if ($first_post) {
                    $title = $row['title'];
                } else {
                    $title = '';
                }

                $post = $this->fix_links($_postdetails['message'], $db, $table_prefix);
                $validated = 1;
                $from_id = import_id_remap_get('member', strval($_postdetails['fromuserid']), true);
                if ($from_id === null) {
                    $from_id = $GLOBALS['CNS_DRIVER']->get_guest_id();
                }
                $poster_name_if_guest = $_postdetails['fromusername'];
                $ip_address = $GLOBALS['CNS_DRIVER']->get_member_row_field($from_id, 'm_ip_address');
                $time = $_postdetails['dateline'];
                $poster = $from_id;
                $last_edit_time = null;
                $last_edit_member = null;

                cns_make_post($topic_id, $title, $post, 0, $first_post, $validated, 0, $poster_name_if_guest, $ip_address, $time, $poster, null, $last_edit_time, $last_edit_member, false, false, null, false);
                $first_post = false;
            }

            import_id_remap_put('pt', strval($row['pmid']), $topic_id);
        }
    }

    /**
     * Convert a VB topic icon code into a standard software theme image code.
     *
     * @param  integer $iconid VB code
     * @return ID_TEXT Software code
     */
    public function convert_topic_emoticon(int $iconid) : string
    {
        switch ($iconid) {
            case 6:
                return 'cns_emoticons/cool';
            case 7:
                return 'cns_emoticons/smile';
            case 8:
                return 'cns_emoticons/angry';
            case 9:
                return 'cns_emoticons/sad';
            case 10:
                return 'cns_emoticons/grin';
            case 11:
                return 'cns_emoticons/shocked';
            case 12:
                return 'cns_emoticons/wink';
            case 14:
                return 'cns_emoticons/thumbs';
        }
        return '';
    }

    /**
     * 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');
        require_code('comcode_compiler');

        init_valid_comcode_tags();

        $rows = $db->query_select('bbcode', ['*']);
        foreach ($rows as $row) {
            if (import_check_if_imported('custom_comcode', strval($row['bbcodeid']))) {
                continue;
            }

            global $VALID_COMCODE_TAGS;
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('custom_comcode', 'tag_tag', ['tag_tag' => $row['bbcodetag']]);
            if ((array_key_exists($row['bbcodetag'], $VALID_COMCODE_TAGS)) || ($test !== null)) {
                import_id_remap_put('custom_comcode', strval($row['bbcodeid']), 1);
                continue;
            }

            $tag = $row['bbcodetag'];
            $title = $row['title'];
            $description = $row['bbcodeexplanation'];
            $replace = $row['bbcodereplacement'];
            $example = $row['bbcodeexample'];
            $parameters = '';
            $enabled = 1;
            $dangerous_tag = 0;
            $block_tag = 0;
            $textual_tag = 1;

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

            import_id_remap_put('custom_comcode', strval($row['bbcodeid']), 1);
        }
    }

    /**
     * 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)
    {
        require_code('notifications');

        $rows = $db->query_select('subscribeforum', ['*']);
        foreach ($rows as $row) {
            if (import_check_if_imported('forum_notification', strval($row['subscribeforumid']))) {
                continue;
            }

            $member_id = import_id_remap_get('member', strval($row['userid']), true);
            if ($member_id === null) {
                continue;
            }
            $forum_id = import_id_remap_get('forum', strval($row['forumid']), true);
            if ($forum_id === null) {
                continue;
            }
            set_notifications('cns_topic', 'forum:' . strval($forum_id), $member_id);

            import_id_remap_put('forum_notification', strval($row['subscribeforumid']), 1);
        }
        $row_start = 0;
        do {
            $rows = $db->query_select('subscribethread', ['*'], [], '', 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('topic_notification', strval($row['subscribethreadid']))) {
                    continue;
                }

                $member_id = import_id_remap_get('member', strval($row['userid']), true);
                if ($member_id === null) {
                    continue;
                }
                $topic_id = import_id_remap_get('topic', strval($row['threadid']), true);
                if ($topic_id === null) {
                    continue;
                }
                set_notifications('cns_topic', strval($topic_id), $member_id);

                import_id_remap_put('topic_notification', strval($row['subscribethreadid']), 1);
            }

            $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_logs(object $db, string $table_prefix, string $file_base)
    {
        require_code('cns_general_action2');

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

            cns_mod_log_it('EDIT_POST', strval(import_id_remap_get('post', strval($row['postid']), true)), '', $row['reason'], $row['userid'], $row['dateline']);

            import_id_remap_put('editlog', strval($row['postid']), -1);
        }
        $rows = $db->query_select('deletionlog', ['*']);
        foreach ($rows as $row) {
            if (import_check_if_imported('deletionlog', strval($row['primaryid']))) {
                continue;
            }

            if ($row['type'] == 'post') {
                cns_mod_log_it('DELETE_POST', '', '', $row['reason'], $row['userid']);
            } elseif ($row['type'] == 'thread') {
                cns_mod_log_it('DELETE_TOPIC', '', '', $row['reason'], $row['userid']);
            }

            import_id_remap_put('deletionlog', strval($row['primaryid']), -1);
        }
        /*
        moderatorlog
         Closed
         Closed Thread
         Opened Thread
         Post * by * Removed
         Post * Edited
         Stuck
         Stuck Thread
         Thread edited. (visible: ?; open: ?; sticky: ?)
         Thread moved to '*'.
         Thread Removed
        actionlog
        */
    }

    /**
     * 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('points');

        $row_start = get_param_integer('JUMPSTART_import_points_ledger', 0);
        do {
            $rows = $db->query_select('reputation', ['*'], [], '', 200, $row_start);
            foreach ($rows as $row) {
                if (import_check_if_imported('points', strval($row['reputationid']))) {
                    continue;
                }

                $time = $row['dateline'];
                $amount = $row['reputation'];
                $viewer_member = import_id_remap_get('member', strval($row['whoadded']), true);
                $member_id = import_id_remap_get('member', strval($row['userid']), true);
                $reason = $row['reason'];
                $anonymous = 0;
                $map = [
                    'date_and_time' => $time,
                    'amount_gift_points' => $amount,
                    'amount_points' => 0,
                    'sending_member' => $viewer_member,
                    'receiving_member' => $member_id,
                    'anonymous' => $anonymous,
                    'linked_ledger_id' => null,
                    't_type' => 'legacy',
                    't_subtype' => 'admin_import',
                    't_type_id' => 'vb3',
                    'status' => LEDGER_STATUS_NORMAL,
                    'locked' => 0,
                    'is_ranked' => 0,
                ];
                $map += insert_lang_comcode('reason', $reason, 4);
                $GLOBALS['SITE_DB']->query_insert('points_ledger', $map);

                import_id_remap_put('points', strval($row['reputationid']), -1);
            }

            $row_start += 200;
            $GLOBALS['JUMPSTART_import_points_ledger'] = $row_start;
        } 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_wordfilter(object $db, string $table_prefix, string $file_base)
    {
        $rows = $db->query_select('setting', ['value'], ['varname' => 'censorwords']);
        $censorwords = $rows[0]['value'];
        foreach (explode(' ', $censorwords) as $word) {
            if ($word != '') {
                add_wordfilter_word($word);
            }
        }
    }
}
