backups = $backups; $this->db = $db; $this->jobs = $jobs; $this->settings = $settings; } public function register(): void { $this->route('/status', 'GET', fn (\WP_REST_Request $r) => $this->status((int) $r->get_param('subsite'))); $this->route('/site/subsites', 'GET', fn () => \Relay\Multisite\Subsites::list()); $this->route('/diagnostics', 'GET', fn () => $this->diagnostics()); $this->route('/site/files', 'GET', fn (\WP_REST_Request $r) => \Relay\Files\Inventory::current(is_multisite() && (int) $r->get_param('subsite') > 0 ? \Relay\Multisite\Scope::subsite((int) $r->get_param('subsite')) : null)); $this->route('/errors', 'GET', fn () => $this->jobs->recent_errors()); $this->route('/regex-validate', 'POST', fn (\WP_REST_Request $r) => ['valid' => RegexPair::is_valid((string) $r->get_param('pattern'))]); } /** This site's state; on a network, the tables and details are those of $subsite (none until one is chosen). */ private function status(int $subsite): array { global $wp_version; $scope = is_multisite() && $subsite > 0 ? \Relay\Multisite\Scope::subsite($subsite) : null; $names = !is_multisite() ? $this->db->tables() : ($scope !== null ? \Relay\Multisite\SubsiteTables::of($scope->prefix, $scope->base_prefix, $this->db->tables()) : []); $tables = []; foreach ($this->db->table_status($names) as $name => $s) { $tables[] = ['name' => $name, 'rows' => $s['rows'], 'bytes' => $s['bytes']]; } usort($tables, fn ($a, $b) => strcmp($a['name'], $b['name'])); $active = $this->jobs->active(); return [ 'version' => RELAY_VERSION, 'site' => [ 'name' => $scope !== null ? $scope->name : SiteInfo::plain_name(get_bloginfo('name')), 'url' => $scope !== null ? $scope->site_url : site_url(), 'home' => $scope !== null ? $scope->home_url : home_url(), 'path' => ABSPATH, 'prefix' => $scope !== null ? $scope->prefix : $this->db->prefix(), 'wp' => $wp_version, 'php' => PHP_VERSION, 'mysql' => $this->db->server_version(), ], 'tables' => $tables, 'active_job' => $active !== null ? $active->to_array() : null, 'settings' => $this->settings->all(), 'backups' => ['count' => count($this->backups->ready()), 'limit' => (int) $this->settings->get('backups_limit')], 'mode' => \Relay\Security\SiteMode::to_api(), 'subsite' => $scope !== null ? $scope->to_array() : null, ]; } private function diagnostics(): array { global $wp_version; $uploads = wp_upload_dir(null, false); return [ 'relay' => RELAY_VERSION, 'wordpress' => $wp_version, 'php' => PHP_VERSION, 'mysql' => $this->db->server_version(), 'site_url' => site_url(), 'home_url' => home_url(), 'multisite' => is_multisite(), 'max_execution_time' => ini_get('max_execution_time'), 'memory_limit' => ini_get('memory_limit'), 'post_max_size' => ini_get('post_max_size'), 'upload_max_filesize' => ini_get('upload_max_filesize'), 'max_allowed_packet' => (int) $this->db->var('SELECT @@max_allowed_packet'), 'uploads_writable' => wp_is_writable($uploads['basedir']), 'backups_dir_writable' => $this->backups_dir_writable(), 'disk_free_bytes' => Fs::free_space($uploads['basedir']) === false ? null : Fs::free_space($uploads['basedir']), 'extensions' => [ 'zlib' => extension_loaded('zlib'), 'mbstring' => extension_loaded('mbstring'), 'mysqli' => extension_loaded('mysqli'), 'sodium' => extension_loaded('sodium'), ], 'theme' => get_stylesheet(), 'active_plugins' => (array) get_option('active_plugins', []), ]; } private function backups_dir_writable(): bool { try { return wp_is_writable(Paths::backups_dir()); } catch (\Relay\Support\RelayError $e) { return false; } } }