PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
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
← All changes | src/ai/content-planner/application/content-suggestion-command-handler.php +37 -72 28.0 → trunk View file →
@@ -2,19 +2,19 @@
2 2 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
3 3
4 4 namespace Yoast\WP\SEO\AI\Content_Planner\Application;
5 5
6 -use Yoast\WP\SEO\AI\Authorization\Application\Token_Manager;
6 +use Yoast\WP\SEO\AI\Authentication\Application\AI_Request_Sender_Factory;
7 7 use Yoast\WP\SEO\AI\Consent\Application\Consent_Handler;
8 8 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion;
9 9 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_List;
10 +use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_Parameters;
10 11 use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_Response;
11 12 use Yoast\WP\SEO\AI\Content_Planner\Domain\Post_List;
12 13 use Yoast\WP\SEO\AI\Content_Planner\Infrastructure\Recent_Content\Recent_Content_Collector;
13 -use Yoast\WP\SEO\AI\HTTP_Request\Application\Request_Handler;
14 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;
15 16 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Unauthorized_Exception;
16 -use Yoast\WP\SEO\AI\HTTP_Request\Domain\Request;
17 17 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Response;
18 18
19 19 /**
20 20 * Handles the content suggestion command.
@@ -28,22 +28,15 @@
28 28 */
29 29 private $recent_content_collector;
30 30
31 31 /**
32 - * The token manager.
32 + * The auth strategy factory.
33 33 *
34 - * @var Token_Manager
34 + * @var AI_Request_Sender_Factory
35 35 */
36 - private $token_manager;
36 + private $ai_request_sender_factory;
37 37
38 38 /**
39 - * The request handler.
40 - *
41 - * @var Request_Handler
42 - */
43 - private $request_handler;
44 -
45 - /**
46 39 * The consent handler.
47 40 *
48 41 * @var Consent_Handler
49 42 */
@@ -58,96 +51,68 @@
58 51
59 52 /**
60 53 * The constructor.
61 54 *
62 - * @param Recent_Content_Collector $recent_content_collector The recent content collector.
63 - * @param Token_Manager $token_manager The token manager.
64 - * @param Request_Handler $request_handler The request handler.
65 - * @param Consent_Handler $consent_handler The consent handler.
66 - * @param Category_Repository_Interface $category_repository The category repository.
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.
67 59 */
68 60 public function __construct(
69 61 Recent_Content_Collector $recent_content_collector,
70 - Token_Manager $token_manager,
71 - Request_Handler $request_handler,
62 + AI_Request_Sender_Factory $ai_request_sender_factory,
72 63 Consent_Handler $consent_handler,
73 64 Category_Repository_Interface $category_repository
74 65 ) {
75 - $this->recent_content_collector = $recent_content_collector;
76 - $this->token_manager = $token_manager;
77 - $this->request_handler = $request_handler;
78 - $this->consent_handler = $consent_handler;
79 - $this->category_repository = $category_repository;
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;
80 70 }
81 71
72 + // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't track exceptions thrown by called services.
73 +
82 74 /**
83 75 * Handles the content suggestion command by collecting recent content and requesting suggestions from the AI API.
84 76 *
85 - * @param Content_Suggestion_Command $command The content suggestion command.
86 - * @param bool $retry_on_unauthorized Whether to retry on unauthorized response.
77 + * @param Content_Suggestion_Command $command The content suggestion command.
87 78 *
88 - * @throws Unauthorized_Exception When the API returns an unauthorized response and retry is exhausted.
89 - * @throws Forbidden_Exception When consent has been revoked.
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.
90 82 *
91 83 * @return Content_Suggestion_Response The response containing suggestions and recent content.
92 84 */
93 - public function handle(
94 - Content_Suggestion_Command $command,
95 - bool $retry_on_unauthorized = true
96 - ): Content_Suggestion_Response {
97 - $post_list = $this->recent_content_collector->collect( $command->get_post_type() );
98 - $about_page = $this->recent_content_collector->collect_about_page( $command->get_post_type() );
99 - try {
100 - $token = $this->token_manager->get_or_request_access_token( $command->get_user() );
101 - } catch ( Forbidden_Exception $exception ) {
102 - // Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
103 - // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
104 - $this->consent_handler->revoke_consent( $command->get_user()->ID );
105 - throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() );
106 - // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
107 - }
108 - $recent_content = $post_list->to_array();
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() );
109 88
110 89 $content = [
111 - 'posts' => $recent_content,
90 + 'posts' => $recent_content->to_array(),
112 91 ];
113 92 if ( $about_page ) {
114 93 $content['about_page'] = $about_page;
115 94 }
116 - $request_body = [
117 - 'subject' => [
118 - 'language' => $command->get_language(),
119 - 'content' => $content,
120 - ],
121 - ];
95 + $parameters = new Content_Suggestion_Parameters(
96 + $command->get_user(),
97 + $command->get_language(),
98 + $content,
99 + $command->get_editor(),
100 + );
122 101
123 - $request_headers = [
124 - 'Authorization' => "Bearer $token",
125 - 'X-Yst-Cohort' => $command->get_editor(),
126 - ];
127 -
128 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 ) {
129 106
130 - $response = $this->request_handler->handle( new Request( '/content-planner/next-post-suggestions', $request_body, $request_headers ) );
131 - } catch ( Unauthorized_Exception $exception ) {
132 - // Delete the stored JWT tokens, as they appear to be no longer valid.
133 - $this->token_manager->clear_tokens( (string) $command->get_user()->ID );
134 -
135 - if ( ! $retry_on_unauthorized ) {
136 - throw $exception;
137 - }
138 -
139 - // Try again once more by fetching a new set of tokens and trying the suggestions endpoint again.
140 - return $this->handle( $command, false );
107 + throw $exception;
141 108 } catch ( Forbidden_Exception $exception ) {
142 - // Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
143 - // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
144 109 $this->consent_handler->revoke_consent( $command->get_user()->ID );
110 + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
145 111 throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() );
146 - // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
147 112 }
148 113
149 - return $this->build_response( $response, $post_list );
114 + return $this->build_response( $response, $recent_content );
150 115 }
151 116
152 117 /**
153 118 * Builds a response object bundling the content suggestions with the recent content used to generate them.