JezK
Edit File: content-outline-command-handler.php
<?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\AI\Content_Planner\Application; use Yoast\WP\SEO\AI\Authentication\Application\AI_Request_Sender_Factory; use Yoast\WP\SEO\AI\Consent\Application\Consent_Handler; use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Outline_Parameters; use Yoast\WP\SEO\AI\Content_Planner\Domain\Section; use Yoast\WP\SEO\AI\Content_Planner\Domain\Section_List; use Yoast\WP\SEO\AI\Content_Planner\Infrastructure\Recent_Content\Recent_Content_Collector; use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Forbidden_Exception; use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Insufficient_Scope_Exception; use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Unauthorized_Exception; use Yoast\WP\SEO\AI\HTTP_Request\Domain\Response; /** * Handles the content outline command. */ class Content_Outline_Command_Handler { /** * The recent content collector. * * @var Recent_Content_Collector */ private $recent_content_collector; /** * The auth strategy factory. * * @var AI_Request_Sender_Factory */ private $ai_request_sender_factory; /** * The consent handler. * * @var Consent_Handler */ private $consent_handler; /** * The constructor. * * @param Recent_Content_Collector $recent_content_collector The recent content collector. * @param AI_Request_Sender_Factory $ai_request_sender_factory The auth strategy factory. * @param Consent_Handler $consent_handler The consent handler. */ public function __construct( Recent_Content_Collector $recent_content_collector, AI_Request_Sender_Factory $ai_request_sender_factory, Consent_Handler $consent_handler ) { $this->recent_content_collector = $recent_content_collector; $this->ai_request_sender_factory = $ai_request_sender_factory; $this->consent_handler = $consent_handler; } // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't track exceptions thrown by called services. /** * Handles the content outline command by collecting recent content and requesting an outline from the AI API. * * @param Content_Outline_Command $command The content outline 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 Section_List A list of outline sections. */ public function handle( Content_Outline_Command $command ): Section_List { $recent_content = $command->get_recent_content(); $about_page = $this->recent_content_collector->collect_about_page( $command->get_post_type() ); $existing_posts = \array_map( static function ( $post ) { return [ 'title' => $post['title'], 'description' => $post['description'], ]; }, $recent_content, ); $content = [ 'new_post_metadata' => [ 'title' => $command->get_title(), 'intent' => $command->get_intent(), 'explanation' => $command->get_explanation(), 'keyphrase' => $command->get_keyphrase(), 'meta_description' => $command->get_meta_description(), 'category' => $command->get_category()->to_array(), ], 'existing_posts' => $existing_posts, ]; if ( $about_page ) { $content['about_page'] = $about_page; } $parameters = new Content_Outline_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_outline_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 throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() ); } return $this->build_outline( $response ); } /** * Builds a list of outline sections from the API response. * * @param Response $response The API response. * * @return Section_List The list of outline sections. */ private function build_outline( Response $response ): Section_List { $section_list = new Section_List(); $json = \json_decode( $response->get_body() ); if ( $json === null || ! isset( $json->choices ) ) { return $section_list; } foreach ( $json->choices as $choice ) { $section_list->add( new Section( ( $choice->content_notes ?? [] ), ( $choice->subheading_text ?? null ), ), ); } return $section_list; } }