PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.6
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.6
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 / user-interface / get-outline-route.php

get-outline-route.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 27.6, at src/ai/content-planner/user-interface/get-outline-route.php

207 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4 namespace Yoast\WP\SEO\AI\Content_Planner\User_Interface;
5
6 use RuntimeException;
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use Yoast\WP\SEO\AI\Content_Planner\Application\Content_Outline_Command;
10 use Yoast\WP\SEO\AI\Content_Planner\Application\Content_Outline_Command_Handler;
11 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Payment_Required_Exception;
12 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Remote_Request_Exception;
13 use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Too_Many_Requests_Exception;
14 use Yoast\WP\SEO\Conditionals\AI_Conditional;
15 use Yoast\WP\SEO\Main;
16 use Yoast\WP\SEO\Routes\Route_Interface;
17
18 /**
19 * Registers a route to get a content outline from the AI API.
20 *
21 * @internal This route powers the Yoast SEO admin UI's Content Planner feature. It is not part of the plugin's public REST API surface, requires the `edit_posts` capability (see {@see self::check_permissions()}), and may change at any time without notice.
22 *
23 * @makePublic
24 *
25 * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
26 */
27 class Get_Outline_Route implements Route_Interface {
28
29 /**
30 * The namespace for this route.
31 *
32 * @var string
33 */
34 public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE;
35
36 /**
37 * The prefix for this route.
38 *
39 * @var string
40 */
41 public const ROUTE_PREFIX = '/ai_content_planner/get_outline';
42
43 /**
44 * The command handler instance.
45 *
46 * @var Content_Outline_Command_Handler
47 */
48 private $command_handler;
49
50 /**
51 * Returns the conditionals based in which this loadable should be active.
52 *
53 * @return array<string> The conditionals.
54 */
55 public static function get_conditionals() {
56 return [ AI_Conditional::class ];
57 }
58
59 /**
60 * Class constructor.
61 *
62 * @param Content_Outline_Command_Handler $command_handler The command handler instance.
63 */
64 public function __construct( Content_Outline_Command_Handler $command_handler ) {
65 $this->command_handler = $command_handler;
66 }
67
68 /**
69 * Registers routes with WordPress.
70 *
71 * @return void
72 */
73 public function register_routes() {
74 \register_rest_route(
75 self::ROUTE_NAMESPACE,
76 self::ROUTE_PREFIX,
77 [
78 'methods' => 'POST',
79 'args' => [
80 'post_type' => [
81 'required' => true,
82 'type' => 'string',
83 'description' => 'The post type to get a content outline for.',
84 ],
85 'language' => [
86 'required' => true,
87 'type' => 'string',
88 'description' => 'The language the content is written in.',
89 ],
90 'editor' => [
91 'required' => true,
92 'type' => 'string',
93 'enum' => [
94 'classic',
95 'elementor',
96 'gutenberg',
97 ],
98 'description' => 'The current editor.',
99 ],
100 'title' => [
101 'required' => true,
102 'type' => 'string',
103 'description' => 'The title of the chosen content suggestion.',
104 ],
105 'intent' => [
106 'required' => true,
107 'type' => 'string',
108 'description' => 'The intent of the chosen content suggestion.',
109 ],
110 'explanation' => [
111 'required' => true,
112 'type' => 'string',
113 'description' => 'The explanation of the chosen content suggestion.',
114 ],
115 'keyphrase' => [
116 'required' => true,
117 'type' => 'string',
118 'description' => 'The keyphrase of the chosen content suggestion.',
119 ],
120 'meta_description' => [
121 'required' => true,
122 'type' => 'string',
123 'description' => 'The meta description of the chosen content suggestion.',
124 ],
125 'category' => [
126 'required' => true,
127 'type' => 'object',
128 'properties' => [
129 'name' => [
130 'type' => 'string',
131 'required' => true,
132 ],
133 'id' => [
134 'type' => 'integer',
135 'required' => true,
136 ],
137 ],
138 'description' => 'The category of the chosen content suggestion. Use name "" and id -1 to indicate no category.',
139 ],
140 ],
141 'callback' => [ $this, 'get_outline' ],
142 'permission_callback' => [ $this, 'check_permissions' ],
143 ],
144 );
145 }
146
147 /**
148 * Runs the callback to get an AI-generated content outline.
149 *
150 * @param WP_REST_Request $request The request object.
151 *
152 * @return WP_REST_Response The response of the get_outline action.
153 */
154 public function get_outline( WP_REST_Request $request ): WP_REST_Response {
155 try {
156 $user = \wp_get_current_user();
157
158 $category_param = $request->get_param( 'category' );
159
160 $command = new Content_Outline_Command(
161 $user,
162 $request->get_param( 'post_type' ),
163 $request->get_param( 'language' ),
164 $request->get_param( 'editor' ),
165 $request->get_param( 'title' ),
166 $request->get_param( 'intent' ),
167 $request->get_param( 'explanation' ),
168 $request->get_param( 'keyphrase' ),
169 $request->get_param( 'meta_description' ),
170 $category_param['name'],
171 (int) $category_param['id'],
172 );
173 $data = $this->command_handler->handle( $command );
174 } catch ( Remote_Request_Exception $e ) {
175 $message = [
176 'message' => $e->getMessage(),
177 'errorIdentifier' => $e->get_error_identifier(),
178 ];
179 if ( $e instanceof Payment_Required_Exception || $e instanceof Too_Many_Requests_Exception ) {
180 $message['missingLicenses'] = $e->get_missing_licenses();
181 }
182 return new WP_REST_Response(
183 $message,
184 $e->getCode(),
185 );
186 } catch ( RuntimeException $e ) {
187 return new WP_REST_Response( 'Failed to get content outline.', 500 );
188 }
189
190 return new WP_REST_Response( $data->to_array() );
191 }
192
193 /**
194 * Checks if the user is logged in and can edit posts.
195 *
196 * @return bool Whether the user is logged in and can edit posts.
197 */
198 public function check_permissions(): bool {
199 $user = \wp_get_current_user();
200 if ( $user === null || $user->ID < 1 ) {
201 return false;
202 }
203
204 return \user_can( $user, 'edit_posts' );
205 }
206 }
207