• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 243456fc29be5c3b67753a5f1c3b74bb69eccadc

28 Jul 2026 12:20PM UTC coverage: 55.263% (+0.2%) from 55.031%
243456fc29be5c3b67753a5f1c3b74bb69eccadc

push

github

thijsoo
Merge branch 'release/28.2' of github.com:Yoast/wordpress-seo into trunk

10505 of 18782 branches covered (55.93%)

Branch coverage included in aggregate %.

30 of 35 new or added lines in 5 files covered. (85.71%)

135 existing lines in 1 file now uncovered.

39929 of 72479 relevant lines covered (55.09%)

41523.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.31
/src/abilities/user-interface/abilities-integration.php
1
<?php
2

3
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4
namespace Yoast\WP\SEO\Abilities\User_Interface;
5

6
use Yoast\WP\SEO\Abilities\Application\Post_SEO_Data_Collector;
7
use Yoast\WP\SEO\Abilities\Application\Post_SEO_Data_Updater;
8
use Yoast\WP\SEO\Abilities\Application\Score_Retriever;
9
use Yoast\WP\SEO\Conditionals\Abilities_API_Conditional;
10
use Yoast\WP\SEO\Conditionals\Should_Index_Indexables_Conditional;
11
use Yoast\WP\SEO\Config\Schema_Types;
12
use Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository;
13
use Yoast\WP\SEO\Editors\Framework\Inclusive_Language_Analysis;
14
use Yoast\WP\SEO\Editors\Framework\Keyphrase_Analysis;
15
use Yoast\WP\SEO\Editors\Framework\Readability_Analysis;
16
use Yoast\WP\SEO\Helpers\Capability_Helper;
17
use Yoast\WP\SEO\Integrations\Integration_Interface;
18

19
/**
20
 * Integration that registers Yoast SEO abilities with the WordPress Abilities API.
21
 */
22
class Abilities_Integration implements Integration_Interface {
23

24
        /**
25
         * The score retriever.
26
         *
27
         * @var Score_Retriever
28
         */
29
        private $score_retriever;
30

31
        /**
32
         * The capability helper.
33
         *
34
         * @var Capability_Helper
35
         */
36
        private $capability_helper;
37

38
        /**
39
         * The enabled analysis features repository.
40
         *
41
         * @var Enabled_Analysis_Features_Repository
42
         */
43
        private $enabled_analysis_features_repository;
44

45
        /**
46
         * The post SEO data collector.
47
         *
48
         * @var Post_SEO_Data_Collector
49
         */
50
        private $post_seo_data_collector;
51

52
        /**
53
         * The post SEO data updater.
54
         *
55
         * @var Post_SEO_Data_Updater
56
         */
57
        private $post_seo_data_updater;
58

59
        /**
60
         * Returns the conditionals based on which this loadable should be active.
61
         *
62
         * @return array<string> The conditionals.
63
         */
64
        public static function get_conditionals() {
2✔
65
                return [
2✔
66
                        Abilities_API_Conditional::class,
2✔
67
                        Should_Index_Indexables_Conditional::class,
2✔
68
                ];
2✔
69
        }
70

71
        /**
72
         * Constructor.
73
         *
74
         * @param Score_Retriever                      $score_retriever                      The score retriever.
75
         * @param Capability_Helper                    $capability_helper                    The capability helper.
76
         * @param Enabled_Analysis_Features_Repository $enabled_analysis_features_repository The enabled analysis features repository.
77
         * @param Post_SEO_Data_Collector              $post_seo_data_collector              The post SEO data collector.
78
         * @param Post_SEO_Data_Updater                $post_seo_data_updater                The post SEO data updater.
79
         */
80
        public function __construct(
×
81
                Score_Retriever $score_retriever,
82
                Capability_Helper $capability_helper,
83
                Enabled_Analysis_Features_Repository $enabled_analysis_features_repository,
84
                Post_SEO_Data_Collector $post_seo_data_collector,
85
                Post_SEO_Data_Updater $post_seo_data_updater
86
        ) {
87
                $this->score_retriever                      = $score_retriever;
×
88
                $this->capability_helper                    = $capability_helper;
×
89
                $this->enabled_analysis_features_repository = $enabled_analysis_features_repository;
×
90
                $this->post_seo_data_collector              = $post_seo_data_collector;
×
91
                $this->post_seo_data_updater                = $post_seo_data_updater;
×
92
        }
93

94
        /**
95
         * Registers hooks with WordPress.
96
         *
97
         * @return void
98
         */
99
        public function register_hooks() {
2✔
100
                \add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] );
2✔
101
        }
102

103
        /**
104
         * Registers the Yoast SEO abilities.
105
         *
106
         * @return void
107
         */
108
        public function register_abilities() {
6✔
109
                $enabled_features = $this->enabled_analysis_features_repository->get_features_by_keys(
6✔
110
                        [
6✔
111
                                Keyphrase_Analysis::NAME,
6✔
112
                                Readability_Analysis::NAME,
6✔
113
                                Inclusive_Language_Analysis::NAME,
6✔
114
                        ],
6✔
115
                )->to_array();
6✔
116

117
                if ( $enabled_features[ Keyphrase_Analysis::NAME ] === true ) {
6✔
118
                        $this->register_seo_scores_ability();
4✔
119
                }
120

121
                if ( $enabled_features[ Readability_Analysis::NAME ] === true ) {
6✔
122
                        $this->register_readability_scores_ability();
2✔
123
                }
124

125
                if ( $enabled_features[ Inclusive_Language_Analysis::NAME ] === true ) {
6✔
126
                        $this->register_inclusive_language_scores_ability();
2✔
127
                }
128
        }
129

130
        /**
131
         * Checks whether the current user can manage Yoast SEO.
132
         *
133
         * Gates the score abilities behind the Yoast SEO management capability.
134
         *
135
         * @return bool Whether the current user can manage Yoast SEO.
136
         */
137
        public function can_manage_seo(): bool {
4✔
138
                return $this->capability_helper->current_user_can( 'wpseo_manage_options' );
4✔
139
        }
140

141
        /**
142
         * Checks whether the current user can edit advanced SEO metadata.
143
         *
144
         * Gates the post SEO data abilities on the same capability that gates the advanced
145
         * and schema fields in the editors, so the abilities can never grant a field the
146
         * editor UI denies. The capability helper also passes wpseo_manage_options holders.
147
         * Per-post edit access is enforced on top, in the execute callbacks.
148
         *
149
         * @return bool Whether the current user can edit advanced SEO metadata.
150
         */
151
        public function can_edit_advanced_metadata(): bool {
4✔
152
                return $this->capability_helper->current_user_can( 'wpseo_edit_advanced_metadata' );
4✔
153
        }
154

155
        /**
156
         * Checks whether the current user can read scores.
157
         *
158
         * @deprecated 28.2
159
         * @codeCoverageIgnore Because of deprecation.
160
         *
161
         * @return bool Whether the current user can read scores.
162
         */
163
        public function can_read_scores(): bool {
164
                \_deprecated_function( __METHOD__, 'Yoast SEO 28.2', 'Use can_manage_seo() instead.' );
165

166
                return $this->can_manage_seo();
167
        }
168

169
        /**
170
         * Registers the SEO scores ability.
171
         *
172
         * @return void
173
         */
174
        private function register_seo_scores_ability(): void {
×
175
                $output_schema                                  = $this->get_score_output_schema();
×
176
                $output_schema['properties']['focus_keyphrase'] = [
×
177
                        'type'        => [ 'string', 'null' ],
×
178
                        'description' => \__( 'The focus keyphrase for the post, or null if not set.', 'wordpress-seo' ),
×
179
                ];
×
180

181
                \wp_register_ability(
×
182
                        Ability_Categories_Integration::CATEGORY_SLUG . '/get-seo-scores',
×
183
                        $this->get_shared_ability_args(
×
184
                                [
×
185
                                        'label'            => \__( 'Get SEO Scores', 'wordpress-seo' ),
×
186
                                        'description'      => \__( 'Get the SEO scores for the most recently modified posts.', 'wordpress-seo' ),
×
187
                                        'output_schema'    => $this->wrap_in_array_schema( $output_schema ),
×
188
                                        'execute_callback' => [ $this->score_retriever, 'get_seo_scores' ],
×
189
                                ],
×
190
                        ),
×
191
                );
×
192
        }
193

194
        /**
195
         * Registers the readability scores ability.
196
         *
197
         * @return void
198
         */
199
        private function register_readability_scores_ability(): void {
×
200
                \wp_register_ability(
×
201
                        Ability_Categories_Integration::CATEGORY_SLUG . '/get-readability-scores',
×
202
                        $this->get_shared_ability_args(
×
203
                                [
×
204
                                        'label'            => \__( 'Get Readability Scores', 'wordpress-seo' ),
×
205
                                        'description'      => \__( 'Get the readability scores for the most recently modified posts.', 'wordpress-seo' ),
×
206
                                        'output_schema'    => $this->wrap_in_array_schema( $this->get_score_output_schema() ),
×
207
                                        'execute_callback' => [ $this->score_retriever, 'get_readability_scores' ],
×
208
                                ],
×
209
                        ),
×
210
                );
×
211
        }
212

213
        /**
214
         * Registers the inclusive language scores ability.
215
         *
216
         * @return void
217
         */
218
        private function register_inclusive_language_scores_ability(): void {
×
219
                \wp_register_ability(
×
220
                        Ability_Categories_Integration::CATEGORY_SLUG . '/get-inclusive-language-scores',
×
221
                        $this->get_shared_ability_args(
×
222
                                [
×
223
                                        'label'            => \__( 'Get Inclusive Language Scores', 'wordpress-seo' ),
×
224
                                        'description'      => \__( 'Get the inclusive language scores for the most recently modified posts.', 'wordpress-seo' ),
×
225
                                        'output_schema'    => $this->wrap_in_array_schema( $this->get_score_output_schema() ),
×
226
                                        'execute_callback' => [ $this->score_retriever, 'get_inclusive_language_scores' ],
×
227
                                ],
×
228
                        ),
×
229
                );
×
230
        }
231

232
        /**
233
         * Registers the get post SEO data ability.
234
         *
235
         * @return void
236
         */
UNCOV
237
        private function register_get_post_seo_data_ability(): void {
×
UNCOV
238
                \wp_register_ability(
×
UNCOV
239
                        Ability_Categories_Integration::CATEGORY_SLUG . '/get-post-seo-data',
×
UNCOV
240
                        $this->get_shared_ability_args(
×
UNCOV
241
                                [
×
UNCOV
242
                                        'label'               => \__( 'Get Post SEO Data', 'wordpress-seo' ),
×
NEW
243
                                        'description'         => \__( 'Get the SEO data for a post. Identify the post by post_id, by permalink (URL), or by title keywords; the title may be a comma-separated list and returns the SEO data for every post matching any of the values, paginated most recently modified first (use the page parameter to reach older matches). At least one identifier is required. Only posts the current user is allowed to edit are returned.', 'wordpress-seo' ),
×
UNCOV
244
                                        'input_schema'        => $this->get_post_identifier_input_schema(),
×
UNCOV
245
                                        'output_schema'       => $this->wrap_in_array_schema( $this->get_post_seo_data_output_schema() ),
×
NEW
246
                                        'permission_callback' => [ $this, 'can_edit_advanced_metadata' ],
×
UNCOV
247
                                        'execute_callback'    => [ $this->post_seo_data_collector, 'get_post_seo_data' ],
×
UNCOV
248
                                ],
×
UNCOV
249
                        ),
×
UNCOV
250
                );
×
251
        }
252

253
        /**
254
         * Registers the update post SEO data ability.
255
         *
256
         * @return void
257
         */
UNCOV
258
        private function register_update_post_seo_data_ability(): void {
×
UNCOV
259
                \wp_register_ability(
×
UNCOV
260
                        Ability_Categories_Integration::CATEGORY_SLUG . '/update-post-seo-data',
×
UNCOV
261
                        $this->get_shared_ability_args(
×
UNCOV
262
                                [
×
UNCOV
263
                                        'label'               => \__( 'Update Post SEO Data', 'wordpress-seo' ),
×
NEW
264
                                        'description'         => \__( 'Update the SEO data for a single post. Identify the post by post_id or by permalink (URL). Only the fields you provide are changed; a provided empty value clears that field. Only posts the current user is allowed to edit can be updated.', 'wordpress-seo' ),
×
UNCOV
265
                                        'input_schema'        => $this->get_update_post_seo_data_input_schema(),
×
UNCOV
266
                                        'output_schema'       => $this->get_post_seo_data_output_schema(),
×
NEW
267
                                        'permission_callback' => [ $this, 'can_edit_advanced_metadata' ],
×
UNCOV
268
                                        'execute_callback'    => [ $this->post_seo_data_updater, 'update_post_seo_data' ],
×
UNCOV
269
                                        'meta'                => [
×
UNCOV
270
                                                'show_in_rest' => true,
×
UNCOV
271
                                                'annotations'  => [
×
UNCOV
272
                                                        'readonly'    => false,
×
UNCOV
273
                                                        'destructive' => false,
×
UNCOV
274
                                                        'idempotent'  => true,
×
UNCOV
275
                                                ],
×
UNCOV
276
                                                'mcp'          => [
×
UNCOV
277
                                                        'public' => true,
×
UNCOV
278
                                                ],
×
UNCOV
279
                                        ],
×
UNCOV
280
                                ],
×
UNCOV
281
                        ),
×
UNCOV
282
                );
×
283
        }
284

285
        // phpcs:disable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint -- Too complicated of a param declaration for this case.
286

287
        /**
288
         * Returns the shared ability arguments merged with ability-specific arguments.
289
         *
290
         * @param array<string, mixed> $ability_specific_args The ability-specific arguments.
291
         *
292
         * @return array<string, mixed> The merged ability arguments.
293
         */
294
        private function get_shared_ability_args( array $ability_specific_args ): array {
×
295
        // phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint
296
                return \array_merge(
×
297
                        [
×
298
                                'category'            => Ability_Categories_Integration::CATEGORY_SLUG,
×
299
                                'input_schema'        => [
×
300
                                        'type'                 => 'object',
×
301
                                        'additionalProperties' => false,
×
302
                                        'properties'           => [
×
303
                                                'number_of_posts' => [
×
304
                                                        'type'        => 'integer',
×
305
                                                        'description' => \__( 'The number of recently modified posts to retrieve scores for. Defaults to 10.', 'wordpress-seo' ),
×
306
                                                        'minimum'     => 1,
×
307
                                                        'maximum'     => 100,
×
308
                                                        'default'     => 10,
×
309
                                                ],
×
310
                                        ],
×
311
                                ],
×
312
                                'permission_callback' => [ $this, 'can_manage_seo' ],
×
313
                                'meta'                => [
×
314
                                        'show_in_rest' => true,
×
315
                                        'annotations'  => [
×
316
                                                'readonly'    => true,
×
317
                                                'destructive' => false,
×
318
                                                'idempotent'  => true,
×
319
                                        ],
×
320
                                        'mcp'          => [
×
321
                                                'public' => true,
×
322
                                        ],
×
323
                                ],
×
324
                        ],
×
325
                        $ability_specific_args,
×
326
                );
×
327
        }
328

329
        /**
330
         * Wraps an item schema in an array schema.
331
         *
332
         * @param array<string, array<string, string>> $item_schema The item schema.
333
         *
334
         * @return array<string, array<string, string>> The array schema.
335
         */
336
        private function wrap_in_array_schema( array $item_schema ): array {
×
337
                return [
×
338
                        'type'  => 'array',
×
339
                        'items' => $item_schema,
×
340
                ];
×
341
        }
342

343
        /**
344
         * Returns the score output schema, including the title property.
345
         *
346
         * @return array<string, array<string, string>> The score output schema.
347
         */
348
        private function get_score_output_schema(): array {
×
349
                return [
×
350
                        'type'       => 'object',
×
351
                        'properties' => [
×
352
                                'title' => [
×
353
                                        'type'        => 'string',
×
354
                                        'description' => \__( 'The post title.', 'wordpress-seo' ),
×
355
                                ],
×
356
                                'score' => [
×
357
                                        'type'        => 'string',
×
358
                                        'enum'        => [ 'na', 'bad', 'ok', 'good' ],
×
359
                                        'description' => \__( 'The score slug.', 'wordpress-seo' ),
×
360
                                ],
×
361
                                'label' => [
×
362
                                        'type'        => 'string',
×
363
                                        'description' => \__( 'A human-readable label for the score.', 'wordpress-seo' ),
×
364
                                ],
×
365
                        ],
×
366
                ];
×
367
        }
368

369
        // phpcs:disable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint -- The JSON schema arrays are heterogeneous by nature.
370

371
        /**
372
         * Returns the input schema for identifying a post (read path).
373
         *
374
         * @return array<string, mixed> The input schema.
375
         */
376
        private function get_post_identifier_input_schema(): array {
×
377
                return [
×
378
                        'type'                 => 'object',
×
379
                        'additionalProperties' => false,
×
380
                        'properties'           => [
×
381
                                'post_id'   => [
×
382
                                        'type'        => 'integer',
×
383
                                        'description' => \__( 'The ID of the post to retrieve.', 'wordpress-seo' ),
×
384
                                        'minimum'     => 1,
×
385
                                ],
×
386
                                'permalink' => [
×
387
                                        'type'        => 'string',
×
388
                                        'description' => \__( 'The permalink (URL) of the post to retrieve.', 'wordpress-seo' ),
×
389
                                ],
×
390
                                'title'     => [
×
391
                                        'type'        => 'string',
×
392
                                        'description' => \__( 'Keywords to search for in post titles. Provide a comma-separated list to search for several titles at once; each value is matched as a whole phrase against the post title, and a post matching any value is returned. At most 10 phrases are used per request; any beyond the first 10 are ignored. Results are paginated to 10 entities per page; see the page parameter.', 'wordpress-seo' ),
×
393
                                ],
×
394
                                'page'      => [
×
395
                                        'type'        => 'integer',
×
396
                                        'description' => \__( 'The page of title-search results to return, 1-based and defaulting to 1. Matches are ordered most recently modified first, so request a later page to reach older matches. An empty result means there are no further pages. Only applies to a title search.', 'wordpress-seo' ),
×
397
                                        'minimum'     => 1,
×
398
                                        'default'     => 1,
×
399
                                ],
×
400
                        ],
×
401
                ];
×
402
        }
403

404
        /**
405
         * Returns the input schema for updating a post's SEO data (write path).
406
         *
407
         * @return array<string, mixed> The input schema.
408
         */
UNCOV
409
        private function get_update_post_seo_data_input_schema(): array {
×
UNCOV
410
                $nullable_string = [ 'type' => [ 'string', 'null' ] ];
×
411

UNCOV
412
                return [
×
UNCOV
413
                        'type'                 => 'object',
×
UNCOV
414
                        'additionalProperties' => false,
×
UNCOV
415
                        'properties'           => [
×
UNCOV
416
                                'post_id'                => [
×
UNCOV
417
                                        'type'        => 'integer',
×
UNCOV
418
                                        'description' => \__( 'The ID of the post to update.', 'wordpress-seo' ),
×
UNCOV
419
                                        'minimum'     => 1,
×
UNCOV
420
                                ],
×
UNCOV
421
                                'permalink'              => [
×
UNCOV
422
                                        'type'        => 'string',
×
UNCOV
423
                                        'description' => \__( 'The permalink (URL) of the post to update.', 'wordpress-seo' ),
×
UNCOV
424
                                ],
×
UNCOV
425
                                'seo_title'              => $nullable_string,
×
UNCOV
426
                                'meta_description'       => $nullable_string,
×
UNCOV
427
                                'focus_keyphrase'        => \array_merge( $nullable_string, [ 'maxLength' => 191 ] ),
×
UNCOV
428
                                'canonical'              => $nullable_string,
×
UNCOV
429
                                'is_cornerstone'         => [ 'type' => 'boolean' ],
×
UNCOV
430
                                'noindex'                => [
×
UNCOV
431
                                        'type'        => [ 'boolean', 'null' ],
×
UNCOV
432
                                        'description' => \__( 'Whether search engines should be told not to index this post. true sets noindex (the post is excluded from search results); false forces the post to be indexed; null clears the setting and falls back to the post-type default.', 'wordpress-seo' ),
×
UNCOV
433
                                ],
×
UNCOV
434
                                'nofollow'               => [ 'type' => 'boolean' ],
×
UNCOV
435
                                'noimageindex'           => [ 'type' => 'boolean' ],
×
UNCOV
436
                                'noarchive'              => [ 'type' => 'boolean' ],
×
UNCOV
437
                                'nosnippet'              => [ 'type' => 'boolean' ],
×
UNCOV
438
                                'open_graph_title'       => $nullable_string,
×
UNCOV
439
                                'open_graph_description' => $nullable_string,
×
UNCOV
440
                                'twitter_title'          => $nullable_string,
×
UNCOV
441
                                'twitter_description'    => $nullable_string,
×
UNCOV
442
                                'schema_page_type'       => $this->nullable_enum_schema(
×
UNCOV
443
                                        \array_keys( Schema_Types::PAGE_TYPES ),
×
UNCOV
444
                                        \__( 'The Schema.org page type for the post. Must be one of the supported page types. Use null to clear it and fall back to the default.', 'wordpress-seo' ),
×
UNCOV
445
                                ),
×
UNCOV
446
                                'schema_article_type'    => $this->nullable_enum_schema(
×
UNCOV
447
                                        $this->get_schema_article_types(),
×
UNCOV
448
                                        \__( 'The Schema.org article type for the post. Must be one of the supported article types. Use null to clear it and fall back to the default.', 'wordpress-seo' ),
×
UNCOV
449
                                ),
×
UNCOV
450
                        ],
×
UNCOV
451
                ];
×
452
        }
453

454
        /**
455
         * Returns the allowed Schema.org article type values.
456
         *
457
         * Mirrors the validation in WPSEO_Option_Titles so the ability accepts exactly the
458
         * article types the editor does, including any registered through the filter.
459
         *
460
         * @return array<int, string> The allowed article type values.
461
         */
462
        private function get_schema_article_types(): array {
×
463
                /**
464
                 * Filter: 'wpseo_schema_article_types' - Allow developers to filter the available article types.
465
                 *
466
                 * Make sure when you filter this to also filter `wpseo_schema_article_types_labels`.
467
                 *
468
                 * @param array $schema_article_types The available schema article types.
469
                 */
470
                return \array_keys( \apply_filters( 'wpseo_schema_article_types', Schema_Types::ARTICLE_TYPES ) );
×
471
        }
472

473
        /**
474
         * Returns a nullable-string input schema constrained to a fixed set of allowed values.
475
         *
476
         * Null and the empty string are always allowed on top of the enum so the field can be
477
         * cleared, matching the patch-clear semantics of the other write fields.
478
         *
479
         * @param array<int, string> $allowed_values The allowed string values.
480
         * @param string             $description    The field description.
481
         *
482
         * @return array<string, mixed> The input schema fragment.
483
         */
484
        private function nullable_enum_schema( array $allowed_values, string $description ): array {
×
485
                return [
×
486
                        'type'        => [ 'string', 'null' ],
×
487
                        'description' => $description,
×
488
                        'enum'        => \array_merge( $allowed_values, [ '', null ] ),
×
489
                ];
×
490
        }
491

492
        /**
493
         * Returns the output schema describing a post's SEO data.
494
         *
495
         * @return array<string, mixed> The output schema.
496
         */
UNCOV
497
        private function get_post_seo_data_output_schema(): array {
×
UNCOV
498
                $nullable_string = [
×
UNCOV
499
                        'type' => [ 'string', 'null' ],
×
UNCOV
500
                ];
×
501
                $score           = static function ( $analysis ) {
UNCOV
502
                        return [
×
UNCOV
503
                                'type'        => 'string',
×
UNCOV
504
                                'enum'        => [ 'na', 'bad', 'ok', 'good' ],
×
UNCOV
505
                                'description' => \sprintf(
×
506
                                        /* translators: %s expands to the name of the analysis, e.g. "SEO analysis". */
UNCOV
507
                                        \__( 'The result of the %s that ran on the post when it was last saved.', 'wordpress-seo' ),
×
UNCOV
508
                                        $analysis,
×
UNCOV
509
                                ),
×
UNCOV
510
                        ];
×
511
                };
512

513
                // The rendered companion of a field carries the value as actually output on the front end: the global default template applied where no custom value is set, with replacement variables expanded.
514
                $rendered = static function ( $field ) {
UNCOV
515
                        return [
×
UNCOV
516
                                'type'        => [ 'string', 'null' ],
×
UNCOV
517
                                'description' => \sprintf(
×
518
                                        /* translators: %s expands to the name of the SEO field, e.g. "SEO title". */
UNCOV
519
                                        \__( 'The %s as output on the front end: the global default template applied when no custom value is set, with replacement variables expanded. Null when nothing is output.', 'wordpress-seo' ),
×
UNCOV
520
                                        $field,
×
UNCOV
521
                                ),
×
UNCOV
522
                        ];
×
523
                };
524

UNCOV
525
                return [
×
UNCOV
526
                        'type'       => 'object',
×
UNCOV
527
                        'properties' => [
×
UNCOV
528
                                'post_id'                         => [ 'type' => 'integer' ],
×
UNCOV
529
                                'post_title'                      => $nullable_string,
×
UNCOV
530
                                'permalink'                       => $nullable_string,
×
UNCOV
531
                                'post_type'                       => [ 'type' => 'string' ],
×
UNCOV
532
                                'post_status'                     => $nullable_string,
×
UNCOV
533
                                'seo_title'                       => $nullable_string,
×
UNCOV
534
                                'seo_title_rendered'              => $rendered( \__( 'SEO title', 'wordpress-seo' ) ),
×
UNCOV
535
                                'meta_description'                => $nullable_string,
×
UNCOV
536
                                'meta_description_rendered'       => $rendered( \__( 'meta description', 'wordpress-seo' ) ),
×
UNCOV
537
                                'focus_keyphrase'                 => $nullable_string,
×
UNCOV
538
                                'canonical'                       => $nullable_string,
×
UNCOV
539
                                'canonical_rendered'              => $rendered( \__( 'canonical URL', 'wordpress-seo' ) ),
×
UNCOV
540
                                'is_cornerstone'                  => [ 'type' => 'boolean' ],
×
UNCOV
541
                                'noindex'                         => [
×
UNCOV
542
                                        'type'        => [ 'boolean', 'null' ],
×
UNCOV
543
                                        'description' => \__( 'Whether search engines are told not to index this post. true means noindex (the post is excluded from search results); false means the post is forced to be indexed; null means no setting is stored and the post-type default applies.', 'wordpress-seo' ),
×
UNCOV
544
                                ],
×
UNCOV
545
                                'nofollow'                        => [ 'type' => 'boolean' ],
×
UNCOV
546
                                'noimageindex'                    => [ 'type' => 'boolean' ],
×
UNCOV
547
                                'noarchive'                       => [ 'type' => 'boolean' ],
×
UNCOV
548
                                'nosnippet'                       => [ 'type' => 'boolean' ],
×
UNCOV
549
                                'open_graph_title'                => $nullable_string,
×
UNCOV
550
                                'open_graph_title_rendered'       => $rendered( \__( 'Open Graph title', 'wordpress-seo' ) ),
×
UNCOV
551
                                'open_graph_description'          => $nullable_string,
×
UNCOV
552
                                'open_graph_description_rendered' => $rendered( \__( 'Open Graph description', 'wordpress-seo' ) ),
×
UNCOV
553
                                'twitter_title'                   => $nullable_string,
×
UNCOV
554
                                'twitter_title_rendered'          => $rendered( \__( 'Twitter title', 'wordpress-seo' ) ),
×
UNCOV
555
                                'twitter_description'             => $nullable_string,
×
UNCOV
556
                                'twitter_description_rendered'    => $rendered( \__( 'Twitter description', 'wordpress-seo' ) ),
×
UNCOV
557
                                'schema_page_type'                => $nullable_string,
×
UNCOV
558
                                'schema_article_type'             => $nullable_string,
×
UNCOV
559
                                'seo_score'                       => $score( \__( 'SEO analysis', 'wordpress-seo' ) ),
×
UNCOV
560
                                'readability_score'               => $score( \__( 'readability analysis', 'wordpress-seo' ) ),
×
UNCOV
561
                                'inclusive_language_score'        => $score( \__( 'inclusive language analysis', 'wordpress-seo' ) ),
×
UNCOV
562
                        ],
×
UNCOV
563
                ];
×
564
        }
565

566
        // phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint
567
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc