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 +73 -2 27.6 → trunk View file →
@@ -100,14 +100,18 @@
100 100 */
101 101 public static $meta_fields = [
102 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,8 +131,16 @@
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 ],
@@ -273,8 +287,9 @@
273 287
274 288 foreach ( self::$meta_fields as $subset => $field_group ) {
275 289 foreach ( $field_group as $key => $field_def ) {
276 290
291 + // Register for all post types: sanitise callback only, REST disabled.
277 292 register_meta(
278 293 'post',
279 294 self::$meta_prefix . $key,
280 295 [ 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ] ],
@@ -279,8 +294,26 @@
279 294 self::$meta_prefix . $key,
280 295 [ 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ] ],
281 296 );
282 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 +
283 316 // Set the $fields_index property for efficiency.
284 317 self::$fields_index[ self::$meta_prefix . $key ] = [
285 318 'subset' => $subset,
286 319 'key' => $key,
@@ -297,8 +330,14 @@
297 330 }
298 331 }
299 332 unset( $subset, $field_group, $key, $field_def );
300 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 +
301 340 self::filter_schema_article_types();
302 341
303 342 add_filter( 'update_post_metadata', [ self::class, 'remove_meta_if_default' ], 10, 5 );
304 343 add_filter( 'add_post_metadata', [ self::class, 'dont_save_meta_if_default' ], 10, 4 );
@@ -411,8 +450,16 @@
411 450 $clean = (string) $int; // Convert to string to make sure default check works.
412 451 }
413 452 break;
414 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 +
415 462 case ( $field_def['type'] === 'checkbox' ):
416 463 // Only allow value if it's one of the predefined options.
417 464 if ( in_array( $meta_value, [ 'on', 'off' ], true ) ) {
418 465 $clean = $meta_value;
@@ -1040,8 +1087,32 @@
1040 1087 $post_types = [];
1041 1088 }
1042 1089
1043 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;
1044 1115 }
1045 1116
1046 1117 /**
1047 1118 * Filter the schema article types.