db = $db; $this->swap = $swap; $this->points = $points; $this->settings = $settings; } /** * SiteRelay's options (settings, schema version, mode) and the site's stored salts stay as they are here. * Which plugins are active comes with the incoming options as it is: SiteRelay never activates a plugin, itself * included. $tables are this site's names of the incoming tables. Returns whether SiteRelay is still active once * they are in (false: it switches off, see off_warning()). */ public function carry_options(string $prefix, array $tables): bool { $wpdb = $this->db->wpdb(); $live = $prefix . 'options'; $temp = Naming::temp($live); if (!in_array($live, $tables, true) || !$this->db->table_exists($temp)) { return true; } $like = $wpdb->esc_like('relay_') . '%'; $salts = "'" . implode("','", self::SALT_OPTIONS) . "'"; $this->db->query($wpdb->prepare('DELETE FROM ' . Db::ident($temp) . " WHERE option_name LIKE %s OR option_name IN ({$salts})", $like)); // phpcs:ignore WordPress.DB.PreparedSQL -- table quoted by Db::ident(); $salts are fixed option names if ($this->db->table_exists($live)) { $this->db->query($wpdb->prepare('INSERT INTO ' . Db::ident($temp) . ' (option_name, option_value, autoload) SELECT option_name, option_value, autoload FROM ' . Db::ident($live) . " WHERE option_name LIKE %s OR option_name IN ({$salts})", $like)); // phpcs:ignore WordPress.DB.PreparedSQL -- table quoted by Db::ident(); $salts are fixed option names } if (is_multisite()) { return true; // on a network SiteRelay is network-activated, never a subsite's own plugin } $raw = $this->db->var('SELECT option_value FROM ' . Db::ident($temp) . " WHERE option_name = 'active_plugins'"); $plugins = is_string($raw) ? maybe_unserialize($raw) : []; return is_array($plugins) && in_array(plugin_basename(RELAY_FILE), $plugins, true); } /** What a job says when the database it brought in doesn't have SiteRelay active. */ public static function off_warning(): string { return __('SiteRelay is not active in the database that came in, so it is now switched off on this site. Activate it again under Plugins to see or undo this job.', 'siterelay'); } /** * The incoming options keep this site's (or subsite's) siteurl and home, so it never moves to another address — * also when the incoming address wasn't recognised and nothing was mapped. $tables are this site's names of the * incoming tables. */ public function keep_address(string $prefix, array $tables, Scope $scope): void { $live = $prefix . 'options'; $temp = Naming::temp($live); if (!in_array($live, $tables, true) || !$this->db->table_exists($temp)) { return; } $wpdb = $this->db->wpdb(); $address = $scope->is_subsite() ? ['siteurl' => (string) get_blog_option($scope->blog_id, 'siteurl'), 'home' => (string) get_blog_option($scope->blog_id, 'home')] : ['siteurl' => (string) get_option('siteurl'), 'home' => (string) get_option('home')]; foreach ($address as $name => $value) { $this->db->query($wpdb->prepare('DELETE FROM ' . Db::ident($temp) . ' WHERE option_name = %s', $name)); // phpcs:ignore WordPress.DB.PreparedSQL -- table names quoted by Db::ident(), values prepared $this->db->query($wpdb->prepare('INSERT INTO ' . Db::ident($temp) . " (option_name, option_value, autoload) VALUES (%s, %s, 'yes')", $name, $value)); // phpcs:ignore WordPress.DB.PreparedSQL -- table names quoted by Db::ident(), values prepared } } /** * Load the site after the swap. If it doesn't load, the tables (and, via $put_back, anything else that * was switched) go back and restore.site_broken is thrown. Returns a warning when the site couldn't be * reached to check it, else null. */ public function check(Job $job, array $point, callable $put_back, string $hint, ?Scope $scope = null): ?string { wp_cache_flush(); $check = SiteCheck::run($scope !== null ? $scope->blog_id : 0); if ($check['status'] === SiteCheck::BROKEN) { $this->swap->drop($this->swap->restore($point, substr(sha1($job->id . ':undo'), 0, 6))); $put_back(); wp_cache_flush(); throw new RelayError( 'restore.site_broken', /* translators: %s: what went wrong, e.g. "https://example.com/ answered HTTP 500" */ esc_html(sprintf(__('The site did not load after the switch (%s), so SiteRelay put it back the way it was.', 'siterelay'), $check['detail'])), esc_html($hint) ); } if ($check['status'] !== SiteCheck::UNKNOWN) { return null; } /* translators: %s: the address Relay tried and why it failed, e.g. "https://example.com/: cURL error 28: Operation timed out" */ return sprintf(__('SiteRelay could not load the site after the switch to check it (%s). Open the site to make sure it works; you can undo this from History.', 'siterelay'), $check['detail']); } /** * The new tables (and files) are live: caches and rewrite rules reset, older restore points pruned (this * job's is kept). A switch that replaced nothing — files written in place in low-disk mode — keeps none. */ public function settle(Job $job, array $point, ?Scope $scope = null): void { $job->restore_point = empty($point['tables']) && empty($point['absent']) && empty($point['files']) && empty($point['users']) && empty($point['created_subsite']) ? null : $point; wp_cache_flush(); if ($scope !== null && $scope->is_subsite()) { delete_blog_option($scope->blog_id, 'rewrite_rules'); update_blog_details($scope->blog_id, ['last_updated' => current_time('mysql', true)]); clean_blog_cache($scope->blog_id); } else { delete_option('rewrite_rules'); } $this->points->prune(max(0, (int) $this->settings->get('restore_points_limit') - 1)); } public function swap(): Swap { return $this->swap; } }