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

Yoast / wordpress-seo / 6078396534

04 Sep 2023 11:31PM UTC coverage: 45.745% (-0.03%) from 45.771%
6078396534

Pull #20620

github

web-flow
Merge b27c83eed into 773e21dd7
Pull Request #20620: Add black friday checklist promotion in product editor

0 of 12 new or added lines in 3 files covered. (0.0%)

549 existing lines in 1 file now uncovered.

12149 of 26558 relevant lines covered (45.75%)

3.38 hits per line

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

15.0
/admin/metabox/class-metabox.php
1
<?php
2
/**
3
 * WPSEO plugin file.
4
 *
5
 * @package WPSEO\Admin
6
 */
7

8
use Yoast\WP\SEO\Actions\Alert_Dismissal_Action;
9
use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Active_Conditional;
10
use Yoast\WP\SEO\Conditionals\Third_Party\Jetpack_Boost_Not_Premium_Conditional;
11
use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional;
12
use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository;
13
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
14
use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter;
15

16
/**
17
 * This class generates the metabox on the edit post / page as well as contains all page analysis functionality.
18
 */
19
class WPSEO_Metabox extends WPSEO_Meta {
20

21
        /**
22
         * Whether the social tab is enabled.
23
         *
24
         * @var bool
25
         */
26
        private $social_is_enabled;
27

28
        /**
29
         * Helper to determine whether the SEO analysis is enabled.
30
         *
31
         * @var WPSEO_Metabox_Analysis_SEO
32
         */
33
        protected $seo_analysis;
34

35
        /**
36
         * Helper to determine whether the readability analysis is enabled.
37
         *
38
         * @var WPSEO_Metabox_Analysis_Readability
39
         */
40
        protected $readability_analysis;
41

42
        /**
43
         * Helper to determine whether the inclusive language analysis is enabled.
44
         *
45
         * @var WPSEO_Metabox_Analysis_Inclusive_Language
46
         */
47
        protected $inclusive_language_analysis;
48

49
        /**
50
         * The metabox editor object.
51
         *
52
         * @var WPSEO_Metabox_Editor
53
         */
54
        protected $editor;
55

56
        /**
57
         * The Metabox post.
58
         *
59
         * @var WP_Post
60
         */
61
        protected $post = null;
62

63
        /**
64
         * Whether the advanced metadata is enabled.
65
         *
66
         * @var bool
67
         */
68
        protected $is_advanced_metadata_enabled;
69

70
        /**
71
         * Class constructor.
72
         */
73
        public function __construct() {
74
                if ( $this->is_internet_explorer() ) {
×
75
                        add_action( 'add_meta_boxes', [ $this, 'internet_explorer_metabox' ] );
×
76

77
                        return;
×
78
                }
79

80
                add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] );
×
81
                add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
×
82
                add_action( 'wp_insert_post', [ $this, 'save_postdata' ] );
×
83
                add_action( 'edit_attachment', [ $this, 'save_postdata' ] );
×
84
                add_action( 'add_attachment', [ $this, 'save_postdata' ] );
×
85
                add_action( 'admin_init', [ $this, 'translate_meta_boxes' ] );
×
86

87
                $this->editor = new WPSEO_Metabox_Editor();
×
88
                $this->editor->register_hooks();
×
89

90
                $this->social_is_enabled            = WPSEO_Options::get( 'opengraph', false ) || WPSEO_Options::get( 'twitter', false );
×
91
                $this->is_advanced_metadata_enabled = WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta' ) === false;
×
92

93
                $this->seo_analysis                = new WPSEO_Metabox_Analysis_SEO();
×
94
                $this->readability_analysis        = new WPSEO_Metabox_Analysis_Readability();
×
95
                $this->inclusive_language_analysis = new WPSEO_Metabox_Analysis_Inclusive_Language();
×
96
        }
97

98
        /**
99
         * Checks whether the request comes from an IE 11 browser.
100
         *
101
         * @return bool Whether the request comes from an IE 11 browser.
102
         */
103
        public static function is_internet_explorer() {
104
                if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
×
105
                        return false;
×
106
                }
107

108
                $user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
×
109

110
                if ( stripos( $user_agent, 'Trident/7.0' ) === false ) {
×
111
                        return false;
×
112
                }
113

114
                return true;
×
115
        }
116

117
        /**
118
         * Adds an alternative metabox for internet explorer users.
119
         */
120
        public function internet_explorer_metabox() {
121
                $post_types = WPSEO_Post_Type::get_accessible_post_types();
×
122
                $post_types = array_filter( $post_types, [ $this, 'display_metabox' ] );
×
123

124
                if ( ! is_array( $post_types ) || $post_types === [] ) {
×
125
                        return;
×
126
                }
127

128
                $product_title = $this->get_product_title();
×
129

130
                foreach ( $post_types as $post_type ) {
×
131
                        add_filter( "postbox_classes_{$post_type}_wpseo_meta", [ $this, 'wpseo_metabox_class' ] );
×
132

133
                        add_meta_box(
×
134
                                'wpseo_meta',
×
135
                                $product_title,
×
136
                                [ $this, 'render_internet_explorer_notice' ],
×
137
                                $post_type,
×
138
                                'normal',
×
139
                                apply_filters( 'wpseo_metabox_prio', 'high' ),
×
140
                                [ '__block_editor_compatible_meta_box' => true ]
×
141
                        );
142
                }
143
        }
144

145
        /**
146
         * Renders the content for the internet explorer metabox.
147
         *
148
         * @return void
149
         */
150
        public function render_internet_explorer_notice() {
151
                $content = sprintf(
×
152
                        /* translators: 1: Link start tag to the Firefox website, 2: Link start tag to the Chrome website, 3: Link start tag to the Edge website, 4: Link closing tag. */
153
                        esc_html__( 'The browser you are currently using is unfortunately rather dated. Since we strive to give you the best experience possible, we no longer support this browser. Instead, please use %1$sFirefox%4$s, %2$sChrome%4$s or %3$sMicrosoft Edge%4$s.', 'wordpress-seo' ),
×
154
                        '<a href="https://www.mozilla.org/firefox/new/">',
×
155
                        '<a href="https://www.google.com/chrome/">',
×
156
                        '<a href="https://www.microsoft.com/windows/microsoft-edge">',
×
157
                        '</a>'
×
158
                );
159

160
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
161
                echo new Alert_Presenter( $content );
×
162
        }
163

164
        /**
165
         * Translates text strings for use in the meta box.
166
         *
167
         * IMPORTANT: if you want to add a new string (option) somewhere, make sure you add that array key to
168
         * the main meta box definition array in the class WPSEO_Meta() as well!!!!
169
         */
170
        public static function translate_meta_boxes() {
171
                WPSEO_Meta::$meta_fields['general']['title']['title']    = __( 'SEO title', 'wordpress-seo' );
×
172
                WPSEO_Meta::$meta_fields['general']['metadesc']['title'] = __( 'Meta description', 'wordpress-seo' );
×
173

174
                /* translators: %s expands to the post type name. */
175
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['title'] = __( 'Allow search engines to show this %s in search results?', 'wordpress-seo' );
×
176
                if ( (string) get_option( 'blog_public' ) === '0' ) {
×
177
                        WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['description'] = '<span class="error-message">' . __( 'Warning: even though you can set the meta robots setting here, the entire site is set to noindex in the sitewide privacy settings, so these settings won\'t have an effect.', 'wordpress-seo' ) . '</span>';
×
178
                }
179
                /* translators: %1$s expands to Yes or No,  %2$s expands to the post type name.*/
180
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['0'] = __( 'Default for %2$s, currently: %1$s', 'wordpress-seo' );
×
181
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['2'] = __( 'Yes', 'wordpress-seo' );
×
182
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['1'] = __( 'No', 'wordpress-seo' );
×
183

184
                /* translators: %1$s expands to the post type name.*/
185
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['title']        = __( 'Should search engines follow links on this %1$s?', 'wordpress-seo' );
×
186
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['options']['0'] = __( 'Yes', 'wordpress-seo' );
×
187
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['options']['1'] = __( 'No', 'wordpress-seo' );
×
188

189
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['title']                   = __( 'Meta robots advanced', 'wordpress-seo' );
×
190
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['description']             = __( 'If you want to apply advanced <code>meta</code> robots settings for this page, please define them in the following field.', 'wordpress-seo' );
×
191
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['noimageindex'] = __( 'No Image Index', 'wordpress-seo' );
×
192
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['noarchive']    = __( 'No Archive', 'wordpress-seo' );
×
193
                WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['nosnippet']    = __( 'No Snippet', 'wordpress-seo' );
×
194

195
                WPSEO_Meta::$meta_fields['advanced']['bctitle']['title']       = __( 'Breadcrumbs Title', 'wordpress-seo' );
×
196
                WPSEO_Meta::$meta_fields['advanced']['bctitle']['description'] = __( 'Title to use for this page in breadcrumb paths', 'wordpress-seo' );
×
197

198
                WPSEO_Meta::$meta_fields['advanced']['canonical']['title'] = __( 'Canonical URL', 'wordpress-seo' );
×
199

200
                WPSEO_Meta::$meta_fields['advanced']['canonical']['description'] = sprintf(
×
201
                        /* translators: 1: link open tag; 2: link close tag. */
202
                        __( 'The canonical URL that this page should point to. Leave empty to default to permalink. %1$sCross domain canonical%2$s supported too.', 'wordpress-seo' ),
×
203
                        '<a href="https://googlewebmastercentral.blogspot.com/2009/12/handling-legitimate-cross-domain.html" target="_blank" rel="noopener">',
×
204
                        WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
×
205
                );
206
                /* translators: %s expands to the post type name. */
207
                WPSEO_Meta::$meta_fields['advanced']['wordproof_timestamp']['title']        = __( 'Timestamp this %s', 'wordpress-seo' );
×
208
                WPSEO_Meta::$meta_fields['advanced']['wordproof_timestamp']['description']  = __( 'Use WordProof to timestamp this page to comply with legal regulations and join the fight for a more transparant and accountable internet.', 'wordpress-seo' );
×
209
                WPSEO_Meta::$meta_fields['advanced']['wordproof_timestamp']['options']['0'] = __( 'Off', 'wordpress-seo' );
×
210
                WPSEO_Meta::$meta_fields['advanced']['wordproof_timestamp']['options']['1'] = __( 'On', 'wordpress-seo' );
×
211
                WPSEO_Meta::$meta_fields['advanced']['wordproof_timestamp']['type']         = 'hidden';
×
212

213
                WPSEO_Meta::$meta_fields['advanced']['redirect']['title']       = __( '301 Redirect', 'wordpress-seo' );
×
214
                WPSEO_Meta::$meta_fields['advanced']['redirect']['description'] = __( 'The URL that this page should redirect to.', 'wordpress-seo' );
×
215

216
                do_action( 'wpseo_tab_translate' );
×
217
        }
218

219
        /**
220
         * Determines whether the metabox should be shown for the passed identifier.
221
         *
222
         * By default the check is done for post types, but can also be used for taxonomies.
223
         *
224
         * @param string|null $identifier The identifier to check.
225
         * @param string      $type       The type of object to check. Defaults to post_type.
226
         *
227
         * @return bool Whether or not the metabox should be displayed.
228
         */
229
        public function display_metabox( $identifier = null, $type = 'post_type' ) {
230
                return WPSEO_Utils::is_metabox_active( $identifier, $type );
×
231
        }
232

233
        /**
234
         * Adds the Yoast SEO meta box to the edit boxes in the edit post, page,
235
         * attachment, and custom post types pages.
236
         *
237
         * @return void
238
         */
239
        public function add_meta_box() {
240
                $post_types = WPSEO_Post_Type::get_accessible_post_types();
4✔
241
                $post_types = array_filter( $post_types, [ $this, 'display_metabox' ] );
4✔
242

243
                if ( ! is_array( $post_types ) || $post_types === [] ) {
4✔
244
                        return;
×
245
                }
246

247
                $product_title = $this->get_product_title();
4✔
248

249
                foreach ( $post_types as $post_type ) {
4✔
250
                        add_filter( "postbox_classes_{$post_type}_wpseo_meta", [ $this, 'wpseo_metabox_class' ] );
4✔
251

252
                        add_meta_box(
4✔
253
                                'wpseo_meta',
4✔
254
                                $product_title,
4✔
255
                                [ $this, 'meta_box' ],
4✔
256
                                $post_type,
4✔
257
                                'normal',
4✔
258
                                apply_filters( 'wpseo_metabox_prio', 'high' ),
4✔
259
                                [ '__block_editor_compatible_meta_box' => true ]
4✔
260
                        );
2✔
261
                }
262
        }
2✔
263

264
        /**
265
         * Adds CSS classes to the meta box.
266
         *
267
         * @param array $classes An array of postbox CSS classes.
268
         *
269
         * @return array List of classes that will be applied to the editbox container.
270
         */
271
        public function wpseo_metabox_class( $classes ) {
272
                $classes[] = 'yoast wpseo-metabox';
×
273

274
                return $classes;
×
275
        }
276

277
        /**
278
         * Passes variables to js for use with the post-scraper.
279
         *
280
         * @return array
281
         */
282
        public function get_metabox_script_data() {
283
                $permalink = '';
×
284

285
                if ( is_object( $this->get_metabox_post() ) ) {
×
286
                        $permalink = get_sample_permalink( $this->get_metabox_post()->ID );
×
287
                        $permalink = $permalink[0];
×
288
                }
289

290
                $post_formatter = new WPSEO_Metabox_Formatter(
×
291
                        new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink )
×
292
                );
293

294
                $values = $post_formatter->get_values();
×
295

296
                /** This filter is documented in admin/filters/class-cornerstone-filter.php. */
297
                $post_types = apply_filters( 'wpseo_cornerstone_post_types', WPSEO_Post_Type::get_accessible_post_types() );
×
298
                if ( $values['cornerstoneActive'] && ! in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) {
×
299
                        $values['cornerstoneActive'] = false;
×
300
                }
301

302
                if ( $values['semrushIntegrationActive'] && $this->post->post_type === 'attachment' ) {
×
303
                        $values['semrushIntegrationActive'] = 0;
×
304
                }
305

306
                if ( $values['wincherIntegrationActive'] && $this->post->post_type === 'attachment' ) {
×
307
                        $values['wincherIntegrationActive'] = 0;
×
308
                }
309

310
                return $values;
×
311
        }
312

313
        /**
314
         * Determines whether or not the current post type has registered taxonomies.
315
         *
316
         * @return bool Whether the current post type has taxonomies.
317
         */
318
        private function current_post_type_has_taxonomies() {
319
                $post_taxonomies = get_object_taxonomies( get_post_type() );
×
320

321
                return ! empty( $post_taxonomies );
×
322
        }
323

324
        /**
325
         * Determines the scope based on the post type.
326
         * This can be used by the replacevar plugin to determine if a replacement needs to be executed.
327
         *
328
         * @return string String describing the current scope.
329
         */
330
        private function determine_scope() {
331
                if ( $this->get_metabox_post()->post_type === 'page' ) {
×
332
                        return 'page';
×
333
                }
334

335
                return 'post';
×
336
        }
337

338
        /**
339
         * Outputs the meta box.
340
         */
341
        public function meta_box() {
342
                $this->render_hidden_fields();
×
343
                $this->render_tabs();
×
344
        }
345

346
        /**
347
         * Renders the metabox hidden fields.
348
         *
349
         * @return void
350
         */
351
        protected function render_hidden_fields() {
352
                wp_nonce_field( 'yoast_free_metabox', 'yoast_free_metabox_nonce' );
×
353

354
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class.
355
                echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'general' );
×
356

357
                if ( $this->is_advanced_metadata_enabled ) {
×
358
                        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class.
359
                        echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'advanced' );
×
360
                }
361

362
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class.
363
                echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'schema', $this->get_metabox_post()->post_type );
×
364

365
                if ( $this->social_is_enabled ) {
×
366
                        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class.
367
                        echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' );
×
368
                }
369

370
                /**
371
                 * Filter: 'wpseo_content_meta_section_content' - Allow filtering the metabox content before outputting.
372
                 *
373
                 * @api string $post_content The metabox content string.
374
                 */
375
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output should be escaped in the filter.
376
                echo apply_filters( 'wpseo_content_meta_section_content', '' );
×
377
        }
378

379
        /**
380
         * Renders the metabox tabs.
381
         *
382
         * @return void
383
         */
384
        protected function render_tabs() {
385
                echo '<div class="wpseo-metabox-content">';
×
386
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $this->get_product_title() returns a hard-coded string.
387
                printf( '<div class="wpseo-metabox-menu"><ul role="tablist" class="yoast-aria-tabs" aria-label="%s">', $this->get_product_title() );
×
388

389
                $tabs = $this->get_tabs();
×
390

391
                foreach ( $tabs as $tab ) {
×
392
                        if ( $tab->name === 'premium' ) {
×
393
                                continue;
×
394
                        }
395

396
                        $tab->display_link();
×
397
                }
398

399
                echo '</ul></div>';
×
400

401
                foreach ( $tabs as $tab ) {
×
402
                        $tab->display_content();
×
403
                }
404

405
                echo '</div>';
×
406
        }
407

408
        /**
409
         * Returns the relevant metabox tabs for the current view.
410
         *
411
         * @return WPSEO_Metabox_Section[]
412
         */
413
        private function get_tabs() {
414
                $tabs = [];
×
415

416
                $label = __( 'SEO', 'wordpress-seo' );
×
417
                if ( $this->seo_analysis->is_enabled() ) {
×
418
                        $label = '<span class="wpseo-score-icon-container" id="wpseo-seo-score-icon"></span>' . $label;
×
419
                }
420
                $tabs[] = new WPSEO_Metabox_Section_React( 'content', $label );
×
421

422
                if ( $this->readability_analysis->is_enabled() ) {
×
423
                        $tabs[] = new WPSEO_Metabox_Section_Readability();
×
424
                }
425

426
                if ( $this->inclusive_language_analysis->is_enabled() ) {
×
427
                        $tabs[] = new WPSEO_Metabox_Section_Inclusive_Language();
×
428
                }
429

430
                if ( $this->is_advanced_metadata_enabled ) {
×
431
                        $tabs[] = new WPSEO_Metabox_Section_React(
×
432
                                'schema',
×
433
                                '<span class="wpseo-schema-icon"></span>' . __( 'Schema', 'wordpress-seo' ),
×
434
                                ''
×
435
                        );
436
                }
437

438
                if ( $this->social_is_enabled ) {
×
439
                        $tabs[] = new WPSEO_Metabox_Section_React(
×
440
                                'social',
×
441
                                '<span class="dashicons dashicons-share"></span>' . __( 'Social', 'wordpress-seo' ),
×
442
                                '',
×
443
                                [
444
                                        'html_after' => '<div id="wpseo-section-social"></div>',
×
445
                                ]
446
                        );
447
                }
448

449
                $tabs = array_merge( $tabs, $this->get_additional_tabs() );
×
450

451
                return $tabs;
×
452
        }
453

454
        /**
455
         * Returns the metabox tabs that have been added by other plugins.
456
         *
457
         * @return WPSEO_Metabox_Section_Additional[]
458
         */
459
        protected function get_additional_tabs() {
460
                $tabs = [];
8✔
461

462
                /**
463
                 * Private filter: 'yoast_free_additional_metabox_sections'.
464
                 *
465
                 * Meant for internal use only. Allows adding additional tabs to the Yoast SEO metabox.
466
                 *
467
                 * @since 11.9
468
                 *
469
                 * @param array[] $tabs {
470
                 *     An array of arrays with tab specifications.
471
                 *
472
                 *     @type array $tab {
473
                 *          A tab specification.
474
                 *
475
                 *          @type string $name         The name of the tab. Used in the HTML IDs, href and aria properties.
476
                 *          @type string $link_content The content of the tab link.
477
                 *          @type string $content      The content of the tab.
478
                 *          @type array $options {
479
                 *              Optional. Extra options.
480
                 *
481
                 *              @type string $link_class      Optional. The class for the tab link.
482
                 *              @type string $link_aria_label Optional. The aria label of the tab link.
483
                 *          }
484
                 *     }
485
                 * }
486
                 */
487
                $requested_tabs = apply_filters( 'yoast_free_additional_metabox_sections', [] );
8✔
488

489
                foreach ( $requested_tabs as $tab ) {
8✔
490
                        if ( is_array( $tab ) && array_key_exists( 'name', $tab ) && array_key_exists( 'link_content', $tab ) && array_key_exists( 'content', $tab ) ) {
6✔
491
                                $options = array_key_exists( 'options', $tab ) ? $tab['options'] : [];
4✔
492
                                $tabs[]  = new WPSEO_Metabox_Section_Additional(
4✔
493
                                        $tab['name'],
4✔
494
                                        $tab['link_content'],
4✔
495
                                        $tab['content'],
4✔
496
                                        $options
4✔
497
                                );
2✔
498
                        }
499
                }
500

501
                return $tabs;
8✔
502
        }
503

504
        /**
505
         * Adds a line in the meta box.
506
         *
507
         * @todo [JRF] Check if $class is added appropriately everywhere.
508
         *
509
         * @param array  $meta_field_def Contains the vars based on which output is generated.
510
         * @param string $key            Internal key (without prefix).
511
         *
512
         * @return string
513
         */
514
        public function do_meta_box( $meta_field_def, $key = '' ) {
515
                $content      = '';
×
516
                $esc_form_key = esc_attr( WPSEO_Meta::$form_prefix . $key );
×
517
                $meta_value   = WPSEO_Meta::get_value( $key, $this->get_metabox_post()->ID );
×
518

519
                $class = '';
×
520
                if ( isset( $meta_field_def['class'] ) && $meta_field_def['class'] !== '' ) {
×
521
                        $class = ' ' . $meta_field_def['class'];
×
522
                }
523

524
                $placeholder = '';
×
525
                if ( isset( $meta_field_def['placeholder'] ) && $meta_field_def['placeholder'] !== '' ) {
×
526
                        $placeholder = $meta_field_def['placeholder'];
×
527
                }
528

529
                $aria_describedby = '';
×
530
                $description      = '';
×
531
                if ( isset( $meta_field_def['description'] ) ) {
×
532
                        $aria_describedby = ' aria-describedby="' . $esc_form_key . '-desc"';
×
533
                        $description      = '<p id="' . $esc_form_key . '-desc" class="yoast-metabox__description">' . $meta_field_def['description'] . '</p>';
×
534
                }
535

536
                // Add a hide_on_pages option that returns nothing when the field is rendered on a page.
537
                if ( isset( $meta_field_def['hide_on_pages'] ) && $meta_field_def['hide_on_pages'] && get_post_type() === 'page' ) {
×
538
                        return '';
×
539
                }
540

541
                switch ( $meta_field_def['type'] ) {
×
542
                        case 'text':
×
543
                                $ac = '';
×
544
                                if ( isset( $meta_field_def['autocomplete'] ) && $meta_field_def['autocomplete'] === false ) {
×
545
                                        $ac = 'autocomplete="off" ';
×
546
                                }
547
                                if ( $placeholder !== '' ) {
×
548
                                        $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"';
×
549
                                }
550
                                $content .= '<input type="text"' . $placeholder . ' id="' . $esc_form_key . '" ' . $ac . 'name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '" class="large-text' . $class . '"' . $aria_describedby . '/>';
×
551
                                break;
×
552

553
                        case 'url':
×
554
                                if ( $placeholder !== '' ) {
×
555
                                        $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"';
×
556
                                }
557
                                $content .= '<input type="url"' . $placeholder . ' id="' . $esc_form_key . '" name="' . $esc_form_key . '" value="' . esc_attr( urldecode( $meta_value ) ) . '" class="large-text' . $class . '"' . $aria_describedby . '/>';
×
558
                                break;
×
559

560
                        case 'textarea':
×
561
                                $rows = 3;
×
562
                                if ( isset( $meta_field_def['rows'] ) && $meta_field_def['rows'] > 0 ) {
×
563
                                        $rows = $meta_field_def['rows'];
×
564
                                }
565
                                $content .= '<textarea class="large-text' . $class . '" rows="' . esc_attr( $rows ) . '" id="' . $esc_form_key . '" name="' . $esc_form_key . '"' . $aria_describedby . '>' . esc_textarea( $meta_value ) . '</textarea>';
×
566
                                break;
×
567

568
                        case 'hidden':
×
569
                                $default = '';
×
570
                                if ( isset( $meta_field_def['default'] ) ) {
×
571
                                        $default = sprintf( ' data-default="%s"', esc_attr( $meta_field_def['default'] ) );
×
572
                                }
573
                                $content .= '<input type="hidden" id="' . $esc_form_key . '" name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '"' . $default . '/>' . "\n";
×
574
                                break;
×
575
                        case 'select':
×
576
                                if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) {
×
577
                                        $content .= '<select name="' . $esc_form_key . '" id="' . $esc_form_key . '" class="yoast' . $class . '">';
×
578
                                        foreach ( $meta_field_def['options'] as $val => $option ) {
×
579
                                                $selected = selected( $meta_value, $val, false );
×
580
                                                $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
×
581
                                        }
582
                                        unset( $val, $option, $selected );
×
583
                                        $content .= '</select>';
×
584
                                }
585
                                break;
×
586

587
                        case 'multiselect':
×
588
                                if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) {
×
589

590
                                        // Set $meta_value as $selected_arr.
591
                                        $selected_arr = $meta_value;
×
592

593
                                        // If the multiselect field is 'meta-robots-adv' we should explode on ,.
594
                                        if ( $key === 'meta-robots-adv' ) {
×
595
                                                $selected_arr = explode( ',', $meta_value );
×
596
                                        }
597

598
                                        if ( ! is_array( $selected_arr ) ) {
×
599
                                                $selected_arr = (array) $selected_arr;
×
600
                                        }
601

602
                                        $options_count = count( $meta_field_def['options'] );
×
603

604
                                        $content .= '<select multiple="multiple" size="' . esc_attr( $options_count ) . '" name="' . $esc_form_key . '[]" id="' . $esc_form_key . '" class="yoast' . $class . '"' . $aria_describedby . '>';
×
605
                                        foreach ( $meta_field_def['options'] as $val => $option ) {
×
606
                                                $selected = '';
×
607
                                                if ( in_array( $val, $selected_arr, true ) ) {
×
608
                                                        $selected = ' selected="selected"';
×
609
                                                }
610
                                                $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
×
611
                                        }
612
                                        $content .= '</select>';
×
613
                                        unset( $val, $option, $selected, $selected_arr, $options_count );
×
614
                                }
615
                                break;
×
616

617
                        case 'checkbox':
×
618
                                $checked  = checked( $meta_value, 'on', false );
×
619
                                $expl     = ( isset( $meta_field_def['expl'] ) ) ? esc_html( $meta_field_def['expl'] ) : '';
×
620
                                $content .= '<input type="checkbox" id="' . $esc_form_key . '" name="' . $esc_form_key . '" ' . $checked . ' value="on" class="yoast' . $class . '"' . $aria_describedby . '/> <label for="' . $esc_form_key . '">' . $expl . '</label>';
×
621
                                unset( $checked, $expl );
×
622
                                break;
×
623

624
                        case 'radio':
×
625
                                if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) {
×
626
                                        foreach ( $meta_field_def['options'] as $val => $option ) {
×
627
                                                $checked  = checked( $meta_value, $val, false );
×
628
                                                $content .= '<input type="radio" ' . $checked . ' id="' . $esc_form_key . '_' . esc_attr( $val ) . '" name="' . $esc_form_key . '" value="' . esc_attr( $val ) . '"/> <label for="' . $esc_form_key . '_' . esc_attr( $val ) . '">' . esc_html( $option ) . '</label> ';
×
629
                                        }
630
                                        unset( $val, $option, $checked );
×
631
                                }
632
                                break;
×
633

634
                        case 'upload':
×
635
                                $content .= '<input' .
636
                                        ' id="' . $esc_form_key . '"' .
×
637
                                        ' type="text"' .
×
638
                                        ' size="36"' .
×
639
                                        ' class="' . $class . '"' .
×
640
                                        ' name="' . $esc_form_key . '"' .
×
641
                                        ' value="' . esc_attr( $meta_value ) . '"' . $aria_describedby .
×
642
                                        ' readonly="readonly"' .
×
643
                                        ' /> ';
×
644
                                $content .= '<input' .
645
                                        ' id="' . esc_attr( $esc_form_key ) . '_button"' .
×
646
                                        ' class="wpseo_image_upload_button button"' .
×
647
                                        ' data-target="' . esc_attr( $esc_form_key ) . '"' .
×
648
                                        ' data-target-id="' . esc_attr( $esc_form_key ) . '-id"' .
×
649
                                        ' type="button"' .
×
650
                                        ' value="' . esc_attr__( 'Upload Image', 'wordpress-seo' ) . '"' .
×
651
                                        ' /> ';
×
652
                                $content .= '<input' .
653
                                        ' class="wpseo_image_remove_button button"' .
654
                                        ' type="button"' .
655
                                        ' value="' . esc_attr__( 'Clear Image', 'wordpress-seo' ) . '"' .
×
656
                                        ' />';
×
657
                                break;
×
658
                }
659

660
                $html = '';
×
661
                if ( $content === '' ) {
×
662
                        $content = apply_filters( 'wpseo_do_meta_box_field_' . $key, $content, $meta_value, $esc_form_key, $meta_field_def, $key );
×
663
                }
664

665
                if ( $content !== '' ) {
×
666

667
                        $title = esc_html( $meta_field_def['title'] );
×
668

669
                        // By default, use the field title as a label element.
670
                        $label = '<label for="' . $esc_form_key . '">' . $title . '</label>';
×
671

672
                        // Set the inline help and help panel, if any.
673
                        $help_button = '';
×
674
                        $help_panel  = '';
×
675
                        if ( isset( $meta_field_def['help'] ) && $meta_field_def['help'] !== '' ) {
×
676
                                $help        = new WPSEO_Admin_Help_Panel( $key, $meta_field_def['help-button'], $meta_field_def['help'] );
×
677
                                $help_button = $help->get_button_html();
×
678
                                $help_panel  = $help->get_panel_html();
×
679
                        }
680

681
                        // If it's a set of radio buttons, output proper fieldset and legend.
682
                        if ( $meta_field_def['type'] === 'radio' ) {
×
683
                                return '<fieldset><legend>' . $title . '</legend>' . $help_button . $help_panel . $content . $description . '</fieldset>';
×
684
                        }
685

686
                        // If it's a single checkbox, ignore the title.
687
                        if ( $meta_field_def['type'] === 'checkbox' ) {
×
688
                                $label = '';
×
689
                        }
690

691
                        // Other meta box content or form fields.
692
                        if ( $meta_field_def['type'] === 'hidden' ) {
×
693
                                $html = $content;
×
694
                        }
695
                        else {
696
                                $html = $label . $description . $help_button . $help_panel . $content;
×
697
                        }
698
                }
699

700
                return $html;
×
701
        }
702

703
        /**
704
         * Saves the WP SEO metadata for posts.
705
         *
706
         * {@internal $_POST parameters are validated via sanitize_post_meta().}}
707
         *
708
         * @param int $post_id Post ID.
709
         *
710
         * @return bool|void Boolean false if invalid save post request.
711
         */
712
        public function save_postdata( $post_id ) {
713
                // Bail if this is a multisite installation and the site has been switched.
714
                if ( is_multisite() && ms_is_switched() ) {
64✔
715
                        return false;
×
716
                }
717

718
                if ( $post_id === null ) {
64✔
719
                        return false;
×
720
                }
721

722
                // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in wp_verify_none.
723
                if ( ! isset( $_POST['yoast_free_metabox_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['yoast_free_metabox_nonce'] ), 'yoast_free_metabox' ) ) {
64✔
724
                        return false;
×
725
                }
726

727
                if ( wp_is_post_revision( $post_id ) ) {
64✔
728
                        $post_id = wp_is_post_revision( $post_id );
×
729
                }
730

731
                /**
732
                 * Determine we're not accidentally updating a different post.
733
                 * We can't use filter_input here as the ID isn't available at this point, other than in the $_POST data.
734
                 */
735
                if ( ! isset( $_POST['ID'] ) || $post_id !== (int) $_POST['ID'] ) {
64✔
736
                        return false;
×
737
                }
738

739
                clean_post_cache( $post_id );
64✔
740
                $post = get_post( $post_id );
64✔
741

742
                if ( ! is_object( $post ) ) {
64✔
743
                        // Non-existent post.
744
                        return false;
×
745
                }
746

747
                do_action( 'wpseo_save_compare_data', $post );
64✔
748

749
                $social_fields = [];
64✔
750
                if ( $this->social_is_enabled ) {
64✔
751
                        $social_fields = WPSEO_Meta::get_meta_field_defs( 'social' );
64✔
752
                }
753

754
                $meta_boxes = apply_filters( 'wpseo_save_metaboxes', [] );
64✔
755
                $meta_boxes = array_merge(
64✔
756
                        $meta_boxes,
64✔
757
                        WPSEO_Meta::get_meta_field_defs( 'general', $post->post_type ),
64✔
758
                        WPSEO_Meta::get_meta_field_defs( 'advanced' ),
64✔
759
                        $social_fields,
64✔
760
                        WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type )
64✔
761
                );
32✔
762

763
                foreach ( $meta_boxes as $key => $meta_box ) {
64✔
764

765
                        // If analysis is disabled remove that analysis score value from the DB.
766
                        if ( $this->is_meta_value_disabled( $key ) ) {
64✔
767
                                WPSEO_Meta::delete( $key, $post_id );
64✔
768
                                continue;
64✔
769
                        }
770

771
                        $data       = null;
64✔
772
                        $field_name = WPSEO_Meta::$form_prefix . $key;
64✔
773

774
                        if ( $meta_box['type'] === 'checkbox' ) {
64✔
775
                                $data = isset( $_POST[ $field_name ] ) ? 'on' : 'off';
×
776
                        }
777
                        else {
778
                                if ( isset( $_POST[ $field_name ] ) ) {
64✔
779
                                        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We're preparing to do just that.
780
                                        $data = wp_unslash( $_POST[ $field_name ] );
64✔
781

782
                                        // For multi-select.
783
                                        if ( is_array( $data ) ) {
64✔
784
                                                $data = array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $data );
8✔
785
                                        }
786

787
                                        if ( is_string( $data ) ) {
64✔
788
                                                $data = ( $key !== 'canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
56✔
789
                                        }
790
                                }
791

792
                                // Reset options when no entry is present with multiselect - only applies to `meta-robots-adv` currently.
793
                                if ( ! isset( $_POST[ $field_name ] ) && ( $meta_box['type'] === 'multiselect' ) ) {
64✔
794
                                        $data = [];
×
795
                                }
796
                        }
797

798
                        if ( $data !== null ) {
64✔
799
                                WPSEO_Meta::set_value( $key, $data, $post_id );
64✔
800
                        }
801
                }
802

803
                do_action( 'wpseo_saved_postdata' );
64✔
804
        }
32✔
805

806
        /**
807
         * Determines if the given meta value key is disabled.
808
         *
809
         * @param string $key The key of the meta value.
810
         *
811
         * @return bool Whether the given meta value key is disabled.
812
         */
813
        public function is_meta_value_disabled( $key ) {
814
                if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) {
×
815
                        return true;
×
816
                }
817

818
                if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) {
×
819
                        return true;
×
820
                }
821

822
                if ( $key === 'inclusive_language_score' && ! $this->inclusive_language_analysis->is_enabled() ) {
×
823
                        return true;
×
824
                }
825

826
                return false;
×
827
        }
828

829
        /**
830
         * Enqueues all the needed JS and CSS.
831
         *
832
         * @todo [JRF => whomever] Create css/metabox-mp6.css file and add it to the below allowed colors array when done.
833
         */
834
        public function enqueue() {
835
                global $pagenow;
8✔
836

837
                $asset_manager = new WPSEO_Admin_Asset_Manager();
8✔
838

839
                $is_editor = self::is_post_overview( $pagenow ) || self::is_post_edit( $pagenow );
8✔
840

841
                if ( self::is_post_overview( $pagenow ) ) {
8✔
842
                        $asset_manager->enqueue_style( 'edit-page' );
×
843
                        $asset_manager->enqueue_script( 'edit-page' );
×
844

845
                        return;
×
846
                }
847

848
                /* Filter 'wpseo_always_register_metaboxes_on_admin' documented in wpseo-main.php */
849
                if ( ( $is_editor === false && apply_filters( 'wpseo_always_register_metaboxes_on_admin', false ) === false ) || $this->display_metabox() === false ) {
8✔
850
                        return;
8✔
851
                }
852

853
                $post_id = get_queried_object_id();
×
854
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
855
                if ( empty( $post_id ) && isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) {
×
856
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
857
                        $post_id = sanitize_text_field( wp_unslash( $_GET['post'] ) );
×
858
                }
859

860
                if ( $post_id !== 0 ) {
×
861
                        // Enqueue files needed for upload functionality.
862
                        wp_enqueue_media( [ 'post' => $post_id ] );
×
863
                }
864

865
                $asset_manager->enqueue_style( 'metabox-css' );
×
866
                $asset_manager->enqueue_style( 'scoring' );
×
867
                $asset_manager->enqueue_style( 'monorepo' );
×
868
                $asset_manager->enqueue_style( 'tailwind' );
×
869
                $asset_manager->enqueue_style( 'ai-generator' );
×
870

871
                $is_block_editor  = WP_Screen::get()->is_block_editor();
×
872
                $post_edit_handle = 'post-edit';
×
873
                if ( ! $is_block_editor ) {
×
874
                        $post_edit_handle = 'post-edit-classic';
×
875
                }
876
                $asset_manager->enqueue_script( $post_edit_handle );
×
877
                $asset_manager->enqueue_style( 'admin-css' );
×
878

879
                /**
880
                 * Removes the emoji script as it is incompatible with both React and any
881
                 * contenteditable fields.
882
                 */
883
                remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
×
884

885
                $asset_manager->localize_script( $post_edit_handle, 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
×
886

887
                $plugins_script_data = [
888
                        'replaceVars' => [
889
                                'no_parent_text'           => __( '(no parent)', 'wordpress-seo' ),
×
890
                                'replace_vars'             => $this->get_replace_vars(),
×
891
                                'hidden_replace_vars'      => $this->get_hidden_replace_vars(),
×
892
                                'recommended_replace_vars' => $this->get_recommended_replace_vars(),
×
893
                                'scope'                    => $this->determine_scope(),
×
894
                                'has_taxonomies'           => $this->current_post_type_has_taxonomies(),
×
895
                        ],
896
                        'shortcodes' => [
897
                                'wpseo_filter_shortcodes_nonce' => wp_create_nonce( 'wpseo-filter-shortcodes' ),
×
898
                                'wpseo_shortcode_tags'          => $this->get_valid_shortcode_tags(),
×
899
                        ],
900
                ];
901

902
                $worker_script_data = [
903
                        'url'                     => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
×
904
                        'dependencies'            => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
×
905
                        'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
×
906
                        'log_level'               => WPSEO_Utils::get_analysis_worker_log_level(),
×
907
                ];
908

909
                $alert_dismissal_action            = YoastSEO()->classes->get( Alert_Dismissal_Action::class );
×
910
                $dismissed_alerts                  = $alert_dismissal_action->all_dismissed();
×
911
                $woocommerce_conditional           = new WooCommerce_Conditional();
×
912
                $woocommerce_active                = $woocommerce_conditional->is_met();
×
913
                $wpseo_plugin_availability_checker = new WPSEO_Plugin_Availability();
×
914
                $woocommerce_seo_file              = 'wpseo-woocommerce/wpseo-woocommerce.php';
×
915
                $woocommerce_seo_active            = $wpseo_plugin_availability_checker->is_active( $woocommerce_seo_file );
×
916

917
                $script_data = [
918
                        // @todo replace this translation with JavaScript translations.
919
                        'media'                      => [ 'choose_image' => __( 'Use Image', 'wordpress-seo' ) ],
×
920
                        'metabox'                    => $this->get_metabox_script_data(),
×
921
                        'userLanguageCode'           => WPSEO_Language_Utils::get_language( \get_user_locale() ),
×
922
                        'isPost'                     => true,
923
                        'isBlockEditor'              => $is_block_editor,
×
924
                        'postId'                     => $post_id,
×
925
                        'postStatus'                 => get_post_status( $post_id ),
×
926
                        'postType'                   => get_post_type( $post_id ),
×
927
                        'usedKeywordsNonce'          => \wp_create_nonce( 'wpseo-keyword-usage-and-post-types' ),
×
928
                        'analysis'                   => [
929
                                'plugins' => $plugins_script_data,
×
930
                                'worker'  => $worker_script_data,
×
931
                        ],
932
                        'dismissedAlerts'            => $dismissed_alerts,
×
NEW
933
                        'currentPromotions'          => YoastSEO()->classes->get( Yoast\WP\SEO\Promotions\Application\Promotion_Manager::class )->get_current_promotions(),
×
934
                        'webinarIntroBlockEditorUrl' => WPSEO_Shortlinker::get( 'https://yoa.st/webinar-intro-block-editor' ),
×
935
                        'isJetpackBoostActive'       => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Active_Conditional::class )->is_met() : false,
×
936
                        'isJetpackBoostNotPremium'   => ( $is_block_editor ) ? YoastSEO()->classes->get( Jetpack_Boost_Not_Premium_Conditional::class )->is_met() : false,
×
937
                        'woocommerceUpsell'          => get_post_type( $post_id ) === 'product' && ! $woocommerce_seo_active && $woocommerce_active,
×
NEW
938
                        'isWooCommerceActive'        => $woocommerce_active,
×
939
                        'linkParams'                 => WPSEO_Shortlinker::get_query_params(),
×
940
                        'pluginUrl'                  => \plugins_url( '', \WPSEO_FILE ),
×
941
                        'wistiaEmbedPermission'      => YoastSEO()->classes->get( Wistia_Embed_Permission_Repository::class )->get_value_for_user( \get_current_user_id() ),
×
942
                ];
943

944
                if ( post_type_supports( get_post_type(), 'thumbnail' ) ) {
×
945
                        $asset_manager->enqueue_style( 'featured-image' );
×
946

947
                        // @todo replace this translation with JavaScript translations.
948
                        $script_data['featuredImage'] = [
×
949
                                'featured_image_notice' => __( 'SEO issue: The featured image should be at least 200 by 200 pixels to be picked up by Facebook and other social media sites.', 'wordpress-seo' ),
×
950
                        ];
951
                }
952

953
                $asset_manager->localize_script( $post_edit_handle, 'wpseoScriptData', $script_data );
×
954
                $asset_manager->enqueue_user_language_script();
×
955
        }
956

957
        /**
958
         * Returns post in metabox context.
959
         *
960
         * @return WP_Post|array
961
         */
962
        protected function get_metabox_post() {
963
                if ( $this->post !== null ) {
×
964
                        return $this->post;
×
965
                }
966

967
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
968
                if ( isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) {
×
969
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, Sanitization happens in the validate_int function.
970
                        $post_id = (int) WPSEO_Utils::validate_int( wp_unslash( $_GET['post'] ) );
×
971

972
                        $this->post = get_post( $post_id );
×
973

974
                        return $this->post;
×
975
                }
976

977
                if ( isset( $GLOBALS['post'] ) ) {
×
978
                        $this->post = $GLOBALS['post'];
×
979

980
                        return $this->post;
×
981
                }
982

983
                return [];
×
984
        }
985

986
        /**
987
         * Returns an array with shortcode tags for all registered shortcodes.
988
         *
989
         * @return array
990
         */
991
        private function get_valid_shortcode_tags() {
992
                $shortcode_tags = [];
×
993

994
                foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
×
995
                        $shortcode_tags[] = $tag;
×
996
                }
997

998
                return $shortcode_tags;
×
999
        }
1000

1001
        /**
1002
         * Prepares the replace vars for localization.
1003
         *
1004
         * @return array Replace vars.
1005
         */
1006
        private function get_replace_vars() {
1007
                $cached_replacement_vars = [];
×
1008

1009
                $vars_to_cache = [
1010
                        'date',
×
1011
                        'id',
1012
                        'sitename',
1013
                        'sitedesc',
1014
                        'sep',
1015
                        'page',
1016
                        'currentdate',
1017
                        'currentyear',
1018
                        'currentmonth',
1019
                        'currentday',
1020
                        'post_year',
1021
                        'post_month',
1022
                        'post_day',
1023
                        'name',
1024
                        'author_first_name',
1025
                        'author_last_name',
1026
                        'permalink',
1027
                        'post_content',
1028
                        'category_title',
1029
                        'tag',
1030
                        'category',
1031
                ];
1032

1033
                foreach ( $vars_to_cache as $var ) {
×
1034
                        $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $this->get_metabox_post() );
×
1035
                }
1036

1037
                // Merge custom replace variables with the WordPress ones.
1038
                return array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $this->get_metabox_post() ) );
×
1039
        }
1040

1041
        /**
1042
         * Returns the list of replace vars that should be hidden inside the editor.
1043
         *
1044
         * @return string[] The hidden replace vars.
1045
         */
1046
        protected function get_hidden_replace_vars() {
1047
                return ( new WPSEO_Replace_Vars() )->get_hidden_replace_vars();
×
1048
        }
1049

1050
        /**
1051
         * Prepares the recommended replace vars for localization.
1052
         *
1053
         * @return array Recommended replacement variables.
1054
         */
1055
        private function get_recommended_replace_vars() {
1056
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
1057

1058
                // What is recommended depends on the current context.
1059
                $post_type = $recommended_replace_vars->determine_for_post( $this->get_metabox_post() );
×
1060

1061
                return $recommended_replace_vars->get_recommended_replacevars_for( $post_type );
×
1062
        }
1063

1064
        /**
1065
         * Gets the custom replace variables for custom taxonomies and fields.
1066
         *
1067
         * @param WP_Post $post The post to check for custom taxonomies and fields.
1068
         *
1069
         * @return array Array containing all the replacement variables.
1070
         */
1071
        private function get_custom_replace_vars( $post ) {
1072
                return [
1073
                        'custom_fields'     => $this->get_custom_fields_replace_vars( $post ),
×
1074
                        'custom_taxonomies' => $this->get_custom_taxonomies_replace_vars( $post ),
×
1075
                ];
1076
        }
1077

1078
        /**
1079
         * Gets the custom replace variables for custom taxonomies.
1080
         *
1081
         * @param WP_Post $post The post to check for custom taxonomies.
1082
         *
1083
         * @return array Array containing all the replacement variables.
1084
         */
1085
        private function get_custom_taxonomies_replace_vars( $post ) {
1086
                $taxonomies          = get_object_taxonomies( $post, 'objects' );
×
1087
                $custom_replace_vars = [];
×
1088

1089
                foreach ( $taxonomies as $taxonomy_name => $taxonomy ) {
×
1090

1091
                        if ( is_string( $taxonomy ) ) { // If attachment, see https://core.trac.wordpress.org/ticket/37368 .
×
1092
                                $taxonomy_name = $taxonomy;
×
1093
                                $taxonomy      = get_taxonomy( $taxonomy_name );
×
1094
                        }
1095

1096
                        if ( $taxonomy->_builtin && $taxonomy->public ) {
×
1097
                                continue;
×
1098
                        }
1099

1100
                        $custom_replace_vars[ $taxonomy_name ] = [
×
1101
                                'name'        => $taxonomy->name,
×
1102
                                'description' => $taxonomy->description,
×
1103
                        ];
1104
                }
1105

1106
                return $custom_replace_vars;
×
1107
        }
1108

1109
        /**
1110
         * Gets the custom replace variables for custom fields.
1111
         *
1112
         * @param WP_Post $post The post to check for custom fields.
1113
         *
1114
         * @return array Array containing all the replacement variables.
1115
         */
1116
        private function get_custom_fields_replace_vars( $post ) {
1117
                $custom_replace_vars = [];
×
1118

1119
                // If no post object is passed, return the empty custom_replace_vars array.
1120
                if ( ! is_object( $post ) ) {
×
1121
                        return $custom_replace_vars;
×
1122
                }
1123

1124
                $custom_fields = get_post_custom( $post->ID );
×
1125

1126
                // If $custom_fields is an empty string or generally not an array, return early.
1127
                if ( ! is_array( $custom_fields ) ) {
×
1128
                        return $custom_replace_vars;
×
1129
                }
1130

1131
                $meta = YoastSEO()->meta->for_post( $post->ID );
×
1132

1133
                if ( ! $meta ) {
×
1134
                        return $custom_replace_vars;
×
1135
                }
1136

1137
                // Simply concatenate all fields containing replace vars so we can handle them all with a single regex find.
1138
                $replace_vars_fields = implode(
×
1139
                        ' ',
×
1140
                        [
1141
                                $meta->presentation->title,
×
1142
                                $meta->presentation->meta_description,
×
1143
                        ]
1144
                );
1145

1146
                preg_match_all( '/%%cf_([A-Za-z0-9_]+)%%/', $replace_vars_fields, $matches );
×
1147
                $fields_to_include = $matches[1];
×
1148
                foreach ( $custom_fields as $custom_field_name => $custom_field ) {
×
1149
                        // Skip private custom fields.
1150
                        if ( substr( $custom_field_name, 0, 1 ) === '_' ) {
×
1151
                                continue;
×
1152
                        }
1153

1154
                        // Skip custom fields that are not used, new ones will be fetched dynamically.
1155
                        if ( ! in_array( $custom_field_name, $fields_to_include, true ) ) {
×
1156
                                continue;
×
1157
                        }
1158

1159
                        // Skip custom field values that are serialized.
1160
                        if ( is_serialized( $custom_field[0] ) ) {
×
1161
                                continue;
×
1162
                        }
1163

1164
                        $custom_replace_vars[ $custom_field_name ] = $custom_field[0];
×
1165
                }
1166

1167
                return $custom_replace_vars;
×
1168
        }
1169

1170
        /**
1171
         * Checks if the page is the post overview page.
1172
         *
1173
         * @param string $page The page to check for the post overview page.
1174
         *
1175
         * @return bool Whether or not the given page is the post overview page.
1176
         */
1177
        public static function is_post_overview( $page ) {
1178
                return $page === 'edit.php';
×
1179
        }
1180

1181
        /**
1182
         * Checks if the page is the post edit page.
1183
         *
1184
         * @param string $page The page to check for the post edit page.
1185
         *
1186
         * @return bool Whether or not the given page is the post edit page.
1187
         */
1188
        public static function is_post_edit( $page ) {
1189
                return $page === 'post.php'
×
1190
                        || $page === 'post-new.php';
×
1191
        }
1192

1193
        /**
1194
         * Retrieves the product title.
1195
         *
1196
         * @return string The product title.
1197
         */
1198
        protected function get_product_title() {
1199
                return YoastSEO()->helpers->product->get_product_name();
×
1200
        }
1201
}
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