http = $http; $this->oauth = $oauth; } /** * Performs an authenticated Google API call, retrying once on an expired * or revoked access token. * * @param string $method HTTP method. * @param string $url Full request URL, including any query string. * @param array|null $json Request body to JSON-encode, or null for none (e.g. GET). * @return array * @throws ApiError On failure. A second consecutive 'unauthorized' is raised as 'reauth_required'. */ protected function call( string $method, string $url, ?array $json = null ): array { $token = $this->oauth->access_token(); try { return $this->send( $method, $url, $token, $json ); } catch ( ApiError $e ) { if ( 'unauthorized' !== $e->slug() ) { throw $e; } } $token = $this->oauth->access_token( true ); try { return $this->send( $method, $url, $token, $json ); } catch ( ApiError $e ) { if ( 'unauthorized' === $e->slug() ) { // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception args, never echoed. throw new ApiError( 'reauth_required', $e->getMessage(), $e->status(), $e->details() ); } throw $e; } } /** * Guards a property resource name before it is interpolated into a URL * path, so a malformed or hostile value can never change the endpoint. * * @param string $property_id Expected form: 'properties/'. * @throws ApiError With slug 'bad_request' when the id is malformed. */ protected function assert_property_id( string $property_id ): void { if ( 1 !== preg_match( '/^properties\/\d+$/D', $property_id ) ) { throw new ApiError( 'bad_request', 'Invalid property id.' ); } } /** * Sends one bearer-authenticated request. * * @param string $method HTTP method. * @param string $url Full request URL. * @param string $token Access token. * @param array|null $json Request body, or null for none. * @return array * @throws ApiError On failure. */ private function send( string $method, string $url, #[\SensitiveParameter] string $token, ?array $json ): array { $opts = array( 'headers' => array( 'Authorization' => 'Bearer ' . $token ), ); if ( null !== $json ) { $opts['json'] = $json; } return $this->http->request( $method, $url, $opts ); } }