| @@ -68,10 +68,8 @@ | ||
| 68 | 68 | /** |
| 69 | 69 | * Meta box field definitions for the meta box form. |
| 70 | 70 | * |
| 71 | 71 | * {@internal |
| 72 | - * - Titles, help texts, description text and option labels are added via a translate_meta_boxes() method | |
| 73 | - * in the relevant child classes (WPSEO_Metabox and WPSEO_Social_admin) as they are only needed there. | |
| 74 | 72 | * - Beware: even though the meta keys are divided into subsets, they still have to be uniquely named!}} |
| 75 | 73 | * |
| 76 | 74 | * @var array |
| 77 | 75 | * Array format: |
| @@ -76,9 +74,8 @@ | ||
| 76 | 74 | * @var array |
| 77 | 75 | * Array format: |
| 78 | 76 | * (required) 'type' => (string) field type. i.e. text / textarea / checkbox / |
| 79 | 77 | * radio / select / multiselect / upload etc. |
| 80 | - * (required) 'title' => (string) table row title. | |
| 81 | 78 | * (recommended) 'default_value' => (string|array) default value for the field. |
| 82 | 79 | * IMPORTANT: |
| 83 | 80 | * - if the field has options, the default has to be the |
| 84 | 81 | * key of one of the options. |
| @@ -93,132 +90,128 @@ | ||
| 93 | 90 | * value = (string) text label for the option. |
| 94 | 91 | * (optional) 'autocomplete' => (bool) whether autocomplete is on for text fields, |
| 95 | 92 | * defaults to true. |
| 96 | 93 | * (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 | 94 | * (optional) 'rows' => (int) number of rows for a textarea, defaults to 3. |
| 101 | - * (optional) 'placeholder' => (string) Currently only used by add-on plugins. | |
| 102 | 95 | * (optional) 'serialized' => (bool) whether the value is expected to be serialized, |
| 103 | 96 | * i.e. an array or object, defaults to false. |
| 104 | 97 | * Currently only used by add-on plugins. |
| 105 | 98 | */ |
| 106 | 99 | public static $meta_fields = [ |
| 107 | - 'general' => [ | |
| 100 | + 'general' => [ | |
| 108 | 101 | 'focuskw' => [ |
| 109 | - 'type' => 'hidden', | |
| 110 | - 'title' => '', | |
| 102 | + 'type' => 'hidden', | |
| 103 | + 'title' => '', | |
| 104 | + 'show_in_rest' => true, | |
| 105 | + 'single' => true, | |
| 111 | 106 | ], |
| 112 | 107 | 'title' => [ |
| 113 | 108 | 'type' => 'hidden', |
| 114 | - 'title' => '', // Translation added later. | |
| 115 | 109 | 'default_value' => '', |
| 116 | - 'description' => '', // Translation added later. | |
| 117 | - 'help' => '', // Translation added later. | |
| 110 | + 'show_in_rest' => true, | |
| 111 | + 'single' => true, | |
| 118 | 112 | ], |
| 119 | 113 | 'metadesc' => [ |
| 120 | 114 | 'type' => 'hidden', |
| 121 | - 'title' => '', // Translation added later. | |
| 122 | 115 | 'default_value' => '', |
| 123 | 116 | 'class' => 'metadesc', |
| 124 | 117 | 'rows' => 2, |
| 125 | - 'description' => '', // Translation added later. | |
| 126 | - 'help' => '', // Translation added later. | |
| 118 | + 'show_in_rest' => true, | |
| 119 | + 'single' => true, | |
| 127 | 120 | ], |
| 128 | 121 | 'linkdex' => [ |
| 129 | 122 | 'type' => 'hidden', |
| 130 | - 'title' => 'linkdex', | |
| 131 | 123 | 'default_value' => '0', |
| 132 | - 'description' => '', | |
| 133 | 124 | ], |
| 134 | 125 | 'content_score' => [ |
| 135 | 126 | 'type' => 'hidden', |
| 136 | - 'title' => 'content_score', | |
| 137 | 127 | 'default_value' => '0', |
| 138 | - 'description' => '', | |
| 139 | 128 | ], |
| 129 | + 'inclusive_language_score' => [ | |
| 130 | + 'type' => 'hidden', | |
| 131 | + 'default_value' => '0', | |
| 132 | + ], | |
| 133 | + 'seo_title_score' => [ | |
| 134 | + 'type' => 'hidden', | |
| 135 | + 'default_value' => '0', | |
| 136 | + ], | |
| 137 | + 'meta_description_score' => [ | |
| 138 | + 'type' => 'hidden', | |
| 139 | + 'default_value' => '0', | |
| 140 | + ], | |
| 140 | 141 | 'is_cornerstone' => [ |
| 141 | 142 | 'type' => 'hidden', |
| 142 | - 'title' => 'is_cornerstone', | |
| 143 | 143 | 'default_value' => 'false', |
| 144 | - 'description' => '', | |
| 145 | 144 | ], |
| 146 | 145 | ], |
| 147 | - 'advanced' => [ | |
| 146 | + 'advanced' => [ | |
| 148 | 147 | 'meta-robots-noindex' => [ |
| 149 | 148 | 'type' => 'hidden', |
| 150 | - 'title' => '', // Translation added later. | |
| 151 | 149 | 'default_value' => '0', // = post-type default. |
| 152 | 150 | 'options' => [ |
| 153 | - '0' => '', // Post type default - translation added later. | |
| 154 | - '2' => '', // Index - translation added later. | |
| 155 | - '1' => '', // No-index - translation added later. | |
| 151 | + '0' => '', // Post type default. | |
| 152 | + '2' => '', // Index. | |
| 153 | + '1' => '', // No-index. | |
| 156 | 154 | ], |
| 157 | 155 | ], |
| 158 | 156 | 'meta-robots-nofollow' => [ |
| 159 | 157 | 'type' => 'hidden', |
| 160 | - 'title' => '', // Translation added later. | |
| 161 | 158 | 'default_value' => '0', // = follow. |
| 162 | 159 | 'options' => [ |
| 163 | - '0' => '', // Follow - translation added later. | |
| 164 | - '1' => '', // No-follow - translation added later. | |
| 160 | + '0' => '', // Follow. | |
| 161 | + '1' => '', // No-follow. | |
| 165 | 162 | ], |
| 166 | 163 | ], |
| 167 | 164 | 'meta-robots-adv' => [ |
| 168 | 165 | 'type' => 'hidden', |
| 169 | - 'title' => '', // Translation added later. | |
| 170 | 166 | 'default_value' => '', |
| 171 | - 'description' => '', // Translation added later. | |
| 172 | 167 | 'options' => [ |
| 173 | - 'noimageindex' => '', // Translation added later. | |
| 174 | - 'noarchive' => '', // Translation added later. | |
| 175 | - 'nosnippet' => '', // Translation added later. | |
| 168 | + 'noimageindex' => '', | |
| 169 | + 'noarchive' => '', | |
| 170 | + 'nosnippet' => '', | |
| 176 | 171 | ], |
| 177 | 172 | ], |
| 178 | 173 | 'bctitle' => [ |
| 179 | 174 | 'type' => 'hidden', |
| 180 | - 'title' => '', // Translation added later. | |
| 181 | 175 | 'default_value' => '', |
| 182 | - 'description' => '', // Translation added later. | |
| 183 | 176 | ], |
| 184 | 177 | 'canonical' => [ |
| 185 | 178 | 'type' => 'hidden', |
| 186 | - 'title' => '', // Translation added later. | |
| 187 | 179 | 'default_value' => '', |
| 188 | - 'description' => '', // Translation added later. | |
| 189 | 180 | ], |
| 190 | 181 | 'redirect' => [ |
| 191 | 182 | 'type' => 'url', |
| 192 | - 'title' => '', // Translation added later. | |
| 193 | 183 | 'default_value' => '', |
| 194 | - 'description' => '', // Translation added later. | |
| 195 | 184 | ], |
| 196 | 185 | ], |
| 197 | - 'social' => [], | |
| 198 | - 'schema' => [ | |
| 186 | + 'social' => [], | |
| 187 | + 'schema' => [ | |
| 199 | 188 | 'schema_page_type' => [ |
| 200 | 189 | 'type' => 'hidden', |
| 201 | - 'title' => '', | |
| 202 | 190 | 'options' => Schema_Types::PAGE_TYPES, |
| 203 | 191 | ], |
| 204 | 192 | 'schema_article_type' => [ |
| 205 | 193 | 'type' => 'hidden', |
| 206 | - 'title' => '', | |
| 207 | 194 | 'hide_on_pages' => true, |
| 208 | 195 | 'options' => Schema_Types::ARTICLE_TYPES, |
| 209 | 196 | ], |
| 210 | 197 | ], |
| 211 | 198 | /* Fields we should validate & save, but not show on any form. */ |
| 212 | - 'non_form' => [ | |
| 199 | + 'non_form' => [ | |
| 213 | 200 | 'linkdex' => [ |
| 214 | 201 | 'type' => null, |
| 215 | 202 | 'default_value' => '0', |
| 216 | 203 | ], |
| 217 | - 'zapier_trigger_sent' => [ | |
| 218 | - 'type' => null, | |
| 204 | + ], | |
| 205 | + 'content_planner' => [ | |
| 206 | + 'is_content_planner_banner_rendered' => [ | |
| 207 | + 'type' => 'hidden', | |
| 219 | 208 | 'default_value' => '0', |
| 220 | 209 | ], |
| 210 | + 'is_content_planner_banner_dismissed' => [ | |
| 211 | + 'type' => 'hidden', | |
| 212 | + 'default_value' => '0', | |
| 213 | + ], | |
| 221 | 214 | ], |
| 222 | 215 | ]; |
| 223 | 216 | |
| 224 | 217 | /** |
| @@ -268,15 +261,13 @@ | ||
| 268 | 261 | * @return void |
| 269 | 262 | */ |
| 270 | 263 | public static function init() { |
| 271 | 264 | foreach ( self::$social_networks as $option => $network ) { |
| 272 | - if ( WPSEO_Options::get( $option, false ) === true ) { | |
| 265 | + if ( WPSEO_Options::get( $option, false, [ 'wpseo_social' ] ) === true ) { | |
| 273 | 266 | foreach ( self::$social_fields as $box => $type ) { |
| 274 | 267 | self::$meta_fields['social'][ $network . '-' . $box ] = [ |
| 275 | 268 | 'type' => $type, |
| 276 | - 'title' => '', // Translation added later. | |
| 277 | 269 | 'default_value' => '', |
| 278 | - 'description' => '', // Translation added later. | |
| 279 | 270 | ]; |
| 280 | 271 | } |
| 281 | 272 | } |
| 282 | 273 | } |
| @@ -294,14 +285,33 @@ | ||
| 294 | 285 | |
| 295 | 286 | foreach ( self::$meta_fields as $subset => $field_group ) { |
| 296 | 287 | foreach ( $field_group as $key => $field_def ) { |
| 297 | 288 | |
| 289 | + // Register for all post types: sanitise callback only, REST disabled. | |
| 298 | 290 | register_meta( |
| 299 | 291 | 'post', |
| 300 | 292 | self::$meta_prefix . $key, |
| 301 | - [ 'sanitize_callback' => [ __CLASS__, 'sanitize_post_meta' ] ] | |
| 293 | + [ 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ] ], | |
| 302 | 294 | ); |
| 303 | 295 | |
| 296 | + // Re-register for the 'post' subtype with REST exposure and auth callback when show_in_rest is enabled. | |
| 297 | + if ( ! empty( $field_def['show_in_rest'] ) ) { | |
| 298 | + register_meta( | |
| 299 | + 'post', | |
| 300 | + self::$meta_prefix . $key, | |
| 301 | + [ | |
| 302 | + 'show_in_rest' => true, | |
| 303 | + 'single' => ( $field_def['single'] ?? false ), | |
| 304 | + 'type' => 'string', | |
| 305 | + 'object_subtype' => 'post', | |
| 306 | + 'sanitize_callback' => [ self::class, 'sanitize_post_meta' ], | |
| 307 | + 'auth_callback' => static function ( $allowed, $meta_key, $object_id ) { | |
| 308 | + return current_user_can( 'edit_post', $object_id ); | |
| 309 | + }, | |
| 310 | + ], | |
| 311 | + ); | |
| 312 | + } | |
| 313 | + | |
| 304 | 314 | // Set the $fields_index property for efficiency. |
| 305 | 315 | self::$fields_index[ self::$meta_prefix . $key ] = [ |
| 306 | 316 | 'subset' => $subset, |
| 307 | 317 | 'key' => $key, |
| @@ -318,12 +328,18 @@ | ||
| 318 | 328 | } |
| 319 | 329 | } |
| 320 | 330 | unset( $subset, $field_group, $key, $field_def ); |
| 321 | 331 | |
| 332 | + // Strip meta fields that have show_in_rest enabled from REST responses for users | |
| 333 | + // without edit_post capability. register_meta's auth_callback only covers writes, | |
| 334 | + // so read access must be restricted separately via this filter. | |
| 335 | + // Register only for 'post' post type. Other post types don't expose these fields. | |
| 336 | + add_filter( 'rest_prepare_post', [ self::class, 'hide_meta_from_unauthorized_rest_response' ], 10, 2 ); | |
| 337 | + | |
| 322 | 338 | self::filter_schema_article_types(); |
| 323 | 339 | |
| 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 ); | |
| 340 | + add_filter( 'update_post_metadata', [ self::class, 'remove_meta_if_default' ], 10, 5 ); | |
| 341 | + add_filter( 'add_post_metadata', [ self::class, 'dont_save_meta_if_default' ], 10, 4 ); | |
| 326 | 342 | } |
| 327 | 343 | |
| 328 | 344 | /** |
| 329 | 345 | * Retrieve the meta box form field definitions for the given tab and post type. |
| @@ -364,22 +380,13 @@ | ||
| 364 | 380 | if ( $post_type === '' ) { |
| 365 | 381 | return []; |
| 366 | 382 | } |
| 367 | 383 | |
| 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 | 384 | /* Don't show the breadcrumb title field if breadcrumbs aren't enabled. */ |
| 376 | 385 | if ( WPSEO_Options::get( 'breadcrumbs-enable', false ) !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ) ) { |
| 377 | 386 | unset( $field_defs['bctitle'] ); |
| 378 | 387 | } |
| 379 | 388 | |
| 380 | - global $post; | |
| 381 | - | |
| 382 | 389 | if ( empty( $post->ID ) || ( ! empty( $post->ID ) && self::get_value( 'redirect', $post->ID ) === '' ) ) { |
| 383 | 390 | unset( $field_defs['redirect'] ); |
| 384 | 391 | } |
| 385 | 392 | break; |
| @@ -397,9 +404,9 @@ | ||
| 397 | 404 | |
| 398 | 405 | /** This filter is documented in inc/options/class-wpseo-option-titles.php */ |
| 399 | 406 | $allowed_article_types = apply_filters( 'wpseo_schema_article_types', Schema_Types::ARTICLE_TYPES ); |
| 400 | 407 | |
| 401 | - if ( ! \array_key_exists( $default_schema_article_type, $allowed_article_types ) ) { | |
| 408 | + if ( ! array_key_exists( $default_schema_article_type, $allowed_article_types ) ) { | |
| 402 | 409 | $default_schema_article_type = WPSEO_Options::get_default( 'wpseo_titles', 'schema-article-type-' . $post_type ); |
| 403 | 410 | } |
| 404 | 411 | $field_defs['schema_article_type']['default'] = $default_schema_article_type; |
| 405 | 412 | } |
| @@ -437,12 +444,20 @@ | ||
| 437 | 444 | switch ( true ) { |
| 438 | 445 | case ( $meta_key === self::$meta_prefix . 'linkdex' ): |
| 439 | 446 | $int = WPSEO_Utils::validate_int( $meta_value ); |
| 440 | 447 | if ( $int !== false && $int >= 0 ) { |
| 441 | - $clean = strval( $int ); // Convert to string to make sure default check works. | |
| 448 | + $clean = (string) $int; // Convert to string to make sure default check works. | |
| 442 | 449 | } |
| 443 | 450 | break; |
| 444 | 451 | |
| 452 | + case ( in_array( $meta_key, [ self::$meta_prefix . 'seo_title_score', self::$meta_prefix . 'meta_description_score' ], true ) ): | |
| 453 | + // Per-field scores are 0-100 percentages; out-of-range input keeps the "never scored" default. | |
| 454 | + $int = WPSEO_Utils::validate_int( $meta_value ); | |
| 455 | + if ( $int !== false && $int >= 0 && $int <= 100 ) { | |
| 456 | + $clean = (string) $int; // Convert to string to make sure default check works. | |
| 457 | + } | |
| 458 | + break; | |
| 459 | + | |
| 445 | 460 | case ( $field_def['type'] === 'checkbox' ): |
| 446 | 461 | // Only allow value if it's one of the predefined options. |
| 447 | 462 | if ( in_array( $meta_value, [ 'on', 'off' ], true ) ) { |
| 448 | 463 | $clean = $meta_value; |
| @@ -448,9 +463,8 @@ | ||
| 448 | 463 | $clean = $meta_value; |
| 449 | 464 | } |
| 450 | 465 | break; |
| 451 | 466 | |
| 452 | - | |
| 453 | 467 | case ( $field_def['type'] === 'select' || $field_def['type'] === 'radio' ): |
| 454 | 468 | // Only allow value if it's one of the predefined options. |
| 455 | 469 | if ( isset( $field_def['options'][ $meta_value ] ) ) { |
| 456 | 470 | $clean = $meta_value; |
| @@ -456,14 +470,12 @@ | ||
| 456 | 470 | $clean = $meta_value; |
| 457 | 471 | } |
| 458 | 472 | break; |
| 459 | 473 | |
| 460 | - | |
| 461 | 474 | case ( $field_def['type'] === 'hidden' && $meta_key === self::$meta_prefix . 'meta-robots-adv' ): |
| 462 | 475 | $clean = self::validate_meta_robots_adv( $meta_value ); |
| 463 | 476 | break; |
| 464 | 477 | |
| 465 | - | |
| 466 | 478 | case ( $field_def['type'] === 'url' || $meta_key === self::$meta_prefix . 'canonical' ): |
| 467 | 479 | // Validate as url(-part). |
| 468 | 480 | $url = WPSEO_Utils::sanitize_url( $meta_value ); |
| 469 | 481 | if ( $url !== '' ) { |
| @@ -470,9 +482,8 @@ | ||
| 470 | 482 | $clean = $url; |
| 471 | 483 | } |
| 472 | 484 | break; |
| 473 | 485 | |
| 474 | - | |
| 475 | 486 | case ( $field_def['type'] === 'upload' && in_array( $meta_key, [ self::$meta_prefix . 'opengraph-image', self::$meta_prefix . 'twitter-image' ], true ) ): |
| 476 | 487 | // Validate as url. |
| 477 | 488 | $url = WPSEO_Utils::sanitize_url( $meta_value, [ 'http', 'https', 'ftp', 'ftps' ] ); |
| 478 | 489 | if ( $url !== '' ) { |
| @@ -511,9 +522,8 @@ | ||
| 511 | 522 | case ( $field_def['type'] === 'multiselect' ): |
| 512 | 523 | $clean = $meta_value; |
| 513 | 524 | break; |
| 514 | 525 | |
| 515 | - | |
| 516 | 526 | case ( $field_def['type'] === 'text' ): |
| 517 | 527 | default: |
| 518 | 528 | if ( is_string( $meta_value ) ) { |
| 519 | 529 | $clean = WPSEO_Utils::sanitize_text_field( trim( $meta_value ) ); |
| @@ -765,9 +775,9 @@ | ||
| 765 | 775 | ) |
| 766 | 776 | ;", |
| 767 | 777 | $old_metakey, |
| 768 | 778 | $wpdb->esc_like( self::$meta_prefix . '%' ), |
| 769 | - self::$meta_prefix . 'linkdex' | |
| 779 | + self::$meta_prefix . 'linkdex', | |
| 770 | 780 | ); |
| 771 | 781 | $oldies = $wpdb->get_results( $query ); |
| 772 | 782 | |
| 773 | 783 | if ( is_array( $oldies ) && $oldies !== [] ) { |
| @@ -817,9 +827,9 @@ | ||
| 817 | 827 | ) |
| 818 | 828 | ;", |
| 819 | 829 | self::$meta_prefix . 'meta-robots', |
| 820 | 830 | self::$meta_prefix . 'meta-robots-noindex', |
| 821 | - self::$meta_prefix . 'meta-robots-nofollow' | |
| 831 | + self::$meta_prefix . 'meta-robots-nofollow', | |
| 822 | 832 | ); |
| 823 | 833 | $oldies = $wpdb->get_results( $query ); |
| 824 | 834 | |
| 825 | 835 | if ( is_array( $oldies ) && $oldies !== [] ) { |
| @@ -869,9 +879,9 @@ | ||
| 869 | 879 | $valid = array_keys( $valid ); |
| 870 | 880 | |
| 871 | 881 | $query[] = $wpdb->prepare( |
| 872 | 882 | "( meta_key = %s AND meta_value NOT IN ( '" . implode( "','", esc_sql( $valid ) ) . "' ) )", |
| 873 | - self::$meta_prefix . $key | |
| 883 | + self::$meta_prefix . $key, | |
| 874 | 884 | ); |
| 875 | 885 | unset( $valid ); |
| 876 | 886 | } |
| 877 | 887 | elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) { |
| @@ -877,15 +887,15 @@ | ||
| 877 | 887 | elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) { |
| 878 | 888 | $query[] = $wpdb->prepare( |
| 879 | 889 | '( meta_key = %s AND meta_value = %s )', |
| 880 | 890 | self::$meta_prefix . $key, |
| 881 | - $field_def['default_value'] | |
| 891 | + $field_def['default_value'], | |
| 882 | 892 | ); |
| 883 | 893 | } |
| 884 | 894 | else { |
| 885 | 895 | $query[] = $wpdb->prepare( |
| 886 | 896 | "( meta_key = %s AND meta_value = '' )", |
| 887 | - self::$meta_prefix . $key | |
| 897 | + self::$meta_prefix . $key, | |
| 888 | 898 | ); |
| 889 | 899 | } |
| 890 | 900 | } |
| 891 | 901 | } |
| @@ -921,9 +931,9 @@ | ||
| 921 | 931 | * (hopefully) even smaller set of invalid results. |
| 922 | 932 | */ |
| 923 | 933 | $query = $wpdb->prepare( |
| 924 | 934 | "SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s", |
| 925 | - self::$meta_prefix . 'meta-robots-adv' | |
| 935 | + self::$meta_prefix . 'meta-robots-adv', | |
| 926 | 936 | ); |
| 927 | 937 | $oldies = $wpdb->get_results( $query ); |
| 928 | 938 | |
| 929 | 939 | if ( is_array( $oldies ) && $oldies !== [] ) { |
| @@ -1008,9 +1018,9 @@ | ||
| 1008 | 1018 | |
| 1009 | 1019 | /** |
| 1010 | 1020 | * The indexable repository. |
| 1011 | 1021 | * |
| 1012 | - * @var Indexable_Repository | |
| 1022 | + * @var Indexable_Repository $repository | |
| 1013 | 1023 | */ |
| 1014 | 1024 | $repository = YoastSEO()->classes->get( Indexable_Repository::class ); |
| 1015 | 1025 | |
| 1016 | 1026 | $post_ids = $repository->query() |
| @@ -1017,48 +1027,90 @@ | ||
| 1017 | 1027 | ->select( 'object_id' ) |
| 1018 | 1028 | ->where( 'primary_focus_keyword', $keyword ) |
| 1019 | 1029 | ->where( 'object_type', 'post' ) |
| 1020 | 1030 | ->where_not_equal( 'object_id', $post_id ) |
| 1021 | - ->limit( 2 ) | |
| 1031 | + ->where_not_equal( 'post_status', 'trash' ) | |
| 1032 | + ->limit( 2 ) // Limit to 2 results to save time and resources. | |
| 1022 | 1033 | ->find_array(); |
| 1023 | 1034 | |
| 1024 | - $callback = static function ( $row ) { | |
| 1025 | - return (int) $row['object_id']; | |
| 1026 | - }; | |
| 1027 | - $post_ids = array_map( $callback, $post_ids ); | |
| 1035 | + // Get object_id from each subarray in $post_ids. | |
| 1036 | + $post_ids = ( is_array( $post_ids ) ) ? array_column( $post_ids, 'object_id' ) : []; | |
| 1028 | 1037 | |
| 1029 | 1038 | /* |
| 1030 | - * If Yoast SEO Premium is active, get the additional keywords as well. | |
| 1039 | + * If Premium is installed, get the additional keywords as well. | |
| 1031 | 1040 | * We only check for the additional keywords if we've not already found two. |
| 1032 | 1041 | * In that case there's no use for an additional query as we already know |
| 1033 | 1042 | * that the keyword has been used multiple times before. |
| 1034 | 1043 | */ |
| 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', | |
| 1044 | + if ( count( $post_ids ) < 2 ) { | |
| 1045 | + /** | |
| 1046 | + * Allows enhancing the array of posts' that share their focus keywords with the post's focus keywords. | |
| 1047 | + * | |
| 1048 | + * @param array $post_ids The array of posts' ids that share their related keywords with the post. | |
| 1049 | + * @param string $keyword The keyword to search for. | |
| 1050 | + * @param int $post_id The id of the post the keyword is associated to. | |
| 1051 | + */ | |
| 1052 | + $post_ids = apply_filters( 'wpseo_posts_for_focus_keyword', $post_ids, $keyword, $post_id ); | |
| 1053 | + } | |
| 1047 | 1054 | |
| 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 ); | |
| 1055 | + return $post_ids; | |
| 1056 | + } | |
| 1057 | + | |
| 1058 | + /** | |
| 1059 | + * Returns the post types for the given post ids. | |
| 1060 | + * | |
| 1061 | + * @param array $post_ids The post ids to get the post types for. | |
| 1062 | + * | |
| 1063 | + * @return array The post types. | |
| 1064 | + */ | |
| 1065 | + public static function post_types_for_ids( $post_ids ) { | |
| 1066 | + // Check if post ids is not empty. | |
| 1067 | + if ( ! empty( $post_ids ) ) { | |
| 1068 | + /** | |
| 1069 | + * The indexable repository. | |
| 1070 | + * | |
| 1071 | + * @var Indexable_Repository $repository | |
| 1072 | + */ | |
| 1073 | + $repository = YoastSEO()->classes->get( Indexable_Repository::class ); | |
| 1074 | + | |
| 1075 | + // Get the post subtypes for the posts that share the keyword. | |
| 1076 | + $post_types = $repository->query() | |
| 1077 | + ->select( 'object_sub_type' ) | |
| 1078 | + ->where_in( 'object_id', $post_ids ) | |
| 1079 | + ->find_array(); | |
| 1080 | + | |
| 1081 | + // Get object_sub_type from each subarray in $post_ids. | |
| 1082 | + $post_types = array_column( $post_types, 'object_sub_type' ); | |
| 1058 | 1083 | } |
| 1084 | + else { | |
| 1085 | + $post_types = []; | |
| 1086 | + } | |
| 1059 | 1087 | |
| 1060 | - return $post_ids; | |
| 1088 | + return $post_types; | |
| 1089 | + } | |
| 1090 | + | |
| 1091 | + /** | |
| 1092 | + * Strips REST-exposed Yoast meta fields from the response for users without edit_post capability on the post. | |
| 1093 | + * | |
| 1094 | + * @param WP_REST_Response $response The REST response. | |
| 1095 | + * @param WP_Post $post The post object. | |
| 1096 | + * | |
| 1097 | + * @return WP_REST_Response The (possibly modified) response. | |
| 1098 | + */ | |
| 1099 | + public static function hide_meta_from_unauthorized_rest_response( $response, $post ) { | |
| 1100 | + if ( current_user_can( 'edit_post', $post->ID ) ) { | |
| 1101 | + return $response; | |
| 1102 | + } | |
| 1103 | + $data = $response->get_data(); | |
| 1104 | + foreach ( self::$meta_fields as $field_group ) { | |
| 1105 | + foreach ( $field_group as $key => $field_def ) { | |
| 1106 | + if ( ! empty( $field_def['show_in_rest'] ) ) { | |
| 1107 | + unset( $data['meta'][ self::$meta_prefix . $key ] ); | |
| 1108 | + } | |
| 1109 | + } | |
| 1110 | + } | |
| 1111 | + $response->set_data( $data ); | |
| 1112 | + return $response; | |
| 1061 | 1113 | } |
| 1062 | 1114 | |
| 1063 | 1115 | /** |
| 1064 | 1116 | * Filter the schema article types. |