{subdomain}. Besides its addresses and * path it tells where its uploads and wp-content live, so the other site can map those too (spec 2026-10-16 §4). */ public static function current(Db $db): array { $info = self::of($db, Scope::site(), $db->prefix()); if (is_multisite()) { $info['network'] = ['subdomain' => is_subdomain_install()]; } return $info; } /** * A site Relay moves: this single site or one subsite of this network, with the table prefix its tables have in * flight — the base prefix for a subsite that leaves its network (single-site shape), its own for one that is * written to. A subsite also says its id. */ public static function of(Db $db, Scope $scope, string $prefix): array { global $wp_version, $wpdb; $info = [ 'name' => $scope->name, 'site_url' => $scope->site_url, 'home_url' => $scope->home_url, 'path' => $scope->path, 'prefix' => $prefix, 'wp' => (string) $wp_version, 'php' => PHP_VERSION, 'mysql' => $db->server_version(), 'charset' => (string) $wpdb->charset, 'relay' => RELAY_VERSION, ]; foreach (self::FOLDERS as $key) { $value = $scope->urls()[$key] ?? ''; if ($value !== '') { $info[$key] = $value; } } if ($scope->is_subsite()) { $info['subsite'] = $scope->blog_id; } return $info; } /** * A peer's site info from a request or response, checked and reduced to plain strings (and its optional parts). * * @param mixed $data as the other site sent it */ public static function from_peer($data): array { $out = []; foreach (['name', 'site_url', 'home_url', 'path', 'prefix', 'wp', 'php', 'mysql', 'charset', 'relay'] as $field) { $out[$field] = is_scalar($data[$field] ?? null) ? mb_substr((string) $data[$field], 0, 255) : ''; } $out['name'] = self::plain_name($out['name']); if (!preg_match('#^https?://#i', $out['site_url']) || !preg_match('#^https?://#i', $out['home_url']) || !preg_match('/^[A-Za-z0-9_]{1,40}$/', $out['prefix'])) { throw new RelayError('remote.bad_request', esc_html__('The other site sent incomplete site details.', 'siterelay')); } foreach (self::FOLDERS as $key) { $value = is_string($data[$key] ?? null) ? mb_substr($data[$key], 0, 1024) : ''; if ($value !== '' && ($key === 'uploads_dir' || preg_match('#^https?://#i', $value))) { $out[$key] = $value; } } if (is_int($data['subsite'] ?? null) && $data['subsite'] > 0) { $out['subsite'] = $data['subsite']; } if (is_array($data['network'] ?? null)) { $out['network'] = ['subdomain' => !empty($data['network']['subdomain'])]; } return $out; } /** A site name as people typed it: WordPress keeps it HTML-escaped ("Moroff & Partners"), and older sites send it so. */ public static function plain_name(string $stored): string { return html_entity_decode($stored, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } /** Whether two addresses are the same site: scheme, letter case and a trailing slash don't count. */ public static function same_address(string $a, string $b): bool { $key = static function (string $url): string { $p = wp_parse_url(trim($url)); if (!is_array($p) || empty($p['host'])) { return ''; } return strtolower($p['host']) . (isset($p['port']) ? ':' . $p['port'] : '') . rtrim($p['path'] ?? '', '/'); }; $first = $key($a); return $first !== '' && $first === $key($b); } /** * Stops a connection whose other end is this site (its own connection string): pulling or pushing a site * onto itself copies nothing new and loses whatever is saved there while it runs. */ public static function refuse_this_site(array $peer): void { if (self::same_address((string) ($peer['home_url'] ?? ''), home_url())) { throw new RelayError( 'connection.self', /* translators: %s: this site's address */ esc_html(sprintf(__('This connection string belongs to this site (%s).', 'siterelay'), home_url())), esc_html__('Use the connection string from Access keys on the other site, the one you want to copy from or to.', 'siterelay') ); } } }