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

/**
 * Do upgrader screen: file upgrade UI.
 *
 * @ignore
 * @return string Output messages
 */
function upgrader_file_upgrade_screen() : string
{
    $out = '<h2>' . do_lang('UPGRADER_DOWNLOAD') . '</h2>';

    require_code('version2');
    $personal_upgrader_generation_url = get_brand_base_url() . '/data/endpoint.php/cms_homesite/personal_upgrader/' . urlencode(get_version_dotted()) . '/?';
    if (function_exists('gzopen')) {
        $personal_upgrader_generation_url .= 'supports_gzip=1&';
    }
    if (class_exists('ZipArchive', false)) {
        $personal_upgrader_generation_url .= 'supports_zip=1&';
    }
    $hooks = find_all_hooks('systems', 'addon_registry');
    foreach (array_keys($hooks) as $hook) {
        if (is_file(get_file_base() . '/sources/hooks/systems/addon_registry/' . $hook . '.php')) {
            $personal_upgrader_generation_url .= 'addon_' . $hook . '=1&';
        }
    }
    $personal_upgrader_generation_url = rtrim($personal_upgrader_generation_url, '&');

    require_code('files2');
    $found_upgraders = [];
    foreach (['/data_custom', ''] as $upgrader_place) {
        $files = get_directory_contents(get_file_base() . $upgrader_place, get_file_base() . $upgrader_place, null, false, true, ['cms', 'gz', 'zip']);
        foreach ($files as $file_path) {
            if (preg_match('#^.*/(omni-)?upgrade-' . preg_quote(get_version_dotted(), '#') . '-[^/]*\.(cms(\.gz)?|zip)$#', $file_path) != 0) {
                $found_upgraders[get_base_url() . $upgrader_place . '/' . basename($file_path)] = filemtime($file_path);
            }
        }
    }
    if (empty($found_upgraders)) {
        $upgrader_tar_url = base64_decode(get_param_string('tar_url', '', INPUT_FILTER_URL_GENERAL));
    } else {
        arsort($found_upgraders);
        $found_upgraders = array_keys($found_upgraders);
        $upgrader_tar_url = $found_upgraders[0];
    }

    if ($upgrader_tar_url == '') {
        $out .= do_lang('UPGRADER_FILE_UPGRADE_INFO', escape_html(get_brand_base_url()));
    }

    $out .= do_lang('UPGRADER_FILE_UPGRADE_INFO_MANUAL', escape_html($personal_upgrader_generation_url));
    $out .= '<form title="' . do_lang('PROCEED') . '" enctype="multipart/form-data" action="upgrader.php?type=_file_upgrade" method="post">' . post_fields_relay();
    $out .= '<p><label for="url">' . do_lang('URL') . '</label> <input type="text" id="url" name="url" size="80" value="' . escape_html($upgrader_tar_url) . '" /></p>';
    $out .= '<p><label for="dry_run"><input type="checkbox" id="dry_run" name="dry_run" value="1" /> ' . do_lang('UPGRADER_DRY_RUN') . '</label></p>';
    if ((get_base_url() == get_brand_base_url()) || ($GLOBALS['DEV_MODE'])) { // for the core team to use on own site, for testing
        $out .= '<p><label for="upload">' . do_lang('ALT_FIELD', do_lang('UPLOAD')) . '</label> <input type="file" id="upload" name="upload" /></p>';
        $out .= '<script ' . csp_nonce_html() . '>var url=document.getElementById(\'url\'); url.addEventListener(\'change\', function() { document.getElementById(\'upload\').disabled=url.value!=\'\'; });</script>';
    }
    $proceed_icon = do_template('ICON', ['_GUID' => '7a84502d5457fd3960d8e5056ee1cb49', 'NAME' => 'buttons/proceed']);
    $out .= '<p><button class="btn btn-primary btn-scr buttons--proceed" type="submit">' . $proceed_icon->evaluate() . ' ' . do_lang('PROCEED') . '</button></p>';
    $out .= '</form>';

    return $out;
}

/**
 * Do upgrader screen: file upgrade actualiser.
 *
 * @ignore
 * @return string Output messages
 */
function _upgrader_file_upgrade_screen() : string
{
    $out = '<h2>' . do_lang('UPGRADER_DOWNLOAD') . '</h2>';

    // Dry run?
    $dry_run = (post_param_integer('dry_run', 0) == 1);
    if ($dry_run) {
        $out .= '<p>' . do_lang('UPGRADER_DOING_DRY_RUN') . '</p>';
    } else {
        log_it('UPGRADER_DOWNLOAD');
    }

    // Turn off limits, this may be exhaustive
    cms_extend_time_limit(TIME_LIMIT_EXTEND__CRAWL);
    disable_php_memory_limit();

    // How we can get file
    define('FILE_RETRIEVAL_UPLOAD', 0);
    define('FILE_RETRIEVAL_HTTP', 1);
    define('FILE_RETRIEVAL_LOCAL', 2);

    require_code('version');
    require_code('zones');
    require_code('global3');

    // Get file
    require_code('tar');
    if ((post_param_string('url', '', INPUT_FILTER_URL_GENERAL) == '') && ((get_base_url() == get_brand_base_url()) || ($GLOBALS['DEV_MODE']))) {
        $upgrade_path = $_FILES['upload']['tmp_name'];
        $original_filename = $_FILES['upload']['name'];
        $retrieval_method = FILE_RETRIEVAL_UPLOAD;
    } else {
        if (post_param_string('url', '', INPUT_FILTER_URL_GENERAL) == '') {
            warn_exit(do_lang_tempcode('NO_PARAMETER_SENT', 'url'));
        }

        $url = post_param_string('url', false, INPUT_FILTER_URL_GENERAL);
        if (substr($url, 0, strlen(get_base_url() . '/')) == get_base_url() . '/') {
            $upgrade_path = get_file_base() . '/' . rawurldecode(substr($url, strlen(get_base_url() . '/')));
            if (!is_file($upgrade_path)) {
                warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
            }
            $original_filename = basename($upgrade_path);
            $retrieval_method = FILE_RETRIEVAL_LOCAL;
        } elseif (substr($url, 0, 1) == '/') {
            $upgrade_path = get_file_base() . rawurldecode($url);
            if (!is_file($upgrade_path)) {
                warn_exit(do_lang_tempcode('MISSING_RESOURCE'));
            }
            $original_filename = basename($upgrade_path);
            $retrieval_method = FILE_RETRIEVAL_LOCAL;
        } else {
            $upgrade_path = cms_tempnam();
            $upgrade_path_handle = fopen($upgrade_path, 'wb');
            $request = cms_http_request($url, ['write_to_file' => $upgrade_path_handle, 'timeout' => 30.0]);
            if (($request->message === null) || ($request->filename === null) || (substr($request->message, 0, 1) != '2')) {
                warn_exit(do_lang_tempcode('_HTTP_DOWNLOAD_NO_SERVER', escape_html($url), ($request->message !== null) ? escape_html($request->message) : do_lang('UNKNOWN')));
            }
            fclose($upgrade_path_handle);
            $original_filename = $request->filename;
            $retrieval_method = FILE_RETRIEVAL_HTTP;
        }
    }
    $may_delete_upgrade_path = (($retrieval_method != FILE_RETRIEVAL_LOCAL) && (!$dry_run));

    // We do support using a .zip (e.g. manual installer package), but we need to convert it
    if (cms_strtolower_ascii(substr($original_filename, -4)) == '.zip') {
        require_code('tar2');
        $upgrade_path_new = null;
        convert_zip_to_tar($upgrade_path, $upgrade_path_new);
        if ($may_delete_upgrade_path) {
            @unlink($upgrade_path);
        }
        $upgrade_path = $upgrade_path_new;
        fix_permissions($upgrade_path);
    }

    // Open up TAR (or tarball)
    $upgrade_resource = tar_open($upgrade_path, 'rb', false, $original_filename);
    //tar_extract_to_folder($upgrade_resource, '', true);
    $directory = tar_get_directory($upgrade_resource); // Uses up to around 5MB of RAM

    // Hopefully $popup_simple_extract will be true (i.e. suEXEC mode), as it is safer
    $popup_simple_extract = (_ftp_info() === false);
    $metadata = ['todo' => [], 'skip' => [], 'from_version' => cms_version_number()];
    if (!$popup_simple_extract) {
        $out .= '<p>' . do_lang('EXTRACTING_MESSAGE') . '</p>';
    }

    // Upgrades always contain the full addon_registry directory; hotfixes do not
    $is_hotfix = (tar_get_file($upgrade_resource, 'sources/hooks/systems/addon_registry/index.html') === null);

    /*
     * Files that must be upgraded first and through immediate extraction (if mapped to true, will also require re-running the upgrade step)
     * You should also check data/upgrader2.php when modifying this.
     * Ideally you should update this array between each upgrade and only include files when absolutely necessary.
     */
    $immediately_upgrade = [
        'sources/upgrade_files.php' => true,
        'data/upgrader2.php' => false,
        'sources/crypt_maintenance.php' => false,
    ];
    if (!$dry_run) {
        $must_restart = false;
        foreach ($immediately_upgrade as $path => $restart) {
            $file_data = tar_get_file($upgrade_resource, $path);
            if ($file_data !== null) {
                if ($popup_simple_extract) {
                    if ($is_hotfix) {
                        if ($file_data['mtime'] < filemtime(get_file_base() . '/' . $path)) {
                            continue;
                        }
                    } else {
                        if (cms_file_get_contents_safe(get_file_base() . '/' . $path, FILE_READ_LOCK) == $file_data['data']) {
                            continue;
                        }
                    }
                }

                afm_make_file($path, $file_data['data'], ($file_data['mode'] & 0002) != 0);
                $out .= do_lang('UPGRADER_EXTRACTING_MESSAGE', escape_html($path)) . '<br />';
                if ($restart) {
                    $must_restart = true;
                }
            }
        }
        if ($must_restart) {
            if ($popup_simple_extract) {
                // LEGACY
                $lang = do_lang('UPGRADER_RESTART_REQUIRED', null, null, null, null, false);
                if ($lang === null) {
                    $lang = '<strong>Critical source files for the upgrader have just been updated.</strong> Please go back and re-run this step again to finish the transfer of upgrade files. You may need to re-enter the URL for the upgrade archive.';
                }

                $out .= $lang . '<br />';

                // Exit to require re-running this step.
                tar_close($upgrade_resource);
                return $out;
            } else {
                // LEGACY
                $lang = do_lang('UPGRADER_RESTART_REQUIRED_NOT_HAPPENING', null, null, null, null, false);
                if ($lang === null) {
                    $lang = '<strong>Warning!</strong> Critical source files for the upgrader were updated. This normally requires re-running this upgrader step. But because we cannot reliably determine which files were updated between runs, we are proceeding anyway. This is unsupported behaviour. And You may encounter issues after upgrading.';
                }

                $out .= $lang . '<br />';
            }
        }
    }

    $addon_contents = [];
    $addons_wanted = [];

    /*
     * Full CMS release archives will contain the entire addon_registry directory including index.html; hotfixes will not.
     * Therefore, if this file does not exist, assume we want to apply a hotfix, and thus allow installing alien files.
    */
    if ($is_hotfix) {
        $out .= '<p>' . do_lang('HOTFIX_MESSAGE') . '</p>';

        // Hotfixes do not contain the full addon registry. We must load up what we have installed.
        require_code('files2');
        $d_directory = get_directory_contents(get_file_base() . '/sources/hooks/systems/addon_registry', 'sources/hooks/systems/addon_registry', IGNORE_ACCESS_CONTROLLERS, false, true, ['php']);
        $d_directory = array_merge($d_directory, get_directory_contents(get_file_base() . '/sources_custom/hooks/systems/addon_registry', 'sources_custom/hooks/systems/addon_registry', IGNORE_ACCESS_CONTROLLERS, false, true, ['php']));
        foreach ($d_directory as $upgrade_file2) {
            $wanted_functions = extract_module_functions($upgrade_file2, ['get_file_list']);
            $files = [];
            if ($wanted_functions[0] !== null) {
                $files = is_array($wanted_functions[0]) ? call_user_func_array($wanted_functions[0][0], $wanted_functions[0][1]) : cms_eval($wanted_functions[0], $upgrade_file2, false);
            }
            $addon_contents[basename($upgrade_file2, '.php')] = $files;
            $addons_wanted[] = basename($upgrade_file2, '.php');
        }
    }

    // Find addons in the TAR to extract (should take priority over what we loaded from disk)
    afm_make_directory('exports/upgrade/', false, true);
    foreach ($directory as $offset => $upgrade_file) {
        $extract_addon = false;
        if ((strpos($upgrade_file['path'], '/addon_registry/') !== false) && (substr($upgrade_file['path'], -4) == '.php')) {
            $file_data = tar_get_file($upgrade_resource, $upgrade_file['path']);

            // Extract file temporarily so we can extract functions from it
            afm_make_file('exports/upgrade/' . $upgrade_file['path'], $file_data['data'], ($file_data['mode'] & 0002) != 0);
            $wanted_functions = extract_module_functions(get_file_base() . '/exports/upgrade/' . $upgrade_file['path'], ['get_dependencies', 'get_min_cms_version', 'get_file_list']);

            if ($wanted_functions[2] !== null) {
                $addon_contents[basename($upgrade_file['path'], '.php')] = is_array($wanted_functions[2]) ? call_user_func_array($wanted_functions[2][0], $wanted_functions[2][1]) : cms_eval($wanted_functions[2], get_file_base() . '/exports/upgrade/' . $upgrade_file['path'], false);
            }

            if (((file_exists(get_file_base() . '/' . $upgrade_file['path'])) || (strpos($upgrade_file['path'], '/core_') !== false))) {
                $extract_addon = true;
            } else {
                // Determine if this addon had a previous name, and we have installed an addon by that previous name
                $dependencies = [];
                if ($wanted_functions[0] !== null) {
                    $dependencies = is_array($wanted_functions[0]) ? call_user_func_array($wanted_functions[0][0], $wanted_functions[0][1]) : cms_eval($wanted_functions[0], get_file_base() . '/exports/upgrade/' . $upgrade_file['path'], false);
                }
                if (array_key_exists('previously_in_addon', $dependencies)) {
                    foreach ($dependencies['previously_in_addon'] as $previous_addon) {
                        if (file_exists(get_file_base() . '/' . dirname($upgrade_file['path']) . '/' . $previous_addon . '.php')) {
                            $extract_addon = true;

                            // We need to update the database accordingly with the new name of the addon
                            if ($GLOBALS['SITE_DB']->query_select_value_if_there('addons', 'addon_name', ['addon_name' => basename($upgrade_file['path'])]) === null) {
                                $GLOBALS['SITE_DB']->query_update('addons', [
                                    'addon_name' => str_replace('.php', '', basename($upgrade_file['path'])),
                                ], ['addon_name' => trim($previous_addon, "'")], '', 1);
                                $GLOBALS['SITE_DB']->query_update('addons_dependencies', [
                                    'addon_name' => str_replace('.php', '', basename($upgrade_file['path'])),
                                ], ['addon_name' => trim($previous_addon, "'")], '', 1);
                                $GLOBALS['SITE_DB']->query_update('addons_files', [
                                    'addon_name' => str_replace('.php', '', basename($upgrade_file['path'])),
                                ], ['addon_name' => trim($previous_addon, "'")], '', 1);
                            }

                            echo '<p>' . do_lang('UPGRADER_RENAMED_ADDON_MESSAGE', escape_html(trim($previous_addon, "'")), escape_html(str_replace('.php', '', basename($upgrade_file['path'])))) . '</p>';
                        }
                    }
                }

                // We should allow installation of some new addons if they do not exist as import TARs (which implies it was an uninstalled addon)
                if ((!$extract_addon) && (!is_file(get_file_base() . '/imports/addons/' . basename($upgrade_file['path'], '.php') . '.tar'))) {
                    if ($wanted_functions[1] !== null) {
                        $cms_min_version = is_array($wanted_functions[1]) ? call_user_func_array($wanted_functions[1][0], $wanted_functions[1][1]) : cms_eval($wanted_functions[1], get_file_base() . '/exports/upgrade/' . $upgrade_file['path'], false);
                        if ($cms_min_version >= cms_version_number()) {
                            $extract_addon = true;
                            echo '<p>' . do_lang('UPGRADER_NEW_ADDON_EXTRACTED', escape_html(str_replace('.php', '', basename($upgrade_file['path'])))) . '</p>';
                        }
                    }
                }
            }

            if ($extract_addon) {
                $addons_wanted[] = basename($upgrade_file['path'], '.php');
            }

            // Delete the temporary extracted file
            afm_delete_file('exports/upgrade/' . $upgrade_file['path']);
        }
    }
    afm_delete_directory('exports/upgrade/', true);

    $files_for_tar_updating = [];

    // Now process each file individually
    $i = 0;
    $cnt = count($directory);
    foreach ($directory as $offset => $upgrade_file) {
        $i++;
        $out .= '<!-- Looking at ' . escape_html($upgrade_file['path']) . ' (' . strval($i) . ' / ' . strval($cnt) . ') -->';

        // Skip over these, from manual installer package (which may be used for an upgrade)
        if ($upgrade_file['path'] == '_config.php') {
            continue;
        }
        if ($upgrade_file['path'] == 'install.php') {
            continue;
        }
        if ($upgrade_file['path'] == 'install.sql' || $upgrade_file['path'] == '_config.php.template') {
            continue;
        }

        $is_directory = (substr($upgrade_file['path'], -1) == '/');

        if ($popup_simple_extract) {
            // See if we can skip the file, if the on-disk version is identical?
            if ((is_file(get_file_base() . '/' . $upgrade_file['path'])) && (($is_directory) || (filesize(get_file_base() . '/' . $upgrade_file['path']) == $upgrade_file['size']))) {
                $tar_data = tar_get_file($upgrade_resource, $upgrade_file['path']);

                /*
                 * For hotfixes, do a simple mtime check. We do not want to overwrite files newer than in the hotfix.
                 * For upgrades, we want to do an exact contents comparison.
                 */
                if ($is_hotfix) {
                    if ($tar_data['mtime'] < filemtime(get_file_base() . '/' . $upgrade_file['path'])) {
                        $out .= do_lang('UPGRADER_SKIPPING_MESSAGE_B', escape_html($upgrade_file['path'])) . '<br />';
                        $metadata['skip'][] = do_lang('UPGRADER_SKIPPING_MESSAGE_B', escape_html($upgrade_file['path']));
                        continue;
                    }
                } else {
                    if (cms_file_get_contents_safe(get_file_base() . '/' . $upgrade_file['path'], FILE_READ_LOCK) == $tar_data['data']) {
                        $out .= do_lang('UPGRADER_SKIPPING_MESSAGE', escape_html($upgrade_file['path'])) . '<br />';
                        $metadata['skip'][] = do_lang('UPGRADER_SKIPPING_MESSAGE', escape_html($upgrade_file['path']));
                        continue;
                    }
                }
            }
        }

        $found = null;
        if (!$is_directory) {
            // See if this file belongs to an addon
            foreach ($addon_contents as $addon_name => $addon_files) {
                $addon_file_path = $upgrade_file['path'];
                if (in_array($addon_file_path, $addon_files)) {
                    $found = $addon_name;
                    break;
                }
            }
        }

        // Install if it matches an addon either on disk or in the TAR (and not part of an addon we uninstalled)
        // (if we couldn't find the addon for it we have to assume a corrupt upgrade TAR and must skip the file)
        if (($found !== null) && (in_array($found, $addons_wanted))) {
            if ($is_directory) {
                if (!$dry_run) {
                    afm_make_directory($upgrade_file['path'], false, true);
                }
            } else {
                if (!$popup_simple_extract) {
                    $file_data = tar_get_file($upgrade_resource, $upgrade_file['path']);
                    if (!$dry_run) {
                        if (!file_exists(get_file_base() . '/' . dirname($upgrade_file['path']))) {
                            afm_make_directory(dirname($upgrade_file['path']), false, true);
                        }
                        afm_make_file($upgrade_file['path'], $file_data['data'], ($file_data['mode'] & 0002) != 0);
                    }

                    $out .= do_lang('UPGRADER_EXTRACTING_MESSAGE', escape_html($upgrade_file['path'])) . '<br />';
                } else {
                    $metadata['todo'][] = [$upgrade_file['path'], $upgrade_file['mtime'], $offset + 512, $upgrade_file['size'], ($upgrade_file['mode'] & 0002) != 0];
                }
            }
        }

        // Record to copy the file into our archived addon (if it exists) so that addon is kept up-to-date
        if (!$is_directory) {
            if (($found !== null) && (file_exists(get_file_base() . '/imports/addons/' . $found . '.tar'))) {
                $files_for_tar_updating[$found][$upgrade_file['path']] = [$upgrade_file['mode'], $upgrade_file['mtime']];
            }
        }
    }

    $files_previous_path = 'data/files_previous.bin';
    $file_data = tar_get_file($upgrade_resource, $files_previous_path);
    if ($file_data !== null) {
        $files_previous = unserialize($file_data['data']);
    } else {
        $files_previous = [];
    }
    $files_current_path = 'data/files.bin';
    $file_data = tar_get_file($upgrade_resource, $files_current_path);
    if ($file_data !== null) {
        $files_current = unserialize($file_data['data']);
    } else {
        $files_current = [];
    }

    // Copy it into our archived addon so that addon is kept up-to-date
    foreach ($files_for_tar_updating as $found => $files) {
        if (!is_file(get_file_base() . '/imports/addons/' . $found . '.tar')) { // Nothing to do if an archive does not exist
            continue;
        }
        $old_addon_file = tar_open(get_file_base() . '/imports/addons/' . $found . '.tar', 'rb');
        $directory2 = tar_get_directory($old_addon_file, true);
        if ($directory2 !== null) {
            if (!$dry_run) {
                // New version of TAR file
                $new_addon_file = tar_open(get_file_base() . '/imports/addons/' . $found . '.new.tar', 'wb');

                // Add files from old TAR file, except ones we are replacing or ones that should no longer exist
                foreach ($directory2 as $d) {
                    if (substr($d['path'], -1) == '/') {
                        // Ignore folders
                        continue;
                    }

                    if (array_key_exists($d['path'], $files)) {
                        // We are replacing this
                        continue;
                    }

                    if ((array_key_exists($d['path'], $files_previous)) && (!array_key_exists($d['path'], $files_current))) {
                        // Has been deleted
                        continue;
                    }

                    $file_data = tar_get_file($old_addon_file, $d['path']);

                    $file_data['data'] = preg_replace('#^version=.*#m', 'version=(version-synched)', $file_data['data']);

                    tar_add_file($new_addon_file, $d['path'], $file_data['data'], $d['mode'], $d['mtime']);
                }
            }
            tar_close($old_addon_file);

            foreach ($files as $file_to_update => $_file_to_update) {
                if (!$dry_run) {
                    list($file_to_update_mode, $file_to_update_mtime) = $_file_to_update;

                    $file_data = tar_get_file($upgrade_resource, $file_to_update);

                    tar_add_file($new_addon_file, $file_to_update, $file_data['data'], $upgrade_file['mode'], $upgrade_file['mtime']);
                }

                $out .= do_lang('UPGRADER_PACKING_MESSAGE', escape_html($file_to_update)) . '<br />';
            }

            if (!$dry_run) {
                tar_close($new_addon_file);

                unlink(get_file_base() . '/imports/addons/' . $found . '.tar');
                rename(get_file_base() . '/imports/addons/' . $found . '.new.tar', get_file_base() . '/imports/addons/' . $found . '.tar');

                sync_file(get_file_base() . '/imports/addons/' . $found . '.tar');
            } else {
                @unlink(get_file_base() . '/imports/addons/' . $found . '.new.tar');
            }
        }
    }

    tar_close($upgrade_resource);

    // Do extraction within iframe, if possible
    if ($popup_simple_extract) {
        if (!$dry_run) {
            // These are standardised temp files upgrader2.php will expect
            $tmp_upgrade_path = get_custom_file_base() . '/data_custom/upgrader.cms.tmp';
            $tmp_metadata_path = get_custom_file_base() . '/data_custom/upgrader.tmp';

            // Remove standardised temp files
            @unlink($tmp_upgrade_path);
            @unlink($tmp_metadata_path);

            // How do we get our $upgrade_path file into $tmp_upgrade_path?
            switch ($retrieval_method) {
                case FILE_RETRIEVAL_UPLOAD:
                    move_uploaded_file($upgrade_path, $tmp_upgrade_path);
                    break;
                case FILE_RETRIEVAL_HTTP:
                    rename($upgrade_path, $tmp_upgrade_path);
                    break;
                case FILE_RETRIEVAL_LOCAL:
                    copy($upgrade_path, $tmp_upgrade_path);
                    break;
            }

            // Save our metadata
            require_code('files');
            cms_file_put_contents_safe($tmp_metadata_path, serialize($metadata));

            // LEGACY: Copy by old password key if needed
            global $SITE_INFO;
            if (isset($GLOBALS['SITE_INFO']['admin_password'])) {
                $GLOBALS['SITE_INFO']['maintenance_password'] = $GLOBALS['SITE_INFO']['admin_password'];
                unset($GLOBALS['SITE_INFO']['admin_password']);
            }
            if (isset($GLOBALS['SITE_INFO']['master_password'])) {
                $GLOBALS['SITE_INFO']['maintenance_password'] = $GLOBALS['SITE_INFO']['master_password'];
                unset($GLOBALS['SITE_INFO']['master_password']);
            }

            // Create the iframe
            $extract_url = get_base_url() . '/data/upgrader2.php';
            $extract_url .= '?hashed_password=' . urlencode($SITE_INFO['maintenance_password']);
            $extract_url .= '&file_offset=0';
            $extract_url .= '&done=' . urlencode(do_lang('DONE'));
            $extract_url .= '&original_filename=' . urlencode($original_filename);
            $extract_url .= '&may_delete_upgrade_path=' . strval($may_delete_upgrade_path ? 1 : 0);
            $out .= '<p>' . do_lang('UPGRADER_EXTRACTING_WINDOW', integer_format(count($metadata['todo']))) . '</p>';
            $out .= '<iframe frameBorder="0" style="width: 100%; height: 400px" src="' . escape_html($extract_url) . '"></iframe>';
        } else {
            // Show the dry-run results
            $out .= '<p>' . do_lang('FILES') . ':</p>';
            $out .= '<ul>';
            foreach ($metadata['skip'] as $file) {
                $out .= '<li><kbd>' . escape_html($file) . '</kbd></li>';
            }
            if (empty($metadata['todo'])) {
                $out .= '<li><em>' . do_lang('NONE') . '</em></li>';
            } else {
                foreach ($metadata['todo'] as $file) {
                    $out .= '<li><kbd>' . escape_html($file[0]) . '</kbd></li>';
                }
            }
            $out .= '</ul>';
        }
    } else {
        // We extracted live, so just tidy up
        $out .= '<p>' . do_lang('SUCCESS') . '</p>';
        if ($may_delete_upgrade_path) {
            @unlink($upgrade_path);
        }
    }

    unset($_POST['news_id']);
    unset($_POST['from_version']);

    return $out;
}
