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

Yoast / wordpress-seo / ad2448b82495dcb34eaba09413580e9f24a3579f

11 Jul 2025 08:30AM UTC coverage: 53.541% (-0.02%) from 53.561%
ad2448b82495dcb34eaba09413580e9f24a3579f

Pull #22419

github

web-flow
Merge 98cca884b into 01a6480cf
Pull Request #22419: Make sure that the user can't select a page multiple times in the manual page selection dropdowns

8246 of 14440 branches covered (57.11%)

Branch coverage included in aggregate %.

0 of 35 new or added lines in 6 files covered. (0.0%)

16 existing lines in 6 files now uncovered.

30497 of 57921 relevant lines covered (52.65%)

41222.73 hits per line

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

24.76
/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\Infrastructure\Content\Manual_Post_Collection;
32
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
33

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

39
        public const PAGE = 'wpseo_page_settings';
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

260
        /**
261
         * Returns the conditionals based on which this loadable should be active.
262
         *
263
         * @return array
264
         */
265
        public static function get_conditionals() {
2✔
266
                return [ Settings_Conditional::class ];
2✔
267
        }
268

269
        /**
270
         * Initializes the integration.
271
         *
272
         * This is the place to register hooks and filters.
273
         *
274
         * @return void
275
         */
276
        public function register_hooks() {
×
277
                // Add page.
278
                \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
×
279
                \add_filter( 'admin_menu', [ $this, 'add_settings_saved_page' ] );
×
280

281
                // Are we saving the settings?
282
                if ( $this->current_page_helper->get_current_admin_page() === 'options.php' ) {
×
283
                        $post_action = '';
×
284
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
285
                        if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) {
×
286
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
287
                                $post_action = \wp_unslash( $_POST['action'] );
×
288
                        }
289
                        $option_page = '';
×
290
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information.
291
                        if ( isset( $_POST['option_page'] ) && \is_string( $_POST['option_page'] ) ) {
×
292
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information.
293
                                $option_page = \wp_unslash( $_POST['option_page'] );
×
294
                        }
295

296
                        if ( $post_action === 'update' && $option_page === self::PAGE ) {
×
297
                                \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
298
                                \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
299
                        }
300

301
                        return;
×
302
                }
303

304
                // Are we on the settings page?
305
                if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
×
306
                        \add_action( 'admin_init', [ $this, 'register_setting' ] );
×
307
                        \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
×
308
                        \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
×
309
                }
310
        }
311

312
        /**
313
         * Registers the different options under the setting.
314
         *
315
         * @return void
316
         */
317
        public function register_setting() {
×
318
                foreach ( WPSEO_Options::$options as $name => $instance ) {
×
319
                        if ( \in_array( $name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
320
                                \register_setting( self::PAGE, $name );
×
321
                        }
322
                }
323
                // Only register WP options when the user is allowed to manage them.
324
                if ( \current_user_can( 'manage_options' ) ) {
×
325
                        foreach ( self::WP_OPTIONS as $name ) {
×
326
                                \register_setting( self::PAGE, $name );
×
327
                        }
328
                }
329
        }
330

331
        /**
332
         * Adds the page.
333
         *
334
         * @param array $pages The pages.
335
         *
336
         * @return array The pages.
337
         */
338
        public function add_page( $pages ) {
×
339
                \array_splice(
×
340
                        $pages,
×
341
                        1,
×
342
                        0,
×
343
                        [
×
344
                                [
×
345
                                        'wpseo_dashboard',
×
346
                                        '',
×
347
                                        \__( 'Settings', 'wordpress-seo' ),
×
348
                                        'wpseo_manage_options',
×
349
                                        self::PAGE,
×
350
                                        [ $this, 'display_page' ],
×
351
                                ],
×
352
                        ]
×
353
                );
×
354

355
                return $pages;
×
356
        }
357

358
        /**
359
         * Adds a dummy page.
360
         *
361
         * Because the options route NEEDS to redirect to something.
362
         *
363
         * @param array $pages The pages.
364
         *
365
         * @return array The pages.
366
         */
367
        public function add_settings_saved_page( $pages ) {
2✔
368
                \add_submenu_page(
2✔
369
                        '',
2✔
370
                        '',
2✔
371
                        '',
2✔
372
                        'wpseo_manage_options',
2✔
373
                        self::PAGE . '_saved',
2✔
374
                        static function () {
2✔
375
                                // Add success indication to HTML response.
376
                                $success = empty( \get_settings_errors() ) ? 'true' : 'false';
×
377
                                echo \esc_html( "{{ yoast-success: $success }}" );
×
378
                        }
2✔
379
                );
2✔
380

381
                return $pages;
2✔
382
        }
383

384
        /**
385
         * Displays the page.
386
         *
387
         * @return void
388
         */
389
        public function display_page() {
×
390
                echo '<div id="yoast-seo-settings"></div>';
×
391
        }
392

393
        /**
394
         * Enqueues the assets.
395
         *
396
         * @return void
397
         */
398
        public function enqueue_assets() {
×
399
                // Remove the emoji script as it is incompatible with both React and any contenteditable fields.
400
                \remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
×
401
                \wp_enqueue_media();
×
402
                $this->asset_manager->enqueue_script( 'new-settings' );
×
403
                $this->asset_manager->enqueue_style( 'new-settings' );
×
404
                if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) {
×
405
                        $this->asset_manager->enqueue_style( 'black-friday-banner' );
×
406
                }
407
                $this->asset_manager->localize_script( 'new-settings', 'wpseoScriptData', $this->get_script_data() );
×
408
        }
409

410
        /**
411
         * Removes all current WP notices.
412
         *
413
         * @return void
414
         */
415
        public function remove_notices() {
×
416
                \remove_all_actions( 'admin_notices' );
×
417
                \remove_all_actions( 'user_admin_notices' );
×
418
                \remove_all_actions( 'network_admin_notices' );
×
419
                \remove_all_actions( 'all_admin_notices' );
×
420
        }
421

422
        /**
423
         * Creates the script data.
424
         *
425
         * @return array The script data.
426
         */
427
        protected function get_script_data() {
×
428
                $default_setting_values = $this->get_default_setting_values();
×
429
                $settings               = $this->get_settings( $default_setting_values );
×
430
                $post_types             = $this->post_type_helper->get_indexable_post_type_objects();
×
431
                $taxonomies             = $this->taxonomy_helper->get_indexable_taxonomy_objects();
×
432

433
                // Check if attachments are included in indexation.
434
                if ( ! \array_key_exists( 'attachment', $post_types ) ) {
×
435
                        // Always include attachments in the settings, to let the user enable them again.
436
                        $attachment_object = \get_post_type_object( 'attachment' );
×
437
                        if ( ! empty( $attachment_object ) ) {
×
438
                                $post_types['attachment'] = $attachment_object;
×
439
                        }
440
                }
441
                // Check if post formats are included in indexation.
442
                if ( ! \array_key_exists( 'post_format', $taxonomies ) ) {
×
443
                        // Always include post_format in the settings, to let the user enable them again.
444
                        $post_format_object = \get_taxonomy( 'post_format' );
×
445
                        if ( ! empty( $post_format_object ) ) {
×
446
                                $taxonomies['post_format'] = $post_format_object;
×
447
                        }
448
                }
449

450
                $transformed_post_types = $this->transform_post_types( $post_types );
×
451
                $transformed_taxonomies = $this->transform_taxonomies( $taxonomies, \array_keys( $transformed_post_types ) );
×
452

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

456
                return [
×
457
                        'settings'                       => $this->transform_settings( $settings ),
×
458
                        'defaultSettingValues'           => $default_setting_values,
×
459
                        'disabledSettings'               => $this->get_disabled_settings( $settings ),
×
460
                        'endpoint'                       => \admin_url( 'options.php' ),
×
461
                        'nonce'                          => \wp_create_nonce( self::PAGE . '-options' ),
×
462
                        'separators'                     => WPSEO_Option_Titles::get_instance()->get_separator_options_for_display(),
×
463
                        'replacementVariables'           => $this->get_replacement_variables(),
×
464
                        'schema'                         => $this->get_schema( $transformed_post_types ),
×
465
                        'preferences'                    => $this->get_preferences( $settings ),
×
466
                        'linkParams'                     => WPSEO_Shortlinker::get_query_params(),
×
467
                        'postTypes'                      => $transformed_post_types,
×
468
                        'taxonomies'                     => $transformed_taxonomies,
×
469
                        'fallbacks'                      => $this->get_fallbacks(),
×
470
                        'showNewContentTypeNotification' => $show_new_content_type_notification,
×
471
                        'currentPromotions'              => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
×
472
                        'llmsTxt'                        => $this->llms_txt_configuration->get_configuration(),
×
NEW
473
                        'initialLlmTxtPages'             => $this->get_site_llms_txt_pages( $settings ),
×
UNCOV
474
                ];
×
475
        }
476

477
        /**
478
         * Retrieves the preferences.
479
         *
480
         * @param array $settings The settings.
481
         *
482
         * @return array The preferences.
483
         */
484
        protected function get_preferences( $settings ) {
×
485
                $shop_page_id             = $this->woocommerce_helper->get_shop_page_id();
×
486
                $homepage_is_latest_posts = \get_option( 'show_on_front' ) === 'posts';
×
487
                $page_on_front            = \get_option( 'page_on_front' );
×
488
                $page_for_posts           = \get_option( 'page_for_posts' );
×
489

490
                $addon_manager          = new WPSEO_Addon_Manager();
×
491
                $woocommerce_seo_active = \is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) );
×
492

493
                if ( empty( $page_on_front ) ) {
×
494
                        $page_on_front = $page_for_posts;
×
495
                }
496

497
                $business_settings_url = \get_admin_url( null, 'admin.php?page=wpseo_local' );
×
498
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
499
                        $local_options      = \get_option( 'wpseo_local' );
×
500
                        $multiple_locations = $local_options['use_multiple_locations'];
×
501
                        $same_organization  = $local_options['multiple_locations_same_organization'];
×
502
                        if ( $multiple_locations === 'on' && $same_organization !== 'on' ) {
×
503
                                $business_settings_url = \get_admin_url( null, 'edit.php?post_type=wpseo_locations' );
×
504
                        }
505
                }
506

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

547
        /**
548
         * Retrieves the currently represented person.
549
         *
550
         * @param array $settings The settings.
551
         *
552
         * @return array The currently represented person.
553
         */
554
        protected function get_site_represents_person( $settings ) {
×
555
                $person = [
×
556
                        'id'   => false,
×
557
                        'name' => '',
×
558
                ];
×
559

560
                if ( isset( $settings['wpseo_titles']['company_or_person_user_id'] ) ) {
×
561
                        $person['id'] = $settings['wpseo_titles']['company_or_person_user_id'];
×
562
                        $user         = \get_userdata( $person['id'] );
×
563
                        if ( $user instanceof WP_User ) {
×
564
                                $person['name'] = $user->get( 'display_name' );
×
565
                        }
566
                }
567

568
                return $person;
×
569
        }
570

571
        /**
572
         * Get site policy data.
573
         *
574
         * @param array $settings The settings.
575
         *
576
         * @return array The policy data.
577
         */
578
        private function get_site_basics_policies( $settings ) {
×
579
                $policies = [];
×
580

581
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['publishing_principles_id'], 'publishing_principles_id' );
×
582
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ownership_funding_info_id'], 'ownership_funding_info_id' );
×
583
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['actionable_feedback_policy_id'], 'actionable_feedback_policy_id' );
×
584
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['corrections_policy_id'], 'corrections_policy_id' );
×
585
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ethics_policy_id'], 'ethics_policy_id' );
×
586
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_policy_id'], 'diversity_policy_id' );
×
587
                $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_staffing_report_id'], 'diversity_staffing_report_id' );
×
588

589
                return $policies;
×
590
        }
591

592
        /**
593
         * Adds policy data if it is present.
594
         *
595
         * @param array  $policies The existing policy data.
596
         * @param int    $policy   The policy id to check.
597
         * @param string $key      The option key name.
598
         *
599
         * @return array<int, string> The policy data.
600
         */
601
        private function maybe_add_policy( $policies, $policy, $key ) {
×
602
                $policy_array = [
×
603
                        'id'   => 0,
×
604
                        'name' => \__( 'None', 'wordpress-seo' ),
×
605
                ];
×
606

607
                if ( isset( $policy ) && \is_int( $policy ) ) {
×
608
                        $policy_array['id'] = $policy;
×
609
                        $post               = \get_post( $policy );
×
610
                        if ( $post instanceof WP_Post ) {
×
611
                                if ( $post->post_status !== 'publish' || $post->post_password !== '' ) {
×
612
                                        return $policies;
×
613
                                }
614
                                $policy_array['name'] = $post->post_title;
×
615
                        }
616
                }
617

618
                $policies[ $key ] = $policy_array;
×
619

620
                return $policies;
×
621
        }
622

623
        /**
624
         * Adds page if it is present.
625
         *
626
         * @param array<int, string> $pages   The existing pages.
627
         * @param int                $page_id The page id to check.
628
         * @param string             $key     The option key name.
629
         *
630
         * @return array<int, string> The policy data.
631
         */
632
        private function maybe_add_page( $pages, $page_id, $key ) {
×
633
                if ( isset( $page_id ) && \is_int( $page_id ) && $page_id !== 0 ) {
×
634
                        $post = $this->manual_post_collection->get_content_type_entry( $page_id );
×
635
                        if ( $post === null ) {
×
636
                                return $pages;
×
637
                        }
638

639
                        $pages[ $key ] = [
×
NEW
640
                                'id'    => $page_id,
×
NEW
641
                                'title' => ( $post->get_title() ) ? $post->get_title() : $post->get_slug(),
×
NEW
642
                                'slug'  => $post->get_slug(),
×
UNCOV
643
                        ];
×
644
                }
645

646
                return $pages;
×
647
        }
648

649
        /**
650
         * Get site llms.txt pages.
651
         *
652
         * @param array $settings The settings.
653
         *
654
         * @return array<string, array<string, int|string>> The llms.txt pages.
655
         */
656
        private function get_site_llms_txt_pages( $settings ) {
×
657
                $llms_txt_pages = [];
×
658

659
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['about_us_page'], 'about_us_page' );
×
660
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['contact_page'], 'contact_page' );
×
661
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['terms_page'], 'terms_page' );
×
662
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['privacy_policy_page'], 'privacy_policy_page' );
×
663
                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $settings['wpseo_llmstxt']['shop_page'], 'shop_page' );
×
664

665
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) && \is_array( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
666
                        foreach ( $settings['wpseo_llmstxt']['other_included_pages'] as $key => $page_id ) {
×
667
                                $llms_txt_pages = $this->maybe_add_page( $llms_txt_pages, $page_id, 'other_included_pages-' . $key );
×
668
                        }
669
                }
670

671
                return $llms_txt_pages;
×
672
        }
673

674
        /**
675
         * Returns settings for the Call to Buy (CTB) buttons.
676
         *
677
         * @return array<string> The array of CTB settings.
678
         */
679
        public function get_upsell_settings() {
×
680
                return [
×
681
                        'actionId'     => 'load-nfd-ctb',
×
682
                        'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2',
×
683
                ];
×
684
        }
685

686
        /**
687
         * Retrieves the default setting values.
688
         *
689
         * These default values are currently being used in the UI for dummy fields.
690
         * Dummy fields should not expose or reflect the actual data.
691
         *
692
         * @return array The default setting values.
693
         */
694
        protected function get_default_setting_values() {
×
695
                $defaults = [];
×
696

697
                // Add Yoast settings.
698
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
699
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
700
                                $option_instance          = WPSEO_Options::get_option_instance( $option_name );
×
701
                                $defaults[ $option_name ] = ( $option_instance ) ? $option_instance->get_defaults() : [];
×
702
                        }
703
                }
704
                // Add WP settings.
705
                foreach ( self::WP_OPTIONS as $option_name ) {
×
706
                        $defaults[ $option_name ] = '';
×
707
                }
708

709
                // Remove disallowed settings.
710
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
711
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
712
                                unset( $defaults[ $option_name ][ $disallowed_setting ] );
×
713
                        }
714
                }
715

716
                if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
×
717
                        $defaults = $this->get_defaults_from_local_seo( $defaults );
×
718
                }
719

720
                return $defaults;
×
721
        }
722

723
        /**
724
         * Retrieves the organization schema values from Local SEO for defaults in Site representation fields.
725
         * Specifically for the org-vat-id, org-tax-id, org-email and org-phone options.
726
         *
727
         * @param array<string|int|bool> $defaults The settings defaults.
728
         *
729
         * @return array<string|int|bool> The settings defaults with local seo overides.
730
         */
731
        protected function get_defaults_from_local_seo( $defaults ) {
8✔
732
                $local_options      = \get_option( 'wpseo_local' );
8✔
733
                $multiple_locations = $local_options['use_multiple_locations'];
8✔
734
                $same_organization  = $local_options['multiple_locations_same_organization'];
8✔
735
                $shared_info        = $local_options['multiple_locations_shared_business_info'];
8✔
736
                if ( $multiple_locations !== 'on' || ( $multiple_locations === 'on' && $same_organization === 'on' && $shared_info === 'on' ) ) {
8✔
737
                        $defaults['wpseo_titles']['org-vat-id'] = $local_options['location_vat_id'];
6✔
738
                        $defaults['wpseo_titles']['org-tax-id'] = $local_options['location_tax_id'];
6✔
739
                        $defaults['wpseo_titles']['org-email']  = $local_options['location_email'];
6✔
740
                        $defaults['wpseo_titles']['org-phone']  = $local_options['location_phone'];
6✔
741
                }
742

743
                if ( \wpseo_has_primary_location() ) {
8✔
744
                        $primary_location = $local_options['multiple_locations_primary_location'];
4✔
745

746
                        $location_keys = [
4✔
747
                                'org-phone'  => [
4✔
748
                                        'is_overridden' => '_wpseo_is_overridden_business_phone',
4✔
749
                                        'value'         => '_wpseo_business_phone',
4✔
750
                                ],
4✔
751
                                'org-email'  => [
4✔
752
                                        'is_overridden' => '_wpseo_is_overridden_business_email',
4✔
753
                                        'value'         => '_wpseo_business_email',
4✔
754
                                ],
4✔
755
                                'org-tax-id' => [
4✔
756
                                        'is_overridden' => '_wpseo_is_overridden_business_tax_id',
4✔
757
                                        'value'         => '_wpseo_business_tax_id',
4✔
758
                                ],
4✔
759
                                'org-vat-id' => [
4✔
760
                                        'is_overridden' => '_wpseo_is_overridden_business_vat_id',
4✔
761
                                        'value'         => '_wpseo_business_vat_id',
4✔
762
                                ],
4✔
763
                        ];
4✔
764

765
                        foreach ( $location_keys as $key => $meta_keys ) {
4✔
766
                                $is_overridden = ( $shared_info === 'on' ) ? \get_post_meta( $primary_location, $meta_keys['is_overridden'], true ) : false;
4✔
767
                                if ( $is_overridden === 'on' || $shared_info !== 'on' ) {
4✔
768
                                        $post_meta_value                  = \get_post_meta( $primary_location, $meta_keys['value'], true );
4✔
769
                                        $defaults['wpseo_titles'][ $key ] = ( $post_meta_value ) ? $post_meta_value : '';
4✔
770
                                }
771
                        }
772
                }
773

774
                return $defaults;
8✔
775
        }
776

777
        /**
778
         * Retrieves the settings and their values.
779
         *
780
         * @param array $default_setting_values The default setting values.
781
         *
782
         * @return array The settings.
783
         */
784
        protected function get_settings( $default_setting_values ) {
×
785
                $settings = [];
×
786

787
                // Add Yoast settings.
788
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
789
                        if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
790
                                $settings[ $option_name ] = \array_merge( $default_setting_values[ $option_name ], WPSEO_Options::get_option( $option_name ) );
×
791
                        }
792
                }
793
                // Add WP settings.
794
                foreach ( self::WP_OPTIONS as $option_name ) {
×
795
                        $settings[ $option_name ] = \get_option( $option_name );
×
796
                }
797

798
                // Remove disallowed settings.
799
                foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) {
×
800
                        foreach ( $disallowed_settings as $disallowed_setting ) {
×
801
                                unset( $settings[ $option_name ][ $disallowed_setting ] );
×
802
                        }
803
                }
804

805
                return $settings;
×
806
        }
807

808
        /**
809
         * Transforms setting values.
810
         *
811
         * @param array $settings The settings.
812
         *
813
         * @return array The settings.
814
         */
815
        protected function transform_settings( $settings ) {
×
816
                if ( isset( $settings['wpseo_titles']['breadcrumbs-sep'] ) ) {
×
817
                        /**
818
                         * The breadcrumbs separator default value is the HTML entity `&raquo;`.
819
                         * Which does not get decoded in our JS, while it did in our Yoast form. Decode it here as an exception.
820
                         */
821
                        $settings['wpseo_titles']['breadcrumbs-sep'] = \html_entity_decode(
×
822
                                $settings['wpseo_titles']['breadcrumbs-sep'],
×
823
                                ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
824
                                'UTF-8'
×
825
                        );
×
826
                }
827

828
                /**
829
                 * Decode some WP options.
830
                 */
831
                $settings['blogdescription'] = \html_entity_decode(
×
832
                        $settings['blogdescription'],
×
833
                        ( \ENT_NOQUOTES | \ENT_HTML5 ),
×
834
                        'UTF-8'
×
835
                );
×
836

837
                if ( isset( $settings['wpseo_llmstxt']['other_included_pages'] ) ) {
×
838
                        // Append an empty page to the other included pages, so that we manage to show an empty field in the UI.
839
                        $settings['wpseo_llmstxt']['other_included_pages'][] = 0;
×
840
                }
841

842
                return $settings;
×
843
        }
844

845
        /**
846
         * Retrieves the disabled settings.
847
         *
848
         * @param array $settings The settings.
849
         *
850
         * @return array The settings.
851
         */
852
        protected function get_disabled_settings( $settings ) {
×
853
                $disabled_settings = [];
×
854
                $site_language     = $this->language_helper->get_language();
×
855

856
                foreach ( WPSEO_Options::$options as $option_name => $instance ) {
×
857
                        if ( ! \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) {
×
858
                                continue;
×
859
                        }
860

861
                        $disabled_settings[ $option_name ] = [];
×
862
                        $option_instance                   = WPSEO_Options::get_option_instance( $option_name );
×
863
                        if ( $option_instance === false ) {
×
864
                                continue;
×
865
                        }
866
                        foreach ( $settings[ $option_name ] as $setting_name => $setting_value ) {
×
867
                                if ( $option_instance->is_disabled( $setting_name ) ) {
×
868
                                        $disabled_settings[ $option_name ][ $setting_name ] = 'network';
×
869
                                }
870
                        }
871
                }
872

873
                // Remove disabled on multisite settings.
874
                if ( \is_multisite() ) {
×
875
                        foreach ( self::DISABLED_ON_MULTISITE_SETTINGS as $option_name => $disabled_ms_settings ) {
×
876
                                if ( \array_key_exists( $option_name, $disabled_settings ) ) {
×
877
                                        foreach ( $disabled_ms_settings as $disabled_ms_setting ) {
×
878
                                                $disabled_settings[ $option_name ][ $disabled_ms_setting ] = 'multisite';
×
879
                                        }
880
                                }
881
                        }
882
                }
883

884
                if ( \array_key_exists( 'wpseo', $disabled_settings ) && ! $this->language_helper->has_inclusive_language_support( $site_language ) ) {
×
885
                        $disabled_settings['wpseo']['inclusive_language_analysis_active'] = 'language';
×
886
                }
887

888
                return $disabled_settings;
×
889
        }
890

891
        /**
892
         * Retrieves the replacement variables.
893
         *
894
         * @return array The replacement variables.
895
         */
896
        protected function get_replacement_variables() {
×
897
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
898
                $specific_replace_vars    = new WPSEO_Admin_Editor_Specific_Replace_Vars();
×
899
                $replacement_variables    = $this->replace_vars->get_replacement_variables_with_labels();
×
900

901
                return [
×
902
                        'variables'   => $replacement_variables,
×
903
                        'recommended' => $recommended_replace_vars->get_recommended_replacevars(),
×
904
                        'specific'    => $specific_replace_vars->get(),
×
905
                        'shared'      => $specific_replace_vars->get_generic( $replacement_variables ),
×
906
                ];
×
907
        }
908

909
        /**
910
         * Retrieves the schema.
911
         *
912
         * @param array $post_types The post types.
913
         *
914
         * @return array The schema.
915
         */
916
        protected function get_schema( array $post_types ) {
×
917
                $schema = [];
×
918

919
                foreach ( $this->schema_types->get_article_type_options() as $article_type ) {
×
920
                        $schema['articleTypes'][ $article_type['value'] ] = [
×
921
                                'label' => $article_type['name'],
×
922
                                'value' => $article_type['value'],
×
923
                        ];
×
924
                }
925

926
                foreach ( $this->schema_types->get_page_type_options() as $page_type ) {
×
927
                        $schema['pageTypes'][ $page_type['value'] ] = [
×
928
                                'label' => $page_type['name'],
×
929
                                'value' => $page_type['value'],
×
930
                        ];
×
931
                }
932

933
                $schema['articleTypeDefaults'] = [];
×
934
                $schema['pageTypeDefaults']    = [];
×
935
                foreach ( $post_types as $name => $post_type ) {
×
936
                        $schema['articleTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-article-type-$name" );
×
937
                        $schema['pageTypeDefaults'][ $name ]    = WPSEO_Options::get_default( 'wpseo_titles', "schema-page-type-$name" );
×
938
                }
939

940
                return $schema;
×
941
        }
942

943
        /**
944
         * Transforms the post types, to represent them.
945
         *
946
         * @param WP_Post_Type[] $post_types The WP_Post_Type array to transform.
947
         *
948
         * @return array The post types.
949
         */
950
        protected function transform_post_types( $post_types ) {
4✔
951
                $transformed    = [];
4✔
952
                $new_post_types = $this->options->get( 'new_post_types', [] );
4✔
953
                foreach ( $post_types as $post_type ) {
4✔
954
                        $transformed[ $post_type->name ] = [
4✔
955
                                'name'                 => $post_type->name,
4✔
956
                                'route'                => $this->get_route( $post_type->name, $post_type->rewrite, $post_type->rest_base ),
4✔
957
                                'label'                => $post_type->label,
4✔
958
                                'singularLabel'        => $post_type->labels->singular_name,
4✔
959
                                'hasArchive'           => $this->post_type_helper->has_archive( $post_type ),
4✔
960
                                'hasSchemaArticleType' => $this->article_helper->is_article_post_type( $post_type->name ),
4✔
961
                                'menuPosition'         => $post_type->menu_position,
4✔
962
                                'isNew'                => \in_array( $post_type->name, $new_post_types, true ),
4✔
963
                        ];
4✔
964
                }
965

966
                \uasort( $transformed, [ $this, 'compare_post_types' ] );
4✔
967

968
                return $transformed;
4✔
969
        }
970

971
        /**
972
         * Compares two post types.
973
         *
974
         * @param array $a The first post type.
975
         * @param array $b The second post type.
976
         *
977
         * @return int The order.
978
         */
979
        protected function compare_post_types( $a, $b ) {
×
980
                if ( $a['menuPosition'] === null && $b['menuPosition'] !== null ) {
×
981
                        return 1;
×
982
                }
983
                if ( $a['menuPosition'] !== null && $b['menuPosition'] === null ) {
×
984
                        return -1;
×
985
                }
986

987
                if ( $a['menuPosition'] === null && $b['menuPosition'] === null ) {
×
988
                        // No position specified, order alphabetically by label.
989
                        return \strnatcmp( $a['label'], $b['label'] );
×
990
                }
991

992
                return ( ( $a['menuPosition'] < $b['menuPosition'] ) ? -1 : 1 );
×
993
        }
994

995
        /**
996
         * Transforms the taxonomies, to represent them.
997
         *
998
         * @param WP_Taxonomy[] $taxonomies      The WP_Taxonomy array to transform.
999
         * @param string[]      $post_type_names The post type names.
1000
         *
1001
         * @return array The taxonomies.
1002
         */
1003
        protected function transform_taxonomies( $taxonomies, $post_type_names ) {
4✔
1004
                $transformed    = [];
4✔
1005
                $new_taxonomies = $this->options->get( 'new_taxonomies', [] );
4✔
1006
                foreach ( $taxonomies as $taxonomy ) {
4✔
1007
                        $transformed[ $taxonomy->name ] = [
4✔
1008
                                'name'          => $taxonomy->name,
4✔
1009
                                'route'         => $this->get_route( $taxonomy->name, $taxonomy->rewrite, $taxonomy->rest_base ),
4✔
1010
                                'label'         => $taxonomy->label,
4✔
1011
                                'showUi'        => $taxonomy->show_ui,
4✔
1012
                                'singularLabel' => $taxonomy->labels->singular_name,
4✔
1013
                                'postTypes'     => \array_filter(
4✔
1014
                                        $taxonomy->object_type,
4✔
1015
                                        static function ( $object_type ) use ( $post_type_names ) {
4✔
1016
                                                return \in_array( $object_type, $post_type_names, true );
4✔
1017
                                        }
4✔
1018
                                ),
4✔
1019
                                'isNew'         => \in_array( $taxonomy->name, $new_taxonomies, true ),
4✔
1020
                        ];
4✔
1021
                }
1022

1023
                \uasort(
4✔
1024
                        $transformed,
4✔
1025
                        static function ( $a, $b ) {
4✔
1026
                                return \strnatcmp( $a['label'], $b['label'] );
1027
                        }
4✔
1028
                );
4✔
1029

1030
                return $transformed;
1031
        }
1032

1033
        /**
1034
         * Gets the route from a name, rewrite and rest_base.
1035
         *
1036
         * @param string $name      The name.
1037
         * @param array  $rewrite   The rewrite data.
1038
         * @param string $rest_base The rest base.
1039
         *
1040
         * @return string The route.
1041
         */
1042
        protected function get_route( $name, $rewrite, $rest_base ) {
×
1043
                $route = $name;
×
1044
                if ( isset( $rewrite['slug'] ) ) {
1045
                        $route = $rewrite['slug'];
×
1046
                }
1047
                if ( ! empty( $rest_base ) ) {
1048
                        $route = $rest_base;
1049
                }
1050
                // Always strip leading slashes.
1051
                while ( \substr( $route, 0, 1 ) === '/' ) {
1052
                        $route = \substr( $route, 1 );
1053
                }
1054

1055
                return $route;
1056
        }
1057

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

1072
                return [
×
1073
                        'siteLogoId' => $site_logo_id,
×
1074
                ];
×
1075
        }
1076
}
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