PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.2
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.2
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / ai / content-planner / application / content-suggestion-command-handler.php

content-suggestion-command-handler.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.2, at src/ai/content-planner/application/content-suggestion-command-handler.php

164 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
3
4 namespace Yoast\WP\SEO\AI\Content_Planner\Application;
5
6 use Yoast\WP\SEO\AI\Authentication\Application\AI_Request_Sender_Factory;
7 use Yoast\WP\SEO\AI\Consent\Application\Consent_Handler;
8 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion;
9 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_List;
10 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_Parameters;
11 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_Response;
12 use Yoast\WP\SEO\AI\Content_Planner\Domain\Post_List;
13 use Yoast\WP\SEO\AI\Content_Planner\Infrastructure\Recent_Content\Recent_Content_Collector;
14 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Forbidden_Exception;
15 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Insufficient_Scope_Exception;
16 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Unauthorized_Exception;
17 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Response;
18
19 /**
20 * Handles the content suggestion command.
21 */
22 class Content_Suggestion_Command_Handler {
23
24 /**
25 * The recent content collector.
26 *
27 * @var Recent_Content_Collector
28 */
29 private $recent_content_collector;
30
31 /**
32 * The auth strategy factory.
33 *
34 * @var AI_Request_Sender_Factory
35 */
36 private $ai_request_sender_factory;
37
38 /**
39 * The consent handler.
40 *
41 * @var Consent_Handler
42 */
43 private $consent_handler;
44
45 /**
46 * The category repository.
47 *
48 * @var Category_Repository_Interface
49 */
50 private $category_repository;
51
52 /**
53 * The constructor.
54 *
55 * @param Recent_Content_Collector $recent_content_collector The recent content collector.
56 * @param AI_Request_Sender_Factory $ai_request_sender_factory The auth strategy factory.
57 * @param Consent_Handler $consent_handler The consent handler.
58 * @param Category_Repository_Interface $category_repository The category repository.
59 */
60 public function __construct(
61 Recent_Content_Collector $recent_content_collector,
62 AI_Request_Sender_Factory $ai_request_sender_factory,
63 Consent_Handler $consent_handler,
64 Category_Repository_Interface $category_repository
65 ) {
66 $this->recent_content_collector = $recent_content_collector;
67 $this->ai_request_sender_factory = $ai_request_sender_factory;
68 $this->consent_handler = $consent_handler;
69 $this->category_repository = $category_repository;
70 }
71
72 // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't track exceptions thrown by called services.
73
74 /**
75 * Handles the content suggestion command by collecting recent content and requesting suggestions from the AI API.
76 *
77 * @param Content_Suggestion_Command $command The content suggestion command.
78 *
79 * @throws Unauthorized_Exception When the API returns an unauthorized response and retry is exhausted.
80 * @throws Forbidden_Exception When consent has been revoked.
81 * @throws Insufficient_Scope_Exception When the OAuth path's token is missing the required scope.
82 *
83 * @return Content_Suggestion_Response The response containing suggestions and recent content.
84 */
85 public function handle( Content_Suggestion_Command $command ): Content_Suggestion_Response {
86 $recent_content = $this->recent_content_collector->collect( $command->get_post_type() );
87 $about_page = $this->recent_content_collector->collect_about_page( $command->get_post_type() );
88
89 $content = [
90 'posts' => $recent_content->to_array(),
91 ];
92 if ( $about_page ) {
93 $content['about_page'] = $about_page;
94 }
95 $parameters = new Content_Suggestion_Parameters(
96 $command->get_user(),
97 $command->get_language(),
98 $content,
99 $command->get_editor(),
100 );
101
102 try {
103 $sender = $this->ai_request_sender_factory->create( $command->get_user() );
104 $response = $sender->get_content_suggestions( $parameters );
105 } catch ( Insufficient_Scope_Exception $exception ) {
106
107 throw $exception;
108 } catch ( Forbidden_Exception $exception ) {
109 $this->consent_handler->revoke_consent( $command->get_user()->ID );
110 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
111 throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() );
112 }
113
114 return $this->build_response( $response, $recent_content );
115 }
116
117 /**
118 * Builds a response object bundling the content suggestions with the recent content used to generate them.
119 *
120 * @param Response $response The API response.
121 * @param Post_List $recent_content The recent content passed to the AI API.
122 *
123 * @return Content_Suggestion_Response The response containing suggestions and recent content.
124 */
125 public function build_response( Response $response, Post_List $recent_content ): Content_Suggestion_Response {
126 return new Content_Suggestion_Response(
127 $this->build_suggestions_array( $response ),
128 $recent_content,
129 );
130 }
131
132 /**
133 * Builds a list of content suggestions from the API response.
134 *
135 * @param Response $response The API response.
136 *
137 * @return Content_Suggestion_List The list of content suggestions.
138 */
139 public function build_suggestions_array( Response $response ): Content_Suggestion_List {
140 $content_suggestion_list = new Content_Suggestion_List();
141 $json = \json_decode( $response->get_body() );
142
143 if ( $json === null || ! isset( $json->choices ) ) {
144 return $content_suggestion_list;
145 }
146 foreach ( $json->choices as $suggestion ) {
147 $category = $this->category_repository->find_by_name( $suggestion->category->name );
148
149 $content_suggestion_list->add(
150 new Content_Suggestion(
151 $suggestion->title,
152 $suggestion->intent,
153 $suggestion->explanation,
154 $suggestion->keyphrase,
155 $suggestion->meta_description,
156 $category,
157 ),
158 );
159 }
160
161 return $content_suggestion_list;
162 }
163 }
164