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

Yoast / wordpress-seo / b297a102e39b2007cbf436fb16441d0beb8d529d

17 Dec 2025 01:13PM UTC coverage: 41.433% (-11.1%) from 52.525%
b297a102e39b2007cbf436fb16441d0beb8d529d

Pull #22772

github

web-flow
Merge 7e4648015 into cdc37a449
Pull Request #22772: Add schema feature toggle and settings page

2611 of 9726 branches covered (26.85%)

Branch coverage included in aggregate %.

19 of 181 new or added lines in 17 files covered. (10.5%)

82 existing lines in 17 files now uncovered.

22742 of 51465 relevant lines covered (44.19%)

4.71 hits per line

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

26.95
/src/integrations/settings-integration.php
1
<?php
2

3
namespace Yoast\WP\SEO\Integrations;
4

5
use WP_Post;
6
use WP_Post_Type;
7
use WP_Taxonomy;
8
use WP_User;
9
use WPSEO_Addon_Manager;
10
use WPSEO_Admin_Asset_Manager;
11
use WPSEO_Admin_Editor_Specific_Replace_Vars;
12
use WPSEO_Admin_Recommended_Replace_Vars;
13
use WPSEO_Option_Titles;
14
use WPSEO_Options;
15
use WPSEO_Replace_Vars;
16
use WPSEO_Shortlinker;
17
use WPSEO_Sitemaps_Router;
18
use Yoast\WP\SEO\Conditionals\Settings_Conditional;
19
use Yoast\WP\SEO\Config\Schema_Types;
20
use Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications;
21
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
22
use Yoast\WP\SEO\Helpers\Language_Helper;
23
use Yoast\WP\SEO\Helpers\Options_Helper;
24
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
25
use Yoast\WP\SEO\Helpers\Product_Helper;
26
use Yoast\WP\SEO\Helpers\Route_Helper;
27
use Yoast\WP\SEO\Helpers\Schema\Article_Helper;
28
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
29
use Yoast\WP\SEO\Helpers\User_Helper;
30
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
31
use Yoast\WP\SEO\Llms_Txt\Application\Configuration\Llms_Txt_Configuration;
32
use Yoast\WP\SEO\Llms_Txt\Application\Health_Check\File_Runner;
33
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Content\Manual_Post_Collection;
34
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
35
use Yoast\WP\SEO\Schema\Application\Configuration\Schema_Configuration;
36

37
/**
38
 * Class Settings_Integration.
39
 */
40
class Settings_Integration implements Integration_Interface {
41

42
        public const PAGE = 'wpseo_page_settings';
43

44
        /**
45
         * Holds the included WordPress options.
46
         *
47
         * @var string[]
48
         */
49
        public const WP_OPTIONS = [ 'blogdescription' ];
50

51
        /**
52
         * Holds the allowed option groups.
53
         *
54
         * @var array
55
         */
56
        public const ALLOWED_OPTION_GROUPS = [ 'wpseo', 'wpseo_titles', 'wpseo_social', 'wpseo_llmstxt' ];
57

58
        /**
59
         * Holds the disallowed settings, per option group.
60
         *
61
         * @var array
62
         */
63
        public const DISALLOWED_SETTINGS = [
64
                'wpseo'        => [
65
                        'myyoast-oauth',
66
                        'semrush_tokens',
67
                        'custom_taxonomy_slugs',
68
                        'import_cursors',
69
                        'workouts_data',
70
                        'configuration_finished_steps',
71
                        'importing_completed',
72
                        'wincher_tokens',
73
                        'least_readability_ignore_list',
74
                        'least_seo_score_ignore_list',
75
                        'most_linked_ignore_list',
76
                        'least_linked_ignore_list',
77
                        'indexables_page_reading_list',
78
                        'show_new_content_type_notification',
79
                        'new_post_types',
80
                        'new_taxonomies',
81
                ],
82
                'wpseo_titles' => [
83
                        'company_logo_meta',
84
                        'person_logo_meta',
85
                ],
86
        ];
87

88
        /**
89
         * Holds the disabled on multisite settings, per option group.
90
         *
91
         * @var array
92
         */
93
        public const DISABLED_ON_MULTISITE_SETTINGS = [
94
                'wpseo' => [
95
                        'deny_search_crawling',
96
                        'deny_wp_json_crawling',
97
                        'deny_adsbot_crawling',
98
                        'deny_ccbot_crawling',
99
                        'deny_google_extended_crawling',
100
                        'deny_gptbot_crawling',
101
                        'enable_llms_txt',
102
                ],
103
        ];
104

105
        /**
106
         * Holds the WPSEO_Admin_Asset_Manager.
107
         *
108
         * @var WPSEO_Admin_Asset_Manager
109
         */
110
        protected $asset_manager;
111

112
        /**
113
         * Holds the WPSEO_Replace_Vars.
114
         *
115
         * @var WPSEO_Replace_Vars
116
         */
117
        protected $replace_vars;
118

119
        /**
120
         * Holds the Schema_Types.
121
         *
122
         * @var Schema_Types
123
         */
124
        protected $schema_types;
125

126
        /**
127
         * Holds the Current_Page_Helper.
128
         *
129
         * @var Current_Page_Helper
130
         */
131
        protected $current_page_helper;
132

133
        /**
134
         * Holds the Post_Type_Helper.
135
         *
136
         * @var Post_Type_Helper
137
         */
138
        protected $post_type_helper;
139

140
        /**
141
         * Holds the Language_Helper.
142
         *
143
         * @var Language_Helper
144
         */
145
        protected $language_helper;
146

147
        /**
148
         * Holds the Taxonomy_Helper.
149
         *
150
         * @var Taxonomy_Helper
151
         */
152
        protected $taxonomy_helper;
153

154
        /**
155
         * Holds the Product_Helper.
156
         *
157
         * @var Product_Helper
158
         */
159
        protected $product_helper;
160

161
        /**
162
         * Holds the Woocommerce_Helper.
163
         *
164
         * @var Woocommerce_Helper
165
         */
166
        protected $woocommerce_helper;
167

168
        /**
169
         * Holds the Article_Helper.
170
         *
171
         * @var Article_Helper
172
         */
173
        protected $article_helper;
174

175
        /**
176
         * Holds the User_Helper.
177
         *
178
         * @var User_Helper
179
         */
180
        protected $user_helper;
181

182
        /**
183
         * Holds the Options_Helper instance.
184
         *
185
         * @var Options_Helper
186
         */
187
        protected $options;
188

189
        /**
190
         * Holds the Content_Type_Visibility_Dismiss_Notifications instance.
191
         *
192
         * @var Content_Type_Visibility_Dismiss_Notifications
193
         */
194
        protected $content_type_visibility;
195

196
        /**
197
         * Holds the Llms_Txt_Configuration instance.
198
         *
199
         * @var Llms_Txt_Configuration
200
         */
201
        protected $llms_txt_configuration;
202

203
        /**
204
         * Holds the Schema_Configuration instance.
205
         *
206
         * @var Schema_Configuration
207
         */
208
        protected $schema_configuration;
209

210
        /**
211
         * The manual post collection.
212
         *
213
         * @var Manual_Post_Collection
214
         */
215
        private $manual_post_collection;
216

217
        /**
218
         * Runs the health check.
219
         *
220
         * @var File_Runner
221
         */
222
        private $runner;
223

224
        /**
225
         * Holds the Route_Helper.
226
         *
227
         * @var Route_Helper
228
         */
229
        private $route_helper;
230

231
        /**
232
         * Constructs Settings_Integration.
233
         *
234
         * @param WPSEO_Admin_Asset_Manager                     $asset_manager           The WPSEO_Admin_Asset_Manager.
235
         * @param WPSEO_Replace_Vars                            $replace_vars            The WPSEO_Replace_Vars.
236
         * @param Schema_Types                                  $schema_types            The Schema_Types.
237
         * @param Current_Page_Helper                           $current_page_helper     The Current_Page_Helper.
238
         * @param Post_Type_Helper                              $post_type_helper        The Post_Type_Helper.
239
         * @param Language_Helper                               $language_helper         The Language_Helper.
240
         * @param Taxonomy_Helper                               $taxonomy_helper         The Taxonomy_Helper.
241
         * @param Product_Helper                                $product_helper          The Product_Helper.
242
         * @param Woocommerce_Helper                            $woocommerce_helper      The Woocommerce_Helper.
243
         * @param Article_Helper                                $article_helper          The Article_Helper.
244
         * @param User_Helper                                   $user_helper             The User_Helper.
245
         * @param Options_Helper                                $options                 The options helper.
246
         * @param Content_Type_Visibility_Dismiss_Notifications $content_type_visibility The Content_Type_Visibility_Dismiss_Notifications instance.
247
         * @param Llms_Txt_Configuration                        $llms_txt_configuration  The Llms_Txt_Configuration instance.
248
         * @param Manual_Post_Collection                        $manual_post_collection  The manual post collection.
249
         * @param File_Runner                                   $runner                  The file runner.
250
         * @param Route_Helper                                  $route_helper            The Route_Helper.
251
         * @param Schema_Configuration                          $schema_configuration    The Schema_Configuration.
252
         */
253
        public function __construct(
2✔
254
                WPSEO_Admin_Asset_Manager $asset_manager,
255
                WPSEO_Replace_Vars $replace_vars,
256
                Schema_Types $schema_types,
257
                Current_Page_Helper $current_page_helper,
258
                Post_Type_Helper $post_type_helper,
259
                Language_Helper $language_helper,
260
                Taxonomy_Helper $taxonomy_helper,
261
                Product_Helper $product_helper,
262
                Woocommerce_Helper $woocommerce_helper,
263
                Article_Helper $article_helper,
264
                User_Helper $user_helper,
265
                Options_Helper $options,
266
                Content_Type_Visibility_Dismiss_Notifications $content_type_visibility,
267
                Llms_Txt_Configuration $llms_txt_configuration,
268
                Manual_Post_Collection $manual_post_collection,
269
                File_Runner $runner,
270
                Route_Helper $route_helper,
271
                Schema_Configuration $schema_configuration
272
        ) {
273
                $this->asset_manager           = $asset_manager;
2✔
274
                $this->replace_vars            = $replace_vars;
2✔
275
                $this->schema_types            = $schema_types;
2✔
276
                $this->current_page_helper     = $current_page_helper;
2✔
277
                $this->taxonomy_helper         = $taxonomy_helper;
2✔
278
                $this->post_type_helper        = $post_type_helper;
2✔
279
                $this->language_helper         = $language_helper;
2✔
280
                $this->product_helper          = $product_helper;
2✔
281
                $this->woocommerce_helper      = $woocommerce_helper;
2✔
282
                $this->article_helper          = $article_helper;
2✔
283
                $this->user_helper             = $user_helper;
2✔
284
                $this->options                 = $options;
2✔
285
                $this->content_type_visibility = $content_type_visibility;
2✔
286
                $this->llms_txt_configuration  = $llms_txt_configuration;
2✔
287
                $this->manual_post_collection  = $manual_post_collection;
2✔
288
                $this->runner                  = $runner;
2✔
289
                $this->route_helper            = $route_helper;
2✔
290
                $this->schema_configuration    = $schema_configuration;
2✔
291
        }
292

293
        /**
294
         * Returns the conditionals based on which this loadable should be active.
295
         *
296
         * @return array
297
         */
298
        public static function get_conditionals() {
2✔
299
                return [ Settings_Conditional::class ];
2✔
300
        }
301

302
        /**
303
         * Initializes the integration.
304
         *
305
         * This is the place to register hooks and filters.
306
         *
307
         * @return void
308
         */
309
        public function register_hooks() {
×
310
                // Add page.
311
                \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
×
312
                \add_filter( 'admin_menu', [ $this, 'add_settings_saved_page' ] );
×
313

314
                // Are we saving the settings?
315
                if ( $this->current_page_helper->get_current_admin_page() === 'options.php' ) {
×
316
                        $post_action = '';
×
317
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
318
                        if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) {
×
319
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
320
                                $post_action = \wp_unslash( $_POST['action'] );
×
321
                        }
322
                        $option_page = '';
×
323
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
324
                        if ( isset( $_POST['option_page'] ) && \is_string( $_POST['option_page'] ) ) {
×
325
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
326
                                $option_page = \wp_unslash( $_POST['option_page'] );
×
327
                        }
328

329
                        if ( $post_action === 'update' && $option_page === self::PAGE ) {
×
330
                                \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
331
                                \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
332
                        }
333

334
                        return;
×
335
                }
336

337
                // Are we on the settings page?
338
                if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
×
339
                        \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
340
                        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
×
341
                        \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
342
                }
343
        }
344

345
        /**
346
         * Registers the different options under the setting.
347
         *
348
         * @return void
349
         */
350
        public function register_setting() {
×
351
                foreach ( WPSEO_Options::$options as $name => $instance ) {
×
352
                        if ( \in_array( $name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
353
                                \register_setting( self::PAGE, $name );
×
354
                        }
355
                }
356
                // Only register WP options when the user is allowed to manage them.
357
                if ( \current_user_can( 'manage_options' ) ) {
×
358
                        foreach ( self::WP_OPTIONS as $name ) {
×
359
                                \register_setting( self::PAGE, $name );
×
360
                        }
361
                }
362
        }
363

364
        /**
365
         * Adds the page.
366
         *
367
         * @param array $pages The pages.
368
         *
369
         * @return array The pages.
370
         */
371
        public function add_page( $pages ) {
×
372
                \array_splice(
×
373
                        $pages,
×
374
                        1,
×
375
                        0,
×
376
                        [
×
377
                                [
×
378
                                        'wpseo_dashboard',
×
379
                                        '',
×
380
                                        \__( 'Settings', 'wordpress-seo' ),
×
381
                                        'wpseo_manage_options',
×
382
                                        self::PAGE,
×
383
                                        [ $this, 'display_page' ],
×
384
                                ],
×
385
                        ]
×
386
                );
×
387

388
                return $pages;
×
389
        }
390

391
        /**
392
         * Adds a dummy page.
393
         *
394
         * Because the options route NEEDS to redirect to something.
395
         *
396
         * @param array $pages The pages.
397
         *
398
         * @return array The pages.
399
         */
400
        public function add_settings_saved_page( $pages ) {
2✔
401
                $runner = $this->runner;
2✔
402
                \add_submenu_page(
2✔
403
                        '',
2✔
404
                        '',
2✔
405
                        '',
2✔
406
                        'wpseo_manage_options',
2✔
407
                        self::PAGE . '_saved',
2✔
408
                        static function () use ( $runner ) {
2✔
409
                                // Add success indication to HTML response.
410
                                $success = empty( \get_settings_errors() ) ? 'true' : 'false';
×
411
                                echo \esc_html( "{{ yoast-success: $success }}" );
×
412

413
                                $runner->run();
×
414
                                if ( ! $runner->is_successful() ) {
×
415
                                        $failure_reason = $runner->get_generation_failure_reason();
×
416
                                        echo \esc_html( "{{ yoast-llms-txt-generation-failure: $failure_reason }}" );
×
417
                                }
418
                        }
2✔
419
                );
2✔
420

421
                return $pages;
2✔
422
        }
423

424
        /**
425
         * Displays the page.
426
         *
427
         * @return void
428
         */
429
        public function display_page() {
×
430
                echo '<div id="yoast-seo-settings"></div>';
×
431
        }
432

433
        /**
434
         * Enqueues the assets.
435
         *
436
         * @return void
437
         */
438
        public function enqueue_assets() {
×
439
                // Remove the emoji script as it is incompatible with both React and any contenteditable fields.
440
                \remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
×
441
                \wp_enqueue_media();
×
442
                $this->asset_manager->enqueue_script( 'new-settings' );
×
443
                $this->asset_manager->enqueue_style( 'new-settings' );
×
444
                if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-promotion' ) ) {
×
445
                        $this->asset_manager->enqueue_style( 'black-friday-banner' );
×
446
                }
447
                $this->asset_manager->localize_script( 'new-settings', 'wpseoScriptData', $this->get_script_data() );
×
448
        }
449

450
        /**
451
         * Removes all current WP notices.
452
         *
453
         * @return void
454
         */
455
        public function remove_notices() {
×
456
                \remove_all_actions( 'admin_notices' );
×
457
                \remove_all_actions( 'user_admin_notices' );
×
458
                \remove_all_actions( 'network_admin_notices' );
×
459
                \remove_all_actions( 'all_admin_notices' );
×
460
        }
461

462
        /**
463
         * Creates the script data.
464
         *
465
         * @return array The script data.
466
         */
467
        protected function get_script_data() {
×
468
                $default_setting_values = $this->get_default_setting_values();
×
469
                $settings               = $this->get_settings( $default_setting_values );
×
470
                $post_types             = $this->post_type_helper->get_indexable_post_type_objects();
×
471
                $taxonomies             = $this->taxonomy_helper->get_indexable_taxonomy_objects();
×
472

473
                // Check if attachments are included in indexation.
474
                if ( ! \array_key_exists( 'attachment', $post_types ) ) {
×
475
                        // Always include attachments in the settings, to let the user enable them again.
476
                        $attachment_object = \get_post_type_object( 'attachment' );
×
477
                        if ( ! empty( $attachment_object ) ) {
×
478
                                $post_types['attachment'] = $attachment_object;
×
479
                        }
480
                }
481
                // Check if post formats are included in indexation.
482
                if ( ! \array_key_exists( 'post_format', $taxonomies ) ) {
×
483
                        // Always include post_format in the settings, to let the user enable them again.
484
                        $post_format_object = \get_taxonomy( 'post_format' );
×
485
                        if ( ! empty( $post_format_object ) ) {
×
486
                                $taxonomies['post_format'] = $post_format_object;
×
487
                        }
488
                }
489

490
                $transformed_post_types = $this->transform_post_types( $post_types );
×
491
                $transformed_taxonomies = $this->transform_taxonomies( $taxonomies, \array_keys( $transformed_post_types ) );
×
492

493
                // Check if there is a new content type to show notification only once in the settings.
494
                $show_new_content_type_notification = $this->content_type_visibility->maybe_add_settings_notification();
×
495

496
                return [
×
497
                        'settings'                       => $this->transform_settings( $settings ),
×
498
                        'defaultSettingValues'           => $default_setting_values,
×
499
                        'disabledSettings'               => $this->get_disabled_settings( $settings ),
×
500
                        'endpoint'                       => \admin_url( 'options.php' ),
×
501
                        'nonce'                          => \wp_create_nonce( self::PAGE . '-options' ),
×
502
                        'separators'                     => WPSEO_Option_Titles::get_instance()->get_separator_options_for_display(),
×
503
                        'replacementVariables'           => $this->get_replacement_variables(),
×
504
                        'schema'                         => $this->get_schema( $transformed_post_types ),
×
505
                        'preferences'                    => $this->get_preferences( $settings ),
×
506
                        'linkParams'                     => WPSEO_Shortlinker::get_query_params(),
×
507
                        'postTypes'                      => $transformed_post_types,
×
508
                        'taxonomies'                     => $transformed_taxonomies,
×
509
                        'fallbacks'                      => $this->get_fallbacks(),
×
510
                        'showNewContentTypeNotification' => $show_new_content_type_notification,
×
511
                        'currentPromotions'              => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
×
512
                        'llmsTxt'                        => $this->llms_txt_configuration->get_configuration(),
×
513
                        'initialLlmTxtPages'             => $this->get_site_llms_txt_pages( $settings ),
×
NEW
514
                        'schemaFrameworkConfiguration'   => $this->schema_configuration->get_configuration(),
×
UNCOV
515
                ];
×
516
        }
517

518
        /**
519
         * Retrieves the preferences.
520
         *
521
         * @param array $settings The settings.
522
         *
523
         * @return array The preferences.
524
         */
525
        protected function get_preferences( $settings ) {
×
526
                $shop_page_id             = $this->woocommerce_helper->get_shop_page_id();
×
527
                $homepage_is_latest_posts = \get_option( 'show_on_front' ) === 'posts';
×
528
                $page_on_front            = \get_option( 'page_on_front' );
×
529
                $page_for_posts           = \get_option( 'page_for_posts' );
×
530

531
                $addon_manager          = new WPSEO_Addon_Manager();
×
532
                $woocommerce_seo_active = \is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) );
×
533

534
                if ( empty( $page_on_front ) ) {
×
535
                        $page_on_front = $page_for_posts;
×
536
                }
537

538
                $business_settings_url = \get_admin_url( null, 'admin.php?page=wpseo_local' );
×
539
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
540
                        $local_options      = \get_option( 'wpseo_local' );
×
541
                        $multiple_locations = $local_options['use_multiple_locations'];
×
542
                        $same_organization  = $local_options['multiple_locations_same_organization'];
×
543
                        if ( $multiple_locations === 'on' && $same_organization !== 'on' ) {
×
544
                                $business_settings_url = \get_admin_url( null, 'edit.php?post_type=wpseo_locations' );
×
545
                        }
546
                }
547

548
                return [
×
549
                        'isPremium'                     => $this->product_helper->is_premium(),
×
550
                        'isRtl'                         => \is_rtl(),
×
551
                        'isNetworkAdmin'                => \is_network_admin(),
×
552
                        'isMainSite'                    => \is_main_site(),
×
553
                        'isMultisite'                   => \is_multisite(),
×
554
                        'isWooCommerceActive'           => $this->woocommerce_helper->is_active(),
×
555
                        'isLocalSeoActive'              => \defined( 'WPSEO_LOCAL_FILE' ),
×
556
                        'isNewsSeoActive'               => \defined( 'WPSEO_NEWS_FILE' ),
×
557
                        'isWooCommerceSEOActive'        => $woocommerce_seo_active,
×
558
                        'siteUrl'                       => \get_bloginfo( 'url' ),
×
559
                        'siteTitle'                     => \get_bloginfo( 'name' ),
×
560
                        'sitemapUrl'                    => WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ),
×
561
                        'hasWooCommerceShopPage'        => $shop_page_id !== -1,
×
562
                        'editWooCommerceShopPageUrl'    => \get_edit_post_link( $shop_page_id, 'js' ),
×
563
                        'wooCommerceShopPageSettingUrl' => \get_admin_url( null, 'admin.php?page=wc-settings&tab=products' ),
×
564
                        'localSeoPageSettingUrl'        => $business_settings_url,
×
565
                        'homepageIsLatestPosts'         => $homepage_is_latest_posts,
×
566
                        'homepagePageEditUrl'           => \get_edit_post_link( $page_on_front, 'js' ),
×
567
                        'homepagePostsEditUrl'          => \get_edit_post_link( $page_for_posts, 'js' ),
×
568
                        'createUserUrl'                 => \admin_url( 'user-new.php' ),
×
569
                        'createPageUrl'                 => \admin_url( 'post-new.php?post_type=page' ),
×
570
                        'editUserUrl'                   => \admin_url( 'user-edit.php' ),
×
571
                        'editTaxonomyUrl'               => \admin_url( 'edit-tags.php' ),
×
572
                        'generalSettingsUrl'            => \admin_url( 'options-general.php' ),
×
573
                        'companyOrPersonMessage'        => \apply_filters( 'wpseo_knowledge_graph_setting_msg', '' ),
×
574
                        'currentUserId'                 => \get_current_user_id(),
×
575
                        'canCreateUsers'                => \current_user_can( 'create_users' ),
×
576
                        'canCreatePages'                => \current_user_can( 'edit_pages' ),
×
577
                        'canEditUsers'                  => \current_user_can( 'edit_users' ),
×
578
                        'canManageOptions'              => \current_user_can( 'manage_options' ),
×
579
                        'userLocale'                    => \str_replace( '_', '-', \get_user_locale() ),
×
580
                        'pluginUrl'                     => \plugins_url( '', \WPSEO_FILE ),
×
581
                        'showForceRewriteTitlesSetting' => ! \current_theme_supports( 'title-tag' ) && ! ( \function_exists( 'wp_is_block_theme' ) && \wp_is_block_theme() ),
×
582
                        'upsellSettings'                => $this->get_upsell_settings(),
×
583
                        'siteRepresentsPerson'          => $this->get_site_represents_person( $settings ),
×
584
                        'siteBasicsPolicies'            => $this->get_site_basics_policies( $settings ),
×
585
                ];
×
586
        }
587

588
        /**
589
         * Retrieves the currently represented person.
590
         *
591
         * @param array $settings The settings.
592
         *
593
         * @return array The currently represented person.
594
         */
595
        protected function get_site_represents_person( $settings ) {
×
596
                $person = [
×
597
                        'id'   => false,
×
598
                        'name' => '',
×
599
                ];
×
600

601
                if ( isset( $settings['wpseo_titles']['company_or_person_user_id'] ) ) {
×
602
                        $person['id'] = $settings['wpseo_titles']['company_or_person_user_id'];
×
603
                        $user         = \get_userdata( $person['id'] );
×
604
                        if ( $user instanceof WP_User ) {
×
605
                                $person['name'] = $user->get( 'display_name' );
×
606
                        }
607
                }
608

609
                return $person;
×
610
        }
611

612
        /**
613
         * Get site policy data.
614
         *
615
         * @param array $settings The settings.
616
         *
617
         * @return array The policy data.
618
         */
619
        private function get_site_basics_policies( $settings ) {
×
620
                $policies = [];
×
621

622
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['publishing_principles_id'], 'publishing_principles_id' );
×
623
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ownership_funding_info_id'], 'ownership_funding_info_id' );
×
624
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['actionable_feedback_policy_id'], 'actionable_feedback_policy_id' );
×
625
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['corrections_policy_id'], 'corrections_policy_id' );
×
626
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ethics_policy_id'], 'ethics_policy_id' );
×
627
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_policy_id'], 'diversity_policy_id' );
×
628
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_staffing_report_id'], 'diversity_staffing_report_id' );
×
629

630
                return $policies;
×
631
        }
632

633
        /**
634
         * Adds policy data if it is present.
635
         *
636
         * @param array  $policies The existing policy data.
637
         * @param int    $policy   The policy id to check.
638
         * @param string $key      The option key name.
639
         *
640
         * @return array<int, string> The policy data.
641
         */
642
        private function maybe_add_policy( $policies, $policy, $key ) {
×
643
                $policy_array = [
×
644
                        'id'   => 0,
×
645
                        'name' => \__( 'None', 'wordpress-seo' ),
×
646
                ];
×
647

648
                if ( isset( $policy ) && \is_int( $policy ) ) {
×
649
                        $policy_array['id'] = $policy;
×
650
                        $post               = \get_post( $policy );
×
651
                        if ( $post instanceof WP_Post ) {
×
652
                                if ( $post->post_status !== 'publish' || $post->post_password !== '' ) {
×
653
                                        return $policies;
×
654
                                }
655
                                $policy_array['name'] = $post->post_title;
×
656
                        }
657
                }
658

659
                $policies[ $key ] = $policy_array;
×
660

661
                return $policies;
×
662
        }
663

664
        /**
665
         * Adds page if it is present.
666
         *
667
         * @param array<int, string> $pages   The existing pages.
668
         * @param int                $page_id The page id to check.
669
         * @param string             $key     The option key name.
670
         *
671
         * @return array<int, string> The policy data.
672
         */
673
        private function maybe_add_page( $pages, $page_id, $key ) {
×
674
                if ( isset( $page_id ) && \is_int( $page_id ) && $page_id !== 0 ) {
×
675
                        $post = $this->manual_post_collection->get_content_type_entry( $page_id );
×
676
                        if ( $post === null ) {
×
677
                                return $pages;
×
678
                        }
679

680
                        $pages[ $key ] = [
×
681
                                'id'    => $page_id,
×
682
                                'title' => ( $post->get_title() ) ? $post->get_title() : $post->get_slug(),
×
683
                                'slug'  => $post->get_slug(),
×
684
                        ];
×
685
                }
686

687
                return $pages;
×
688
        }
689

690
        /**
691
         * Get site llms.txt pages.
692
         *
693
         * @param array $settings The settings.
694
         *
695
         * @return array<string, array<string, int|string>> The llms.txt pages.
696
         */
697
        private function get_site_llms_txt_pages( $settings ) {
×
698
                $llms_txt_pages = [];
×
699

700
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['about_us_page'], 'about_us_page' );
×
701
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['contact_page'], 'contact_page' );
×
702
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['terms_page'], 'terms_page' );
×
703
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['privacy_policy_page'], 'privacy_policy_page' );
×
704
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['shop_page'], 'shop_page' );
×
705

706
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) && \is_array( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
707
                        foreach ( $settings['wpseo_llmstxt']['other_included_pages'] as $key => $page_id ) {
×
708
                                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $page_id, 'other_included_pages-' . $key );
×
709
                        }
710
                }
711

712
                return $llms_txt_pages;
×
713
        }
714

715
        /**
716
         * Returns settings for the Call to Buy (CTB) buttons.
717
         *
718
         * @return array<string> The array of CTB settings.
719
         */
720
        public function get_upsell_settings() {
×
721
                return [
×
722
                        'actionId'     => 'load-nfd-ctb',
×
723
                        'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
×
724
                ];
×
725
        }
726

727
        /**
728
         * Retrieves the default setting values.
729
         *
730
         * These default values are currently being used in the UI for dummy fields.
731
         * Dummy fields should not expose or reflect the actual data.
732
         *
733
         * @return array The default setting values.
734
         */
735
        protected function get_default_setting_values() {
×
736
                $defaults = [];
×
737

738
                // Add Yoast settings.
739
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
740
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
741
                                $option_instance          = WPSEO_Options::get_option_instance( $option_name );
×
742
                                $defaults[ $option_name ] = ( $option_instance ) ? $option_instance->get_defaults() : [];
×
743
                        }
744
                }
745
                // Add WP settings.
746
                foreach ( self::WP_OPTIONS as $option_name ) {
×
747
                        $defaults[ $option_name ] = '';
×
748
                }
749

750
                // Remove disallowed settings.
751
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
752
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
753
                                unset( $defaults[ $option_name ][ $disallowed_setting ] );
×
754
                        }
755
                }
756

757
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
758
                        $defaults = $this->get_defaults_from_local_seo( $defaults );
×
759
                }
760

761
                return $defaults;
×
762
        }
763

764
        /**
765
         * Retrieves the organization schema values from Local SEO for defaults in Site representation fields.
766
         * Specifically for the org-vat-id, org-tax-id, org-email and org-phone options.
767
         *
768
         * @param array<string|int|bool> $defaults The settings defaults.
769
         *
770
         * @return array<string|int|bool> The settings defaults with local seo overides.
771
         */
772
        protected function get_defaults_from_local_seo( $defaults ) {
8✔
773
                $local_options      = \get_option( 'wpseo_local' );
8✔
774
                $multiple_locations = $local_options['use_multiple_locations'];
8✔
775
                $same_organization  = $local_options['multiple_locations_same_organization'];
8✔
776
                $shared_info        = $local_options['multiple_locations_shared_business_info'];
8✔
777
                if ( $multiple_locations !== 'on' || ( $multiple_locations === 'on' && $same_organization === 'on' && $shared_info === 'on' ) ) {
8✔
778
                        $defaults['wpseo_titles']['org-vat-id'] = $local_options['location_vat_id'];
6✔
779
                        $defaults['wpseo_titles']['org-tax-id'] = $local_options['location_tax_id'];
6✔
780
                        $defaults['wpseo_titles']['org-email']  = $local_options['location_email'];
6✔
781
                        $defaults['wpseo_titles']['org-phone']  = $local_options['location_phone'];
6✔
782
                }
783

784
                if ( \wpseo_has_primary_location() ) {
8✔
785
                        $primary_location = $local_options['multiple_locations_primary_location'];
4✔
786

787
                        $location_keys = [
4✔
788
                                'org-phone'  => [
4✔
789
                                        'is_overridden' => '_wpseo_is_overridden_business_phone',
4✔
790
                                        'value'         => '_wpseo_business_phone',
4✔
791
                                ],
4✔
792
                                'org-email'  => [
4✔
793
                                        'is_overridden' => '_wpseo_is_overridden_business_email',
4✔
794
                                        'value'         => '_wpseo_business_email',
4✔
795
                                ],
4✔
796
                                'org-tax-id' => [
4✔
797
                                        'is_overridden' => '_wpseo_is_overridden_business_tax_id',
4✔
798
                                        'value'         => '_wpseo_business_tax_id',
4✔
799
                                ],
4✔
800
                                'org-vat-id' => [
4✔
801
                                        'is_overridden' => '_wpseo_is_overridden_business_vat_id',
4✔
802
                                        'value'         => '_wpseo_business_vat_id',
4✔
803
                                ],
4✔
804
                        ];
4✔
805

806
                        foreach ( $location_keys as $key => $meta_keys ) {
4✔
807
                                $is_overridden = ( $shared_info === 'on' ) ? \get_post_meta( $primary_location, $meta_keys['is_overridden'], true ) : false;
4✔
808
                                if ( $is_overridden === 'on' || $shared_info !== 'on' ) {
4✔
809
                                        $post_meta_value                  = \get_post_meta( $primary_location, $meta_keys['value'], true );
4✔
810
                                        $defaults['wpseo_titles'][ $key ] = ( $post_meta_value ) ? $post_meta_value : '';
4✔
811
                                }
812
                        }
813
                }
814

815
                return $defaults;
8✔
816
        }
817

818
        /**
819
         * Retrieves the settings and their values.
820
         *
821
         * @param array $default_setting_values The default setting values.
822
         *
823
         * @return array The settings.
824
         */
825
        protected function get_settings( $default_setting_values ) {
×
826
                $settings = [];
×
827

828
                // Add Yoast settings.
829
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
830
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
831
                                $settings[ $option_name ] = \array_merge( $default_setting_values[ $option_name ], WPSEO_Options::get_option( $option_name ) );
×
832
                        }
833
                }
834
                // Add WP settings.
835
                foreach ( self::WP_OPTIONS as $option_name ) {
×
836
                        $settings[ $option_name ] = \get_option( $option_name );
×
837
                }
838

839
                // Remove disallowed settings.
840
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
841
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
842
                                unset( $settings[ $option_name ][ $disallowed_setting ] );
×
843
                        }
844
                }
845

846
                return $settings;
×
847
        }
848

849
        /**
850
         * Transforms setting values.
851
         *
852
         * @param array $settings The settings.
853
         *
854
         * @return array The settings.
855
         */
856
        protected function transform_settings( $settings ) {
×
857
                if ( isset( $settings['wpseo_titles']['breadcrumbs-sep'] ) ) {
×
858
                        /**
859
                         * The breadcrumbs separator default value is the HTML entity `&raquo;`.
860
                         * Which does not get decoded in our JS, while it did in our Yoast form. Decode it here as an exception.
861
                         */
862
                        $settings['wpseo_titles']['breadcrumbs-sep'] = \html_entity_decode(
×
863
                                $settings['wpseo_titles']['breadcrumbs-sep'],
×
864
                                ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
865
                                'UTF-8'
×
866
                        );
×
867
                }
868

869
                /**
870
                 * Decode some WP options.
871
                 */
872
                $settings['blogdescription'] = \html_entity_decode(
×
873
                        $settings['blogdescription'],
×
874
                        ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
875
                        'UTF-8'
×
876
                );
×
877

878
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
879
                        // Append an empty page to the other included pages, so that we manage to show an empty field in the UI.
880
                        $settings['wpseo_llmstxt']['other_included_pages'][] = 0;
881
                }
882

883
                return $settings;
884
        }
885

886
        /**
887
         * Retrieves the disabled settings.
888
         *
889
         * @param array $settings The settings.
890
         *
891
         * @return array The settings.
892
         */
893
        protected function get_disabled_settings( $settings ) {
×
894
                $disabled_settings = [];
895
                $site_language     = $this->language_helper->get_language();
×
896

897
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
898
                        if ( ! \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
899
                                continue;
900
                        }
901

902
                        $disabled_settings[ $option_name ] = [];
×
903
                        $option_instance                   = WPSEO_Options::get_option_instance( $option_name );
×
904
                        if ( $option_instance === false ) {
905
                                continue;
×
906
                        }
907
                        foreach ( $settings[ $option_name ] as $setting_name => $setting_value ) {
×
908
                                if ( $option_instance->is_disabled( $setting_name ) ) {
909
                                        $disabled_settings[ $option_name ][ $setting_name ] = 'network';
910
                                }
911
                        }
912
                }
913

914
                // Remove disabled on multisite settings.
915
                if ( \is_multisite() ) {
×
916
                        foreach ( self::DISABLED_ON_MULTISITE_SETTINGS as $option_name => $disabled_ms_settings ) {
×
917
                                if ( \array_key_exists( $option_name, $disabled_settings ) ) {
×
918
                                        foreach ( $disabled_ms_settings as $disabled_ms_setting ) {
919
                                                $disabled_settings[ $option_name ][ $disabled_ms_setting ] = 'multisite';
920
                                        }
921
                                }
922
                        }
923
                }
924

925
                if ( \array_key_exists( 'wpseo', $disabled_settings ) && ! $this->language_helper->has_inclusive_language_support( $site_language ) ) {
926
                        $disabled_settings['wpseo']['inclusive_language_analysis_active'] = 'language';
927
                }
928

929
                return $disabled_settings;
930
        }
931

932
        /**
933
         * Retrieves the replacement variables.
934
         *
935
         * @return array The replacement variables.
936
         */
937
        protected function get_replacement_variables() {
×
938
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
939
                $specific_replace_vars    = new WPSEO_Admin_Editor_Specific_Replace_Vars();
940
                $replacement_variables    = $this->replace_vars->get_replacement_variables_with_labels();
941

942
                return [
943
                        'variables'   => $replacement_variables,
944
                        'recommended' => $recommended_replace_vars->get_recommended_replacevars(),
945
                        'specific'    => $specific_replace_vars->get(),
946
                        'shared'      => $specific_replace_vars->get_generic( $replacement_variables ),
947
                ];
948
        }
949

950
        /**
951
         * Retrieves the schema.
952
         *
953
         * @param array $post_types The post types.
954
         *
955
         * @return array The schema.
956
         */
957
        protected function get_schema( array $post_types ) {
×
958
                $schema = [];
×
959

960
                foreach ( $this->schema_types->get_article_type_options() as $article_type ) {
×
961
                        $schema['articleTypes'][ $article_type['value'] ] = [
×
962
                                'label' => $article_type['name'],
×
963
                                'value' => $article_type['value'],
×
964
                        ];
×
965
                }
966

967
                foreach ( $this->schema_types->get_page_type_options() as $page_type ) {
×
968
                        $schema['pageTypes'][ $page_type['value'] ] = [
×
969
                                'label' => $page_type['name'],
×
970
                                'value' => $page_type['value'],
×
971
                        ];
×
972
                }
973

974
                $schema['articleTypeDefaults'] = [];
×
975
                $schema['pageTypeDefaults']    = [];
×
976
                foreach ( $post_types as $name => $post_type ) {
×
977
                        $schema['articleTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-article-type-$name" );
978
                        $schema['pageTypeDefaults'][ $name ]    = WPSEO_Options::get_default( 'wpseo_titles', "schema-page-type-$name" );
979
                }
980

981
                return $schema;
982
        }
983

984
        /**
985
         * Transforms the post types, to represent them.
986
         *
987
         * @param WP_Post_Type[] $post_types The WP_Post_Type array to transform.
988
         *
989
         * @return array The post types.
990
         */
991
        protected function transform_post_types( $post_types ) {
4✔
992
                $transformed    = [];
4✔
993
                $new_post_types = $this->options->get( 'new_post_types', [] );
4✔
994
                foreach ( $post_types as $post_type ) {
4✔
995
                        $transformed[ $post_type->name ] = [
4✔
996
                                'name'                 => $post_type->name,
4✔
997
                                'route'                => $this->route_helper->get_route( $post_type->name, $post_type->rewrite, $post_type->rest_base ),
4✔
998
                                'label'                => $post_type->label,
4✔
999
                                'singularLabel'        => $post_type->labels->singular_name,
4✔
1000
                                'hasArchive'           => $this->post_type_helper->has_archive( $post_type ),
4✔
1001
                                'hasSchemaArticleType' => $this->article_helper->is_article_post_type( $post_type->name ),
4✔
1002
                                'menuPosition'         => $post_type->menu_position,
4✔
1003
                                'isNew'                => \in_array( $post_type->name, $new_post_types, true ),
4✔
1004
                        ];
4✔
1005
                }
1006

1007
                \uasort( $transformed, [ $this, 'compare_post_types' ] );
4✔
1008

1009
                return $transformed;
1010
        }
1011

1012
        /**
1013
         * Compares two post types.
1014
         *
1015
         * @param array $a The first post type.
1016
         * @param array $b The second post type.
1017
         *
1018
         * @return int The order.
1019
         */
1020
        protected function compare_post_types( $a, $b ) {
×
1021
                if ( $a['menuPosition'] === null && $b['menuPosition'] !== null ) {
1022
                        return 1;
×
1023
                }
1024
                if ( $a['menuPosition'] !== null && $b['menuPosition'] === null ) {
1025
                        return -1;
1026
                }
1027

1028
                if ( $a['menuPosition'] === null && $b['menuPosition'] === null ) {
×
1029
                        // No position specified, order alphabetically by label.
1030
                        return \strnatcmp( $a['label'], $b['label'] );
1031
                }
1032

1033
                return ( ( $a['menuPosition'] < $b['menuPosition'] ) ? -1 : 1 );
1034
        }
1035

1036
        /**
1037
         * Transforms the taxonomies, to represent them.
1038
         *
1039
         * @param WP_Taxonomy[] $taxonomies      The WP_Taxonomy array to transform.
1040
         * @param string[]      $post_type_names The post type names.
1041
         *
1042
         * @return array The taxonomies.
1043
         */
1044
        protected function transform_taxonomies( $taxonomies, $post_type_names ) {
4✔
1045
                $transformed    = [];
4✔
1046
                $new_taxonomies = $this->options->get( 'new_taxonomies', [] );
4✔
1047
                foreach ( $taxonomies as $taxonomy ) {
4✔
1048
                        $transformed[ $taxonomy->name ] = [
4✔
1049
                                'name'          => $taxonomy->name,
4✔
1050
                                'route'         => $this->route_helper->get_route( $taxonomy->name, $taxonomy->rewrite, $taxonomy->rest_base ),
4✔
1051
                                'label'         => $taxonomy->label,
4✔
1052
                                'showUi'        => $taxonomy->show_ui,
4✔
1053
                                'singularLabel' => $taxonomy->labels->singular_name,
4✔
1054
                                'postTypes'     => \array_filter(
4✔
1055
                                        $taxonomy->object_type,
4✔
1056
                                        static function ( $object_type ) use ( $post_type_names ) {
4✔
1057
                                                return \in_array( $object_type, $post_type_names, true );
4✔
1058
                                        }
4✔
1059
                                ),
4✔
1060
                                'isNew'         => \in_array( $taxonomy->name, $new_taxonomies, true ),
4✔
1061
                        ];
4✔
1062
                }
1063

1064
                \uasort(
4✔
1065
                        $transformed,
4✔
1066
                        static function ( $a, $b ) {
4✔
1067
                                return \strnatcmp( $a['label'], $b['label'] );
4✔
1068
                        }
4✔
1069
                );
1070

1071
                return $transformed;
1072
        }
1073

1074
        /**
1075
         * Retrieves the fallbacks.
1076
         *
1077
         * @return array The fallbacks.
1078
         */
1079
        protected function get_fallbacks() {
×
1080
                $site_logo_id = \get_option( 'site_logo' );
×
1081
                if ( ! $site_logo_id ) {
×
1082
                        $site_logo_id = \get_theme_mod( 'custom_logo' );
1083
                }
1084
                if ( ! $site_logo_id ) {
1085
                        $site_logo_id = '0';
×
1086
                }
1087

1088
                return [
×
1089
                        'siteLogoId' => $site_logo_id,
×
1090
                ];
×
1091
        }
1092
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc