get_service = isset( $definition['get_service'] ) ? $definition['get_service'] : null; $this->prepare_args = isset( $definition['prepare_args'] ) ? $definition['prepare_args'] : null; $this->create_request = isset( $definition['create_request'] ) ? $definition['create_request'] : null; } /** * Creates a request object. * * @since 1.170.0 * * @param Data_Request $data_request Data request object. * @return callable|WP_Error Callable to execute the batch request, or WP_Error. * @throws Missing_Required_Param_Exception Thrown when required parameters are missing. */ public function create_request( Data_Request $data_request ) { $requests = isset( $data_request->data['requests'] ) ? $data_request->data['requests'] : null; if ( empty( $requests ) || ! is_array( $requests ) ) { throw new Missing_Required_Param_Exception( 'requests' ); } $this->request_identifiers = array(); $this->request_errors = array(); $service = $this->get_searchconsole_service(); $batch = $service->createBatch(); $has_valid_requests = false; foreach ( $requests as $request_data ) { $identifier = $this->normalize_identifier( $request_data ); $this->request_identifiers[] = $identifier; try { $args = $this->prepare_request_args( $request_data ); if ( is_wp_error( $args ) ) { $this->request_errors[ $identifier ] = $args; continue; } $single_request = $this->build_single_request( $args ); if ( is_wp_error( $single_request ) ) { $this->request_errors[ $identifier ] = $single_request; continue; } $batch->add( $single_request, $identifier ); $has_valid_requests = true; } catch ( Exception $exception ) { $this->request_errors[ $identifier ] = $this->exception_to_error( $exception ); } } if ( empty( $this->request_identifiers ) ) { return new WP_Error( 'missing_required_param', /* translators: %s: Missing parameter name */ sprintf( __( 'Request parameter is empty: %s.', 'google-site-kit' ), 'requests' ), array( 'status' => 400 ) ); } if ( ! $has_valid_requests ) { return function () { return array(); }; } return function () use ( $batch ) { return $batch->execute(); }; } /** * Parses a response. * * @since 1.170.0 * * @param mixed $response Request response. * @param Data_Request $data_request Data request object. * @return array|WP_Error Associative array of responses keyed by identifier, or WP_Error on batch failure. */ public function parse_response( $response, Data_Request $data_request ) { if ( is_wp_error( $response ) ) { return $response; } $results = $this->request_errors; if ( is_array( $response ) ) { foreach ( $response as $identifier => $single_response ) { $normalized_identifier = $this->normalize_response_identifier( $identifier ); $results[ $normalized_identifier ] = $this->parse_single_response( $single_response ); } } // Preserve the original request ordering and ensure all identifiers are represented. $ordered_results = array(); foreach ( $this->request_identifiers as $identifier ) { if ( array_key_exists( $identifier, $results ) ) { $ordered_results[ $identifier ] = $results[ $identifier ]; } else { $ordered_results[ $identifier ] = new WP_Error( 'searchanalytics_batch_missing_response', __( 'Missing response from Search Console.', 'google-site-kit' ) ); } } // Append any unexpected identifiers returned by the API. foreach ( $results as $identifier => $single_result ) { if ( array_key_exists( $identifier, $ordered_results ) ) { continue; } $ordered_results[ $identifier ] = $single_result; } return $ordered_results; } /** * Parses a single batch response. * * @since 1.170.0 * * @param mixed $response Single response. * @return array|WP_Error Parsed rows or WP_Error. */ private function parse_single_response( $response ) { if ( is_wp_error( $response ) ) { return $response; } if ( $response instanceof Google_Service_Exception ) { return $this->exception_to_error( $response ); } if ( $response instanceof SearchAnalyticsQueryResponse ) { return $response->getRows(); } if ( is_object( $response ) && method_exists( $response, 'getRows' ) ) { return $response->getRows(); } return $response; } /** * Builds a single request. * * @since 1.170.0 * * @param array $args Prepared request arguments. * @return mixed Request instance or WP_Error. */ private function build_single_request( array $args ) { if ( ! is_callable( $this->create_request ) ) { return new WP_Error( 'invalid_request_callback', __( 'Invalid Search Console request callback.', 'google-site-kit' ) ); } return call_user_func( $this->create_request, $args ); } /** * Prepares request arguments for a single search analytics request. * * @since 1.170.0 * * @param array $request_data Raw request data. * @return array|WP_Error Prepared arguments or WP_Error. */ private function prepare_request_args( array $request_data ) { if ( ! is_callable( $this->prepare_args ) ) { return new WP_Error( 'invalid_request_args_callback', __( 'Invalid Search Console request arguments.', 'google-site-kit' ) ); } return call_user_func( $this->prepare_args, $request_data ); } /** * Gets the Search Console service instance. * * @since 1.170.0 * * @return Google_Service_SearchConsole Search Console service instance. * @throws Missing_Required_Param_Exception When the service callback is missing. */ private function get_searchconsole_service() { if ( is_callable( $this->get_service ) ) { return call_user_func( $this->get_service ); } throw new Missing_Required_Param_Exception( 'service' ); } /** * Normalizes a request identifier to a string. * * @since 1.170.0 * * @param array $request_data Request data. * @return string Normalized identifier. * @throws Missing_Required_Param_Exception When the identifier is missing or invalid. */ private function normalize_identifier( array $request_data ) { if ( isset( $request_data['identifier'] ) ) { $identifier = $request_data['identifier']; } elseif ( isset( $request_data['id'] ) ) { $identifier = $request_data['id']; } else { throw new Missing_Required_Param_Exception( 'identifier' ); } if ( ! is_scalar( $identifier ) ) { throw new Missing_Required_Param_Exception( 'identifier' ); } $identifier = (string) $identifier; if ( '' === $identifier ) { throw new Missing_Required_Param_Exception( 'identifier' ); } return $identifier; } /** * Normalizes a response identifier to align with requested keys. * * @since 1.170.0 * * @param string|int $identifier Raw response identifier. * @return string|int Normalized identifier. */ private function normalize_response_identifier( $identifier ) { if ( is_string( $identifier ) && 0 === strpos( $identifier, 'response-' ) ) { $identifier = substr( $identifier, strlen( 'response-' ) ); } return $identifier; } /** * Converts an exception to a WP_Error instance. * * @since 1.170.0 * * @param Exception $exception Exception instance. * @return WP_Error WP_Error instance. */ private function exception_to_error( Exception $exception ) { $status = (int) ( $exception->getCode() ?: 500 ); return new WP_Error( 'searchanalytics_batch_request_failed', $exception->getMessage(), array( 'status' => $status ) ); } }