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

Yoast / wordpress-seo / ee4677e4cd0be805d043f54630b0a49c7217abaf

pending completion
ee4677e4cd0be805d043f54630b0a49c7217abaf

Pull #23154

github

web-flow
Merge 8e35118d2 into a6e5bd6c9
Pull Request #23154: 1132 create endpoint for sending the 2nd request to ai api

8680 of 16250 branches covered (53.42%)

Branch coverage included in aggregate %.

213 of 218 new or added lines in 6 files covered. (97.71%)

24 existing lines in 1 file now uncovered.

34356 of 64872 relevant lines covered (52.96%)

46369.59 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
         */
UNCOV
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
        ) {
65
                $this->recent_content_collector = $recent_content_collector;
×
66
                $this->token_manager            = $token_manager;
×
UNCOV
67
                $this->request_handler          = $request_handler;
×
UNCOV
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
         */
UNCOV
82
        public function handle(
×
83
                Content_Suggestion_Command $command,
84
                bool $retry_on_unauthorized = true
85
        ): Content_Suggestion_List {
UNCOV
86
                $recent_content = $this->recent_content_collector->collect( $command->get_post_type() );
×
UNCOV
87
                $about_page     = $this->recent_content_collector->collect_about_page( $command->get_post_type() );
×
UNCOV
88
                $token          = $this->token_manager->get_or_request_access_token( $command->get_user() );
×
UNCOV
89
                $recent_content = $recent_content->to_array();
×
90

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

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

109
                try {
110

111
                        $response = $this->request_handler->handle( new Request( '/content-planner/next-post-suggestions', $request_body, $request_headers ) );
×
UNCOV
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( (string) $command->get_user()->ID );
×
115

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

120
                        // Try again once more by fetching a new set of tokens and trying the suggestions endpoint again.
121
                        return $this->handle( $command, false );
×
UNCOV
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.
125
                        $this->consent_handler->revoke_consent( $command->get_user()->ID );
×
126
                        throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() );
×
127
                        // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
128
                }
129

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
         */
UNCOV
140
        public function build_suggestions_array( Response $response ): Content_Suggestion_List {
×
UNCOV
141
                $content_suggestion_list = new Content_Suggestion_List();
×
UNCOV
142
                $json                    = \json_decode( $response->get_body() );
×
143

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

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

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