transport = $transport; } public function capabilities(): RunCapabilities { $response = $this->transport->send_signed( new ApiRequest( 'GET', '/installation', '', null ) ); $data = $this->data( $response ); $capabilities = $data['capabilities'] ?? null; if ( ! is_array( $capabilities ) ) { throw new ApiException( '2UA returned incomplete run capabilities.' ); } return new RunCapabilities( $this->required_int( $capabilities, 'max_urls_per_run' ), $this->required_int( $capabilities, 'manual_runs_remaining' ), $this->required_bool( $capabilities, 'automatic_runs_enabled' ), new RunAccountAccess( $this->required_bool( $data, 'connected' ), $this->required_int( $capabilities, 'history_days' ) ) ); } public function queue( ReleaseEvent $event, ModuleSelection $modules ): RunSummary { $request = new ApiRequest( 'POST', '/runs', $this->encode( $this->payload( $event, $modules ) ), 'two-ua-run-' . substr( hash( 'sha256', $event->external_id ), 0, 48 ) ); $data = $this->data( $this->transport->send_signed( $request ) ); return $this->summary( $data ); } public function status( string $public_id ): RunSummary { $path = '/runs/' . rawurlencode( $public_id ); $data = $this->data( $this->transport->send_signed( new ApiRequest( 'GET', $path, '', null ) ) ); return $this->summary( $data ); } public function handoff( string $public_id, string $purpose ): RunHandoff { $body = $this->encode( array( 'run_public_id' => $public_id, 'purpose' => $purpose, ) ); $key = 'two-ua-handoff-' . substr( hash( 'sha256', $public_id . '|' . $purpose ), 0, 48 ); $data = $this->data( $this->transport->send_signed( new ApiRequest( 'POST', '/handoffs', $body, $key ) ) ); $handoff = RunHandoff::from_payload( $data, $public_id, $purpose ); if ( ! $handoff instanceof RunHandoff ) { throw new ApiException( '2UA returned an invalid result link.' ); } return $handoff; } /** @return array API boundary payload. */ private function payload( ReleaseEvent $event, ModuleSelection $modules ): array { return array( 'event' => array( 'type' => $event->type, 'external_id' => $event->external_id, 'occurred_at' => $event->occurred_at, ), 'modules' => $modules->to_payload(), 'urls' => array_map( static fn ( RunUrl $url ): array => $url->to_payload(), $event->urls ), 'client' => array( 'client_version' => TWO_UA_SEO_RELEASE_GUARD_VERSION, 'platform_version' => get_bloginfo( 'version' ), ), ); } /** @param array $payload JSON object payload. */ private function encode( array $payload ): string { $encoded = wp_json_encode( $payload ); if ( ! is_string( $encoded ) ) { throw new ApiException( '2UA request data could not be encoded.' ); } return $encoded; } /** @return array Decoded API response data. */ private function data( ApiResponse $response ): array { $decoded = json_decode( $response->body, true ); $data = is_array( $decoded ) ? ( $decoded['data'] ?? null ) : null; if ( ! is_array( $data ) ) { throw new ApiException( '2UA returned an invalid response.' ); } return $data; } /** @param array $data Decoded API data. */ private function required_string( array $data, string $key ): string { $value = $data[ $key ] ?? null; if ( ! is_string( $value ) || '' === $value ) { throw new ApiException( '2UA returned an incomplete run response.' ); } return $value; } /** @param array $data Decoded API data. */ private function required_int( array $data, string $key ): int { $value = $data[ $key ] ?? null; if ( ! is_int( $value ) ) { throw new ApiException( '2UA returned incomplete run capabilities.' ); } return $value; } /** @param array $data Decoded API data. */ private function required_bool( array $data, string $key ): bool { $value = $data[ $key ] ?? null; if ( ! is_bool( $value ) ) { throw new ApiException( '2UA returned incomplete run capabilities.' ); } return $value; } /** @param array $data Decoded API data. */ private function summary( array $data ): RunSummary { $summary = RunSummary::from_payload( $data ); if ( ! $summary instanceof RunSummary ) { throw new ApiException( '2UA returned an incomplete run response.' ); } return $summary; } }