• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 5108940f13a246d7f86a2cfa059551ab77d027dd

09 Apr 2026 01:38PM UTC coverage: 52.827% (-0.7%) from 53.559%
5108940f13a246d7f86a2cfa059551ab77d027dd

push

github

web-flow
Merge pull request #23125 from Yoast/1131-next-post-create-endpoint-for-sending-the-1st-request-to-ai-api-for-generating-post-suggestions

1131 next post create endpoint for sending the 1st request to ai api for generating post suggestions

8680 of 16250 branches covered (53.42%)

Branch coverage included in aggregate %.

4 of 227 new or added lines in 11 files covered. (1.76%)

34 existing lines in 3 files now uncovered.

34034 of 64607 relevant lines covered (52.68%)

46559.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/ai/content-planner/application/content-suggestion-command-handler.php
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\Authorization\Application\Token_Manager;
7
use Yoast\WP\SEO\AI\Consent\Application\Consent_Handler;
8
use Yoast\WP\SEO\AI\Content_Planner\Domain\Category;
9
use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion;
10
use Yoast\WP\SEO\AI\Content_Planner\Domain\Content_Suggestion_List;
11
use Yoast\WP\SEO\AI\Content_Planner\Infrastructure\Recent_Content\Recent_Content_Collector;
12
use Yoast\WP\SEO\AI\HTTP_Request\Application\Request_Handler;
13
use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Forbidden_Exception;
14
use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Unauthorized_Exception;
15
use Yoast\WP\SEO\AI\HTTP_Request\Domain\Request;
16
use Yoast\WP\SEO\AI\HTTP_Request\Domain\Response;
17

18
/**
19
 * Handles the content suggestion command.
20
 */
21
class Content_Suggestion_Command_Handler {
22

23
        /**
24
         * The recent content collector.
25
         *
26
         * @var Recent_Content_Collector
27
         */
28
        private $recent_content_collector;
29

30
        /**
31
         * The token manager.
32
         *
33
         * @var Token_Manager
34
         */
35
        private $token_manager;
36

37
        /**
38
         * The request handler.
39
         *
40
         * @var Request_Handler
41
         */
42
        private $request_handler;
43

44
        /**
45
         * The consent handler.
46
         *
47
         * @var Consent_Handler
48
         */
49
        private $consent_handler;
50

51
        /**
52
         * The constructor.
53
         *
54
         * @param Recent_Content_Collector $recent_content_collector The recent content collector.
55
         * @param Token_Manager            $token_manager            The token manager.
56
         * @param Request_Handler          $request_handler          The request handler.
57
         * @param Consent_Handler          $consent_handler          The consent handler.
58
         */
NEW
59
        public function __construct(
×
60
                Recent_Content_Collector $recent_content_collector,
61
                Token_Manager $token_manager,
62
                Request_Handler $request_handler,
63
                Consent_Handler $consent_handler
64
        ) {
NEW
65
                $this->recent_content_collector = $recent_content_collector;
×
NEW
66
                $this->token_manager            = $token_manager;
×
NEW
67
                $this->request_handler          = $request_handler;
×
NEW
68
                $this->consent_handler          = $consent_handler;
×
69
        }
70

71
        /**
72
         * Handles the content suggestion command by collecting recent content and requesting suggestions from the AI API.
73
         *
74
         * @param Content_Suggestion_Command $command               The content suggestion command.
75
         * @param bool                       $retry_on_unauthorized Whether to retry on unauthorized response.
76
         *
77
         * @throws Unauthorized_Exception When the API returns an unauthorized response and retry is exhausted.
78
         * @throws Forbidden_Exception    When consent has been revoked.
79
         *
80
         * @return Content_Suggestion_List A list of content suggestions.
81
         */
NEW
82
        public function handle(
×
83
                Content_Suggestion_Command $command,
84
                bool $retry_on_unauthorized = true
85
        ): Content_Suggestion_List {
NEW
86
                $recent_content = $this->recent_content_collector->collect( $command->get_post_type() );
×
NEW
87
                $about_page     = $this->recent_content_collector->collect_about_page( $command->get_post_type() );
×
NEW
88
                $token          = $this->token_manager->get_or_request_access_token( $command->get_user() );
×
NEW
89
                $recent_content = $recent_content->to_array();
×
90

NEW
91
                $content = [
×
NEW
92
                        'posts' => $recent_content,
×
NEW
93
                ];
×
NEW
94
                if ( $about_page ) {
×
NEW
95
                        $content['about_page'] = $about_page;
×
96
                }
NEW
97
                $request_body = [
×
NEW
98
                        'subject' => [
×
NEW
99
                                'language' => $command->get_language(),
×
NEW
100
                                'content'  => $content,
×
NEW
101
                        ],
×
NEW
102
                ];
×
103

NEW
104
                $request_headers = [
×
NEW
105
                        'Authorization' => "Bearer $token",
×
NEW
106
                        'X-Yst-Cohort'  => $command->get_editor(),
×
NEW
107
                ];
×
108

109
                try {
110

NEW
111
                        $response = $this->request_handler->handle( new Request( '/content-planner/next-post-suggestions', $request_body, $request_headers ) );
×
NEW
112
                } catch ( Unauthorized_Exception $exception ) {
×
113
                        // Delete the stored JWT tokens, as they appear to be no longer valid.
NEW
114
                        $this->token_manager->clear_tokens( $command->get_user() );
×
115

NEW
116
                        if ( ! $retry_on_unauthorized ) {
×
NEW
117
                                throw $exception;
×
118
                        }
119

120
                        // Try again once more by fetching a new set of tokens and trying the suggestions endpoint again.
NEW
121
                        return $this->handle( $command, false );
×
NEW
122
                } catch ( Forbidden_Exception $exception ) {
×
123
                        // Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
124
                        // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
NEW
125
                        $this->consent_handler->revoke_consent( $command->get_user()->ID );
×
NEW
126
                        throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() );
×
127
                        // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
128
                }
129

NEW
130
                return $this->build_suggestions_array( $response );
×
131
        }
132

133
        /**
134
         * Builds a list of content suggestions from the API response.
135
         *
136
         * @param Response $response The API response.
137
         *
138
         * @return Content_Suggestion_List The list of content suggestions.
139
         */
NEW
140
        public function build_suggestions_array( Response $response ): Content_Suggestion_List {
×
NEW
141
                $content_suggestion_list = new Content_Suggestion_List();
×
NEW
142
                $json                    = \json_decode( $response->get_body() );
×
143

NEW
144
                if ( $json === null || ! isset( $json->choices ) ) {
×
NEW
145
                        return $content_suggestion_list;
×
146
                }
NEW
147
                foreach ( $json->choices as $suggestion ) {
×
148

NEW
149
                        $content_suggestion_list->add(
×
NEW
150
                                new Content_Suggestion(
×
NEW
151
                                        $suggestion->title,
×
NEW
152
                                        $suggestion->intent,
×
NEW
153
                                        $suggestion->explanation,
×
NEW
154
                                        $suggestion->keyphrase,
×
NEW
155
                                        $suggestion->meta_description,
×
NEW
156
                                        ( isset( $suggestion->category->title ) ? new Category( $suggestion->category->title, $suggestion->category->id ) : null ),
×
NEW
157
                                ),
×
NEW
158
                        );
×
159
                }
160

NEW
161
                return $content_suggestion_list;
×
162
        }
163
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc