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

Yoast / wordpress-seo / e57e01309ec9fc3afc714e6ce0fd243e7b6d1f90

27 May 2025 12:40PM UTC coverage: 45.733%. First build
e57e01309ec9fc3afc714e6ce0fd243e7b6d1f90

Pull #22275

github

web-flow
Merge f0a8d33c9 into 5f64dc203
Pull Request #22275: Move and refactor ai generator rest endpoints

0 of 392 new or added lines in 26 files covered. (0.0%)

15548 of 33997 relevant lines covered (45.73%)

3.64 hits per line

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

0.0
/src/ai-generator/application/suggestions-provider.php
1
<?php
2

3
namespace Yoast\WP\SEO\AI_Generator\Application;
4

5
use RuntimeException;
6
use WP_User;
7
use Yoast\WP\SEO\AI_Authorization\Application\Token_Manager;
8
use Yoast\WP\SEO\AI_Consent\Application\Consent_Handler;
9
use Yoast\WP\SEO\AI_Generator\Domain\Suggestion;
10
use Yoast\WP\SEO\AI_Generator\Domain\Suggestions_Bucket;
11
use Yoast\WP\SEO\AI_HTTP_Request\Application\Request_Handler;
12
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Bad_Request_Exception;
13
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Forbidden_Exception;
14
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Internal_Server_Error_Exception;
15
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Not_Found_Exception;
16
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Payment_Required_Exception;
17
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Request_Timeout_Exception;
18
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Service_Unavailable_Exception;
19
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Too_Many_Requests_Exception;
20
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Exceptions\Unauthorized_Exception;
21
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Request;
22
use Yoast\WP\SEO\AI_HTTP_Request\Domain\Response;
23
use Yoast\WP\SEO\Helpers\User_Helper;
24

25
/**
26
 * The class that handles the suggestions from the AI API.
27
 */
28
class Suggestions_Provider {
29

30
        /**
31
         * The consent handler instance.
32
         *
33
         * @var Consent_Handler
34
         */
35
        private $consent_handler;
36

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

44
        /**
45
         * The token manager instance.
46
         *
47
         * @var Token_Manager
48
         */
49
        private $token_manager;
50

51
        /**
52
         * The user helper instance.
53
         *
54
         * @var User_Helper
55
         */
56
        private $user_helper;
57

58
        /**
59
         * Class constructor.
60
         *
61
         * @param Consent_Handler $consent_handler The consent handler instance.
62
         * @param Request_Handler $request_handler The request handler instance.
63
         * @param Token_Manager   $token_manager   The token manager instance.
64
         * @param User_Helper     $user_helper     The user helper instance.
65
         */
NEW
66
        public function __construct(
×
67
                Consent_Handler $consent_handler,
68
                Request_Handler $request_handler,
69
                Token_Manager $token_manager,
70
                User_Helper $user_helper
71
        ) {
NEW
72
                $this->consent_handler = $consent_handler;
×
NEW
73
                $this->request_handler = $request_handler;
×
NEW
74
                $this->token_manager   = $token_manager;
×
NEW
75
                $this->user_helper     = $user_helper;
×
76
        }
77

78
        // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't take into account exceptions thrown in called methods.
79

80
        /**
81
         * Method used to generate suggestions through AI.
82
         *
83
         * @param WP_User $user                  The WP user.
84
         * @param string  $suggestion_type       The type of the requested suggestion.
85
         * @param string  $prompt_content        The excerpt taken from the post.
86
         * @param string  $focus_keyphrase       The focus keyphrase associated to the post.
87
         * @param string  $language              The language of the post.
88
         * @param string  $platform              The platform the post is intended for.
89
         * @param string  $editor                The current editor.
90
         * @param bool    $retry_on_unauthorized Whether to retry when unauthorized (mechanism to retry once).
91
         *
92
         * @throws Bad_Request_Exception Bad_Request_Exception.
93
         * @throws Forbidden_Exception Forbidden_Exception.
94
         * @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
95
         * @throws Not_Found_Exception Not_Found_Exception.
96
         * @throws Payment_Required_Exception Payment_Required_Exception.
97
         * @throws Request_Timeout_Exception Request_Timeout_Exception.
98
         * @throws Service_Unavailable_Exception Service_Unavailable_Exception.
99
         * @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
100
         * @throws Unauthorized_Exception Unauthorized_Exception.
101
         * @throws RuntimeException Unable to retrieve the access token.
102
         * @return string[] The suggestions.
103
         */
NEW
104
        public function get_suggestions(
×
105
                WP_User $user,
106
                string $suggestion_type,
107
                string $prompt_content,
108
                string $focus_keyphrase,
109
                string $language,
110
                string $platform,
111
                string $editor,
112
                bool $retry_on_unauthorized = true
113
        ): array {
NEW
114
                $token = $this->token_manager->get_or_request_access_token( $user );
×
115

NEW
116
                $request_body    = [
×
NEW
117
                        'service' => 'openai',
×
NEW
118
                        'user_id' => (string) $user->ID,
×
NEW
119
                        'subject' => [
×
NEW
120
                                'content'         => $prompt_content,
×
NEW
121
                                'focus_keyphrase' => $focus_keyphrase,
×
NEW
122
                                'language'        => $language,
×
NEW
123
                                'platform'        => $platform,
×
NEW
124
                        ],
×
NEW
125
                ];
×
NEW
126
                $request_headers = [
×
NEW
127
                        'Authorization' => "Bearer $token",
×
NEW
128
                        'X-Yst-Cohort'  => $editor,
×
NEW
129
                ];
×
130

131
                try {
NEW
132
                        $response = $this->request_handler->handle( new Request( "/openai/suggestions/$suggestion_type", $request_body, $request_headers ) );
×
NEW
133
                } catch ( Unauthorized_Exception $exception ) {
×
134
                        // Delete the stored JWT tokens, as they appear to be no longer valid.
NEW
135
                        $this->user_helper->delete_meta( $user->ID, '_yoast_wpseo_ai_generator_access_jwt' );
×
NEW
136
                        $this->user_helper->delete_meta( $user->ID, '_yoast_wpseo_ai_generator_refresh_jwt' );
×
137

NEW
138
                        if ( ! $retry_on_unauthorized ) {
×
NEW
139
                                throw $exception;
×
140
                        }
141

142
                        // Try again once more by fetching a new set of tokens and trying the suggestions endpoint again.
NEW
143
                        return $this->get_suggestions( $user, $suggestion_type, $prompt_content, $focus_keyphrase, $language, $platform, $editor, false );
×
NEW
144
                } catch ( Forbidden_Exception $exception ) {
×
145
                        // Follow the API in the consent being revoked (Use case: user sent an e-mail to revoke?).
146
                        // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- false positive.
NEW
147
                        $this->consent_handler->revoke_consent( $user->ID );
×
NEW
148
                        throw new Forbidden_Exception( 'CONSENT_REVOKED', $exception->getCode() );
×
149
                        // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
150
                }
151

NEW
152
                return $this->build_suggestions_array( $response )->to_array();
×
153
        }
154

155
        // phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
156

157
        /**
158
         * Generates the list of 5 suggestions to return.
159
         *
160
         * @param Response $response The response from the API.
161
         *
162
         * @return Suggestions_Bucket The array of suggestions.
163
         */
NEW
164
        public function build_suggestions_array( Response $response ): Suggestions_Bucket {
×
NEW
165
                $suggestions_bucket = new Suggestions_Bucket();
×
NEW
166
                $json               = \json_decode( $response->get_body() );
×
NEW
167
                if ( $json === null || ! isset( $json->choices ) ) {
×
NEW
168
                        return $suggestions_bucket;
×
169
                }
NEW
170
                foreach ( $json->choices as $suggestion ) {
×
NEW
171
                        $suggestions_bucket->add_suggestion( new Suggestion( $suggestion->text ) );
×
172
                }
173

NEW
174
                return $suggestions_bucket;
×
175
        }
176
}
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

© 2025 Coveralls, Inc