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

Yoast / wordpress-seo / 530e8fcfbf98ec097d23257add5297438f26d3fc

20 Nov 2025 11:27AM UTC coverage: 53.366% (-0.02%) from 53.381%
530e8fcfbf98ec097d23257add5297438f26d3fc

push

github

web-flow
Merge pull request #22704 from Yoast/321-highlight-google-docs-and-duplicate-post-on-plans

321 highlight google docs and duplicate post on plans

8618 of 15923 branches covered (54.12%)

Branch coverage included in aggregate %.

29 of 74 new or added lines in 12 files covered. (39.19%)

6 existing lines in 1 file now uncovered.

32320 of 60789 relevant lines covered (53.17%)

47433.46 hits per line

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

26.25
/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\Schema\Article_Helper;
27
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
28
use Yoast\WP\SEO\Helpers\User_Helper;
29
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
30
use Yoast\WP\SEO\Llms_Txt\Application\Configuration\Llms_Txt_Configuration;
31
use Yoast\WP\SEO\Llms_Txt\Application\Health_Check\File_Runner;
32
use Yoast\WP\SEO\Llms_Txt\Infrastructure\Content\Manual_Post_Collection;
33
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
34

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

40
        public const PAGE = 'wpseo_page_settings';
41

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

271
        /**
272
         * Returns the conditionals based on which this loadable should be active.
273
         *
274
         * @return array
275
         */
276
        public static function get_conditionals() {
2✔
277
                return [ Settings_Conditional::class ];
2✔
278
        }
279

280
        /**
281
         * Initializes the integration.
282
         *
283
         * This is the place to register hooks and filters.
284
         *
285
         * @return void
286
         */
287
        public function register_hooks() {
×
288
                // Add page.
289
                \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
×
290
                \add_filter( 'admin_menu', [ $this, 'add_settings_saved_page' ] );
×
291

292
                // Are we saving the settings?
293
                if ( $this->current_page_helper->get_current_admin_page() === 'options.php' ) {
×
294
                        $post_action = '';
×
295
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
296
                        if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) {
×
297
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
298
                                $post_action = \wp_unslash( $_POST['action'] );
×
299
                        }
300
                        $option_page = '';
×
301
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
302
                        if ( isset( $_POST['option_page'] ) && \is_string( $_POST['option_page'] ) ) {
×
303
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
304
                                $option_page = \wp_unslash( $_POST['option_page'] );
×
305
                        }
306

307
                        if ( $post_action === 'update' && $option_page === self::PAGE ) {
×
308
                                \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
309
                                \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
310
                        }
311

312
                        return;
×
313
                }
314

315
                // Are we on the settings page?
316
                if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
×
317
                        \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
318
                        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
×
319
                        \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
320
                }
321
        }
322

323
        /**
324
         * Registers the different options under the setting.
325
         *
326
         * @return void
327
         */
328
        public function register_setting() {
×
329
                foreach ( WPSEO_Options::$options as $name => $instance ) {
×
330
                        if ( \in_array( $name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
331
                                \register_setting( self::PAGE, $name );
×
332
                        }
333
                }
334
                // Only register WP options when the user is allowed to manage them.
335
                if ( \current_user_can( 'manage_options' ) ) {
×
336
                        foreach ( self::WP_OPTIONS as $name ) {
×
337
                                \register_setting( self::PAGE, $name );
×
338
                        }
339
                }
340
        }
341

342
        /**
343
         * Adds the page.
344
         *
345
         * @param array $pages The pages.
346
         *
347
         * @return array The pages.
348
         */
349
        public function add_page( $pages ) {
×
350
                \array_splice(
×
351
                        $pages,
×
352
                        1,
×
353
                        0,
×
354
                        [
×
355
                                [
×
356
                                        'wpseo_dashboard',
×
357
                                        '',
×
358
                                        \__( 'Settings', 'wordpress-seo' ),
×
359
                                        'wpseo_manage_options',
×
360
                                        self::PAGE,
×
361
                                        [ $this, 'display_page' ],
×
362
                                ],
×
363
                        ]
×
364
                );
×
365

366
                return $pages;
×
367
        }
368

369
        /**
370
         * Adds a dummy page.
371
         *
372
         * Because the options route NEEDS to redirect to something.
373
         *
374
         * @param array $pages The pages.
375
         *
376
         * @return array The pages.
377
         */
378
        public function add_settings_saved_page( $pages ) {
2✔
379
                $runner = $this->runner;
2✔
380
                \add_submenu_page(
2✔
381
                        '',
2✔
382
                        '',
2✔
383
                        '',
2✔
384
                        'wpseo_manage_options',
2✔
385
                        self::PAGE . '_saved',
2✔
386
                        static function () use ( $runner ) {
2✔
387
                                // Add success indication to HTML response.
388
                                $success = empty( \get_settings_errors() ) ? 'true' : 'false';
×
389
                                echo \esc_html( "{{ yoast-success: $success }}" );
×
390

391
                                $runner->run();
×
392
                                if ( ! $runner->is_successful() ) {
×
393
                                        $failure_reason = $runner->get_generation_failure_reason();
×
394
                                        echo \esc_html( "{{ yoast-llms-txt-generation-failure: $failure_reason }}" );
×
395
                                }
396
                        }
2✔
397
                );
2✔
398

399
                return $pages;
2✔
400
        }
401

402
        /**
403
         * Displays the page.
404
         *
405
         * @return void
406
         */
407
        public function display_page() {
×
408
                echo '<div id="yoast-seo-settings"></div>';
×
409
        }
410

411
        /**
412
         * Enqueues the assets.
413
         *
414
         * @return void
415
         */
416
        public function enqueue_assets() {
×
417
                // Remove the emoji script as it is incompatible with both React and any contenteditable fields.
418
                \remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
×
419
                \wp_enqueue_media();
×
420
                $this->asset_manager->enqueue_script( 'new-settings' );
×
421
                $this->asset_manager->enqueue_style( 'new-settings' );
×
422
                if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-promotion' ) ) {
×
423
                        $this->asset_manager->enqueue_style( 'black-friday-banner' );
×
424
                }
425
                $this->asset_manager->localize_script( 'new-settings', 'wpseoScriptData', $this->get_script_data() );
×
426
        }
427

428
        /**
429
         * Removes all current WP notices.
430
         *
431
         * @return void
432
         */
433
        public function remove_notices() {
×
434
                \remove_all_actions( 'admin_notices' );
×
435
                \remove_all_actions( 'user_admin_notices' );
×
436
                \remove_all_actions( 'network_admin_notices' );
×
437
                \remove_all_actions( 'all_admin_notices' );
×
438
        }
439

440
        /**
441
         * Creates the script data.
442
         *
443
         * @return array The script data.
444
         */
445
        protected function get_script_data() {
×
446
                $default_setting_values = $this->get_default_setting_values();
×
447
                $settings               = $this->get_settings( $default_setting_values );
×
448
                $post_types             = $this->post_type_helper->get_indexable_post_type_objects();
×
449
                $taxonomies             = $this->taxonomy_helper->get_indexable_taxonomy_objects();
×
450

451
                // Check if attachments are included in indexation.
452
                if ( ! \array_key_exists( 'attachment', $post_types ) ) {
×
453
                        // Always include attachments in the settings, to let the user enable them again.
454
                        $attachment_object = \get_post_type_object( 'attachment' );
×
455
                        if ( ! empty( $attachment_object ) ) {
×
456
                                $post_types['attachment'] = $attachment_object;
×
457
                        }
458
                }
459
                // Check if post formats are included in indexation.
460
                if ( ! \array_key_exists( 'post_format', $taxonomies ) ) {
×
461
                        // Always include post_format in the settings, to let the user enable them again.
462
                        $post_format_object = \get_taxonomy( 'post_format' );
×
463
                        if ( ! empty( $post_format_object ) ) {
×
464
                                $taxonomies['post_format'] = $post_format_object;
×
465
                        }
466
                }
467

468
                $transformed_post_types = $this->transform_post_types( $post_types );
×
469
                $transformed_taxonomies = $this->transform_taxonomies( $taxonomies, \array_keys( $transformed_post_types ) );
×
470

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

474
                return [
×
475
                        'settings'                       => $this->transform_settings( $settings ),
×
476
                        'defaultSettingValues'           => $default_setting_values,
×
477
                        'disabledSettings'               => $this->get_disabled_settings( $settings ),
×
478
                        'endpoint'                       => \admin_url( 'options.php' ),
×
479
                        'nonce'                          => \wp_create_nonce( self::PAGE . '-options' ),
×
480
                        'separators'                     => WPSEO_Option_Titles::get_instance()->get_separator_options_for_display(),
×
481
                        'replacementVariables'           => $this->get_replacement_variables(),
×
482
                        'schema'                         => $this->get_schema( $transformed_post_types ),
×
483
                        'preferences'                    => $this->get_preferences( $settings ),
×
484
                        'linkParams'                     => WPSEO_Shortlinker::get_query_params(),
×
485
                        'postTypes'                      => $transformed_post_types,
×
486
                        'taxonomies'                     => $transformed_taxonomies,
×
487
                        'fallbacks'                      => $this->get_fallbacks(),
×
488
                        'showNewContentTypeNotification' => $show_new_content_type_notification,
×
489
                        'currentPromotions'              => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
×
490
                        'llmsTxt'                        => $this->llms_txt_configuration->get_configuration(),
×
491
                        'initialLlmTxtPages'             => $this->get_site_llms_txt_pages( $settings ),
×
492
                ];
×
493
        }
494

495
        /**
496
         * Retrieves the preferences.
497
         *
498
         * @param array $settings The settings.
499
         *
500
         * @return array The preferences.
501
         */
502
        protected function get_preferences( $settings ) {
×
503
                $shop_page_id             = $this->woocommerce_helper->get_shop_page_id();
×
504
                $homepage_is_latest_posts = \get_option( 'show_on_front' ) === 'posts';
×
505
                $page_on_front            = \get_option( 'page_on_front' );
×
506
                $page_for_posts           = \get_option( 'page_for_posts' );
×
507

508
                $addon_manager          = new WPSEO_Addon_Manager();
×
509
                $woocommerce_seo_active = \is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) );
×
510

511
                if ( empty( $page_on_front ) ) {
×
512
                        $page_on_front = $page_for_posts;
×
513
                }
514

515
                $business_settings_url = \get_admin_url( null, 'admin.php?page=wpseo_local' );
×
516
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
517
                        $local_options      = \get_option( 'wpseo_local' );
×
518
                        $multiple_locations = $local_options['use_multiple_locations'];
×
519
                        $same_organization  = $local_options['multiple_locations_same_organization'];
×
520
                        if ( $multiple_locations === 'on' && $same_organization !== 'on' ) {
×
521
                                $business_settings_url = \get_admin_url( null, 'edit.php?post_type=wpseo_locations' );
×
522
                        }
523
                }
524

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

565
        /**
566
         * Retrieves the currently represented person.
567
         *
568
         * @param array $settings The settings.
569
         *
570
         * @return array The currently represented person.
571
         */
572
        protected function get_site_represents_person( $settings ) {
×
573
                $person = [
×
574
                        'id'   => false,
×
575
                        'name' => '',
×
576
                ];
×
577

578
                if ( isset( $settings['wpseo_titles']['company_or_person_user_id'] ) ) {
×
579
                        $person['id'] = $settings['wpseo_titles']['company_or_person_user_id'];
×
580
                        $user         = \get_userdata( $person['id'] );
×
581
                        if ( $user instanceof WP_User ) {
×
582
                                $person['name'] = $user->get( 'display_name' );
×
583
                        }
584
                }
585

586
                return $person;
×
587
        }
588

589
        /**
590
         * Get site policy data.
591
         *
592
         * @param array $settings The settings.
593
         *
594
         * @return array The policy data.
595
         */
596
        private function get_site_basics_policies( $settings ) {
×
597
                $policies = [];
×
598

599
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['publishing_principles_id'], 'publishing_principles_id' );
×
600
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ownership_funding_info_id'], 'ownership_funding_info_id' );
×
601
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['actionable_feedback_policy_id'], 'actionable_feedback_policy_id' );
×
602
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['corrections_policy_id'], 'corrections_policy_id' );
×
603
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ethics_policy_id'], 'ethics_policy_id' );
×
604
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_policy_id'], 'diversity_policy_id' );
×
605
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_staffing_report_id'], 'diversity_staffing_report_id' );
×
606

607
                return $policies;
×
608
        }
609

610
        /**
611
         * Adds policy data if it is present.
612
         *
613
         * @param array  $policies The existing policy data.
614
         * @param int    $policy   The policy id to check.
615
         * @param string $key      The option key name.
616
         *
617
         * @return array<int, string> The policy data.
618
         */
619
        private function maybe_add_policy( $policies, $policy, $key ) {
×
620
                $policy_array = [
×
621
                        'id'   => 0,
×
622
                        'name' => \__( 'None', 'wordpress-seo' ),
×
623
                ];
×
624

625
                if ( isset( $policy ) && \is_int( $policy ) ) {
×
626
                        $policy_array['id'] = $policy;
×
627
                        $post               = \get_post( $policy );
×
628
                        if ( $post instanceof WP_Post ) {
×
629
                                if ( $post->post_status !== 'publish' || $post->post_password !== '' ) {
×
630
                                        return $policies;
×
631
                                }
632
                                $policy_array['name'] = $post->post_title;
×
633
                        }
634
                }
635

636
                $policies[ $key ] = $policy_array;
×
637

638
                return $policies;
×
639
        }
640

641
        /**
642
         * Adds page if it is present.
643
         *
644
         * @param array<int, string> $pages   The existing pages.
645
         * @param int                $page_id The page id to check.
646
         * @param string             $key     The option key name.
647
         *
648
         * @return array<int, string> The policy data.
649
         */
650
        private function maybe_add_page( $pages, $page_id, $key ) {
×
651
                if ( isset( $page_id ) && \is_int( $page_id ) && $page_id !== 0 ) {
×
652
                        $post = $this->manual_post_collection->get_content_type_entry( $page_id );
×
653
                        if ( $post === null ) {
×
654
                                return $pages;
×
655
                        }
656

657
                        $pages[ $key ] = [
×
658
                                'id'    => $page_id,
×
659
                                'title' => ( $post->get_title() ) ? $post->get_title() : $post->get_slug(),
×
660
                                'slug'  => $post->get_slug(),
×
661
                        ];
×
662
                }
663

664
                return $pages;
×
665
        }
666

667
        /**
668
         * Get site llms.txt pages.
669
         *
670
         * @param array $settings The settings.
671
         *
672
         * @return array<string, array<string, int|string>> The llms.txt pages.
673
         */
674
        private function get_site_llms_txt_pages( $settings ) {
×
675
                $llms_txt_pages = [];
×
676

677
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['about_us_page'], 'about_us_page' );
×
678
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['contact_page'], 'contact_page' );
×
679
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['terms_page'], 'terms_page' );
×
680
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['privacy_policy_page'], 'privacy_policy_page' );
×
681
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['shop_page'], 'shop_page' );
×
682

683
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) && \is_array( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
684
                        foreach ( $settings['wpseo_llmstxt']['other_included_pages'] as $key => $page_id ) {
×
685
                                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $page_id, 'other_included_pages-' . $key );
×
686
                        }
687
                }
688

689
                return $llms_txt_pages;
×
690
        }
691

692
        /**
693
         * Returns settings for the Call to Buy (CTB) buttons.
694
         *
695
         * @return array<string> The array of CTB settings.
696
         */
697
        public function get_upsell_settings() {
×
698
                return [
×
699
                        'actionId'     => 'load-nfd-ctb',
×
700
                        'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
×
701
                ];
×
702
        }
703

704
        /**
705
         * Retrieves the default setting values.
706
         *
707
         * These default values are currently being used in the UI for dummy fields.
708
         * Dummy fields should not expose or reflect the actual data.
709
         *
710
         * @return array The default setting values.
711
         */
712
        protected function get_default_setting_values() {
×
713
                $defaults = [];
×
714

715
                // Add Yoast settings.
716
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
717
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
718
                                $option_instance          = WPSEO_Options::get_option_instance( $option_name );
×
719
                                $defaults[ $option_name ] = ( $option_instance ) ? $option_instance->get_defaults() : [];
×
720
                        }
721
                }
722
                // Add WP settings.
723
                foreach ( self::WP_OPTIONS as $option_name ) {
×
724
                        $defaults[ $option_name ] = '';
×
725
                }
726

727
                // Remove disallowed settings.
728
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
729
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
730
                                unset( $defaults[ $option_name ][ $disallowed_setting ] );
×
731
                        }
732
                }
733

734
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
735
                        $defaults = $this->get_defaults_from_local_seo( $defaults );
×
736
                }
737

738
                return $defaults;
×
739
        }
740

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

761
                if ( \wpseo_has_primary_location() ) {
8✔
762
                        $primary_location = $local_options['multiple_locations_primary_location'];
4✔
763

764
                        $location_keys = [
4✔
765
                                'org-phone'  => [
4✔
766
                                        'is_overridden' => '_wpseo_is_overridden_business_phone',
4✔
767
                                        'value'         => '_wpseo_business_phone',
4✔
768
                                ],
4✔
769
                                'org-email'  => [
4✔
770
                                        'is_overridden' => '_wpseo_is_overridden_business_email',
4✔
771
                                        'value'         => '_wpseo_business_email',
4✔
772
                                ],
4✔
773
                                'org-tax-id' => [
4✔
774
                                        'is_overridden' => '_wpseo_is_overridden_business_tax_id',
4✔
775
                                        'value'         => '_wpseo_business_tax_id',
4✔
776
                                ],
4✔
777
                                'org-vat-id' => [
4✔
778
                                        'is_overridden' => '_wpseo_is_overridden_business_vat_id',
4✔
779
                                        'value'         => '_wpseo_business_vat_id',
4✔
780
                                ],
4✔
781
                        ];
4✔
782

783
                        foreach ( $location_keys as $key => $meta_keys ) {
4✔
784
                                $is_overridden = ( $shared_info === 'on' ) ? \get_post_meta( $primary_location, $meta_keys['is_overridden'], true ) : false;
4✔
785
                                if ( $is_overridden === 'on' || $shared_info !== 'on' ) {
4✔
786
                                        $post_meta_value                  = \get_post_meta( $primary_location, $meta_keys['value'], true );
4✔
787
                                        $defaults['wpseo_titles'][ $key ] = ( $post_meta_value ) ? $post_meta_value : '';
4✔
788
                                }
789
                        }
790
                }
791

792
                return $defaults;
8✔
793
        }
794

795
        /**
796
         * Retrieves the settings and their values.
797
         *
798
         * @param array $default_setting_values The default setting values.
799
         *
800
         * @return array The settings.
801
         */
802
        protected function get_settings( $default_setting_values ) {
×
803
                $settings = [];
×
804

805
                // Add Yoast settings.
806
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
807
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
808
                                $settings[ $option_name ] = \array_merge( $default_setting_values[ $option_name ], WPSEO_Options::get_option( $option_name ) );
×
809
                        }
810
                }
811
                // Add WP settings.
812
                foreach ( self::WP_OPTIONS as $option_name ) {
×
813
                        $settings[ $option_name ] = \get_option( $option_name );
×
814
                }
815

816
                // Remove disallowed settings.
817
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
818
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
819
                                unset( $settings[ $option_name ][ $disallowed_setting ] );
×
820
                        }
821
                }
822

823
                return $settings;
×
824
        }
825

826
        /**
827
         * Transforms setting values.
828
         *
829
         * @param array $settings The settings.
830
         *
831
         * @return array The settings.
832
         */
833
        protected function transform_settings( $settings ) {
×
834
                if ( isset( $settings['wpseo_titles']['breadcrumbs-sep'] ) ) {
×
835
                        /**
836
                         * The breadcrumbs separator default value is the HTML entity `&raquo;`.
837
                         * Which does not get decoded in our JS, while it did in our Yoast form. Decode it here as an exception.
838
                         */
839
                        $settings['wpseo_titles']['breadcrumbs-sep'] = \html_entity_decode(
×
840
                                $settings['wpseo_titles']['breadcrumbs-sep'],
×
841
                                ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
842
                                'UTF-8'
×
843
                        );
×
844
                }
845

846
                /**
847
                 * Decode some WP options.
848
                 */
849
                $settings['blogdescription'] = \html_entity_decode(
×
850
                        $settings['blogdescription'],
×
851
                        ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
852
                        'UTF-8'
×
853
                );
×
854

855
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
856
                        // Append an empty page to the other included pages, so that we manage to show an empty field in the UI.
857
                        $settings['wpseo_llmstxt']['other_included_pages'][] = 0;
858
                }
859

860
                return $settings;
861
        }
862

863
        /**
864
         * Retrieves the disabled settings.
865
         *
866
         * @param array $settings The settings.
867
         *
868
         * @return array The settings.
869
         */
870
        protected function get_disabled_settings( $settings ) {
×
871
                $disabled_settings = [];
872
                $site_language     = $this->language_helper->get_language();
×
873

874
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
875
                        if ( ! \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
876
                                continue;
877
                        }
878

879
                        $disabled_settings[ $option_name ] = [];
×
880
                        $option_instance                   = WPSEO_Options::get_option_instance( $option_name );
×
881
                        if ( $option_instance === false ) {
882
                                continue;
×
883
                        }
884
                        foreach ( $settings[ $option_name ] as $setting_name => $setting_value ) {
×
885
                                if ( $option_instance->is_disabled( $setting_name ) ) {
886
                                        $disabled_settings[ $option_name ][ $setting_name ] = 'network';
887
                                }
888
                        }
889
                }
890

891
                // Remove disabled on multisite settings.
892
                if ( \is_multisite() ) {
×
893
                        foreach ( self::DISABLED_ON_MULTISITE_SETTINGS as $option_name => $disabled_ms_settings ) {
×
894
                                if ( \array_key_exists( $option_name, $disabled_settings ) ) {
×
895
                                        foreach ( $disabled_ms_settings as $disabled_ms_setting ) {
896
                                                $disabled_settings[ $option_name ][ $disabled_ms_setting ] = 'multisite';
897
                                        }
898
                                }
899
                        }
900
                }
901

902
                if ( \array_key_exists( 'wpseo', $disabled_settings ) && ! $this->language_helper->has_inclusive_language_support( $site_language ) ) {
903
                        $disabled_settings['wpseo']['inclusive_language_analysis_active'] = 'language';
904
                }
905

906
                return $disabled_settings;
907
        }
908

909
        /**
910
         * Retrieves the replacement variables.
911
         *
912
         * @return array The replacement variables.
913
         */
914
        protected function get_replacement_variables() {
×
915
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
916
                $specific_replace_vars    = new WPSEO_Admin_Editor_Specific_Replace_Vars();
917
                $replacement_variables    = $this->replace_vars->get_replacement_variables_with_labels();
918

919
                return [
920
                        'variables'   => $replacement_variables,
921
                        'recommended' => $recommended_replace_vars->get_recommended_replacevars(),
922
                        'specific'    => $specific_replace_vars->get(),
923
                        'shared'      => $specific_replace_vars->get_generic( $replacement_variables ),
924
                ];
925
        }
926

927
        /**
928
         * Retrieves the schema.
929
         *
930
         * @param array $post_types The post types.
931
         *
932
         * @return array The schema.
933
         */
934
        protected function get_schema( array $post_types ) {
×
935
                $schema = [];
×
936

937
                foreach ( $this->schema_types->get_article_type_options() as $article_type ) {
×
938
                        $schema['articleTypes'][ $article_type['value'] ] = [
×
939
                                'label' => $article_type['name'],
×
940
                                'value' => $article_type['value'],
×
941
                        ];
×
942
                }
943

944
                foreach ( $this->schema_types->get_page_type_options() as $page_type ) {
×
945
                        $schema['pageTypes'][ $page_type['value'] ] = [
×
946
                                'label' => $page_type['name'],
×
947
                                'value' => $page_type['value'],
×
948
                        ];
×
949
                }
950

951
                $schema['articleTypeDefaults'] = [];
×
952
                $schema['pageTypeDefaults']    = [];
×
953
                foreach ( $post_types as $name => $post_type ) {
×
954
                        $schema['articleTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-article-type-$name" );
955
                        $schema['pageTypeDefaults'][ $name ]    = WPSEO_Options::get_default( 'wpseo_titles', "schema-page-type-$name" );
956
                }
957

958
                return $schema;
959
        }
960

961
        /**
962
         * Transforms the post types, to represent them.
963
         *
964
         * @param WP_Post_Type[] $post_types The WP_Post_Type array to transform.
965
         *
966
         * @return array The post types.
967
         */
968
        protected function transform_post_types( $post_types ) {
4✔
969
                $transformed    = [];
4✔
970
                $new_post_types = $this->options->get( 'new_post_types', [] );
4✔
971
                foreach ( $post_types as $post_type ) {
4✔
972
                        $transformed[ $post_type->name ] = [
4✔
973
                                'name'                 => $post_type->name,
4✔
974
                                'route'                => $this->get_route( $post_type->name, $post_type->rewrite, $post_type->rest_base ),
4✔
975
                                'label'                => $post_type->label,
4✔
976
                                'singularLabel'        => $post_type->labels->singular_name,
4✔
977
                                'hasArchive'           => $this->post_type_helper->has_archive( $post_type ),
4✔
978
                                'hasSchemaArticleType' => $this->article_helper->is_article_post_type( $post_type->name ),
4✔
979
                                'menuPosition'         => $post_type->menu_position,
4✔
980
                                'isNew'                => \in_array( $post_type->name, $new_post_types, true ),
4✔
981
                        ];
4✔
982
                }
983

984
                \uasort( $transformed, [ $this, 'compare_post_types' ] );
4✔
985

986
                return $transformed;
987
        }
988

989
        /**
990
         * Compares two post types.
991
         *
992
         * @param array $a The first post type.
993
         * @param array $b The second post type.
994
         *
995
         * @return int The order.
996
         */
997
        protected function compare_post_types( $a, $b ) {
×
998
                if ( $a['menuPosition'] === null && $b['menuPosition'] !== null ) {
999
                        return 1;
×
1000
                }
1001
                if ( $a['menuPosition'] !== null && $b['menuPosition'] === null ) {
1002
                        return -1;
1003
                }
1004

1005
                if ( $a['menuPosition'] === null && $b['menuPosition'] === null ) {
×
1006
                        // No position specified, order alphabetically by label.
1007
                        return \strnatcmp( $a['label'], $b['label'] );
1008
                }
1009

1010
                return ( ( $a['menuPosition'] < $b['menuPosition'] ) ? -1 : 1 );
1011
        }
1012

1013
        /**
1014
         * Transforms the taxonomies, to represent them.
1015
         *
1016
         * @param WP_Taxonomy[] $taxonomies      The WP_Taxonomy array to transform.
1017
         * @param string[]      $post_type_names The post type names.
1018
         *
1019
         * @return array The taxonomies.
1020
         */
1021
        protected function transform_taxonomies( $taxonomies, $post_type_names ) {
4✔
1022
                $transformed    = [];
4✔
1023
                $new_taxonomies = $this->options->get( 'new_taxonomies', [] );
4✔
1024
                foreach ( $taxonomies as $taxonomy ) {
4✔
1025
                        $transformed[ $taxonomy->name ] = [
4✔
1026
                                'name'          => $taxonomy->name,
4✔
1027
                                'route'         => $this->get_route( $taxonomy->name, $taxonomy->rewrite, $taxonomy->rest_base ),
4✔
1028
                                'label'         => $taxonomy->label,
4✔
1029
                                'showUi'        => $taxonomy->show_ui,
4✔
1030
                                'singularLabel' => $taxonomy->labels->singular_name,
4✔
1031
                                'postTypes'     => \array_filter(
4✔
1032
                                        $taxonomy->object_type,
4✔
1033
                                        static function ( $object_type ) use ( $post_type_names ) {
4✔
1034
                                                return \in_array( $object_type, $post_type_names, true );
4✔
1035
                                        }
4✔
1036
                                ),
4✔
1037
                                'isNew'         => \in_array( $taxonomy->name, $new_taxonomies, true ),
4✔
1038
                        ];
4✔
1039
                }
1040

1041
                \uasort(
4✔
1042
                        $transformed,
4✔
1043
                        static function ( $a, $b ) {
4✔
1044
                                return \strnatcmp( $a['label'], $b['label'] );
4✔
1045
                        }
4✔
1046
                );
1047

1048
                return $transformed;
1049
        }
1050

1051
        /**
1052
         * Gets the route from a name, rewrite and rest_base.
1053
         *
1054
         * @param string $name      The name.
1055
         * @param array  $rewrite   The rewrite data.
1056
         * @param string $rest_base The rest base.
1057
         *
1058
         * @return string The route.
1059
         */
1060
        protected function get_route( $name, $rewrite, $rest_base ) {
×
1061
                $route = $name;
×
UNCOV
1062
                if ( isset( $rewrite['slug'] ) ) {
×
1063
                        $route = $rewrite['slug'];
1064
                }
UNCOV
1065
                if ( ! empty( $rest_base ) ) {
×
UNCOV
1066
                        $route = $rest_base;
×
1067
                }
1068
                // Always strip leading slashes.
UNCOV
1069
                while ( \substr( $route, 0, 1 ) === '/' ) {
×
1070
                        $route = \substr( $route, 1 );
1071
                }
1072

1073
                return $route;
1074
        }
1075

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

1090
                return [
×
1091
                        'siteLogoId' => $site_logo_id,
×
1092
                ];
×
1093
        }
1094
}
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

© 2025 Coveralls, Inc