db = $db; $this->table = Schema::tables()['connections']; } /** Save (or update, for the same site and key) a connection from a connection string. */ public function save(string $connection_string, string $label, bool $allow_insecure, ?array $basic_auth = null): array { $c = ConnectionString::decode($connection_string); $existing = $this->db->results($this->db->wpdb()->prepare('SELECT id FROM ' . Db::ident($this->table) . ' WHERE site_url = %s AND key_id = %s', $c['url'], $c['key_id']), ARRAY_A); $fields = [ 'label' => mb_substr(sanitize_text_field($label !== '' ? $label : (string) wp_parse_url($c['url'], PHP_URL_HOST)), 0, 100), 'site_url' => $c['url'], 'key_id' => $c['key_id'], 'secret_enc' => Crypto::encrypt($c['secret']), 'basic_auth_enc' => self::encrypt_basic_auth($basic_auth), 'allow_insecure' => $allow_insecure ? 1 : 0, ]; if ($existing) { $id = $existing[0]['id']; $this->db->quietly(fn () => $this->db->wpdb()->update($this->table, $fields, ['id' => $id])); } else { $id = wp_generate_uuid4(); $this->db->quietly(fn () => $this->db->wpdb()->insert($this->table, $fields + ['id' => $id, 'created_at' => gmdate('Y-m-d H:i:s')])); } return $this->get($id); } /** * The username and password the other site's server asks for (HTTP basic auth), added or changed after the * connection was made: the same connection, so its jobs and their undo use them. Null or no username clears them. */ public function set_basic_auth(string $id, ?array $basic_auth): array { $this->get($id); $this->db->quietly(fn () => $this->db->wpdb()->update($this->table, ['basic_auth_enc' => self::encrypt_basic_auth($basic_auth)], ['id' => $id])); return $this->get($id); } private static function encrypt_basic_auth(?array $basic_auth): ?string { return $basic_auth !== null && is_scalar($basic_auth['user'] ?? null) && (string) $basic_auth['user'] !== '' ? Crypto::encrypt((string) wp_json_encode(['user' => (string) $basic_auth['user'], 'pass' => is_scalar($basic_auth['pass'] ?? null) ? (string) $basic_auth['pass'] : ''])) : null; } public function find(string $id): ?array { $rows = $this->db->results($this->db->wpdb()->prepare('SELECT * FROM ' . Db::ident($this->table) . ' WHERE id = %s', $id), ARRAY_A); return $rows ? $rows[0] : null; } public function get(string $id): array { $row = $this->find($id); if ($row === null) { throw new RelayError('connection.not_found', esc_html__('That saved connection no longer exists.', 'siterelay')); } return $row; } /** @return array[] most recently used first */ public function all(): array { return $this->db->results('SELECT * FROM ' . Db::ident($this->table) . ' ORDER BY COALESCE(last_used_at, created_at) DESC', ARRAY_A); } public function delete(string $id): void { $this->db->quietly(fn () => $this->db->wpdb()->delete($this->table, ['id' => $id])); } public function secret(array $row): string { return Crypto::decrypt((string) $row['secret_enc']); } /** @return array{user: string, pass: string}|null */ public function basic_auth(array $row): ?array { if (empty($row['basic_auth_enc'])) { return null; } $data = json_decode(Crypto::decrypt((string) $row['basic_auth_enc']), true); return is_array($data) ? ['user' => (string) ($data['user'] ?? ''), 'pass' => (string) ($data['pass'] ?? '')] : null; } public function remember(string $id, array $fields): void { if (array_key_exists('peer', $fields)) { $fields['peer'] = wp_json_encode($fields['peer']); } $this->db->quietly(fn () => $this->db->wpdb()->update($this->table, $fields + ['last_used_at' => gmdate('Y-m-d H:i:s')], ['id' => $id])); } public static function to_api(array $row): array { return [ 'id' => $row['id'], 'label' => $row['label'], 'site_url' => $row['site_url'], 'key_id' => $row['key_id'], 'allow_insecure' => (bool) $row['allow_insecure'], 'basic_auth' => !empty($row['basic_auth_enc']), 'peer' => json_decode((string) $row['peer'], true) ?: null, 'last_used_at' => $row['last_used_at'], 'created_at' => $row['created_at'], ]; } }