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 | inc/class-wpseo-meta.php +88 -7 27.5 → trunk View file →
@@ -98,16 +98,20 @@
98 98 * i.e. an array or object, defaults to false.
99 99 * Currently only used by add-on plugins.
100 100 */
101 101 public static $meta_fields = [
102 - 'general' => [
102 + 'general' => [
103 103 'focuskw' => [
104 - 'type' => 'hidden',
105 - 'title' => '',
104 + 'type' => 'hidden',
105 + 'title' => '',
106 + 'show_in_rest' => true,
107 + 'single' => true,
106 108 ],
107 109 'title' => [
108 110 'type' => 'hidden',
109 111 'default_value' => '',
112 + 'show_in_rest' => true,
113 + 'single' => true,
110 114 ],
111 115 'metadesc' => [
112 116 'type' => 'hidden',
113 117 'default_value' => '',
@@ -112,8 +116,10 @@
112 116 'type' => 'hidden',
113 117 'default_value' => '',
114 118 'class' => 'metadesc',
115 119 'rows' => 2,
120 + 'show_in_rest' => true,
121 + 'single' => true,
116 122 ],
117 123 'linkdex' => [
118 124 'type' => 'hidden',
119 125 'default_value' => '0',
@@ -125,14 +131,22 @@
125 131 'inclusive_language_score' => [
126 132 'type' => 'hidden',
127 133 'default_value' => '0',
128 134 ],
135 + 'seo_title_score' => [
136 + 'type' => 'hidden',
137 + 'default_value' => '0',
138 + ],
139 + 'meta_description_score' => [
140 + 'type' => 'hidden',
141 + 'default_value' => '0',
142 + ],
129 143 'is_cornerstone' => [
130 144 'type' => 'hidden',
131 145 'default_value' => 'false',
132 146 ],
133 147 ],
134 - 'advanced' => [
148 + 'advanced' => [
135 149 'meta-robots-noindex' => [
136 150 'type' => 'hidden',
137 151 'default_value' => '0', // = post-type default.
138 152 'options' => [
@@ -170,10 +184,10 @@
170 184 'type' => 'url',
171 185 'default_value' => '',
172 186 ],
173 187 ],
174 - 'social' => [],
175 - 'schema' => [
188 + 'social' => [],
189 + 'schema' => [
176 190 'schema_page_type' => [
177 191 'type' => 'hidden',
178 192 'options' => Schema_Types::PAGE_TYPES,
179 193 ],
@@ -183,14 +197,24 @@
183 197 'options' => Schema_Types::ARTICLE_TYPES,
184 198 ],
185 199 ],
186 200 /* Fields we should validate & save, but not show on any form. */
187 - 'non_form' => [
201 + 'non_form' => [
188 202 'linkdex' => [
189 203 'type' => null,
190 204 'default_value' => '0',
191 205 ],
192 206 ],
207 + 'content_planner' => [
208 + 'is_content_planner_banner_rendered' => [
209 + 'type' => 'hidden',
210 + 'default_value' => '0',
211 + ],
212 + 'is_content_planner_banner_dismissed' => [
213 + 'type' => 'hidden',
214 + 'default_value' => '0',
215 + ],
216 + ],
193 217 ];
194 218
195 219 /**
196 220 * Helper property - reverse index of the definition array.
@@ -263,8 +287,9 @@
263 287
264 288 foreach ( self::$meta_fields as $subset => $field_group ) {
265 289 foreach ( $field_group as $key => $field_def ) {
266 290
291 + // Register for all post types: sanitise callback only, REST disabled.
267 292 register_meta(
268 293 'post',
269 294 self::$meta_prefix . $key,
270 295 [ 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ] ],
@@ -269,8 +294,26 @@
269 294 self::$meta_prefix . $key,
270 295 [ 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ] ],
271 296 );
272 297
298 + // Re-register for the 'post' subtype with REST exposure and auth callback when show_in_rest is enabled.
299 + if ( ! empty( $field_def['show_in_rest'] ) ) {
300 + register_meta(
301 + 'post',
302 + self::$meta_prefix . $key,
303 + [
304 + 'show_in_rest' => true,
305 + 'single' => ( $field_def['single'] ?? false ),
306 + 'type' => 'string',
307 + 'object_subtype' => 'post',
308 + 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ],
309 + 'auth_callback' => static function ( $allowed, $meta_key, $object_id ) {
310 + return current_user_can( 'edit_post', $object_id );
311 + },
312 + ],
313 + );
314 + }
315 +
273 316 // Set the $fields_index property for efficiency.
274 317 self::$fields_index[ self::$meta_prefix . $key ] = [
275 318 'subset' => $subset,
276 319 'key' => $key,
@@ -287,8 +330,14 @@
287 330 }
288 331 }
289 332 unset( $subset, $field_group, $key, $field_def );
290 333
334 + // Strip meta fields that have show_in_rest enabled from REST responses for users
335 + // without edit_post capability. register_meta's auth_callback only covers writes,
336 + // so read access must be restricted separately via this filter.
337 + // Register only for 'post' post type. Other post types don't expose these fields.
338 + add_filter( 'rest_prepare_post', [ self::class, 'hide_meta_from_unauthorized_rest_response' ], 10, 2 );
339 +
291 340 self::filter_schema_article_types();
292 341
293 342 add_filter( 'update_post_metadata', [ self::class, 'remove_meta_if_default' ], 10, 5 );
294 343 add_filter( 'add_post_metadata', [ self::class, 'dont_save_meta_if_default' ], 10, 4 );
@@ -401,8 +450,16 @@
401 450 $clean = (string) $int; // Convert to string to make sure default check works.
402 451 }
403 452 break;
404 453
454 + case ( in_array( $meta_key, [ self::$meta_prefix . 'seo_title_score', self::$meta_prefix . 'meta_description_score' ], true ) ):
455 + // Per-field scores are 0-100 percentages; out-of-range input keeps the "never scored" default.
456 + $int = WPSEO_Utils::validate_int( $meta_value );
457 + if ( $int !== false && $int >= 0 && $int <= 100 ) {
458 + $clean = (string) $int; // Convert to string to make sure default check works.
459 + }
460 + break;
461 +
405 462 case ( $field_def['type'] === 'checkbox' ):
406 463 // Only allow value if it's one of the predefined options.
407 464 if ( in_array( $meta_value, [ 'on', 'off' ], true ) ) {
408 465 $clean = $meta_value;
@@ -1030,8 +1087,32 @@
1030 1087 $post_types = [];
1031 1088 }
1032 1089
1033 1090 return $post_types;
1091 + }
1092 +
1093 + /**
1094 + * Strips REST-exposed Yoast meta fields from the response for users without edit_post capability on the post.
1095 + *
1096 + * @param WP_REST_Response $response The REST response.
1097 + * @param WP_Post $post The post object.
1098 + *
1099 + * @return WP_REST_Response The (possibly modified) response.
1100 + */
1101 + public static function hide_meta_from_unauthorized_rest_response( $response, $post ) {
1102 + if ( current_user_can( 'edit_post', $post->ID ) ) {
1103 + return $response;
1104 + }
1105 + $data = $response->get_data();
1106 + foreach ( self::$meta_fields as $field_group ) {
1107 + foreach ( $field_group as $key => $field_def ) {
1108 + if ( ! empty( $field_def['show_in_rest'] ) ) {
1109 + unset( $data['meta'][ self::$meta_prefix . $key ] );
1110 + }
1111 + }
1112 + }
1113 + $response->set_data( $data );
1114 + return $response;
1034 1115 }
1035 1116
1036 1117 /**
1037 1118 * Filter the schema article types.