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/user-interface/get-outline-route.php +41 -5 27.8 → trunk View file →
@@ -17,9 +17,9 @@
17 17
18 18 /**
19 19 * Registers a route to get a content outline from the AI API.
20 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.
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 capability to edit posts of the requested post type (see {@see self::check_permissions()}), and may change at any time without notice.
22 22 *
23 23 * @makePublic
24 24 *
25 25 * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded
@@ -136,8 +136,30 @@
136 136 ],
137 137 ],
138 138 'description' => 'The category of the chosen content suggestion. Use name "" and id -1 to indicate no category.',
139 139 ],
140 + 'recent_content' => [
141 + 'required' => true,
142 + 'type' => 'array',
143 + 'maxItems' => 100,
144 + 'items' => [
145 + 'type' => 'object',
146 + 'properties' => [
147 + 'title' => [
148 + 'type' => 'string',
149 + 'required' => true,
150 + 'maxLength' => 500,
151 + ],
152 + 'description' => [
153 + 'type' => 'string',
154 + 'required' => true,
155 + 'maxLength' => 1000,
156 + ],
157 + ],
158 + 'additionalProperties' => false,
159 + ],
160 + 'description' => 'The recent content returned by the get_suggestions response.',
161 + ],
140 162 ],
141 163 'callback' => [ $this, 'get_outline' ],
142 164 'permission_callback' => [ $this, 'check_permissions' ],
143 165 ],
@@ -168,8 +190,9 @@
168 190 $request->get_param( 'keyphrase' ),
169 191 $request->get_param( 'meta_description' ),
170 192 $category_param['name'],
171 193 (int) $category_param['id'],
194 + $request->get_param( 'recent_content' ),
172 195 );
173 196 $data = $this->command_handler->handle( $command );
174 197 } catch ( Remote_Request_Exception $e ) {
175 198 $message = [
@@ -190,17 +213,30 @@
190 213 return new WP_REST_Response( $data->to_array() );
191 214 }
192 215
193 216 /**
194 - * Checks if the user is logged in and can edit posts.
217 + * Checks if the user is logged in and can edit posts of the requested post type.
195 218 *
196 - * @return bool Whether the user is logged in and can edit posts.
219 + * The requested post_type is caller-controlled, so the permission must be evaluated
220 + * against that post type's own edit_posts meta-capability — not the generic
221 + * 'edit_posts' string, which would let a user with edit_posts but no edit_pages
222 + * (e.g. an Author) trigger outline generation for pages or arbitrary CPTs.
223 + *
224 + * @param WP_REST_Request $request The request object.
225 + *
226 + * @return bool Whether the user can edit posts of the requested post type.
197 227 */
198 - public function check_permissions(): bool {
228 + public function check_permissions( WP_REST_Request $request ): bool {
199 229 $user = \wp_get_current_user();
200 230 if ( $user === null || $user->ID < 1 ) {
201 231 return false;
202 232 }
203 233
204 - return \user_can( $user, 'edit_posts' );
234 + $post_type = $request->get_param( 'post_type' );
235 + $post_type_object = \get_post_type_object( $post_type );
236 + if ( $post_type_object === null ) {
237 + return false;
238 + }
239 +
240 + return \user_can( $user, $post_type_object->cap->edit_posts );
205 241 }
206 242 }