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

/**
 * Module page class.
 */
class Module_admin_setupwizard
{
    /**
     * Find details of the module.
     *
     * @return ?array Map of module info (null: module is disabled)
     */
    public function info() : ?array
    {
        $info = [];
        $info['author'] = 'Chris Graham';
        $info['organisation'] = 'Composr';
        $info['hacked_by'] = null;
        $info['hack_version'] = null;
        $info['version'] = 2;
        $info['locked'] = false;
        $info['min_cms_version'] = 11.0;
        $info['addon'] = 'setupwizard';
        return $info;
    }

    /**
     * Find entry-points available within this module.
     *
     * @param  boolean $check_perms Whether to check permissions
     * @param  ?MEMBER $member_id The member to check permissions as (null: current user)
     * @param  boolean $support_crosslinks Whether to allow cross links to other modules (identifiable via a full-page-link rather than a screen-name)
     * @param  boolean $be_deferential Whether to avoid any entry-point (or even return null to disable the page in the Sitemap) if we know another module, or page_group, is going to link to that entry-point. Note that "!" and "browse" entry points are automatically merged with container page nodes (likely called by page-groupings) as appropriate.
     * @return ?array A map of entry points (screen-name=>language-code/string or screen-name=>[language-code/string, icon-theme-image]) (null: disabled)
     */
    public function get_entry_points(bool $check_perms = true, ?int $member_id = null, bool $support_crosslinks = true, bool $be_deferential = false) : ?array
    {
        if (!addon_installed('setupwizard')) {
            return null;
        }

        return [
            'browse' => ['SETUPWIZARD', 'menu/adminzone/setup/setupwizard'],
            'install_test_content' => ['INSTALL_TEST_CONTENT', 'admin/add_one_category'],
            'uninstall_test_content' => ['UNINSTALL_TEST_CONTENT', 'buttons/clear'],
        ];
    }

    public $title;

    /**
     * Module pre-run function. Allows us to know metadata for <head> before we start streaming output.
     *
     * @return ?Tempcode Tempcode indicating some kind of exceptional output (null: none)
     */
    public function pre_run() : ?object
    {
        $error_msg = new Tempcode();
        if (!addon_installed__messaged('setupwizard', $error_msg)) {
            return $error_msg;
        }

        require_code('form_templates'); // Needs to run high so that the anti-click-hacking header is sent

        $type = get_param_string('type', 'browse');

        require_lang('config');

        set_helper_panel_tutorial('tut_configuration');

        breadcrumb_set_self(do_lang_tempcode('SETUPWIZARD'));

        if ($type == 'browse') {
            $step = 1;
        }

        if (!in_array($type, ['browse', 'install_test_content', 'uninstall_test_content'])) {
            //breadcrumb_set_parents([['_SELF:_SELF:browse', do_lang_tempcode('START')]]);

            $step = min(10, intval(substr($type, 4)));
        }

        if ($type == 'install_test_content') {
            $this->title = get_screen_title('INSTALL_TEST_CONTENT');
        } elseif ($type == 'uninstall_test_content') {
            $this->title = get_screen_title('UNINSTALL_TEST_CONTENT');
        } else {
            $this->title = get_screen_title('SETUPWIZARD');
        }

        return null;
    }

    /**
     * Execute the module.
     *
     * @return Tempcode The result of execution
     */
    public function run() : object
    {
        appengine_live_guard();

        require_css('setupwizard');
        require_code('setupwizard');
        require_javascript('setupwizard');
        require_code('content2');

        $type = get_param_string('type', 'browse');

        if (($type != 'browse') && ($type != 'step11') && ($type != 'install_test_content') && ($type != 'uninstall_test_content')) {
            if ($_SERVER['REQUEST_METHOD'] != 'POST') {
                warn_exit(do_lang_tempcode('IMPROPERLY_FILLED_IN'), false, false, 400);
            }
        }

        if ($type == 'browse') {
            return $this->step1(); // welcome
        }
        if ($type == 'step2') {
            return $this->step2(); // information
        }
        if ($type == 'step3') {
            return $this->step3(); // config
        }
        if ($type == 'step4') {
            return $this->step4(); // addons
        }
        if ($type == 'step5') {
            return $this->step5(); // zone/feature configuration
        }
        if ($type == 'step6') {
            return $this->step6(); // block choice
        }
        if ($type == 'step7') {
            return $this->step7(); // rules
        }
        if ($type == 'step8') {
            return $this->step8(); // Theme Wizard
        }
        if ($type == 'step9') {
            return $this->step9(); // close-status
        }
        if ($type == 'step10') {
            return $this->step10(); // actualisation (redirects to step11 automatically)
        }
        if ($type == 'step11') {
            return $this->step11(); // do-next
        }
        if ($type == 'install_test_content') {
            return $this->install_test_content();
        }
        if ($type == 'uninstall_test_content') {
            return $this->uninstall_test_content();
        }

        return new Tempcode();
    }

    /**
     * UI for a setup wizard step (welcome).
     *
     * @return Tempcode The UI
     */
    public function step1() : object
    {
        $dh = @opendir(get_custom_file_base() . '/imports/addons/');
        $addons_available = [];
        if ($dh !== false) {
            while (($file = readdir($dh)) !== false) {
                if (substr($file, -4) == '.tar') {
                    $addons_available[] = basename($file, '.tar');
                }
            }
            closedir($dh);
        }
        foreach ($addons_available as $aa) {
            if (!addon_installed($aa)) {
                $addon_management = build_url(['page' => 'admin_addons'], get_module_zone('admin_addons'));
                attach_message(do_lang_tempcode('ADDONS_NOT_INSTALLED_IN_SETUPWIZARD', escape_html($addon_management->evaluate())), 'notice');
                break;
            }
        }

        $_done_once = get_value('setupwizard_completed', '0', true);
        $done_once = $_done_once != '0';

        $post_url = build_url(['page' => '_SELF', 'type' => 'step2'], '_SELF', ['keep_theme_seed' => true, 'keep_theme_dark' => true, 'keep_theme_source' => true, 'keep_theme_algorithm' => true]);
        $text = new Tempcode();
        $addons_url = build_url(['page' => 'admin_addons'], get_module_zone('admin_addons'));
        if ($done_once) {
            $text_1 = do_lang_tempcode('SETUPWIZARD_1_DESCRIBE_ALT', escape_html($addons_url->evaluate()));
        } else {
            $text_1 = do_lang_tempcode('SETUPWIZARD_1_DESCRIBE', escape_html($addons_url->evaluate()));
            if (get_param_integer('came_from_installer', 0) == 0) {
                $text_1->attach(do_lang_tempcode('SETUPWIZARD_1_DESCRIBE_SUP', escape_html($addons_url->evaluate())));
                $text_1 = protect_from_escaping($text_1);
            }
        }
        $text->attach(paragraph($text_1));
        $rescue_url = build_url(['page' => '', 'keep_safe_mode' => '1'], '');
        $text->attach(paragraph(do_lang_tempcode('SETUPWIZARD_SAFE_MODE', escape_html($rescue_url->evaluate()), do_template('ICON', ['_GUID' => '049d21a64c40a98fc06229bb3985c85a', 'NAME' => 'tool_buttons/software_chat']))));
        $submit_name = do_lang_tempcode('START');

        $fields = new Tempcode();

        $hidden = new Tempcode();
        $hidden->attach(form_input_hidden('csrf_token_preserve', '1'));

        $inner = do_template('FORM', [
            '_GUID' => '71316d91703e3549301f57182405c997',
            'SKIP_WEBSTANDARDS' => true,
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => '38a02343903542f8bbe1fb49a7b21eb7',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(1)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (information).
     *
     * @return Tempcode The UI
     */
    public function step2() : object
    {
        $post_url = build_url(['page' => '_SELF', 'type' => 'step3'], '_SELF');
        $submit_name = do_lang_tempcode('PROCEED');
        $hidden = static_evaluate_tempcode(build_keep_post_fields());

        // Choice of install profile...

        $installprofiles = new Tempcode();
        $installprofiles->attach(form_input_list_entry('', true, do_lang_tempcode('NA_EM')));

        // Install-profiles of installed themes
        require_code('themes2');
        $themes = find_all_themes();
        foreach ($themes as $theme => $theme_title) {
            $hook = get_theme_option('setupwizard__install_profile', null, $theme);
            if ($hook != '') {
                $path = get_file_base() . '/sources_custom/hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($hook) . '.php';
                if (!file_exists($path)) {
                    $path = get_file_base() . '/sources/hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($hook) . '.php';
                }
                if (file_exists($path)) {
                    $_hook_bits = extract_module_functions($path, ['info']);
                    $installprofile = is_array($_hook_bits[0]) ? call_user_func_array($_hook_bits[0][0], $_hook_bits[0][1]) : cms_eval($_hook_bits[0], $path);
                    if ($installprofile !== null) {
                        $installprofiles->attach(form_input_list_entry($hook . '__' . $theme, get_param_string('id', '') == $hook . '__' . $theme, do_lang('INSTALLPROFILE_WITH_THEME', $installprofile['title'], $theme_title)));
                    }
                }
            }
        }

        // Pure install profiles
        $hooks = find_all_hooks('modules', 'admin_setupwizard_installprofiles');
        require_code('zones2');
        foreach (array_keys($hooks) as $hook) {
            $path = get_file_base() . '/sources_custom/hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($hook) . '.php';
            if (!file_exists($path)) {
                $path = get_file_base() . '/sources/hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($hook) . '.php';
            }
            $_hook_bits = extract_module_functions($path, ['info']);
            $installprofile = is_array($_hook_bits[0]) ? call_user_func_array($_hook_bits[0][0], $_hook_bits[0][1]) : cms_eval($_hook_bits[0], $path);
            if ($installprofile !== null) {
                $installprofiles->attach(form_input_list_entry($hook, get_param_string('id', '') == $hook, $installprofile['title']));
            }
        }

        $fields = new Tempcode();
        $fields->attach(form_input_huge_list(do_lang_tempcode('INSTALLPROFILE'), do_lang_tempcode('DESCRIPTION_INSTALLPROFILE'), 'installprofile', $installprofiles, null, true, false));

        // --

        $text = do_template('SETUPWIZARD_2', [
            '_GUID' => '2042f3786d10c7c5be5d38ea28942b47',
            'SKIP_WEBSTANDARDS' => true,
            'URL' => $post_url,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
        ]);

        $inner = do_template('FORM', [
            '_GUID' => '185da65845b3001cc152b15845b63b89',
            'SKIP_WEBSTANDARDS' => true,
            'SKIP_REQUIRED' => true,
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);

        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => 'e04bc40dfc4047b62b586711d15ad875',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(2)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (config).
     *
     * @return Tempcode The UI
     */
    public function step3() : object
    {
        $post_url = build_url(['page' => '_SELF', 'type' => 'step4'], '_SELF');
        $text = do_lang_tempcode('SETUPWIZARD_3_DESCRIBE');
        $submit_name = do_lang_tempcode('PROCEED');

        $fields = new Tempcode();
        $hidden = static_evaluate_tempcode(build_keep_post_fields(['installprofile']));

        $installprofile = post_param_string('installprofile', '');
        if (strpos($installprofile, '__') !== false) {
            list($installprofile, $theme) = explode('__', $installprofile);
            $hidden .= static_evaluate_tempcode(form_input_hidden('source_theme', $theme));
        } else {
            $hidden .= static_evaluate_tempcode(form_input_hidden('source_theme', 'default'));
        }
        $hidden .= static_evaluate_tempcode(form_input_hidden('installprofile', $installprofile));

        require_lang('zones');

        $site_name = get_option('site_name');
        $description = get_option('description');
        $site_scope = get_option('site_scope');
        $copyright = get_option('copyright');
        $keywords = get_option('keywords');
        $google_analytics = get_option('google_analytics');
        $timezone = get_site_timezone();

        if ($site_name == '???') {
            $site_name = do_lang('EXAMPLE_SITE_NAME');
        }
        if ($description == '???') {
            $description = do_lang('EXAMPLE_DESCRIPTION');
        }
        if ($site_scope == '???') {
            $site_scope = do_lang('EXAMPLE_SITE_SCOPE');
        }
        if (strpos($copyright, '???') !== false) {
            $copyright = do_lang('EXAMPLE_COPYRIGHT');
        }
        if ($keywords == '') {
            $keywords = do_lang('EXAMPLE_KEYWORDS');
        }

        $fields->attach(form_input_line(do_lang_tempcode('SITE_NAME'), do_lang_tempcode('CONFIG_OPTION_site_name'), 'site_name', $site_name, true));

        $fields->attach(form_input_line(do_lang_tempcode('DESCRIPTION'), do_lang_tempcode('CONFIG_OPTION_description'), 'description', $description, false));

        $fields->attach(form_input_line(do_lang_tempcode('SITE_SCOPE'), do_lang_tempcode('CONFIG_OPTION_site_scope'), 'site_scope', $site_scope, true));

        $fields->attach(form_input_line(do_lang_tempcode('KEYWORDS'), do_lang_tempcode('CONFIG_OPTION_keywords'), 'keywords', $keywords, false));

        $fields->attach(form_input_timezone(do_lang_tempcode('TIMEZONE'), do_lang_tempcode('CONFIG_OPTION_timezone'), 'timezone', $timezone));

        $fields->attach(form_input_line(do_lang_tempcode('GOOGLE_ANALYTICS'), do_lang_tempcode('CONFIG_OPTION_google_analytics'), 'google_analytics', $google_analytics, false));

        if (get_theme_option('setupwizard__provide_cms_advert_choice', null, post_param_string('source_theme', 'default')) == '1') {
            $panel_path = get_custom_file_base() . '/pages/comcode_custom/' . get_site_default_lang() . '/panel_right.txt';
            if (file_exists($panel_path)) {
                $include_cms_advert = strpos(cms_file_get_contents_safe($panel_path, FILE_READ_LOCK), 'logos/') !== false;
            } else {
                $include_cms_advert = false;
            }
            $fields->attach(form_input_tick(do_lang_tempcode('INCLUDE_CMS_ADVERT'), do_lang_tempcode('DESCRIPTION_INCLUDE_CMS_ADVERT'), 'include_cms_advert', $include_cms_advert));
        } else {
            $hidden .= static_evaluate_tempcode(form_input_hidden('include_cms_advert', '0'));
        }

        $pass_length = get_option('minimum_password_length');
        if ($pass_length >= 16) {
            $security_level = 'extreme';
        } elseif ($pass_length >= 12) {
            $security_level = 'high';
        } elseif ($pass_length >= 8) {
            $security_level = 'medium';
        } elseif ($pass_length >= 4) {
            $security_level = 'low';
        } else {
            $security_level = 'minimum';
        }

        $security_levels = new Tempcode();
        foreach (['minimum', 'low', 'medium', 'high', 'extreme'] as $_security_level) {
            $security_levels->attach(form_input_list_entry($_security_level, $_security_level == $security_level, do_lang_tempcode('SECURITY_LEVEL_' . $_security_level)));
        }
        $fields->attach(form_input_list(do_lang_tempcode('SECURITY_LEVEL'), do_lang_tempcode('DESCRIPTION_SECURITY_LEVEL'), 'security_level', $security_levels));

        $inner = do_template('FORM', [
            '_GUID' => '3126441524b51cba6a1e0de336c8a9d5',
            'SKIP_WEBSTANDARDS' => true,
            'SKIPPABLE' => 'skip_3',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => '6bdae2f0aa24b5dbe81fd0fc72e87feb',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(3)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (addons).
     *
     * @return Tempcode The UI
     */
    public function step4() : object
    {
        require_code('addons2');
        require_lang('addons');

        $post_url = build_url(['page' => '_SELF', 'type' => 'step5'], '_SELF');
        $text = do_lang_tempcode('SETUPWIZARD_4_DESCRIBE');
        $submit_name = do_lang_tempcode('PROCEED');
        $hidden = static_evaluate_tempcode(build_keep_post_fields());

        $addons_installed = find_installed_addons();
        $_addons_not_installed = find_available_addons(false, false);
        $addons_not_installed = list_to_map('name', $_addons_not_installed);

        $fields = [];
        $fields_advanced = '';

        $installprofile = post_param_string('installprofile', '');
        $addon_list_override_to_off_by_default = [];
        $addon_list_force_on = [];
        if ($installprofile != '') {
            require_code('hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($installprofile));
            $object = object_factory('Hook_admin_setupwizard_installprofiles_' . filter_naughty_harsh($installprofile));
            $profile_addons = $object->get_addon_list();
            list($addon_list_on_by_default, $addon_list_advanced_on_by_default) = $profile_addons;
            if (array_key_exists(2, $profile_addons)) {
                $addon_list_override_to_off_by_default = $profile_addons[2];
            }
            if (array_key_exists(3, $profile_addons)) {
                $addon_list_force_on = $profile_addons[3];
            }
        } else {
            $addon_list_on_by_default = null;
            $addon_list_advanced_on_by_default = [];
            $addon_list_force_on = [];
        }

        /*$addon_list_on_by_default = [   These will be put on in individual Setup Wizard profiles; we list them here just so our addon_setupwizard automated test can ensure we haven't forgotten to consider their status
            'aggregate_types',
            'authors',
            'calendar',
            'chat',
            'content_privacy',
            'content_reviews',
            'debrand',
            'downloads',
            'forum_blocks',
            'galleries',
            'match_key_permissions',
            'news',
            'newsletter',
            'cns_clubs',
            'cns_contact_member',
            'cns_forum',
            'cns_member_photos',
            'cns_member_titles',
            'cns_multi_moderations',
            'cns_post_templates',
            'cns_signatures',
            'cns_warnings',
            'points',
            'leader_board',
            'polls',
            'quizzes',
            'random_quotes',
            'realtime_rain',
            'recommend',
            'shopping',
            'welcome_emails',
            'wiki',
        ];*/

        // These are on by default regardless of install profile. It's useful, because we don't want install profiles to have to be too prescriptive, and we want old ones to keep working well even if new addons have been introduced.
        if ($addon_list_on_by_default !== null) {
            $addon_list_on_by_default = array_merge($addon_list_on_by_default, [
                'banners',
                'ecommerce',
                'cns_avatars',
                'cns_cartoon_avatars',
                'cns_member_avatars',
                'cns_thematic_avatars',
                'wordfilter',
            ]);
        }
        $addon_list_advanced_on_by_default = array_merge($addon_list_advanced_on_by_default, [
            'actionlog',
            //'core_all_icons',
            'awards',
            'breadcrumbs',
            'captcha',
            'catalogues',
            'counting_blocks',
            'custom_comcode',
            'errorlog',
            'health_check',
            'help_page',
            'import',
            'language_block',
            'commandr',
            'cns_cpfs',
            'page_management',
            'redirects_editor',
            'search',
            'securitylogging',
            'setupwizard',
            'tickets',
            'stats',
            'stats_block',
            'syndication',
            'syndication_blocks',
            'themewizard',
            'uninstaller',
            'validation',
            'phpinfo',
            'apache_config_files',
            'code_editor',
            'helper_scripts',
            'weather',
            'xml_fields',
            'users_online_block',
            'news_shared',
            'filedump',
            'filebased_persistent_caching',
            'robots_txt',
            'getid3', // this will be downloaded as it is not bundled, for licensing reasons
        ]);
        // ... unless the install profile really is shunning them
        foreach ($addon_list_override_to_off_by_default as $_to_find) {
            if ($addon_list_on_by_default !== null) {
                $_found = array_search($_to_find, $addon_list_on_by_default);
                if ($_found !== false) {
                    unset($addon_list_on_by_default[$_found]);
                }
            }
            $_found = array_search($_to_find, $addon_list_advanced_on_by_default);
            if ($_found !== false) {
                unset($addon_list_advanced_on_by_default[$_found]);
            }
        }

        $addon_list_advanced_off_by_default = [
            // Hint that these must go under advanced (as they default as visible). Note that presence of an addon in an 'on' list gives it precedence.
            'installer',
            'rootkit_detector',
            'failover',
            'msn',
            'backup',
            'ldap',
            'sms',
            'printer_friendly_block',
            'data_mappr', // this will be downloaded as it is not bundled
            'user_mappr', // this will be downloaded as it is not bundled
            'facebook_support', // this will be downloaded as it is not bundled
            'hybridauth', // this will be downloaded as it is not bundled
            'twitter_support', // this will be downloaded as it is not bundled
        ];
        if (GOOGLE_APPENGINE) {
            $addon_list_advanced_off_by_default[] = 'google_appengine';
        }

        // Do we need to download any from composr.app?
        $GLOBALS['DEV_MODE'] = false;
        foreach (array_merge(($addon_list_on_by_default === null) ? [] : $addon_list_on_by_default, $addon_list_advanced_on_by_default, $addon_list_advanced_off_by_default) as $mentioned_addon) {
            if ((!array_key_exists($mentioned_addon, $addons_installed)) && (!array_key_exists($mentioned_addon, $addons_not_installed))) {
                $remote_addons = find_remote_addons();
                $_mentioned_addon = titleify($mentioned_addon);
                if (array_key_exists($_mentioned_addon, $remote_addons)) {
                    $id = $remote_addons[$_mentioned_addon];
                    require_code('uploads');
                    $_POST['url'] = 'https://composr.app/site/dload.php?id=' . strval($id);
                    get_url('url', 'file', 'imports/addons', OBFUSCATE_NEVER, CMS_UPLOAD_ANYTHING, false, '', '', true); // Download it
                }
            }
        }
        $addons_not_installed = list_to_map('name', find_available_addons(false, false, $_addons_not_installed)); // Re-search for these, as more may have been downloaded above

        $all_addons = $addons_installed + $addons_not_installed;
        foreach ($all_addons as $addon_name => $row) {
            if (!isset($all_addons[$addon_name]['name'])) {
                $all_addons[$addon_name]['name'] = titleify($addon_name);
            }
        }
        sort_maps_by($all_addons, 'name', false, true);
        $_lock_addons_on = get_theme_option('setupwizard__lock_addons_on', null, post_param_string('source_theme', 'default'));
        $lock_addons_on = ($_lock_addons_on == '') ? [] : explode(',', $_lock_addons_on);
        require_code('addons');
        foreach ($all_addons as $addon_name => $row) {
            $is_core = ($addon_name == 'core') || (substr($addon_name, 0, 5) == 'core_') || (in_array($addon_name, $lock_addons_on));
            if ((!$is_core) && (substr($addon_name, -7) != '_shared') && ($addon_name != 'setupwizard') && (!in_array($addon_name, $addon_list_force_on))) {
                $is_advanced_on_by_default = in_array($addon_name, $addon_list_advanced_on_by_default);
                $is_advanced_off_by_default = in_array($addon_name, $addon_list_advanced_off_by_default);
                $install_by_default = (($addon_list_on_by_default !== null) && (in_array($addon_name, $addon_list_on_by_default)) || ($is_advanced_on_by_default) || (($addon_list_on_by_default === null) && (!$is_advanced_off_by_default)));

                $addon_description = $row['description'];
                if ((substr($addon_description, -1) != '.') && ($addon_description != '')) {
                    $addon_description .= '.';
                }
                $_addon_description = protect_from_escaping(generate_truncation($addon_description, 'left', 1000, true));

                $addon_icon = find_addon_icon($addon_name, false, array_key_exists('tar_path', $row) ? $row['tar_path'] : null);
                $addon_name_pretty = protect_from_escaping(do_template('ADDON_NAME', ['_GUID' => 'c036db4d27417f79e1f395d1edb44020', 'IMAGE_URL' => $addon_icon, 'NAME' => $row['name']]));

                $field = form_input_tick($addon_name_pretty, $_addon_description, 'addon_' . $addon_name, $install_by_default);

                $advanced = ($is_advanced_on_by_default) || ($is_advanced_off_by_default);
                if ($advanced) {
                    $fields_advanced .= $field->evaluate();
                } else {
                    if (!array_key_exists($row['category'], $fields)) {
                        $fields[$row['category']] = '';
                    }
                    $fields[$row['category']] .= $field->evaluate();
                }
            } elseif (!$is_core) {
                $hidden .= static_evaluate_tempcode(form_input_hidden('addon_' . $addon_name, '1'));
            }
        }

        ksort($fields, SORT_NATURAL | SORT_FLAG_CASE); // Sort by category name

        $_fields = '';
        foreach ($fields as $category => $category_fields) {
            $_fields .= static_evaluate_tempcode(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '10948cc876d0ecb8b511800eabd8cae2', 'SECTION_HIDDEN' => false, 'TITLE' => $category]));
            $_fields .= $category_fields;
        }
        $_fields .= static_evaluate_tempcode(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '00948cc876d0ecb8b511800eabd8cae2', 'SECTION_HIDDEN' => true, 'TITLE' => do_lang_tempcode('ADVANCED')]));
        $_fields .= $fields_advanced;

        $inner = do_template('FORM', [
            '_GUID' => '0f361a3ac0e020ba71f3a7a900eca0e4',
            'NO_SIZING' => true,
            'SKIP_WEBSTANDARDS' => true,
            'SKIPPABLE' => 'skip_4',
            'FIELDS' => $_fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => 'ca91a76aa418d5c9ae956247ebc70652',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(4)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (zone/feature configuration).
     *
     * @return Tempcode The UI
     */
    public function step5() : object
    {
        require_lang('menus');

        $post_url = build_url(['page' => '_SELF', 'type' => $this->has_block_step() ? 'step6' : 'step7'], '_SELF');
        $text = do_lang_tempcode('SETUPWIZARD_5_DESCRIBE');
        $submit_name = do_lang_tempcode('PROCEED');
        $hidden = static_evaluate_tempcode(build_keep_post_fields());

        $fields = '';

        $installprofile = post_param_string('installprofile', '');
        if ($installprofile != '') {
            $path = get_file_base() . '/sources_custom/hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($installprofile) . '.php';
            if (!file_exists($path)) {
                $path = get_file_base() . '/sources/hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($installprofile) . '.php';
            }
            $_hook_bits = extract_module_functions($path, ['field_defaults']);
            $field_defaults = is_array($_hook_bits[0]) ? call_user_func_array($_hook_bits[0][0], $_hook_bits[0][1]) : cms_eval($_hook_bits[0], $path);
        } else {
            $field_defaults = [];
        }

        $fields .= static_evaluate_tempcode(do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => '9f81456d95f2ff66a46e2a5b9901aaf3', 'TITLE' => do_lang_tempcode('FEATURES')]));

        $hooks = find_all_hooks('modules', 'admin_setupwizard');
        foreach (array_keys($hooks) as $hook) {
            if ((post_param_integer('addon_' . $hook, 0) == 1) && ($hook != 'core')) {
                $path = get_file_base() . '/sources_custom/hooks/modules/admin_setupwizard/' . filter_naughty_harsh($hook) . '.php';
                if (!file_exists($path)) {
                    $path = get_file_base() . '/sources/hooks/modules/admin_setupwizard/' . filter_naughty_harsh($hook) . '.php';
                }
                if (strpos(cms_file_get_contents_safe($path, FILE_READ_LOCK), 'get_fields') !== false) { // Memory optimisation
                    require_code('hooks/modules/admin_setupwizard/' . filter_naughty_harsh($hook));
                    $hook = object_factory('Hook_sw_' . filter_naughty_harsh($hook), true);
                    if ($hook === null) {
                        continue;
                    }
                    if (method_exists($hook, 'get_fields')) {
                        list($hook_fields, $hook_hidden) = $hook->get_fields($field_defaults);
                        $hidden .= $hook_hidden->evaluate();
                        $fields .= static_evaluate_tempcode($hook_fields);
                    }
                }
            }
        }
        require_code('hooks/modules/admin_setupwizard/core'); // Core one explicitly goes last
        $hook = object_factory('Hook_sw_core', true);
        if (method_exists($hook, 'get_fields')) {
            list($hook_fields, $hook_hidden) = $hook->get_fields($field_defaults);
            $hidden .= $hook_hidden->evaluate();
            $fields .= static_evaluate_tempcode($hook_fields);
        }

        $fields .= static_evaluate_tempcode(form_input_tick(do_lang_tempcode('INSTALL_TEST_CONTENT'), do_lang_tempcode('DESCRIPTION_INSTALL_TEST_CONTENT'), 'install_test_content', true));

        $inner = do_template('FORM', [
            '_GUID' => 'f1e9a4d271c7d68ff9da6dc0438f6e3f',
            'SKIP_WEBSTANDARDS' => true,
            'JS_FUNCTION_CALLS' => ['adminSetupWizardStep5'],
            'SKIPPABLE' => 'skip_5',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => '8dfd885199d3d1416c044fad7a97d953',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(5)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (block choice).
     *
     * @return Tempcode The UI
     */
    public function step6() : object
    {
        require_all_lang();

        $post_url = build_url(['page' => '_SELF', 'type' => 'step7'], '_SELF');
        $fields = '';
        $hidden = static_evaluate_tempcode(build_keep_post_fields());

        $installprofile = post_param_string('installprofile', '');
        if ($installprofile != '') {
            require_code('hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($installprofile));
            $object = object_factory('Hook_admin_setupwizard_installprofiles_' . filter_naughty_harsh($installprofile));
            $default_blocks = $object->default_blocks();
        } else {
            $default_blocks = null;
        }

        $main_blocks = [];
        $either_blocks = [];
        $side_blocks = [];
        $hooks = find_all_hook_obs('modules', 'admin_setupwizard', 'Hook_sw_');
        foreach ($hooks as $hook => $ob) {
            if ((post_param_integer('addon_' . $hook, 0) == 1) || (substr($hook, 0, 5) == 'core_') || ($hook == 'core')) {
                if (method_exists($ob, 'get_blocks')) {
                    $ret = $ob->get_blocks();
                    foreach ($ret as $block_name => $block_details) {
                        $can_be_main = (($block_details[0] & BLOCK_POSITION_MAIN) != 0) || (($block_details[0] & BLOCK_POSITION_CELL) != 0);
                        $can_be_side = (($block_details[0] & BLOCK_POSITION_PANEL) != 0);

                        if (($can_be_main) && ($can_be_side)) {
                            $either_blocks[$block_name] = $block_details;
                        } elseif ($can_be_main) {
                            $main_blocks[$block_name] = $block_details;
                        } elseif ($can_be_side) {
                            $side_blocks[$block_name] = $block_details;
                        }
                    }
                }
            }
        }
        ksort($main_blocks);
        ksort($either_blocks);
        ksort($side_blocks);

        if (empty($main_blocks) && empty($either_blocks) && empty($side_blocks)) {
            return $this->step7();
        }

        $submit_name = do_lang_tempcode('PROCEED');
        $text = do_lang_tempcode('SETUPWIZARD_6_DESCRIBE');

        require_lang('blocks');
        require_lang('zones');
        require_code('zones2');

        foreach (['HOME' => $main_blocks, 'GENERAL' => $either_blocks, 'PANELS' => $side_blocks] as $block_section_header => $block_set) {
            $tmp = do_template('FORM_SCREEN_FIELD_SPACER', ['_GUID' => 'dfc20251e4f6b37ec1e046d0903250aa', 'TITLE' => do_lang_tempcode($block_section_header)]);
            $fields .= $tmp->evaluate();

            ksort($block_set);

            foreach ($block_set as $block => $block_details) {
                if ((!file_exists(get_file_base() . '/sources_custom/blocks/' . $block . '.php')) && (!file_exists(get_file_base() . '/sources/blocks/' . $block . '.php'))) {
                    continue;
                }

                $description = paragraph(do_lang_tempcode('BLOCK_' . $block . '_DESCRIPTION'));
                $description->attach(paragraph(do_lang_tempcode('BLOCK_' . $block . '_USE')));
                $block_nice = cleanup_block_name($block);
                if ($default_blocks === null) {
                    $position = $block_details[1];
                } else {
                    $position = null;
                    foreach ([BLOCK_POSITION_MAIN, BLOCK_POSITION_CELL, BLOCK_POSITION_PANEL] as $p) {
                        if (in_array($block, $default_blocks[$p])) {
                            $position = $p;
                        }
                    }
                }

                $main_list = new Tempcode();
                $main_list->attach(form_input_list_entry('NO', $position === null, do_lang_tempcode('BLOCK_CONFIGURATION__PANEL_NO')));
                if (($block_details[0] & BLOCK_POSITION_MAIN) != 0) {
                    $main_list->attach(form_input_list_entry('YES', $position === BLOCK_POSITION_MAIN, do_lang_tempcode('BLOCK_CONFIGURATION__PANEL_YES')));
                }
                if (($block_details[0] & BLOCK_POSITION_CELL) != 0) {
                    $main_list->attach(form_input_list_entry('YES_CELL', $position === BLOCK_POSITION_CELL, do_lang_tempcode('BLOCK_CONFIGURATION__PANEL_YES_CELL')));
                }
                if (($block_details[0] & BLOCK_POSITION_PANEL) != 0) {
                    $main_list->attach(form_input_list_entry('PANEL_LEFT', false, do_lang_tempcode('BLOCK_CONFIGURATION__PANEL_LEFT')));
                    $main_list->attach(form_input_list_entry('PANEL_RIGHT', $position === BLOCK_POSITION_PANEL, do_lang_tempcode('BLOCK_CONFIGURATION__PANEL_RIGHT')));
                }
                $tmp = form_input_list($block_nice, $description, 'block_SITE_' . $block, $main_list);
                $fields .= $tmp->evaluate();
            }
        }

        $preview = true;

        $inner = do_template('FORM', [
            '_GUID' => 'd463906b9e2cd8c37577d64783aa844c',
            'SKIP_WEBSTANDARDS' => true,
            'SKIPPABLE' => 'skip_6',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'PREVIEW' => $preview,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);

        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => '7c2cbc9577974b210e196b92158b4bb8',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(6)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * Get Comcode to save as the rules.
     *
     * @param  ID_TEXT $code A code relating to which rules set to get
     * @return string The Comcode
     */
    protected function get_rules_file(string $code) : string
    {
        require_code('zones3');
        return get_template_contents('rules_' . $code);
    }

    /**
     * UI for a setup wizard step (rules).
     *
     * @return Tempcode The UI
     */
    public function step7() : object
    {
        $post_url = build_url(['page' => '_SELF', 'type' => $this->has_themewizard_step() ? 'step8' : 'step9'], '_SELF');
        $text = do_lang_tempcode('SETUPWIZARD_7_DESCRIBE');
        $submit_name = do_lang_tempcode('PROCEED');
        $hidden = static_evaluate_tempcode(build_keep_post_fields());

        $installprofile = post_param_string('installprofile', '');
        if ($installprofile != '') {
            require_code('hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($installprofile));
            $object = object_factory('Hook_admin_setupwizard_installprofiles_' . filter_naughty_harsh($installprofile));
            $field_defaults = $object->field_defaults();
        } else {
            $field_defaults = [];
        }

        $list = new Tempcode();
        $list->attach(form_input_list_entry('balanced', array_key_exists('rules', $field_defaults) ? ($field_defaults['rules'] == 'balanced') : true, do_lang_tempcode('SETUPWIZARD_RULES_balanced')));
        $list->attach(form_input_list_entry('liberal', array_key_exists('rules', $field_defaults) ? ($field_defaults['rules'] == 'liberal') : false, do_lang_tempcode('SETUPWIZARD_RULES_liberal')));
        $list->attach(form_input_list_entry('corporate', array_key_exists('rules', $field_defaults) ? ($field_defaults['rules'] == 'corporate') : false, do_lang_tempcode('SETUPWIZARD_RULES_corporate')));
        $fields = form_input_list(do_lang_tempcode('RULES'), do_lang_tempcode('DESCRIPTION_RULES'), 'rules', $list, null, true);

        $js_function_calls = ['adminSetupWizardStep7'];
        $form = do_template('FORM', [
            '_GUID' => 'bf01a2b90967e86213ae0672c36a4b4e',
            'SKIPPABLE' => 'skip_7',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
            'JS_FUNCTION_CALLS' => $js_function_calls,
        ]);

        $balanced = comcode_to_tempcode($this->get_rules_file('balanced'), null, true);
        $liberal = comcode_to_tempcode($this->get_rules_file('liberal'), null, true);
        $corporate = comcode_to_tempcode($this->get_rules_file('corporate'), null, true);

        $inner = do_template('SETUPWIZARD_7', ['_GUID' => '5e46c3a989e42fa6eec5a017e8c644c2', 'FORM' => $form, 'BALANCED' => $balanced, 'LIBERAL' => $liberal, 'CORPORATE' => $corporate]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => '04f24f8c44267d2ad315aa34243e9712',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(7)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (Theme Wizard).
     *
     * @return Tempcode The UI
     */
    public function step8() : object
    {
        require_lang('themes');
        require_code('themewizard');

        $post_url = build_url(['page' => '_SELF', 'type' => 'step9'], '_SELF');
        $hidden = static_evaluate_tempcode(build_keep_post_fields());
        $fields = new Tempcode();

        $submit_name = do_lang_tempcode('PROCEED');
        $text = do_lang_tempcode('SETUPWIZARD_8_DESCRIBE');

        require_code('themes2');
        $fields->attach(form_input_colour(do_lang_tempcode('SEED_COLOUR'), do_lang_tempcode('DESCRIPTION_SEED_COLOUR'), 'seed_hex', '#' . find_theme_seed('default'), true));
        $fields->attach(form_input_tick(do_lang_tempcode('DARK_THEME'), do_lang_tempcode('DESCRIPTION_DARK_THEME'), 'dark', get_param_integer('dark', 0) == 1));

        $preview = true;

        $inner = do_template('FORM', [
            '_GUID' => '7ef31eb9712cff98da57a92fc173f7af',
            'PREVIEW' => $preview,
            'SKIP_WEBSTANDARDS' => true,
            'SKIPPABLE' => 'skip_8',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
        ]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => 'e67abf478cea7aed5cda64189549677a',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(8)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (close-status).
     *
     * @return Tempcode The UI
     */
    public function step9() : object
    {
        $post_url = build_url(['page' => '_SELF', 'type' => 'step10'], '_SELF');
        $text = do_lang_tempcode('SETUPWIZARD_9_DESCRIBE');
        $submit_name = do_lang_tempcode('PROCEED');
        $hidden = static_evaluate_tempcode(build_keep_post_fields(['csrf_token_preserve']));

        $fields = new Tempcode();
        $fields->attach(form_input_tick(do_lang_tempcode('CLOSED_SITE'), do_lang_tempcode('CONFIG_OPTION_site_closed'), 'site_closed', true));
        $fields->attach(form_input_text(do_lang_tempcode('MESSAGE'), do_lang_tempcode('CONFIG_OPTION_closed'), 'closed', get_option('closed'), false, false));

        $js_function_calls = ['adminSetupWizardStep9'];
        $inner = do_template('FORM', [
            '_GUID' => 'c405a64a08328f78ac0e3f22a8365411',
            'SKIP_WEBSTANDARDS' => true,
            'SKIPPABLE' => 'skip_9',
            'FIELDS' => $fields,
            'URL' => $post_url,
            'TEXT' => $text,
            'SUBMIT_ICON' => 'buttons/proceed',
            'SUBMIT_NAME' => $submit_name,
            'HIDDEN' => $hidden,
            'JS_FUNCTION_CALLS' => $js_function_calls,
        ]);
        return do_template('SETUPWIZARD_SCREEN', [
            '_GUID' => 'de13f131460e7f342c8beb6ba5ae3f42',
            'TITLE' => $this->title,
            'STEP' => strval($this->get_effective_step(9)),
            'INNER' => $inner,
            'NUM_STEPS_ENUMERABLE' => strval($this->get_num_steps_enumerable()),
        ]);
    }

    /**
     * UI for a setup wizard step (actualisation).
     *
     * @return Tempcode The UI
     */
    public function step10() : object
    {
        push_query_limiting(false);

        $doing_themewizard = $this->has_themewizard_step();

        require_code('themes3');

        $name = post_param_string('site_name');
        $new_theme_name = generate_theme_name($name);
        $generating_new_theme = !file_exists(get_custom_file_base() . '/themes/' . $new_theme_name);
        list($addons_install, $addons_uninstall) = $this->detect_addon_operations();

        require_code('addons2');

        // Initialise AFM...

        require_code('abstract_file_manager');
        $writable_paths = [];
        if ((post_param_integer('skip_8', 0) == 0) && ($doing_themewizard)) {
            if ($generating_new_theme) {
                $writable_paths[] = 'themes';
            }
        }
        if ((post_param_integer('skip_4', 0) == 0) && ($GLOBALS['CURRENT_SHARE_USER'] === null)) {
            foreach (array_keys($addons_uninstall) as $addon_name) {
                $writable_paths = array_merge($writable_paths, get_addon_uninstall_writable_paths($addon_name));
            }
            foreach (array_keys($addons_install) as $addon_file) {
                $writable_paths = array_merge($writable_paths, get_addon_install_writable_paths($addon_file));
            }
        }
        force_have_afm_details($writable_paths);

        // Proceed...

        set_mass_import_mode();

        require_code('config2');
        require_code('themes2');
        require_lang('zones');
        require_code('files');
        require_code('images');

        require_code('fonts');
        $font = post_param_string('font', find_default_font());
        $installprofile = post_param_string('installprofile', '');
        $source_theme = post_param_string('source_theme', '');

        $default_logos = get_all_image_ids_type('logo/default_logos');
        shuffle($default_logos);
        $default_backgrounds = get_all_image_ids_type('logo/default_backgrounds');
        shuffle($default_backgrounds);
        $logo_theme_image = post_param_string('logo_theme_image', array_shift($default_logos));
        $background_theme_image = post_param_string('background_theme_image', array_shift($default_backgrounds));
        $colour = get_theme_option('header_classic_text_colour');

        set_option('header_classic_image', $background_theme_image);

        if ($installprofile != '') {
            // Run any specific code for the profile
            $object = null;
            if ((is_file(get_file_base() . '/sources/hooks/modules/admin_setupwizard_installprofiles/' . $installprofile . '.php')) || (is_file(get_file_base() . '/sources_custom/hooks/modules/admin_setupwizard_installprofiles/' . $installprofile . '.php'))) {
                require_code('hooks/modules/admin_setupwizard_installprofiles/' . filter_naughty_harsh($installprofile));
                $object = object_factory('Hook_admin_setupwizard_installprofiles_' . filter_naughty_harsh($installprofile), true);
            }
            if ($object !== null) {
                $object->install_code();
                $installprofileblocks = $object->default_blocks();
            } else { // Hmm, we probably just uninstalled the install-profile hook as its addon was not chosen! Whoopsie, but do our best.
                $installprofileblocks = [];
            }
        } else {
            $installprofileblocks = [];
        }

        $doing_logowizard = (addon_installed('themewizard')) && (get_theme_option('enable_logowizard', null, $source_theme) == '1');

        if ((post_param_integer('skip_8', 0) == 0) && ($doing_themewizard || $doing_logowizard)) {
            require_code('themewizard');

            // Make/set theme and logos
            if ($doing_themewizard) {
                global $THEME_IMAGES_CACHE;
                $old_img_codes_site = $GLOBALS['SITE_DB']->query_select('theme_images', ['id', 'url'], ['theme' => $GLOBALS['FORUM_DRIVER']->get_theme(), 'lang' => user_lang()]);
                if ($generating_new_theme) {
                    generate_themewizard_theme($new_theme_name, 'default', 'equations', post_param_string('seed_hex'), post_param_integer('dark', 0) == 1, /*$use=*/true);
                }

                $live_theme = $new_theme_name;
            } else {
                $live_theme = $source_theme;

                require_code('themes3');
                set_live_theme($live_theme);
            }
            if ($doing_logowizard) {
                foreach ([$live_theme, 'default'] as $logo_save_theme) {
                    foreach (['large' => '-logo', 'standalone' => 'standalone_logo', 'small' => 'small_logo', 'small_white' => 'small_white_logo'] as $logo_type => $logo_output_theme_image) {
                        $logo = generate_logo($name, $font, $colour, $logo_theme_image, $background_theme_image, false, $logo_save_theme, $logo_type);
                        $path = 'themes/' . $logo_save_theme . '/images_custom/' . $logo_output_theme_image . '.png';
                        if (!file_exists(get_custom_file_base() . '/' . dirname($path))) {
                            require_code('files2');
                            make_missing_directory(get_custom_file_base() . '/' . dirname($path), true);
                        }
                        cms_imagesave($logo, get_custom_file_base() . '/' . $path) or intelligent_write_error($path);
                        actual_edit_theme_image('logo/' . $logo_output_theme_image, $logo_save_theme, get_site_default_lang(), 'logo/' . $logo_output_theme_image, $path, true);
                        imagedestroy($logo);
                    }
                }
            }
        }

        // Set options
        if (post_param_integer('skip_3', 0) == 0) {
            set_option('site_name', $name);
            set_option('description', post_param_string('description'));
            set_option('site_scope', post_param_string('site_scope'));
            set_option('copyright', do_lang('COPYRIGHTED') . ' &copy; $CURRENT_YEAR=' . date('Y') . ' ' . $name . ', ' . do_lang('POWERED_BY', 'Composr CMS'));
            set_option('keywords', post_param_string('keywords'));
            set_option('timezone', post_param_string('timezone'));
            set_option('google_analytics', post_param_string('google_analytics'));

            // Security level (update tut_configuration if you change these)...

            $security_level = post_param_string('security_level');
            $security_level_options = [
                'csp_enabled' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '1',
                    'high' => '1',
                    'extreme' => '1',
                ],
                'session_expiry_time' => [
                    'minimum' => '24',
                    'low' => '3',
                    'medium' => '1',
                    'high' => '0.5',
                    'extreme' => '0.25',
                ],
                'password_reset_process' => [
                    'minimum' => 'emailed',
                    'low' => 'temporary',
                    'medium' => 'temporary',
                    'high' => 'ultra',
                    'extreme' => 'ultra',
                ],
                'password_expiry_days' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '0',
                    'high' => '90',
                    'extreme' => '30',
                ],
                'minimum_password_length' => [
                    'minimum' => '0',
                    'low' => '4',
                    'medium' => '8',
                    'high' => '12',
                    'extreme' => '16',
                ],
                'minimum_password_strength' => [
                    'minimum' => '0',
                    'low' => '2',
                    'medium' => '5',
                    'high' => '8',
                    'extreme' => '10',
                ],
                'login_error_secrecy' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '0',
                    'high' => '1',
                    'extreme' => '1',
                ],
                'ip_strict_for_sessions' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '1',
                    'high' => '1',
                    'extreme' => '1',
                ],
                'crypt_ratchet' => [ // Special case: Array of target calculation time, minimum ratchet
                    'minimum' => [0.025, 6],
                    'low' => [0.05, 8],
                    'medium' => [0.1, 10],
                    'high' => [0.25, 12],
                    'extreme' => [1.0, 14],
                ],
                'captcha_single_guess' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '1',
                    'high' => '1',
                    'extreme' => '1',
                ],
                'captcha_noise' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '1',
                    'high' => '1',
                    'extreme' => '1',
                ],
                'brute_force_threshold' => [
                    'minimum' => '30',
                    'low' => '15',
                    'medium' => '10',
                    'high' => '5',
                    'extreme' => '3',
                ],
                'audio_captcha' => [
                    'minimum' => '1',
                    'low' => '1',
                    'medium' => '1',
                    'high' => '0',
                    'extreme' => '0',
                ],
                'url_monikers_enabled' => [
                    'minimum' => '1',
                    'low' => '1',
                    'medium' => '1',
                    'high' => '1',
                    'extreme' => '0',
                ],
                'maintenance_script_htaccess' => [
                    'minimum' => '0',
                    'low' => '0',
                    'medium' => '0',
                    'high' => '1',
                    'extreme' => '1',
                ],
            ];
            foreach ($security_level_options as $security_level_option => $values) {
                if ($security_level_option == 'crypt_ratchet') {
                    require_code('crypt');
                    set_option($security_level_option, strval(calculate_reasonable_ratchet($values[$security_level][0], $values[$security_level][1])));
                } else {
                    set_option($security_level_option, $values[$security_level]);
                }
            }

            if (get_forum_type() == 'cns') {
                $admin_groups = $GLOBALS['FORUM_DRIVER']->get_super_admin_groups();
                $moderator_groups = $GLOBALS['FORUM_DRIVER']->get_moderator_groups();
                $groups = $GLOBALS['FORUM_DRIVER']->get_usergroup_list(true, true);
                foreach (array_keys($groups) as $id) {
                    switch ($security_level) {
                        // Do not enquire
                        case 'minimum':
                        case 'low':
                            $GLOBALS['FORUM_DB']->query_update('f_groups', ['g_enquire_on_new_ips' => 0], ['id' => $id], '', 1);
                            break;

                        // Only enquire on admins
                        case 'medium':
                            $is_admin = in_array($id, $admin_groups);
                            $GLOBALS['FORUM_DB']->query_update('f_groups', ['g_enquire_on_new_ips' => $is_admin ? 1 : 0], ['id' => $id], '', 1);
                            break;

                        // Enquire on all staff
                        case 'high':
                        case 'extreme':
                            $is_staff = in_array($id, $admin_groups) || in_array($id, $moderator_groups);
                            $GLOBALS['FORUM_DB']->query_update('f_groups', ['g_enquire_on_new_ips' => $is_staff ? 1 : 0], ['id' => $id], '', 1);
                            break;
                    }
                }
            }
        }
        if (post_param_integer('skip_9', 0) == 0) {
            set_option('site_closed', strval(post_param_integer('site_closed', 0)));
            set_option('closed', post_param_string('closed', ''));
        }

        // Set addons
        if ((post_param_integer('skip_4', 0) == 0) && ($GLOBALS['CURRENT_SHARE_USER'] === null)) {
            foreach ($addons_uninstall as $addon_name => $addon_info) {
                // Archive it off to exports/addons
                if (!empty($addon_info['files'])) {
                    $file = preg_replace('#^[_\.\-]#', 'x', preg_replace('#[^\w\.\-]#', '_', $addon_name)) . '.tar';
                    create_addon(
                        $file,
                        $addon_info['files'],
                        $addon_name,
                        implode(',', $addon_info['incompatibilities']),
                        implode(',', $addon_info['dependencies']),
                        $addon_info['author'],
                        $addon_info['organisation'],
                        $addon_info['version'],
                        $addon_info['category'],
                        implode("\n", $addon_info['copyright_attribution']),
                        $addon_info['licence'],
                        $addon_info['description'],
                        $addon_info['min_cms_version'],
                        $addon_info['max_cms_version'],
                        'imports/addons'
                    );
                }

                uninstall_addon($addon_name);
            }

            foreach (array_keys($addons_install) as $addon_file) {
                install_addon($addon_file);
            }
        }

        // Set features
        if (post_param_integer('skip_5', 0) == 0) {
            $hooks = find_all_hooks('modules', 'admin_setupwizard');
            foreach (array_keys($hooks) as $hook) {
                if ((post_param_integer('addon_' . $hook, 0) == 1) || ($hook == 'core')) {
                    $path = get_file_base() . '/sources_custom/hooks/modules/admin_setupwizard/' . filter_naughty_harsh($hook) . '.php';
                    if (!file_exists($path)) {
                        $path = get_file_base() . '/sources/hooks/modules/admin_setupwizard/' . filter_naughty_harsh($hook) . '.php';
                    }
                    $_hook_bits = extract_module_functions($path, ['set_fields']);
                    if (is_array($_hook_bits[0])) {
                        call_user_func_array($_hook_bits[0][0], $_hook_bits[0][1]);
                    } elseif ($_hook_bits[0] !== null) {
                        cms_eval($_hook_bits[0], $path);
                    }
                }
            }
        }

        $collapse_zones = post_param_integer('single_public_zone', 0) == 1;

        // Install test content
        if (post_param_integer('install_test_content', 0) == 1) {
            install_test_content();
        }

        // Rules
        if (post_param_integer('skip_7', 0) == 0) {
            $full_path = get_custom_file_base() . '/pages/comcode_custom/' . get_site_default_lang() . '/_rules.txt';
            if (file_exists($full_path)) {
                @copy($full_path, $full_path . '.' . strval(time()));
                fix_permissions($full_path . '.' . strval(time()));
                sync_file($full_path . '.' . strval(time()));
            }
            $rf = $this->get_rules_file(post_param_string('rules'));
            cms_file_put_contents_safe($full_path, $rf, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE | FILE_WRITE_BOM);
        }

        $block_options = null;
        if (($installprofile != '') && ($object !== null)) {
            $block_options = $object->block_options();
        }

        // Blocks
        if ((post_param_integer('skip_6', 0) == 0) && ($this->has_block_step())) {
            $page_structure = _get_zone_pages($installprofileblocks, $block_options, $collapse_zones, $installprofile);

            foreach ($page_structure as $zone => $zone_pages) {
                // Start
                $full_path = get_custom_file_base() . (($zone == '') ? '' : '/') . $zone . '/pages/comcode_custom/' . get_site_default_lang() . '/' . DEFAULT_ZONE_PAGE_NAME . '.txt';
                if (file_exists($full_path)) {
                    @copy($full_path, $full_path . '.' . strval(time()));
                    fix_permissions($full_path . '.' . strval(time()));
                    sync_file($full_path . '.' . strval(time()));
                }
                cms_file_put_contents_safe($full_path, $zone_pages[DEFAULT_ZONE_PAGE_NAME], FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE | FILE_WRITE_BOM);

                // Left
                $full_path = get_custom_file_base() . (($zone == '') ? '' : '/') . $zone . '/pages/comcode_custom/' . get_site_default_lang() . '/panel_left.txt';
                if (file_exists($full_path)) {
                    @copy($full_path, $full_path . '.' . strval(time()));
                    fix_permissions($full_path . '.' . strval(time()));
                    sync_file($full_path . '.' . strval(time()));
                }
                cms_file_put_contents_safe($full_path, $zone_pages['left'], FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE | FILE_WRITE_BOM);

                // Right
                $full_path = get_custom_file_base() . (($zone == '') ? '' : '/') . $zone . '/pages/comcode_custom/' . get_site_default_lang() . '/panel_right.txt';
                if (file_exists($full_path)) {
                    @copy($full_path, $full_path . '.' . strval(time()));
                    fix_permissions($full_path . '.' . strval(time()));
                    sync_file($full_path . '.' . strval(time()));
                }
                cms_file_put_contents_safe($full_path, $zone_pages['right'], FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE | FILE_WRITE_BOM);
            }
        } elseif (!$this->has_block_step()) {
            require_code('files2');
            foreach (find_all_zones() as $zone) {
                $dir = get_custom_file_base() . '/' . (($zone == '') ? '' : ($zone . '/')) . 'pages/comcode_custom/' . fallback_lang();
                $files = get_directory_contents($dir, '', 0, true, true, ['txt']);
                foreach ($files as $file) {
                    $matches = [];
                    $regexp = '#^\_' . preg_quote($source_theme, '#') . '__([\w\-]+)\.txt$#';
                    if (preg_match($regexp, $file, $matches) != 0) {
                        $page_name = $matches[1];
                        cms_file_put_contents_safe($dir . '/' . $page_name . '.txt', cms_file_get_contents_safe($dir . '/' . $file, FILE_READ_LOCK | FILE_READ_BOM), FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE | FILE_WRITE_BOM);
                    }
                }
            }
        }

        delete_cache_entry('menu');

        // We're done
        set_value('setupwizard_completed', '1', true);

        log_it('SETUPWIZARD');

        $url = build_url(['page' => '_SELF', 'type' => 'step11'], '_SELF');
        return redirect_screen($this->title, $url, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Find what addon operations to perform.
     *
     * @return array A pair: The list of addons to install, The list of addons to uninstall
     */
    protected function detect_addon_operations() : array
    {
        $installing = [];
        $uninstalling = [];

        require_lang('addons');
        require_code('addons');
        require_code('addons2');

        preload_all_addons_info();

        $addons_not_installed = find_available_addons(false); // filename => addon details
        $addons_installed = find_installed_addons(false, true, true); // addon name => addon details

        // What is being installed?
        foreach ($addons_not_installed as $addon_file => $addon_info) {
            if ((post_param_integer('addon_' . $addon_info['name'], 0) == 1) && (!addon_installed($addon_info['name'], true))) {
                $installing[$addon_file] = $addon_info;
            }
        }

        // What is being uninstalled?
        foreach ($addons_installed as $addon_name => $addon_info) {
            if ((post_param_integer('addon_' . $addon_name, 0) == 0) && ($addon_name != 'core') && (substr($addon_name, 0, 5) != 'core_')) {
                $uninstalling[$addon_name] = $addon_info;
            }
        }

        resolve_addon_dependency_problems($installing, $uninstalling);

        return [$installing, $uninstalling];
    }

    /**
     * Clear caches we want to clear to clean up.
     */
    protected function clear_caching()
    {
        require_code('caches3');
        erase_comcode_page_cache();
        erase_block_cache(true);
        //persistent_cache_delete('OPTIONS');  Done by set_option
        erase_persistent_cache();
        erase_cached_templates(false, null, TEMPLATE_DECACHE_WITH_ANYTHING_INTERESTING);
    }

    /**
     * UI for a setup wizard step (done, message after cache emptied - need lower memory usage to rebuild them).
     *
     * @return Tempcode The UI
     */
    public function step11() : object
    {
        // Clear some caching
        $this->clear_caching();

        require_code('templates_donext');

        require_lang('zones');

        // Show nice interface to start adding pages
        return do_next_manager(
            $this->title,
            do_lang_tempcode('SUCCESS'),
            [
                ['menu/cms/comcode_page_edit', ['cms_comcode_pages', ['type' => 'edit'], get_module_zone('cms_comcode_pages')], do_lang('COMCODE_PAGE_ADD')],
                ['menu/' . DEFAULT_ZONE_PAGE_NAME, [null, [], '']],
                ['menu/cms/cms', [null, [], 'cms']],
                ['menu/adminzone/adminzone', [null, [], 'adminzone']],
            ],
            do_lang('PAGES'),
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            null,
            [],
            [],
            [],
            null,
            paragraph(do_lang_tempcode('SETUPWIZARD_10_DESCRIBE'))
        );
    }

    /**
     * Install test content.
     *
     * @return Tempcode The UI
     */
    public function install_test_content() : object
    {
        if ((post_param_integer('submitting', 0) == 0) && (strpos($_SERVER['HTTP_REFERER'], get_base_url() . '/install.php?') !== 0)) {
            $post_url = build_url(['page' => '_SELF', 'type' => 'install_test_content'], '_SELF');

            return do_template('CONFIRM_SCREEN', [
                '_GUID' => 'a91c415cb14fcc78f98c36e67229894c',
                'TITLE' => $this->title,
                'TEXT' => do_lang_tempcode('Q_SURE'),
                'URL' => $post_url,
                'HIDDEN' => form_input_hidden('submitting', '1'),
                'FIELDS' => '',
                'PREVIEW' => '',
            ]);
        }

        install_test_content();

        if (get_param_integer('came_from_installer', 0) == 1) {
            require_code('templates_redirect_screen');
            return redirect_screen($this->title, build_url(['page' => ''], ''), do_lang_tempcode('INSTALLED_TEST_CONTENT'));
        }

        return inform_screen($this->title, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Uninstall test content.
     *
     * @return Tempcode The UI
     */
    public function uninstall_test_content() : object
    {
        if (post_param_integer('submitting', 0) == 0) {
            $post_url = build_url(['page' => '_SELF', 'type' => 'uninstall_test_content'], '_SELF');

            return do_template('CONFIRM_SCREEN', [
                '_GUID' => '1ae3e932b2fa1f32ffebb531c0278ab7',
                'TITLE' => $this->title,
                'TEXT' => do_lang_tempcode('Q_SURE'),
                'URL' => $post_url,
                'HIDDEN' => form_input_hidden('submitting', '1'),
                'FIELDS' => '',
                'PREVIEW' => '',
            ]);
        }

        uninstall_test_content();

        return inform_screen($this->title, do_lang_tempcode('SUCCESS'));
    }

    /**
     * Get the number of steps that the user will see.
     *
     * @return integer The number of steps
     */
    protected function get_num_steps_enumerable() : int
    {
        $steps = 10;
        if (!$this->has_block_step()) {
            $steps--;
        }
        if (!$this->has_themewizard_step()) {
            $steps--;
        }
        return $steps;
    }

    /**
     * Get the effective step number, when missing steps are considered.
     *
     * @param  integer $step System step number
     * @return integer Effective step number
     */
    protected function get_effective_step(int $step) : int
    {
        if ($step > 6) {
            if (!$this->has_block_step()) {
                $step--;
            }
        }
        if ($step > 8) {
            if (!$this->has_themewizard_step()) {
                $step--;
            }
        }
        return $step;
    }

    /**
     * Find if the block selection step will run.
     *
     * @return boolean Whether it will
     */
    protected function has_block_step() : bool
    {
        return (get_theme_option('capability_block_layouts', null, post_param_string('source_theme', 'default')) == '1');
    }

    /**
     * Find if the Theme Wizard step will run.
     *
     * @return boolean Whether it will
     */
    protected function has_themewizard_step() : bool
    {
        return (addon_installed('themewizard')) && (get_theme_option('enable_themewizard', null, post_param_string('source_theme', 'default')) == '1');
    }
}
