replaceMethods() public static method

public static replaceMethods ( )
Ejemplo n.º 1
0
        FileSystem::remove(VP_PROJECT_ROOT . '/.git');
    }
    FileSystem::remove(VP_VPDB_DIR);
    unlink(VERSIONPRESS_PLUGIN_DIR . '/.abort-initialization');
}
//----------------------------------------
// Hooks for VersionPress functionality
//----------------------------------------
if (VersionPress::isActive()) {
    add_action('init', 'vp_register_hooks');
    //----------------------------------
    // Replacing wpdb
    //----------------------------------
    register_shutdown_function(function () {
        if (!WpdbReplacer::isReplaced() && !defined('VP_DEACTIVATING') && VersionPress::isActive()) {
            WpdbReplacer::replaceMethods();
        }
    });
    //----------------------------------
    // Flushing rewrite rules after clone / pull / push
    //----------------------------------
    add_action('wp_loaded', function () {
        if (get_transient('vp_flush_rewrite_rules') && !defined('WP_CLI')) {
            require_once ABSPATH . 'wp-admin/includes/misc.php';
            require_once ABSPATH . 'wp-admin/includes/file.php';
            flush_rewrite_rules();
            delete_transient('vp_flush_rewrite_rules');
        }
    });
}
//----------------------------------
Ejemplo n.º 2
0
 private function activateVersionPress()
 {
     WpdbReplacer::replaceMethods();
     touch(VERSIONPRESS_ACTIVATION_FILE);
     $this->reportProgressChange(InitializerStates::VERSIONPRESS_ACTIVATED);
 }
Ejemplo n.º 3
0
function vp_register_hooks()
{
    global $wpdb, $versionPressContainer;
    /** @var Committer $committer */
    $committer = $versionPressContainer->resolve(VersionPressServices::COMMITTER);
    /** @var Mirror $mirror */
    $mirror = $versionPressContainer->resolve(VersionPressServices::MIRROR);
    /** @var DbSchemaInfo $dbSchemaInfo */
    $dbSchemaInfo = $versionPressContainer->resolve(VersionPressServices::DB_SCHEMA);
    /** @var VpidRepository $vpidRepository */
    $vpidRepository = $versionPressContainer->resolve(VersionPressServices::VPID_REPOSITORY);
    /** @var WpdbMirrorBridge $wpdbMirrorBridge */
    $wpdbMirrorBridge = $versionPressContainer->resolve(VersionPressServices::WPDB_MIRROR_BRIDGE);
    /**
     *  Hook for saving taxonomies into files
     *  WordPress creates plain INSERT query and executes it using wpdb::query method instead of wpdb::insert.
     *  It's too difficult to parse every INSERT query, that's why the WordPress hook is used.
     */
    add_action('save_post', createUpdatePostTermsHook($mirror, $vpidRepository));
    add_filter('update_feedback', function () {
        touch(ABSPATH . 'versionpress.maintenance');
    });
    add_action('_core_updated_successfully', function () use($committer, $mirror) {
        require ABSPATH . 'wp-includes/version.php';
        // load constants (like $wp_version)
        /** @var string $wp_version */
        $changeInfo = new WordPressUpdateChangeInfo($wp_version);
        $committer->forceChangeInfo($changeInfo);
        $mirror->save('option', array('option_name' => 'db_version', 'option_value' => get_option('db_version')));
        // We have to re-save the option because WP upgrader uses $wpdb->query()
        if (!WpdbReplacer::isReplaced()) {
            WpdbReplacer::replaceMethods();
        }
    });
    add_action('activated_plugin', function ($pluginName) use($committer) {
        $committer->forceChangeInfo(new PluginChangeInfo($pluginName, 'activate'));
    });
    add_action('deactivated_plugin', function ($pluginName) use($committer) {
        $committer->forceChangeInfo(new PluginChangeInfo($pluginName, 'deactivate'));
    });
    add_action('upgrader_process_complete', function ($upgrader, $hook_extra) use($committer) {
        if ($hook_extra['type'] === 'theme') {
            $themes = isset($hook_extra['bulk']) && $hook_extra['bulk'] === true ? $hook_extra['themes'] : array($upgrader->result['destination_name']);
            foreach ($themes as $theme) {
                $themeName = wp_get_theme($theme)->get('Name');
                if ($themeName === $theme && isset($upgrader->skin->api, $upgrader->skin->api->name)) {
                    $themeName = $upgrader->skin->api->name;
                }
                $action = $hook_extra['action'];
                // can be "install" or "update", see WP_Upgrader and search for `'hook_extra' =>`
                $committer->forceChangeInfo(new ThemeChangeInfo($theme, $action, $themeName));
            }
        }
        if (!($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'update')) {
            return;
        }
        // handled by different hook
        if (isset($hook_extra['bulk']) && $hook_extra['bulk'] === true) {
            $plugins = $hook_extra['plugins'];
        } else {
            $plugins = array($hook_extra['plugin']);
        }
        foreach ($plugins as $plugin) {
            $committer->forceChangeInfo(new PluginChangeInfo($plugin, 'update'));
        }
    }, 10, 2);
    add_action('added_option', function ($name) use($wpdb, $mirror) {
        $option = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}options WHERE option_name='{$name}'", ARRAY_A);
        $mirror->save("option", $option);
    });
    add_filter('upgrader_pre_install', function ($_, $hook_extra) use($committer) {
        if (!(isset($hook_extra['type']) && $hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install')) {
            return;
        }
        $pluginsBeforeInstallation = get_plugins();
        $postInstallHook = function ($_, $hook_extra) use($pluginsBeforeInstallation, $committer, &$postInstallHook) {
            if (!($hook_extra['type'] === 'plugin' && $hook_extra['action'] === 'install')) {
                return;
            }
            wp_cache_delete('plugins', 'plugins');
            $pluginsAfterInstallation = get_plugins();
            $installedPlugin = array_diff_key($pluginsAfterInstallation, $pluginsBeforeInstallation);
            reset($installedPlugin);
            $pluginName = key($installedPlugin);
            $committer->forceChangeInfo(new PluginChangeInfo($pluginName, 'install'));
            remove_filter('upgrader_post_install', $postInstallHook);
        };
        add_filter('upgrader_post_install', $postInstallHook, 10, 2);
    }, 10, 2);
    add_filter('upgrader_pre_download', function ($reply, $_, $upgrader) use($committer) {
        if (!isset($upgrader->skin->language_update)) {
            return $reply;
        }
        $languages = get_available_languages();
        $postInstallHook = function ($_, $hook_extra) use($committer, $languages, &$postInstallHook) {
            if (!isset($hook_extra['language_update_type'])) {
                return;
            }
            $translations = wp_get_available_translations();
            $type = $hook_extra['language_update_type'];
            $languageCode = $hook_extra['language_update']->language;
            $languageName = isset($translations[$languageCode]) ? $translations[$languageCode]['native_name'] : 'English (United States)';
            $name = $type === "core" ? null : $hook_extra['language_update']->slug;
            $action = in_array($languageCode, $languages) ? "update" : "install";
            $committer->forceChangeInfo(new TranslationChangeInfo($action, $languageCode, $languageName, $type, $name));
            remove_filter('upgrader_post_install', $postInstallHook);
        };
        add_filter('upgrader_post_install', $postInstallHook, 10, 2);
        return false;
    }, 10, 3);
    add_action('switch_theme', function () use($committer) {
        if (defined('WP_CLI') && WP_CLI) {
            file_get_contents(admin_url());
            //
        } else {
            $committer->disableCommit();
            // the change will be committed on next load
        }
    });
    add_action('after_switch_theme', function () use($committer) {
        $theme = wp_get_theme();
        $stylesheet = $theme->get_stylesheet();
        $themeName = $theme->get('Name');
        $committer->forceChangeInfo(new ThemeChangeInfo($stylesheet, 'switch', $themeName));
    });
    add_action('customize_save_after', function ($customizeManager) use($committer) {
        /** @var WP_Customize_Manager $customizeManager */
        $stylesheet = $customizeManager->theme()->get_stylesheet();
        $committer->forceChangeInfo(new ThemeChangeInfo($stylesheet, 'customize'));
        register_shutdown_function(function () {
            wp_remote_get(admin_url("admin.php"));
        });
    });
    add_action('untrashed_post_comments', function ($postId) use($wpdb, $dbSchemaInfo, $wpdbMirrorBridge) {
        $commentsTable = $dbSchemaInfo->getPrefixedTableName("comment");
        $commentStatusSql = "select comment_ID, comment_approved from {$commentsTable} where comment_post_ID = {$postId}";
        $comments = $wpdb->get_results($commentStatusSql, ARRAY_A);
        foreach ($comments as $comment) {
            $wpdbMirrorBridge->update($commentsTable, array("comment_approved" => $comment["comment_approved"]), array("comment_ID" => $comment["comment_ID"]));
        }
    });
    add_action('delete_post_meta', function ($metaIds) use($wpdbMirrorBridge, $dbSchemaInfo) {
        $idColumnName = $dbSchemaInfo->getEntityInfo("postmeta")->idColumnName;
        foreach ($metaIds as $metaId) {
            $wpdbMirrorBridge->delete($dbSchemaInfo->getPrefixedTableName("postmeta"), array($idColumnName => $metaId));
        }
    });
    add_action('delete_user_meta', function ($metaIds) use($wpdbMirrorBridge, $dbSchemaInfo) {
        $idColumnName = $dbSchemaInfo->getEntityInfo("usermeta")->idColumnName;
        foreach ($metaIds as $metaId) {
            $wpdbMirrorBridge->delete($dbSchemaInfo->getPrefixedTableName("usermeta"), array($idColumnName => $metaId));
        }
    });
    add_action('wp_ajax_save-widget', function () use($committer) {
        if (defined('DOING_AJAX') && DOING_AJAX && isset($_POST['delete_widget']) && $_POST['delete_widget']) {
            $committer->postponeCommit('widgets');
        }
    }, 0);
    // zero because the default WP action with priority 1 calls wp_die()
    function _vp_get_language_name_by_code($code)
    {
        $translations = wp_get_available_translations();
        return isset($translations[$code]) ? $translations[$code]['native_name'] : 'English (United States)';
    }
    add_action('add_option_WPLANG', function ($option, $value) use($committer) {
        $defaultLanguage = defined('WPLANG') ? WPLANG : '';
        if ($value === $defaultLanguage) {
            return;
            // It's just submitted settings form without changing language
        }
        $languageName = _vp_get_language_name_by_code($value);
        $committer->forceChangeInfo(new TranslationChangeInfo("activate", $value, $languageName));
    }, 10, 2);
    add_action('update_option_WPLANG', function ($oldValue, $newValue) use($committer) {
        $languageName = _vp_get_language_name_by_code($newValue);
        $committer->forceChangeInfo(new TranslationChangeInfo("activate", $newValue, $languageName));
    }, 10, 2);
    add_action('wp_update_nav_menu_item', function ($menu_id, $menu_item_db_id) use($committer) {
        $key = 'menu-item-' . $menu_item_db_id;
        if (defined('DOING_AJAX') && DOING_AJAX && isset($_POST['action']) && $_POST['action'] === 'add-menu-item') {
            $committer->postponeCommit($key);
            $committer->commit();
        } elseif (isset($_POST['action']) && $_POST['action'] === 'update') {
            $committer->usePostponedChangeInfos($key);
        }
        if (!defined('DOING_AJAX')) {
            global $versionPressContainer;
            /** @var Mirror $mirror */
            $mirror = $versionPressContainer->resolve(VersionPressServices::MIRROR);
            $vpidRepository = $versionPressContainer->resolve(VersionPressServices::VPID_REPOSITORY);
            $func = createUpdatePostTermsHook($mirror, $vpidRepository);
            $func($menu_item_db_id);
        }
    }, 10, 2);
    add_action('pre_delete_term', function ($termId, $taxonomy) use($committer, $vpidRepository) {
        $termVpid = $vpidRepository->getVpidForEntity('term', $termId);
        $term = get_term($termId, $taxonomy);
        $committer->forceChangeInfo(new \VersionPress\ChangeInfos\TermChangeInfo('delete', $termVpid, $term->name, $taxonomy));
    }, 10, 2);
    add_action('set_object_terms', createUpdatePostTermsHook($mirror, $vpidRepository));
    add_filter('plugin_install_action_links', function ($links, $plugin) {
        $compatibility = CompatibilityChecker::testCompatibilityBySlug($plugin['slug']);
        if ($compatibility === CompatibilityResult::COMPATIBLE) {
            $cssClass = 'vp-compatible';
            $compatibilityAdjective = 'Compatible';
        } elseif ($compatibility === CompatibilityResult::INCOMPATIBLE) {
            $cssClass = 'vp-incompatible';
            $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin is not compatible with VersionPress. These plugins will not work correctly when used together.">Incompatible</a>';
        } else {
            $cssClass = 'vp-untested';
            $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin was not yet tested with VersionPress. Some functionality may not work as intended.">Untested</a>';
        }
        $compatibilityNotice = '<span class="vp-compatibility %s" data-plugin-name="%s"><strong>%s</strong> with VersionPress</span>';
        $links[] = sprintf($compatibilityNotice, $cssClass, $plugin['name'], $compatibilityAdjective);
        return $links;
    }, 10, 2);
    add_filter('plugin_row_meta', function ($plugin_meta, $plugin_file, $plugin_data, $status) {
        if ($status === "dropins") {
            return $plugin_meta;
        }
        $compatibility = CompatibilityChecker::testCompatibilityByPluginFile($plugin_file);
        if ($compatibility === CompatibilityResult::COMPATIBLE) {
            $cssClass = 'vp-compatible';
            $compatibilityAdjective = 'Compatible';
        } elseif ($compatibility === CompatibilityResult::INCOMPATIBLE) {
            $cssClass = 'vp-incompatible';
            $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin is not compatible with VersionPress. These plugins will not work correctly when used together.">Incompatible</a>';
        } elseif ($compatibility === CompatibilityResult::UNTESTED) {
            $cssClass = 'vp-untested';
            $compatibilityAdjective = '<a href="http://docs.versionpress.net/en/integrations/plugins" target="_blank" title="This plugin was not yet tested with VersionPress. Some functionality may not work as intended.">Untested</a>';
        } else {
            return $plugin_meta;
        }
        $compatibilityNotice = '<span class="vp-compatibility %s" data-plugin-name="%s"><strong>%s</strong> with VersionPress</span>';
        $plugin_meta[] = sprintf($compatibilityNotice, $cssClass, $plugin_data['Name'], $compatibilityAdjective);
        return $plugin_meta;
    }, 10, 4);
    add_filter('plugin_action_links', function ($actions, $plugin_file) {
        $compatibility = CompatibilityChecker::testCompatibilityByPluginFile($plugin_file);
        if (isset($actions['activate'])) {
            if ($compatibility === CompatibilityResult::UNTESTED) {
                $actions['activate'] = "<span class=\"vp-plugin-list vp-untested\">{$actions['activate']}</span>";
            } elseif ($compatibility === CompatibilityResult::INCOMPATIBLE) {
                $actions['activate'] = "<span class=\"vp-plugin-list vp-incompatible\">{$actions['activate']}</span>";
            }
        }
        return $actions;
    }, 10, 2);
    add_action('vp_revert', function () {
        // We have to flush the rewrite rules in the next request, because
        // in the current one the changed rewrite rules are not yet effective.
        set_transient('vp_flush_rewrite_rules', 1);
        vp_flush_regenerable_options();
    });
    add_action('pre_delete_term', function ($term, $taxonomy) use($wpdb, $wpdbMirrorBridge) {
        if (!is_taxonomy_hierarchical($taxonomy)) {
            return;
        }
        $term = get_term($term, $taxonomy);
        if (is_wp_error($term)) {
            return;
        }
        $wpdbMirrorBridge->update($wpdb->term_taxonomy, array('parent' => $term->parent), array('parent' => $term->term_id));
    }, 10, 2);
    add_action('before_delete_post', function ($postId) use($wpdb) {
        // Fixing bug in WP (#34803) and WP-CLI (#2246)
        $post = get_post($postId);
        if (!is_wp_error($post) && $post->post_type === 'nav_menu_item') {
            \Tracy\Debugger::log('Deleting menu item ' . $post->ID);
            $newParent = get_post_meta($post->ID, '_menu_item_menu_item_parent', true);
            $wpdb->update($wpdb->postmeta, array('meta_value' => $newParent), array('meta_key' => '_menu_item_menu_item_parent', 'meta_value' => $post->ID));
        }
    });
    //----------------------------------------
    // URL and WP-CLI "hooks"
    //----------------------------------------
    $requestDetector = new \VersionPress\Utils\RequestDetector();
    if (defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['action']) && $_REQUEST['action'] === 'widgets-order') {
        $committer->usePostponedChangeInfos('widgets');
    }
    if ($requestDetector->isThemeDeleteRequest()) {
        $themeIds = $requestDetector->getThemeStylesheets();
        foreach ($themeIds as $themeId) {
            $committer->forceChangeInfo(new ThemeChangeInfo($themeId, 'delete'));
        }
    }
    if ($requestDetector->isPluginDeleteRequest()) {
        $plugins = $requestDetector->getPluginNames();
        foreach ($plugins as $plugin) {
            $committer->forceChangeInfo(new PluginChangeInfo($plugin, 'delete'));
        }
    }
    if ($requestDetector->isCoreLanguageUninstallRequest()) {
        $languageCode = $requestDetector->getLanguageCode();
        $translations = wp_get_available_translations();
        $languageName = isset($translations[$languageCode]) ? $translations[$languageCode]['native_name'] : 'English (United States)';
        $committer->forceChangeInfo(new TranslationChangeInfo('uninstall', $languageCode, $languageName, 'core'));
    }
    if (basename($_SERVER['PHP_SELF']) === 'theme-editor.php' && isset($_GET['updated']) && $_GET['updated'] === 'true') {
        $committer->forceChangeInfo(new ThemeChangeInfo($_GET['theme'], 'edit'));
    }
    if (basename($_SERVER['PHP_SELF']) === 'plugin-editor.php' && (isset($_POST['action']) && $_POST['action'] === 'update' || isset($_GET['liveupdate']))) {
        $committer->disableCommit();
    }
    if (basename($_SERVER['PHP_SELF']) === 'plugin-editor.php' && isset($_GET['a']) && $_GET['a'] === 'te') {
        if (!function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        $editedFile = $_GET['file'];
        $editedFilePathParts = preg_split("~[/\\\\]~", $editedFile);
        $plugins = array_keys(get_plugins());
        $bestRank = 0;
        $bestMatch = "";
        foreach ($plugins as $plugin) {
            $rank = 0;
            $pluginPathParts = preg_split("~[/\\\\]~", $plugin);
            $maxEqualParts = min(count($editedFilePathParts), count($pluginPathParts));
            for ($part = 0; $part < $maxEqualParts; $part++) {
                if ($editedFilePathParts[$part] !== $pluginPathParts[$part]) {
                    break;
                }
                $rank += 1;
            }
            if ($rank > $bestRank) {
                $bestRank = $rank;
                $bestMatch = $plugin;
            }
        }
        $committer->forceChangeInfo(new PluginChangeInfo($bestMatch, 'edit'));
    }
    register_shutdown_function(array($committer, 'commit'));
}
Ejemplo n.º 4
0
 /**
  * Restores a WP site from Git repo / working directory.
  *
  * ## OPTIONS
  *
  * --siteurl=<url>
  * : The address of the restored site.
  *
  * [--yes]
  * : Answer yes to the confirmation message.
  *
  * ## DESCRIPTION
  *
  * The command will then do the following:
  *
  *    * Drops all tables tracked by VersionPress.
  *    * Recreates and fill them with data from repository.
  *
  * If you just cloned the site from another repository, run `wp core config` first.
  *
  *
  * @subcommand restore-site
  *
  * @when before_wp_load
  */
 public function restoreSite($args, $assoc_args)
 {
     if (file_exists(getcwd() . '/composer.json')) {
         $proc = proc_open("composer install", [1 => ["pipe", "w"], ["pipe", "w"]], $_);
         $result = proc_close($proc);
         if ($result !== 0) {
             WP_CLI::error('Composer dependencies could not be restored.');
         }
     }
     defined('SHORTINIT') or define('SHORTINIT', true);
     require_once __DIR__ . '/../Initialization/WpConfigSplitter.php';
     $wpConfigPath = \WP_CLI\Utils\locate_wp_config();
     $this->requireWpConfig($wpConfigPath, WpConfigSplitter::COMMON_CONFIG_NAME);
     require_once __DIR__ . '/../../bootstrap.php';
     if (!VersionPress::isActive()) {
         WP_CLI::error('Unfortunately, this site was not tracked by VersionPress.
         Therefore, it cannot be restored.');
     }
     // Check if the site is installed
     $process = VPCommandUtils::runWpCliCommand('core', 'is-installed');
     if ($process->isSuccessful()) {
         $this->checkVpRequirements($assoc_args, RequirementsChecker::ENVIRONMENT);
         WP_CLI::confirm("It looks like the site is OK. Do you really want to run the 'restore-site' command?", $assoc_args);
     }
     $url = $assoc_args['siteurl'];
     // Update URLs in wp-config.php
     define('VP_INDEX_DIR', dirname(\WP_CLI\Utils\locate_wp_config()));
     // just for the following method
     $this->setConfigUrl('WP_CONTENT_URL', 'WP_CONTENT_DIR', ABSPATH . 'wp-content', $url);
     $this->setConfigUrl('WP_PLUGIN_URL', 'WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins', $url);
     $this->setConfigUrl('WP_HOME', 'VP_INDEX_DIR', VP_PROJECT_ROOT, $url);
     defined('WP_PLUGIN_DIR') || define('WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins');
     WpConfigSplitter::ensureCommonConfigInclude($wpConfigPath);
     // Disable VersionPress tracking for a while
     WpdbReplacer::restoreOriginal();
     unlink(VERSIONPRESS_ACTIVATION_FILE);
     // Create or empty database
     $this->prepareDatabase($assoc_args);
     // Create WP tables.
     // The only important thing is site URL, all else will be rewritten later during synchronization.
     $installArgs = ['url' => $url, 'title' => 'x', 'admin_user' => 'x', 'admin_password' => 'x', 'admin_email' => '*****@*****.**'];
     if (version_compare(WP_CLI_VERSION, '0.22.0', '>=')) {
         $installArgs['skip-email'] = null;
     }
     $process = VPCommandUtils::runWpCliCommand('core', 'install', $installArgs);
     if (!$process->isSuccessful()) {
         WP_CLI::log("Failed creating database tables");
         WP_CLI::error($process->getConsoleOutput());
     } else {
         WP_CLI::success("Database tables created");
     }
     // Restores "wp-db.php", "wp-db.php.original" and ".active" - enables VP
     $resetCmd = 'git reset --hard';
     $process = VPCommandUtils::exec($resetCmd);
     if (!$process->isSuccessful()) {
         WP_CLI::log("Could not clean working directory");
         WP_CLI::error($process->getConsoleOutput());
     }
     // Fail-safe for gitignored WordPress
     if (!WpdbReplacer::isReplaced()) {
         WpdbReplacer::replaceMethods();
     }
     /* We need correct value in the `active_plugins` option before the synchronization run.
      * Without this option VersionPress doesn't know which schema.yml files it should load and consequently which
      * DB entities it should synchronize.
      */
     $activePluginsOption = IniSerializer::deserialize(file_get_contents(VP_VPDB_DIR . '/options/ac/active_plugins.ini'));
     $activePlugins = json_encode(unserialize($activePluginsOption['active_plugins']['option_value']));
     VPCommandUtils::runWpCliCommand('option', 'update', ['active_plugins', $activePlugins, 'autoload' => 'yes', 'format' => 'json', 'skip-plugins' => null]);
     // The next couple of the steps need to be done after WP is fully loaded; we use `finish-restore-site` for that
     // The main reason for this is that we need properly set WP_CONTENT_DIR constant for reading from storages
     $process = $this->runVPInternalCommand('finish-restore-site');
     WP_CLI::log($process->getConsoleOutput());
     if (!$process->isSuccessful()) {
         WP_CLI::error("Could not finish site restore");
     }
 }