recent_content_collector = $recent_content_collector; $this->ai_request_sender_factory = $ai_request_sender_factory; $this->consent_handler = $consent_handler; $this->category_repository = $category_repository; } // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't track exceptions thrown by called services. /** * Handles the content suggestion command by collecting recent content and requesting suggestions from the AI API. * * @param Content_Suggestion_Command $command The content suggestion command. * * @throws Unauthorized_Exception When the API returns an unauthorized response and retry is exhausted. * @throws Forbidden_Exception When consent has been revoked. * @throws Insufficient_Scope_Exception When the OAuth path's token is missing the required scope. * * @return Content_Suggestion_Response The response containing suggestions and recent content. */ public function handle( Content_Suggestion_Command $command ): Content_Suggestion_Response { $recent_content = $this->recent_content_collector->collect( $command->get_post_type() ); $about_page = $this->recent_content_collector->collect_about_page( $command->get_post_type() ); $content = [ 'posts' => $recent_content->to_array(), ]; if ( $about_page ) { $content['about_page'] = $about_page; } $parameters = new Content_Suggestion_Parameters( $command->get_user(), $command->get_language(), $content, $command->get_editor(), ); try { $sender = $this->ai_request_sender_factory->create( $command->get_user() ); $response = $sender->get_content_suggestions( $parameters ); } catch ( Insufficient_Scope_Exception $exception ) { throw $exception; } catch ( Forbidden_Exception $exception ) { $this->consent_handler->revoke_consent( $command->get_user()->ID ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive. throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() ); } return $this->build_response( $response, $recent_content ); } /** * Builds a response object bundling the content suggestions with the recent content used to generate them. * * @param Response $response The API response. * @param Post_List $recent_content The recent content passed to the AI API. * * @return Content_Suggestion_Response The response containing suggestions and recent content. */ public function build_response( Response $response, Post_List $recent_content ): Content_Suggestion_Response { return new Content_Suggestion_Response( $this->build_suggestions_array( $response ), $recent_content, ); } /** * Builds a list of content suggestions from the API response. * * @param Response $response The API response. * * @return Content_Suggestion_List The list of content suggestions. */ public function build_suggestions_array( Response $response ): Content_Suggestion_List { $content_suggestion_list = new Content_Suggestion_List(); $json = \json_decode( $response->get_body() ); if ( $json === null || ! isset( $json->choices ) ) { return $content_suggestion_list; } foreach ( $json->choices as $suggestion ) { $category = $this->category_repository->find_by_name( $suggestion->category->name ); $content_suggestion_list->add( new Content_Suggestion( $suggestion->title, $suggestion->intent, $suggestion->explanation, $suggestion->keyphrase, $suggestion->meta_description, $category, ), ); } return $content_suggestion_list; } }