• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

Yoast / wordpress-seo / cd5bd938c4d65c703ce9c754323ed3f188bbbd3f

15 Dec 2025 10:11AM UTC coverage: 53.039% (-0.3%) from 53.381%
cd5bd938c4d65c703ce9c754323ed3f188bbbd3f

push

github

YoastBot
Bump version to 26.6 on free

8706 of 16073 branches covered (54.17%)

Branch coverage included in aggregate %.

32426 of 61477 relevant lines covered (52.74%)

46902.67 hits per line

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

26.84
/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

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

41
        public const PAGE = 'wpseo_page_settings';
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

202
        /**
203
         * The manual post collection.
204
         *
205
         * @var Manual_Post_Collection
206
         */
207
        private $manual_post_collection;
208

209
        /**
210
         * Runs the health check.
211
         *
212
         * @var File_Runner
213
         */
214
        private $runner;
215

216
        /**
217
         * Holds the Route_Helper.
218
         *
219
         * @var Route_Helper
220
         */
221
        private $route_helper;
222

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

282
        /**
283
         * Returns the conditionals based on which this loadable should be active.
284
         *
285
         * @return array
286
         */
287
        public static function get_conditionals() {
2✔
288
                return [ Settings_Conditional::class ];
2✔
289
        }
290

291
        /**
292
         * Initializes the integration.
293
         *
294
         * This is the place to register hooks and filters.
295
         *
296
         * @return void
297
         */
298
        public function register_hooks() {
×
299
                // Add page.
300
                \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
×
301
                \add_filter( 'admin_menu', [ $this, 'add_settings_saved_page' ] );
×
302

303
                // Are we saving the settings?
304
                if ( $this->current_page_helper->get_current_admin_page() === 'options.php' ) {
×
305
                        $post_action = '';
×
306
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
307
                        if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) {
×
308
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
309
                                $post_action = \wp_unslash( $_POST['action'] );
×
310
                        }
311
                        $option_page = '';
×
312
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
313
                        if ( isset( $_POST['option_page'] ) && \is_string( $_POST['option_page'] ) ) {
×
314
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
315
                                $option_page = \wp_unslash( $_POST['option_page'] );
×
316
                        }
317

318
                        if ( $post_action === 'update' && $option_page === self::PAGE ) {
×
319
                                \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
320
                                \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
321
                        }
322

323
                        return;
×
324
                }
325

326
                // Are we on the settings page?
327
                if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
×
328
                        \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
329
                        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
×
330
                        \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
331
                }
332
        }
333

334
        /**
335
         * Registers the different options under the setting.
336
         *
337
         * @return void
338
         */
339
        public function register_setting() {
×
340
                foreach ( WPSEO_Options::$options as $name => $instance ) {
×
341
                        if ( \in_array( $name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
342
                                \register_setting( self::PAGE, $name );
×
343
                        }
344
                }
345
                // Only register WP options when the user is allowed to manage them.
346
                if ( \current_user_can( 'manage_options' ) ) {
×
347
                        foreach ( self::WP_OPTIONS as $name ) {
×
348
                                \register_setting( self::PAGE, $name );
×
349
                        }
350
                }
351
        }
352

353
        /**
354
         * Adds the page.
355
         *
356
         * @param array $pages The pages.
357
         *
358
         * @return array The pages.
359
         */
360
        public function add_page( $pages ) {
×
361
                \array_splice(
×
362
                        $pages,
×
363
                        1,
×
364
                        0,
×
365
                        [
×
366
                                [
×
367
                                        'wpseo_dashboard',
×
368
                                        '',
×
369
                                        \__( 'Settings', 'wordpress-seo' ),
×
370
                                        'wpseo_manage_options',
×
371
                                        self::PAGE,
×
372
                                        [ $this, 'display_page' ],
×
373
                                ],
×
374
                        ]
×
375
                );
×
376

377
                return $pages;
×
378
        }
379

380
        /**
381
         * Adds a dummy page.
382
         *
383
         * Because the options route NEEDS to redirect to something.
384
         *
385
         * @param array $pages The pages.
386
         *
387
         * @return array The pages.
388
         */
389
        public function add_settings_saved_page( $pages ) {
2✔
390
                $runner = $this->runner;
2✔
391
                \add_submenu_page(
2✔
392
                        '',
2✔
393
                        '',
2✔
394
                        '',
2✔
395
                        'wpseo_manage_options',
2✔
396
                        self::PAGE . '_saved',
2✔
397
                        static function () use ( $runner ) {
2✔
398
                                // Add success indication to HTML response.
399
                                $success = empty( \get_settings_errors() ) ? 'true' : 'false';
×
400
                                echo \esc_html( "{{ yoast-success: $success }}" );
×
401

402
                                $runner->run();
×
403
                                if ( ! $runner->is_successful() ) {
×
404
                                        $failure_reason = $runner->get_generation_failure_reason();
×
405
                                        echo \esc_html( "{{ yoast-llms-txt-generation-failure: $failure_reason }}" );
×
406
                                }
407
                        }
2✔
408
                );
2✔
409

410
                return $pages;
2✔
411
        }
412

413
        /**
414
         * Displays the page.
415
         *
416
         * @return void
417
         */
418
        public function display_page() {
×
419
                echo '<div id="yoast-seo-settings"></div>';
×
420
        }
421

422
        /**
423
         * Enqueues the assets.
424
         *
425
         * @return void
426
         */
427
        public function enqueue_assets() {
×
428
                // Remove the emoji script as it is incompatible with both React and any contenteditable fields.
429
                \remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
×
430
                \wp_enqueue_media();
×
431
                $this->asset_manager->enqueue_script( 'new-settings' );
×
432
                $this->asset_manager->enqueue_style( 'new-settings' );
×
433
                if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-promotion' ) ) {
×
434
                        $this->asset_manager->enqueue_style( 'black-friday-banner' );
×
435
                }
436
                $this->asset_manager->localize_script( 'new-settings', 'wpseoScriptData', $this->get_script_data() );
×
437
        }
438

439
        /**
440
         * Removes all current WP notices.
441
         *
442
         * @return void
443
         */
444
        public function remove_notices() {
×
445
                \remove_all_actions( 'admin_notices' );
×
446
                \remove_all_actions( 'user_admin_notices' );
×
447
                \remove_all_actions( 'network_admin_notices' );
×
448
                \remove_all_actions( 'all_admin_notices' );
×
449
        }
450

451
        /**
452
         * Creates the script data.
453
         *
454
         * @return array The script data.
455
         */
456
        protected function get_script_data() {
×
457
                $default_setting_values = $this->get_default_setting_values();
×
458
                $settings               = $this->get_settings( $default_setting_values );
×
459
                $post_types             = $this->post_type_helper->get_indexable_post_type_objects();
×
460
                $taxonomies             = $this->taxonomy_helper->get_indexable_taxonomy_objects();
×
461

462
                // Check if attachments are included in indexation.
463
                if ( ! \array_key_exists( 'attachment', $post_types ) ) {
×
464
                        // Always include attachments in the settings, to let the user enable them again.
465
                        $attachment_object = \get_post_type_object( 'attachment' );
×
466
                        if ( ! empty( $attachment_object ) ) {
×
467
                                $post_types['attachment'] = $attachment_object;
×
468
                        }
469
                }
470
                // Check if post formats are included in indexation.
471
                if ( ! \array_key_exists( 'post_format', $taxonomies ) ) {
×
472
                        // Always include post_format in the settings, to let the user enable them again.
473
                        $post_format_object = \get_taxonomy( 'post_format' );
×
474
                        if ( ! empty( $post_format_object ) ) {
×
475
                                $taxonomies['post_format'] = $post_format_object;
×
476
                        }
477
                }
478

479
                $transformed_post_types = $this->transform_post_types( $post_types );
×
480
                $transformed_taxonomies = $this->transform_taxonomies( $taxonomies, \array_keys( $transformed_post_types ) );
×
481

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

485
                return [
×
486
                        'settings'                       => $this->transform_settings( $settings ),
×
487
                        'defaultSettingValues'           => $default_setting_values,
×
488
                        'disabledSettings'               => $this->get_disabled_settings( $settings ),
×
489
                        'endpoint'                       => \admin_url( 'options.php' ),
×
490
                        'nonce'                          => \wp_create_nonce( self::PAGE . '-options' ),
×
491
                        'separators'                     => WPSEO_Option_Titles::get_instance()->get_separator_options_for_display(),
×
492
                        'replacementVariables'           => $this->get_replacement_variables(),
×
493
                        'schema'                         => $this->get_schema( $transformed_post_types ),
×
494
                        'preferences'                    => $this->get_preferences( $settings ),
×
495
                        'linkParams'                     => WPSEO_Shortlinker::get_query_params(),
×
496
                        'postTypes'                      => $transformed_post_types,
×
497
                        'taxonomies'                     => $transformed_taxonomies,
×
498
                        'fallbacks'                      => $this->get_fallbacks(),
×
499
                        'showNewContentTypeNotification' => $show_new_content_type_notification,
×
500
                        'currentPromotions'              => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
×
501
                        'llmsTxt'                        => $this->llms_txt_configuration->get_configuration(),
×
502
                        'initialLlmTxtPages'             => $this->get_site_llms_txt_pages( $settings ),
×
503
                ];
×
504
        }
505

506
        /**
507
         * Retrieves the preferences.
508
         *
509
         * @param array $settings The settings.
510
         *
511
         * @return array The preferences.
512
         */
513
        protected function get_preferences( $settings ) {
×
514
                $shop_page_id             = $this->woocommerce_helper->get_shop_page_id();
×
515
                $homepage_is_latest_posts = \get_option( 'show_on_front' ) === 'posts';
×
516
                $page_on_front            = \get_option( 'page_on_front' );
×
517
                $page_for_posts           = \get_option( 'page_for_posts' );
×
518

519
                $addon_manager          = new WPSEO_Addon_Manager();
×
520
                $woocommerce_seo_active = \is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) );
×
521

522
                if ( empty( $page_on_front ) ) {
×
523
                        $page_on_front = $page_for_posts;
×
524
                }
525

526
                $business_settings_url = \get_admin_url( null, 'admin.php?page=wpseo_local' );
×
527
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
528
                        $local_options      = \get_option( 'wpseo_local' );
×
529
                        $multiple_locations = $local_options['use_multiple_locations'];
×
530
                        $same_organization  = $local_options['multiple_locations_same_organization'];
×
531
                        if ( $multiple_locations === 'on' && $same_organization !== 'on' ) {
×
532
                                $business_settings_url = \get_admin_url( null, 'edit.php?post_type=wpseo_locations' );
×
533
                        }
534
                }
535

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

576
        /**
577
         * Retrieves the currently represented person.
578
         *
579
         * @param array $settings The settings.
580
         *
581
         * @return array The currently represented person.
582
         */
583
        protected function get_site_represents_person( $settings ) {
×
584
                $person = [
×
585
                        'id'   => false,
×
586
                        'name' => '',
×
587
                ];
×
588

589
                if ( isset( $settings['wpseo_titles']['company_or_person_user_id'] ) ) {
×
590
                        $person['id'] = $settings['wpseo_titles']['company_or_person_user_id'];
×
591
                        $user         = \get_userdata( $person['id'] );
×
592
                        if ( $user instanceof WP_User ) {
×
593
                                $person['name'] = $user->get( 'display_name' );
×
594
                        }
595
                }
596

597
                return $person;
×
598
        }
599

600
        /**
601
         * Get site policy data.
602
         *
603
         * @param array $settings The settings.
604
         *
605
         * @return array The policy data.
606
         */
607
        private function get_site_basics_policies( $settings ) {
×
608
                $policies = [];
×
609

610
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['publishing_principles_id'], 'publishing_principles_id' );
×
611
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ownership_funding_info_id'], 'ownership_funding_info_id' );
×
612
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['actionable_feedback_policy_id'], 'actionable_feedback_policy_id' );
×
613
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['corrections_policy_id'], 'corrections_policy_id' );
×
614
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ethics_policy_id'], 'ethics_policy_id' );
×
615
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_policy_id'], 'diversity_policy_id' );
×
616
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_staffing_report_id'], 'diversity_staffing_report_id' );
×
617

618
                return $policies;
×
619
        }
620

621
        /**
622
         * Adds policy data if it is present.
623
         *
624
         * @param array  $policies The existing policy data.
625
         * @param int    $policy   The policy id to check.
626
         * @param string $key      The option key name.
627
         *
628
         * @return array<int, string> The policy data.
629
         */
630
        private function maybe_add_policy( $policies, $policy, $key ) {
×
631
                $policy_array = [
×
632
                        'id'   => 0,
×
633
                        'name' => \__( 'None', 'wordpress-seo' ),
×
634
                ];
×
635

636
                if ( isset( $policy ) && \is_int( $policy ) ) {
×
637
                        $policy_array['id'] = $policy;
×
638
                        $post               = \get_post( $policy );
×
639
                        if ( $post instanceof WP_Post ) {
×
640
                                if ( $post->post_status !== 'publish' || $post->post_password !== '' ) {
×
641
                                        return $policies;
×
642
                                }
643
                                $policy_array['name'] = $post->post_title;
×
644
                        }
645
                }
646

647
                $policies[ $key ] = $policy_array;
×
648

649
                return $policies;
×
650
        }
651

652
        /**
653
         * Adds page if it is present.
654
         *
655
         * @param array<int, string> $pages   The existing pages.
656
         * @param int                $page_id The page id to check.
657
         * @param string             $key     The option key name.
658
         *
659
         * @return array<int, string> The policy data.
660
         */
661
        private function maybe_add_page( $pages, $page_id, $key ) {
×
662
                if ( isset( $page_id ) && \is_int( $page_id ) && $page_id !== 0 ) {
×
663
                        $post = $this->manual_post_collection->get_content_type_entry( $page_id );
×
664
                        if ( $post === null ) {
×
665
                                return $pages;
×
666
                        }
667

668
                        $pages[ $key ] = [
×
669
                                'id'    => $page_id,
×
670
                                'title' => ( $post->get_title() ) ? $post->get_title() : $post->get_slug(),
×
671
                                'slug'  => $post->get_slug(),
×
672
                        ];
×
673
                }
674

675
                return $pages;
×
676
        }
677

678
        /**
679
         * Get site llms.txt pages.
680
         *
681
         * @param array $settings The settings.
682
         *
683
         * @return array<string, array<string, int|string>> The llms.txt pages.
684
         */
685
        private function get_site_llms_txt_pages( $settings ) {
×
686
                $llms_txt_pages = [];
×
687

688
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['about_us_page'], 'about_us_page' );
×
689
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['contact_page'], 'contact_page' );
×
690
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['terms_page'], 'terms_page' );
×
691
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['privacy_policy_page'], 'privacy_policy_page' );
×
692
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['shop_page'], 'shop_page' );
×
693

694
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) && \is_array( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
695
                        foreach ( $settings['wpseo_llmstxt']['other_included_pages'] as $key => $page_id ) {
×
696
                                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $page_id, 'other_included_pages-' . $key );
×
697
                        }
698
                }
699

700
                return $llms_txt_pages;
×
701
        }
702

703
        /**
704
         * Returns settings for the Call to Buy (CTB) buttons.
705
         *
706
         * @return array<string> The array of CTB settings.
707
         */
708
        public function get_upsell_settings() {
×
709
                return [
×
710
                        'actionId'     => 'load-nfd-ctb',
×
711
                        'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
×
712
                ];
×
713
        }
714

715
        /**
716
         * Retrieves the default setting values.
717
         *
718
         * These default values are currently being used in the UI for dummy fields.
719
         * Dummy fields should not expose or reflect the actual data.
720
         *
721
         * @return array The default setting values.
722
         */
723
        protected function get_default_setting_values() {
×
724
                $defaults = [];
×
725

726
                // Add Yoast settings.
727
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
728
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
729
                                $option_instance          = WPSEO_Options::get_option_instance( $option_name );
×
730
                                $defaults[ $option_name ] = ( $option_instance ) ? $option_instance->get_defaults() : [];
×
731
                        }
732
                }
733
                // Add WP settings.
734
                foreach ( self::WP_OPTIONS as $option_name ) {
×
735
                        $defaults[ $option_name ] = '';
×
736
                }
737

738
                // Remove disallowed settings.
739
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
740
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
741
                                unset( $defaults[ $option_name ][ $disallowed_setting ] );
×
742
                        }
743
                }
744

745
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
746
                        $defaults = $this->get_defaults_from_local_seo( $defaults );
×
747
                }
748

749
                return $defaults;
×
750
        }
751

752
        /**
753
         * Retrieves the organization schema values from Local SEO for defaults in Site representation fields.
754
         * Specifically for the org-vat-id, org-tax-id, org-email and org-phone options.
755
         *
756
         * @param array<string|int|bool> $defaults The settings defaults.
757
         *
758
         * @return array<string|int|bool> The settings defaults with local seo overides.
759
         */
760
        protected function get_defaults_from_local_seo( $defaults ) {
8✔
761
                $local_options      = \get_option( 'wpseo_local' );
8✔
762
                $multiple_locations = $local_options['use_multiple_locations'];
8✔
763
                $same_organization  = $local_options['multiple_locations_same_organization'];
8✔
764
                $shared_info        = $local_options['multiple_locations_shared_business_info'];
8✔
765
                if ( $multiple_locations !== 'on' || ( $multiple_locations === 'on' && $same_organization === 'on' && $shared_info === 'on' ) ) {
8✔
766
                        $defaults['wpseo_titles']['org-vat-id'] = $local_options['location_vat_id'];
6✔
767
                        $defaults['wpseo_titles']['org-tax-id'] = $local_options['location_tax_id'];
6✔
768
                        $defaults['wpseo_titles']['org-email']  = $local_options['location_email'];
6✔
769
                        $defaults['wpseo_titles']['org-phone']  = $local_options['location_phone'];
6✔
770
                }
771

772
                if ( \wpseo_has_primary_location() ) {
8✔
773
                        $primary_location = $local_options['multiple_locations_primary_location'];
4✔
774

775
                        $location_keys = [
4✔
776
                                'org-phone'  => [
4✔
777
                                        'is_overridden' => '_wpseo_is_overridden_business_phone',
4✔
778
                                        'value'         => '_wpseo_business_phone',
4✔
779
                                ],
4✔
780
                                'org-email'  => [
4✔
781
                                        'is_overridden' => '_wpseo_is_overridden_business_email',
4✔
782
                                        'value'         => '_wpseo_business_email',
4✔
783
                                ],
4✔
784
                                'org-tax-id' => [
4✔
785
                                        'is_overridden' => '_wpseo_is_overridden_business_tax_id',
4✔
786
                                        'value'         => '_wpseo_business_tax_id',
4✔
787
                                ],
4✔
788
                                'org-vat-id' => [
4✔
789
                                        'is_overridden' => '_wpseo_is_overridden_business_vat_id',
4✔
790
                                        'value'         => '_wpseo_business_vat_id',
4✔
791
                                ],
4✔
792
                        ];
4✔
793

794
                        foreach ( $location_keys as $key => $meta_keys ) {
4✔
795
                                $is_overridden = ( $shared_info === 'on' ) ? \get_post_meta( $primary_location, $meta_keys['is_overridden'], true ) : false;
4✔
796
                                if ( $is_overridden === 'on' || $shared_info !== 'on' ) {
4✔
797
                                        $post_meta_value                  = \get_post_meta( $primary_location, $meta_keys['value'], true );
4✔
798
                                        $defaults['wpseo_titles'][ $key ] = ( $post_meta_value ) ? $post_meta_value : '';
4✔
799
                                }
800
                        }
801
                }
802

803
                return $defaults;
8✔
804
        }
805

806
        /**
807
         * Retrieves the settings and their values.
808
         *
809
         * @param array $default_setting_values The default setting values.
810
         *
811
         * @return array The settings.
812
         */
813
        protected function get_settings( $default_setting_values ) {
×
814
                $settings = [];
×
815

816
                // Add Yoast settings.
817
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
818
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
819
                                $settings[ $option_name ] = \array_merge( $default_setting_values[ $option_name ], WPSEO_Options::get_option( $option_name ) );
×
820
                        }
821
                }
822
                // Add WP settings.
823
                foreach ( self::WP_OPTIONS as $option_name ) {
×
824
                        $settings[ $option_name ] = \get_option( $option_name );
×
825
                }
826

827
                // Remove disallowed settings.
828
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
829
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
830
                                unset( $settings[ $option_name ][ $disallowed_setting ] );
×
831
                        }
832
                }
833

834
                return $settings;
×
835
        }
836

837
        /**
838
         * Transforms setting values.
839
         *
840
         * @param array $settings The settings.
841
         *
842
         * @return array The settings.
843
         */
844
        protected function transform_settings( $settings ) {
×
845
                if ( isset( $settings['wpseo_titles']['breadcrumbs-sep'] ) ) {
×
846
                        /**
847
                         * The breadcrumbs separator default value is the HTML entity `&raquo;`.
848
                         * Which does not get decoded in our JS, while it did in our Yoast form. Decode it here as an exception.
849
                         */
850
                        $settings['wpseo_titles']['breadcrumbs-sep'] = \html_entity_decode(
×
851
                                $settings['wpseo_titles']['breadcrumbs-sep'],
×
852
                                ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
853
                                'UTF-8'
×
854
                        );
×
855
                }
856

857
                /**
858
                 * Decode some WP options.
859
                 */
860
                $settings['blogdescription'] = \html_entity_decode(
×
861
                        $settings['blogdescription'],
×
862
                        ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
863
                        'UTF-8'
×
864
                );
×
865

866
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
867
                        // Append an empty page to the other included pages, so that we manage to show an empty field in the UI.
868
                        $settings['wpseo_llmstxt']['other_included_pages'][] = 0;
869
                }
870

871
                return $settings;
872
        }
873

874
        /**
875
         * Retrieves the disabled settings.
876
         *
877
         * @param array $settings The settings.
878
         *
879
         * @return array The settings.
880
         */
881
        protected function get_disabled_settings( $settings ) {
×
882
                $disabled_settings = [];
883
                $site_language     = $this->language_helper->get_language();
×
884

885
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
886
                        if ( ! \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
887
                                continue;
888
                        }
889

890
                        $disabled_settings[ $option_name ] = [];
×
891
                        $option_instance                   = WPSEO_Options::get_option_instance( $option_name );
×
892
                        if ( $option_instance === false ) {
893
                                continue;
×
894
                        }
895
                        foreach ( $settings[ $option_name ] as $setting_name => $setting_value ) {
×
896
                                if ( $option_instance->is_disabled( $setting_name ) ) {
897
                                        $disabled_settings[ $option_name ][ $setting_name ] = 'network';
898
                                }
899
                        }
900
                }
901

902
                // Remove disabled on multisite settings.
903
                if ( \is_multisite() ) {
×
904
                        foreach ( self::DISABLED_ON_MULTISITE_SETTINGS as $option_name => $disabled_ms_settings ) {
×
905
                                if ( \array_key_exists( $option_name, $disabled_settings ) ) {
×
906
                                        foreach ( $disabled_ms_settings as $disabled_ms_setting ) {
907
                                                $disabled_settings[ $option_name ][ $disabled_ms_setting ] = 'multisite';
908
                                        }
909
                                }
910
                        }
911
                }
912

913
                if ( \array_key_exists( 'wpseo', $disabled_settings ) && ! $this->language_helper->has_inclusive_language_support( $site_language ) ) {
914
                        $disabled_settings['wpseo']['inclusive_language_analysis_active'] = 'language';
915
                }
916

917
                return $disabled_settings;
918
        }
919

920
        /**
921
         * Retrieves the replacement variables.
922
         *
923
         * @return array The replacement variables.
924
         */
925
        protected function get_replacement_variables() {
×
926
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
927
                $specific_replace_vars    = new WPSEO_Admin_Editor_Specific_Replace_Vars();
928
                $replacement_variables    = $this->replace_vars->get_replacement_variables_with_labels();
929

930
                return [
931
                        'variables'   => $replacement_variables,
932
                        'recommended' => $recommended_replace_vars->get_recommended_replacevars(),
933
                        'specific'    => $specific_replace_vars->get(),
934
                        'shared'      => $specific_replace_vars->get_generic( $replacement_variables ),
935
                ];
936
        }
937

938
        /**
939
         * Retrieves the schema.
940
         *
941
         * @param array $post_types The post types.
942
         *
943
         * @return array The schema.
944
         */
945
        protected function get_schema( array $post_types ) {
×
946
                $schema = [];
×
947

948
                foreach ( $this->schema_types->get_article_type_options() as $article_type ) {
×
949
                        $schema['articleTypes'][ $article_type['value'] ] = [
×
950
                                'label' => $article_type['name'],
×
951
                                'value' => $article_type['value'],
×
952
                        ];
×
953
                }
954

955
                foreach ( $this->schema_types->get_page_type_options() as $page_type ) {
×
956
                        $schema['pageTypes'][ $page_type['value'] ] = [
×
957
                                'label' => $page_type['name'],
×
958
                                'value' => $page_type['value'],
×
959
                        ];
×
960
                }
961

962
                $schema['articleTypeDefaults'] = [];
×
963
                $schema['pageTypeDefaults']    = [];
×
964
                foreach ( $post_types as $name => $post_type ) {
×
965
                        $schema['articleTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-article-type-$name" );
966
                        $schema['pageTypeDefaults'][ $name ]    = WPSEO_Options::get_default( 'wpseo_titles', "schema-page-type-$name" );
967
                }
968

969
                return $schema;
970
        }
971

972
        /**
973
         * Transforms the post types, to represent them.
974
         *
975
         * @param WP_Post_Type[] $post_types The WP_Post_Type array to transform.
976
         *
977
         * @return array The post types.
978
         */
979
        protected function transform_post_types( $post_types ) {
4✔
980
                $transformed    = [];
4✔
981
                $new_post_types = $this->options->get( 'new_post_types', [] );
4✔
982
                foreach ( $post_types as $post_type ) {
4✔
983
                        $transformed[ $post_type->name ] = [
4✔
984
                                'name'                 => $post_type->name,
4✔
985
                                'route'                => $this->route_helper->get_route( $post_type->name, $post_type->rewrite, $post_type->rest_base ),
4✔
986
                                'label'                => $post_type->label,
4✔
987
                                'singularLabel'        => $post_type->labels->singular_name,
4✔
988
                                'hasArchive'           => $this->post_type_helper->has_archive( $post_type ),
4✔
989
                                'hasSchemaArticleType' => $this->article_helper->is_article_post_type( $post_type->name ),
4✔
990
                                'menuPosition'         => $post_type->menu_position,
4✔
991
                                'isNew'                => \in_array( $post_type->name, $new_post_types, true ),
4✔
992
                        ];
4✔
993
                }
994

995
                \uasort( $transformed, [ $this, 'compare_post_types' ] );
4✔
996

997
                return $transformed;
998
        }
999

1000
        /**
1001
         * Compares two post types.
1002
         *
1003
         * @param array $a The first post type.
1004
         * @param array $b The second post type.
1005
         *
1006
         * @return int The order.
1007
         */
1008
        protected function compare_post_types( $a, $b ) {
×
1009
                if ( $a['menuPosition'] === null && $b['menuPosition'] !== null ) {
1010
                        return 1;
×
1011
                }
1012
                if ( $a['menuPosition'] !== null && $b['menuPosition'] === null ) {
1013
                        return -1;
1014
                }
1015

1016
                if ( $a['menuPosition'] === null && $b['menuPosition'] === null ) {
×
1017
                        // No position specified, order alphabetically by label.
1018
                        return \strnatcmp( $a['label'], $b['label'] );
1019
                }
1020

1021
                return ( ( $a['menuPosition'] < $b['menuPosition'] ) ? -1 : 1 );
1022
        }
1023

1024
        /**
1025
         * Transforms the taxonomies, to represent them.
1026
         *
1027
         * @param WP_Taxonomy[] $taxonomies      The WP_Taxonomy array to transform.
1028
         * @param string[]      $post_type_names The post type names.
1029
         *
1030
         * @return array The taxonomies.
1031
         */
1032
        protected function transform_taxonomies( $taxonomies, $post_type_names ) {
4✔
1033
                $transformed    = [];
4✔
1034
                $new_taxonomies = $this->options->get( 'new_taxonomies', [] );
4✔
1035
                foreach ( $taxonomies as $taxonomy ) {
4✔
1036
                        $transformed[ $taxonomy->name ] = [
4✔
1037
                                'name'          => $taxonomy->name,
4✔
1038
                                'route'         => $this->route_helper->get_route( $taxonomy->name, $taxonomy->rewrite, $taxonomy->rest_base ),
4✔
1039
                                'label'         => $taxonomy->label,
4✔
1040
                                'showUi'        => $taxonomy->show_ui,
4✔
1041
                                'singularLabel' => $taxonomy->labels->singular_name,
4✔
1042
                                'postTypes'     => \array_filter(
4✔
1043
                                        $taxonomy->object_type,
4✔
1044
                                        static function ( $object_type ) use ( $post_type_names ) {
4✔
1045
                                                return \in_array( $object_type, $post_type_names, true );
4✔
1046
                                        }
4✔
1047
                                ),
4✔
1048
                                'isNew'         => \in_array( $taxonomy->name, $new_taxonomies, true ),
4✔
1049
                        ];
4✔
1050
                }
1051

1052
                \uasort(
4✔
1053
                        $transformed,
4✔
1054
                        static function ( $a, $b ) {
4✔
1055
                                return \strnatcmp( $a['label'], $b['label'] );
4✔
1056
                        }
4✔
1057
                );
1058

1059
                return $transformed;
1060
        }
1061

1062
        /**
1063
         * Retrieves the fallbacks.
1064
         *
1065
         * @return array The fallbacks.
1066
         */
1067
        protected function get_fallbacks() {
×
1068
                $site_logo_id = \get_option( 'site_logo' );
×
1069
                if ( ! $site_logo_id ) {
×
1070
                        $site_logo_id = \get_theme_mod( 'custom_logo' );
1071
                }
1072
                if ( ! $site_logo_id ) {
1073
                        $site_logo_id = '0';
×
1074
                }
1075

1076
                return [
×
1077
                        'siteLogoId' => $site_logo_id,
×
1078
                ];
×
1079
        }
1080
}
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