| @@ -76,9 +76,8 @@ | ||
| 76 | 76 | * @var array |
| 77 | 77 | * Array format: |
| 78 | 78 | * (required) 'type' => (string) field type. i.e. text / textarea / checkbox / |
| 79 | 79 | * radio / select / multiselect / upload etc. |
| 80 | - * (required) 'title' => (string) table row title. | |
| 81 | 80 | * (recommended) 'default_value' => (string|array) default value for the field. |
| 82 | 81 | * IMPORTANT: |
| 83 | 82 | * - if the field has options, the default has to be the |
| 84 | 83 | * key of one of the options. |
| @@ -93,132 +92,128 @@ | ||
| 93 | 92 | * value = (string) text label for the option. |
| 94 | 93 | * (optional) 'autocomplete' => (bool) whether autocomplete is on for text fields, |
| 95 | 94 | * defaults to true. |
| 96 | 95 | * (optional) 'class' => (string) classname(s) to add to the actual <input> tag. |
| 97 | - * (optional) 'description' => (string) description to show underneath the field. | |
| 98 | - * (optional) 'expl' => (string) label for a checkbox. | |
| 99 | - * (optional) 'help' => (string) help text to show on mouse over ? image. | |
| 100 | 96 | * (optional) 'rows' => (int) number of rows for a textarea, defaults to 3. |
| 101 | - * (optional) 'placeholder' => (string) Currently only used by add-on plugins. | |
| 102 | 97 | * (optional) 'serialized' => (bool) whether the value is expected to be serialized, |
| 103 | 98 | * i.e. an array or object, defaults to false. |
| 104 | 99 | * Currently only used by add-on plugins. |
| 105 | 100 | */ |
| 106 | 101 | public static $meta_fields = [ |
| 107 | - 'general' => [ | |
| 102 | + 'general' => [ | |
| 108 | 103 | 'focuskw' => [ |
| 109 | - 'type' => 'hidden', | |
| 110 | - 'title' => '', | |
| 104 | + 'type' => 'hidden', | |
| 105 | + 'title' => '', | |
| 106 | + 'show_in_rest' => true, | |
| 107 | + 'single' => true, | |
| 111 | 108 | ], |
| 112 | 109 | 'title' => [ |
| 113 | 110 | 'type' => 'hidden', |
| 114 | - 'title' => '', // Translation added later. | |
| 115 | 111 | 'default_value' => '', |
| 116 | - 'description' => '', // Translation added later. | |
| 117 | - 'help' => '', // Translation added later. | |
| 112 | + 'show_in_rest' => true, | |
| 113 | + 'single' => true, | |
| 118 | 114 | ], |
| 119 | 115 | 'metadesc' => [ |
| 120 | 116 | 'type' => 'hidden', |
| 121 | - 'title' => '', // Translation added later. | |
| 122 | 117 | 'default_value' => '', |
| 123 | 118 | 'class' => 'metadesc', |
| 124 | 119 | 'rows' => 2, |
| 125 | - 'description' => '', // Translation added later. | |
| 126 | - 'help' => '', // Translation added later. | |
| 120 | + 'show_in_rest' => true, | |
| 121 | + 'single' => true, | |
| 127 | 122 | ], |
| 128 | 123 | 'linkdex' => [ |
| 129 | 124 | 'type' => 'hidden', |
| 130 | - 'title' => 'linkdex', | |
| 131 | 125 | 'default_value' => '0', |
| 132 | - 'description' => '', | |
| 133 | 126 | ], |
| 134 | 127 | 'content_score' => [ |
| 135 | 128 | 'type' => 'hidden', |
| 136 | - 'title' => 'content_score', | |
| 137 | 129 | 'default_value' => '0', |
| 138 | - 'description' => '', | |
| 139 | 130 | ], |
| 131 | + 'inclusive_language_score' => [ | |
| 132 | + 'type' => 'hidden', | |
| 133 | + 'default_value' => '0', | |
| 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 | + ], | |
| 140 | 143 | 'is_cornerstone' => [ |
| 141 | 144 | 'type' => 'hidden', |
| 142 | - 'title' => 'is_cornerstone', | |
| 143 | 145 | 'default_value' => 'false', |
| 144 | - 'description' => '', | |
| 145 | 146 | ], |
| 146 | 147 | ], |
| 147 | - 'advanced' => [ | |
| 148 | + 'advanced' => [ | |
| 148 | 149 | 'meta-robots-noindex' => [ |
| 149 | 150 | 'type' => 'hidden', |
| 150 | - 'title' => '', // Translation added later. | |
| 151 | 151 | 'default_value' => '0', // = post-type default. |
| 152 | 152 | 'options' => [ |
| 153 | - '0' => '', // Post type default - translation added later. | |
| 154 | - '2' => '', // Index - translation added later. | |
| 155 | - '1' => '', // No-index - translation added later. | |
| 153 | + '0' => '', // Post type default. | |
| 154 | + '2' => '', // Index. | |
| 155 | + '1' => '', // No-index. | |
| 156 | 156 | ], |
| 157 | 157 | ], |
| 158 | 158 | 'meta-robots-nofollow' => [ |
| 159 | 159 | 'type' => 'hidden', |
| 160 | - 'title' => '', // Translation added later. | |
| 161 | 160 | 'default_value' => '0', // = follow. |
| 162 | 161 | 'options' => [ |
| 163 | - '0' => '', // Follow - translation added later. | |
| 164 | - '1' => '', // No-follow - translation added later. | |
| 162 | + '0' => '', // Follow. | |
| 163 | + '1' => '', // No-follow. | |
| 165 | 164 | ], |
| 166 | 165 | ], |
| 167 | 166 | 'meta-robots-adv' => [ |
| 168 | 167 | 'type' => 'hidden', |
| 169 | - 'title' => '', // Translation added later. | |
| 170 | 168 | 'default_value' => '', |
| 171 | - 'description' => '', // Translation added later. | |
| 172 | 169 | 'options' => [ |
| 173 | - 'noimageindex' => '', // Translation added later. | |
| 174 | - 'noarchive' => '', // Translation added later. | |
| 175 | - 'nosnippet' => '', // Translation added later. | |
| 170 | + 'noimageindex' => '', | |
| 171 | + 'noarchive' => '', | |
| 172 | + 'nosnippet' => '', | |
| 176 | 173 | ], |
| 177 | 174 | ], |
| 178 | 175 | 'bctitle' => [ |
| 179 | 176 | 'type' => 'hidden', |
| 180 | - 'title' => '', // Translation added later. | |
| 181 | 177 | 'default_value' => '', |
| 182 | - 'description' => '', // Translation added later. | |
| 183 | 178 | ], |
| 184 | 179 | 'canonical' => [ |
| 185 | 180 | 'type' => 'hidden', |
| 186 | - 'title' => '', // Translation added later. | |
| 187 | 181 | 'default_value' => '', |
| 188 | - 'description' => '', // Translation added later. | |
| 189 | 182 | ], |
| 190 | 183 | 'redirect' => [ |
| 191 | 184 | 'type' => 'url', |
| 192 | - 'title' => '', // Translation added later. | |
| 193 | 185 | 'default_value' => '', |
| 194 | - 'description' => '', // Translation added later. | |
| 195 | 186 | ], |
| 196 | 187 | ], |
| 197 | - 'social' => [], | |
| 198 | - 'schema' => [ | |
| 188 | + 'social' => [], | |
| 189 | + 'schema' => [ | |
| 199 | 190 | 'schema_page_type' => [ |
| 200 | 191 | 'type' => 'hidden', |
| 201 | - 'title' => '', | |
| 202 | 192 | 'options' => Schema_Types::PAGE_TYPES, |
| 203 | 193 | ], |
| 204 | 194 | 'schema_article_type' => [ |
| 205 | 195 | 'type' => 'hidden', |
| 206 | - 'title' => '', | |
| 207 | 196 | 'hide_on_pages' => true, |
| 208 | 197 | 'options' => Schema_Types::ARTICLE_TYPES, |
| 209 | 198 | ], |
| 210 | 199 | ], |
| 211 | 200 | /* Fields we should validate & save, but not show on any form. */ |
| 212 | - 'non_form' => [ | |
| 201 | + 'non_form' => [ | |
| 213 | 202 | 'linkdex' => [ |
| 214 | 203 | 'type' => null, |
| 215 | 204 | 'default_value' => '0', |
| 216 | 205 | ], |
| 217 | - 'zapier_trigger_sent' => [ | |
| 218 | - 'type' => null, | |
| 206 | + ], | |
| 207 | + 'content_planner' => [ | |
| 208 | + 'is_content_planner_banner_rendered' => [ | |
| 209 | + 'type' => 'hidden', | |
| 219 | 210 | 'default_value' => '0', |
| 220 | 211 | ], |
| 212 | + 'is_content_planner_banner_dismissed' => [ | |
| 213 | + 'type' => 'hidden', | |
| 214 | + 'default_value' => '0', | |
| 215 | + ], | |
| 221 | 216 | ], |
| 222 | 217 | ]; |
| 223 | 218 | |
| 224 | 219 | /** |
| @@ -268,15 +263,13 @@ | ||
| 268 | 263 | * @return void |
| 269 | 264 | */ |
| 270 | 265 | public static function init() { |
| 271 | 266 | foreach ( self::$social_networks as $option => $network ) { |
| 272 | - if ( WPSEO_Options::get( $option, false ) === true ) { | |
| 267 | + if ( WPSEO_Options::get( $option, false, [ 'wpseo_social' ] ) === true ) { | |
| 273 | 268 | foreach ( self::$social_fields as $box => $type ) { |
| 274 | 269 | self::$meta_fields['social'][ $network . '-' . $box ] = [ |
| 275 | 270 | 'type' => $type, |
| 276 | - 'title' => '', // Translation added later. | |
| 277 | 271 | 'default_value' => '', |
| 278 | - 'description' => '', // Translation added later. | |
| 279 | 272 | ]; |
| 280 | 273 | } |
| 281 | 274 | } |
| 282 | 275 | } |
| @@ -294,14 +287,33 @@ | ||
| 294 | 287 | |
| 295 | 288 | foreach ( self::$meta_fields as $subset => $field_group ) { |
| 296 | 289 | foreach ( $field_group as $key => $field_def ) { |
| 297 | 290 | |
| 291 | + // Register for all post types: sanitise callback only, REST disabled. | |
| 298 | 292 | register_meta( |
| 299 | 293 | 'post', |
| 300 | 294 | self::$meta_prefix . $key, |
| 301 | - [ 'sanitize_callback' => [ __CLASS__, 'sanitize_post_meta' ] ] | |
| 295 | + [ 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ] ], | |
| 302 | 296 | ); |
| 303 | 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 | + | |
| 304 | 316 | // Set the $fields_index property for efficiency. |
| 305 | 317 | self::$fields_index[ self::$meta_prefix . $key ] = [ |
| 306 | 318 | 'subset' => $subset, |
| 307 | 319 | 'key' => $key, |
| @@ -318,12 +330,18 @@ | ||
| 318 | 330 | } |
| 319 | 331 | } |
| 320 | 332 | unset( $subset, $field_group, $key, $field_def ); |
| 321 | 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 | + | |
| 322 | 340 | self::filter_schema_article_types(); |
| 323 | 341 | |
| 324 | - add_filter( 'update_post_metadata', [ __CLASS__, 'remove_meta_if_default' ], 10, 5 ); | |
| 325 | - add_filter( 'add_post_metadata', [ __CLASS__, 'dont_save_meta_if_default' ], 10, 4 ); | |
| 342 | + add_filter( 'update_post_metadata', [ self::class, 'remove_meta_if_default' ], 10, 5 ); | |
| 343 | + add_filter( 'add_post_metadata', [ self::class, 'dont_save_meta_if_default' ], 10, 4 ); | |
| 326 | 344 | } |
| 327 | 345 | |
| 328 | 346 | /** |
| 329 | 347 | * Retrieve the meta box form field definitions for the given tab and post type. |
| @@ -364,22 +382,13 @@ | ||
| 364 | 382 | if ( $post_type === '' ) { |
| 365 | 383 | return []; |
| 366 | 384 | } |
| 367 | 385 | |
| 368 | - /* Adjust the no-index text strings based on the post type. */ | |
| 369 | - $post_type_object = get_post_type_object( $post_type ); | |
| 370 | - | |
| 371 | - $field_defs['meta-robots-noindex']['title'] = sprintf( $field_defs['meta-robots-noindex']['title'], $post_type_object->labels->singular_name ); | |
| 372 | - $field_defs['meta-robots-noindex']['options']['0'] = sprintf( $field_defs['meta-robots-noindex']['options']['0'], ( ( WPSEO_Options::get( 'noindex-' . $post_type, false ) === true ) ? $field_defs['meta-robots-noindex']['options']['1'] : $field_defs['meta-robots-noindex']['options']['2'] ), $post_type_object->label ); | |
| 373 | - $field_defs['meta-robots-nofollow']['title'] = sprintf( $field_defs['meta-robots-nofollow']['title'], $post_type_object->labels->singular_name ); | |
| 374 | - | |
| 375 | 386 | /* Don't show the breadcrumb title field if breadcrumbs aren't enabled. */ |
| 376 | 387 | if ( WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ) ) { |
| 377 | 388 | unset( $field_defs['bctitle'] ); |
| 378 | 389 | } |
| 379 | 390 | |
| 380 | - global $post; | |
| 381 | - | |
| 382 | 391 | if ( empty( $post->ID ) || ( ! empty( $post->ID ) && self::get_value( 'redirect', $post->ID ) === '' ) ) { |
| 383 | 392 | unset( $field_defs['redirect'] ); |
| 384 | 393 | } |
| 385 | 394 | break; |
| @@ -397,9 +406,9 @@ | ||
| 397 | 406 | |
| 398 | 407 | /** This filter is documented in inc/options/class-wpseo-option-titles.php */ |
| 399 | 408 | $allowed_article_types = apply_filters( 'wpseo_schema_article_types', Schema_Types::ARTICLE_TYPES ); |
| 400 | 409 | |
| 401 | - if ( ! \array_key_exists( $default_schema_article_type, $allowed_article_types ) ) { | |
| 410 | + if ( ! array_key_exists( $default_schema_article_type, $allowed_article_types ) ) { | |
| 402 | 411 | $default_schema_article_type = WPSEO_Options::get_default( 'wpseo_titles', 'schema-article-type-' . $post_type ); |
| 403 | 412 | } |
| 404 | 413 | $field_defs['schema_article_type']['default'] = $default_schema_article_type; |
| 405 | 414 | } |
| @@ -437,12 +446,20 @@ | ||
| 437 | 446 | switch ( true ) { |
| 438 | 447 | case ( $meta_key === self::$meta_prefix . 'linkdex' ): |
| 439 | 448 | $int = WPSEO_Utils::validate_int( $meta_value ); |
| 440 | 449 | if ( $int !== false && $int >= 0 ) { |
| 441 | - $clean = strval( $int ); // Convert to string to make sure default check works. | |
| 450 | + $clean = (string) $int; // Convert to string to make sure default check works. | |
| 442 | 451 | } |
| 443 | 452 | break; |
| 444 | 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 | + | |
| 445 | 462 | case ( $field_def['type'] === 'checkbox' ): |
| 446 | 463 | // Only allow value if it's one of the predefined options. |
| 447 | 464 | if ( in_array( $meta_value, [ 'on', 'off' ], true ) ) { |
| 448 | 465 | $clean = $meta_value; |
| @@ -448,9 +465,8 @@ | ||
| 448 | 465 | $clean = $meta_value; |
| 449 | 466 | } |
| 450 | 467 | break; |
| 451 | 468 | |
| 452 | - | |
| 453 | 469 | case ( $field_def['type'] === 'select' || $field_def['type'] === 'radio' ): |
| 454 | 470 | // Only allow value if it's one of the predefined options. |
| 455 | 471 | if ( isset( $field_def['options'][ $meta_value ] ) ) { |
| 456 | 472 | $clean = $meta_value; |
| @@ -456,14 +472,12 @@ | ||
| 456 | 472 | $clean = $meta_value; |
| 457 | 473 | } |
| 458 | 474 | break; |
| 459 | 475 | |
| 460 | - | |
| 461 | 476 | case ( $field_def['type'] === 'hidden' && $meta_key === self::$meta_prefix . 'meta-robots-adv' ): |
| 462 | 477 | $clean = self::validate_meta_robots_adv( $meta_value ); |
| 463 | 478 | break; |
| 464 | 479 | |
| 465 | - | |
| 466 | 480 | case ( $field_def['type'] === 'url' || $meta_key === self::$meta_prefix . 'canonical' ): |
| 467 | 481 | // Validate as url(-part). |
| 468 | 482 | $url = WPSEO_Utils::sanitize_url( $meta_value ); |
| 469 | 483 | if ( $url !== '' ) { |
| @@ -470,9 +484,8 @@ | ||
| 470 | 484 | $clean = $url; |
| 471 | 485 | } |
| 472 | 486 | break; |
| 473 | 487 | |
| 474 | - | |
| 475 | 488 | case ( $field_def['type'] === 'upload' && in_array( $meta_key, [ self::$meta_prefix . 'opengraph-image', self::$meta_prefix . 'twitter-image' ], true ) ): |
| 476 | 489 | // Validate as url. |
| 477 | 490 | $url = WPSEO_Utils::sanitize_url( $meta_value, [ 'http', 'https', 'ftp', 'ftps' ] ); |
| 478 | 491 | if ( $url !== '' ) { |
| @@ -511,9 +524,8 @@ | ||
| 511 | 524 | case ( $field_def['type'] === 'multiselect' ): |
| 512 | 525 | $clean = $meta_value; |
| 513 | 526 | break; |
| 514 | 527 | |
| 515 | - | |
| 516 | 528 | case ( $field_def['type'] === 'text' ): |
| 517 | 529 | default: |
| 518 | 530 | if ( is_string( $meta_value ) ) { |
| 519 | 531 | $clean = WPSEO_Utils::sanitize_text_field( trim( $meta_value ) ); |
| @@ -721,11 +733,11 @@ | ||
| 721 | 733 | /** |
| 722 | 734 | * Deletes a meta value for a post. |
| 723 | 735 | * |
| 724 | 736 | * @param string $key The internal key of the meta value to change (without prefix). |
| 725 | - * @param int $post_id The ID of the post to change the meta for. | |
| 737 | + * @param int $post_id The ID of the post to delete the meta for. | |
| 726 | 738 | * |
| 727 | - * @return bool Whether the value was changed. | |
| 739 | + * @return bool Whether the delete was successful or not. | |
| 728 | 740 | */ |
| 729 | 741 | public static function delete( $key, $post_id ) { |
| 730 | 742 | return delete_post_meta( $post_id, self::$meta_prefix . $key ); |
| 731 | 743 | } |
| @@ -765,9 +777,9 @@ | ||
| 765 | 777 | ) |
| 766 | 778 | ;", |
| 767 | 779 | $old_metakey, |
| 768 | 780 | $wpdb->esc_like( self::$meta_prefix . '%' ), |
| 769 | - self::$meta_prefix . 'linkdex' | |
| 781 | + self::$meta_prefix . 'linkdex', | |
| 770 | 782 | ); |
| 771 | 783 | $oldies = $wpdb->get_results( $query ); |
| 772 | 784 | |
| 773 | 785 | if ( is_array( $oldies ) && $oldies !== [] ) { |
| @@ -817,9 +829,9 @@ | ||
| 817 | 829 | ) |
| 818 | 830 | ;", |
| 819 | 831 | self::$meta_prefix . 'meta-robots', |
| 820 | 832 | self::$meta_prefix . 'meta-robots-noindex', |
| 821 | - self::$meta_prefix . 'meta-robots-nofollow' | |
| 833 | + self::$meta_prefix . 'meta-robots-nofollow', | |
| 822 | 834 | ); |
| 823 | 835 | $oldies = $wpdb->get_results( $query ); |
| 824 | 836 | |
| 825 | 837 | if ( is_array( $oldies ) && $oldies !== [] ) { |
| @@ -869,9 +881,9 @@ | ||
| 869 | 881 | $valid = array_keys( $valid ); |
| 870 | 882 | |
| 871 | 883 | $query[] = $wpdb->prepare( |
| 872 | 884 | "( meta_key = %s AND meta_value NOT IN ( '" . implode( "','", esc_sql( $valid ) ) . "' ) )", |
| 873 | - self::$meta_prefix . $key | |
| 885 | + self::$meta_prefix . $key, | |
| 874 | 886 | ); |
| 875 | 887 | unset( $valid ); |
| 876 | 888 | } |
| 877 | 889 | elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) { |
| @@ -877,15 +889,15 @@ | ||
| 877 | 889 | elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) { |
| 878 | 890 | $query[] = $wpdb->prepare( |
| 879 | 891 | '( meta_key = %s AND meta_value = %s )', |
| 880 | 892 | self::$meta_prefix . $key, |
| 881 | - $field_def['default_value'] | |
| 893 | + $field_def['default_value'], | |
| 882 | 894 | ); |
| 883 | 895 | } |
| 884 | 896 | else { |
| 885 | 897 | $query[] = $wpdb->prepare( |
| 886 | 898 | "( meta_key = %s AND meta_value = '' )", |
| 887 | - self::$meta_prefix . $key | |
| 899 | + self::$meta_prefix . $key, | |
| 888 | 900 | ); |
| 889 | 901 | } |
| 890 | 902 | } |
| 891 | 903 | } |
| @@ -921,9 +933,9 @@ | ||
| 921 | 933 | * (hopefully) even smaller set of invalid results. |
| 922 | 934 | */ |
| 923 | 935 | $query = $wpdb->prepare( |
| 924 | 936 | "SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s", |
| 925 | - self::$meta_prefix . 'meta-robots-adv' | |
| 937 | + self::$meta_prefix . 'meta-robots-adv', | |
| 926 | 938 | ); |
| 927 | 939 | $oldies = $wpdb->get_results( $query ); |
| 928 | 940 | |
| 929 | 941 | if ( is_array( $oldies ) && $oldies !== [] ) { |
| @@ -1008,9 +1020,9 @@ | ||
| 1008 | 1020 | |
| 1009 | 1021 | /** |
| 1010 | 1022 | * The indexable repository. |
| 1011 | 1023 | * |
| 1012 | - * @var Indexable_Repository | |
| 1024 | + * @var Indexable_Repository $repository | |
| 1013 | 1025 | */ |
| 1014 | 1026 | $repository = YoastSEO()->classes->get( Indexable_Repository::class ); |
| 1015 | 1027 | |
| 1016 | 1028 | $post_ids = $repository->query() |
| @@ -1017,48 +1029,90 @@ | ||
| 1017 | 1029 | ->select( 'object_id' ) |
| 1018 | 1030 | ->where( 'primary_focus_keyword', $keyword ) |
| 1019 | 1031 | ->where( 'object_type', 'post' ) |
| 1020 | 1032 | ->where_not_equal( 'object_id', $post_id ) |
| 1021 | - ->limit( 2 ) | |
| 1033 | + ->where_not_equal( 'post_status', 'trash' ) | |
| 1034 | + ->limit( 2 ) // Limit to 2 results to save time and resources. | |
| 1022 | 1035 | ->find_array(); |
| 1023 | 1036 | |
| 1024 | - $callback = static function ( $row ) { | |
| 1025 | - return (int) $row['object_id']; | |
| 1026 | - }; | |
| 1027 | - $post_ids = array_map( $callback, $post_ids ); | |
| 1037 | + // Get object_id from each subarray in $post_ids. | |
| 1038 | + $post_ids = ( is_array( $post_ids ) ) ? array_column( $post_ids, 'object_id' ) : []; | |
| 1028 | 1039 | |
| 1029 | 1040 | /* |
| 1030 | - * If Yoast SEO Premium is active, get the additional keywords as well. | |
| 1041 | + * If Premium is installed, get the additional keywords as well. | |
| 1031 | 1042 | * We only check for the additional keywords if we've not already found two. |
| 1032 | 1043 | * In that case there's no use for an additional query as we already know |
| 1033 | 1044 | * that the keyword has been used multiple times before. |
| 1034 | 1045 | */ |
| 1035 | - if ( YoastSEO()->helpers->product->is_premium() && count( $post_ids ) < 2 ) { | |
| 1036 | - $query = [ | |
| 1037 | - 'meta_query' => [ | |
| 1038 | - [ | |
| 1039 | - 'key' => '_yoast_wpseo_focuskeywords', | |
| 1040 | - 'value' => sprintf( '"keyword":"%s"', $keyword ), | |
| 1041 | - 'compare' => 'LIKE', | |
| 1042 | - ], | |
| 1043 | - ], | |
| 1044 | - 'post__not_in' => [ $post_id ], | |
| 1045 | - 'fields' => 'ids', | |
| 1046 | - 'post_type' => 'any', | |
| 1046 | + if ( count( $post_ids ) < 2 ) { | |
| 1047 | + /** | |
| 1048 | + * Allows enhancing the array of posts' that share their focus keywords with the post's focus keywords. | |
| 1049 | + * | |
| 1050 | + * @param array $post_ids The array of posts' ids that share their related keywords with the post. | |
| 1051 | + * @param string $keyword The keyword to search for. | |
| 1052 | + * @param int $post_id The id of the post the keyword is associated to. | |
| 1053 | + */ | |
| 1054 | + $post_ids = apply_filters( 'wpseo_posts_for_focus_keyword', $post_ids, $keyword, $post_id ); | |
| 1055 | + } | |
| 1047 | 1056 | |
| 1048 | - /* | |
| 1049 | - * We only need to return zero, one or two results: | |
| 1050 | - * - Zero: keyword hasn't been used before | |
| 1051 | - * - One: Keyword has been used once before | |
| 1052 | - * - Two or more: Keyword has been used twice or more before | |
| 1053 | - */ | |
| 1054 | - 'posts_per_page' => 2, | |
| 1055 | - ]; | |
| 1056 | - $get_posts = new WP_Query( $query ); | |
| 1057 | - $post_ids = array_merge( $post_ids, $get_posts->posts ); | |
| 1057 | + return $post_ids; | |
| 1058 | + } | |
| 1059 | + | |
| 1060 | + /** | |
| 1061 | + * Returns the post types for the given post ids. | |
| 1062 | + * | |
| 1063 | + * @param array $post_ids The post ids to get the post types for. | |
| 1064 | + * | |
| 1065 | + * @return array The post types. | |
| 1066 | + */ | |
| 1067 | + public static function post_types_for_ids( $post_ids ) { | |
| 1068 | + // Check if post ids is not empty. | |
| 1069 | + if ( ! empty( $post_ids ) ) { | |
| 1070 | + /** | |
| 1071 | + * The indexable repository. | |
| 1072 | + * | |
| 1073 | + * @var Indexable_Repository $repository | |
| 1074 | + */ | |
| 1075 | + $repository = YoastSEO()->classes->get( Indexable_Repository::class ); | |
| 1076 | + | |
| 1077 | + // Get the post subtypes for the posts that share the keyword. | |
| 1078 | + $post_types = $repository->query() | |
| 1079 | + ->select( 'object_sub_type' ) | |
| 1080 | + ->where_in( 'object_id', $post_ids ) | |
| 1081 | + ->find_array(); | |
| 1082 | + | |
| 1083 | + // Get object_sub_type from each subarray in $post_ids. | |
| 1084 | + $post_types = array_column( $post_types, 'object_sub_type' ); | |
| 1058 | 1085 | } |
| 1086 | + else { | |
| 1087 | + $post_types = []; | |
| 1088 | + } | |
| 1059 | 1089 | |
| 1060 | - return $post_ids; | |
| 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; | |
| 1061 | 1115 | } |
| 1062 | 1116 | |
| 1063 | 1117 | /** |
| 1064 | 1118 | * Filter the schema article types. |