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

Yoast / wordpress-seo / 0fdb51d5ef87fb55b55da5a950cf7350c155980d

11 Mar 2025 10:16AM UTC coverage: 53.422% (-1.3%) from 54.687%
0fdb51d5ef87fb55b55da5a950cf7350c155980d

push

github

web-flow
Merge pull request #22086 from Yoast/add-pregnant-women-to-potentially-non-inclusive-phrases

Inclusive language: Add 'pregnant women' to potentially non-inclusive phrases

7918 of 13987 branches covered (56.61%)

Branch coverage included in aggregate %.

30526 of 57976 relevant lines covered (52.65%)

41097.18 hits per line

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

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

8
use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository;
9
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
10

11
/**
12
 * Class that handles the edit boxes on taxonomy edit pages.
13
 */
14
class WPSEO_Taxonomy {
15

16
        /**
17
         * The current active taxonomy.
18
         *
19
         * @var string
20
         */
21
        private $taxonomy = '';
22

23
        /**
24
         * Holds the metabox SEO analysis instance.
25
         *
26
         * @var WPSEO_Metabox_Analysis_SEO
27
         */
28
        private $analysis_seo;
29

30
        /**
31
         * Holds the metabox readability analysis instance.
32
         *
33
         * @var WPSEO_Metabox_Analysis_Readability
34
         */
35
        private $analysis_readability;
36

37
        /**
38
         * Holds the metabox inclusive language analysis instance.
39
         *
40
         * @var WPSEO_Metabox_Analysis_Inclusive_Language
41
         */
42
        private $analysis_inclusive_language;
43

44
        /**
45
         * Class constructor.
46
         */
47
        public function __construct() {
×
48
                $this->taxonomy = $this::get_taxonomy();
×
49

50
                add_action( 'edit_term', [ $this, 'update_term' ], 99, 3 );
×
51
                add_action( 'init', [ $this, 'custom_category_descriptions_allow_html' ] );
×
52
                add_action( 'admin_init', [ $this, 'admin_init' ] );
×
53

54
                if ( self::is_term_overview( $GLOBALS['pagenow'] ) ) {
×
55
                        new WPSEO_Taxonomy_Columns();
×
56
                }
57
                $this->analysis_seo                = new WPSEO_Metabox_Analysis_SEO();
×
58
                $this->analysis_readability        = new WPSEO_Metabox_Analysis_Readability();
×
59
                $this->analysis_inclusive_language = new WPSEO_Metabox_Analysis_Inclusive_Language();
×
60
        }
61

62
        /**
63
         * Add hooks late enough for taxonomy object to be available for checks.
64
         *
65
         * @return void
66
         */
67
        public function admin_init() {
×
68

69
                $taxonomy = get_taxonomy( $this->taxonomy );
×
70

71
                if ( empty( $taxonomy ) || empty( $taxonomy->public ) || ! $this->show_metabox() ) {
×
72
                        return;
×
73
                }
74

75
                // Adds custom category description editor. Needs a hook that runs before the description field.
76
                add_action( "{$this->taxonomy}_term_edit_form_top", [ $this, 'custom_category_description_editor' ] );
×
77

78
                add_action( sanitize_text_field( $this->taxonomy ) . '_edit_form', [ $this, 'term_metabox' ], 90, 1 );
×
79
                add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
×
80
        }
81

82
        /**
83
         * Show the SEO inputs for term.
84
         *
85
         * @param stdClass|WP_Term $term Term to show the edit boxes for.
86
         *
87
         * @return void
88
         */
89
        public function term_metabox( $term ) {
×
90
                if ( WPSEO_Metabox::is_internet_explorer() ) {
×
91
                        $this->show_internet_explorer_notice();
×
92
                        return;
×
93
                }
94

95
                $metabox = new WPSEO_Taxonomy_Metabox( $this->taxonomy, $term );
×
96
                $metabox->display();
×
97
        }
98

99
        /**
100
         * Renders the content for the internet explorer metabox.
101
         *
102
         * @return void
103
         */
104
        private function show_internet_explorer_notice() {
×
105
                $product_title = YoastSEO()->helpers->product->get_product_name();
×
106

107
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $product_title is hardcoded.
108
                printf( '<div id="wpseo_meta" class="postbox yoast wpseo-taxonomy-metabox-postbox"><h2><span>%1$s</span></h2>', $product_title );
×
109
                echo '<div class="inside">';
×
110

111
                $content = sprintf(
×
112
                        /* 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. */
113
                        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' ),
×
114
                        '<a href="https://www.mozilla.org/firefox/new/">',
×
115
                        '<a href="https://www.google.com/chrome/">',
×
116
                        '<a href="https://www.microsoft.com/windows/microsoft-edge">',
×
117
                        '</a>'
×
118
                );
×
119
                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
120
                echo new Alert_Presenter( $content );
×
121

122
                echo '</div></div>';
×
123
        }
124

125
        /**
126
         * Queue assets for taxonomy screens.
127
         *
128
         * @since 1.5.0
129
         *
130
         * @return void
131
         */
132
        public function admin_enqueue_scripts() {
6✔
133

134
                $pagenow = $GLOBALS['pagenow'];
6✔
135

136
                if ( ! ( self::is_term_edit( $pagenow ) || self::is_term_overview( $pagenow ) ) ) {
6✔
137
                        return;
2✔
138
                }
139

140
                $asset_manager = new WPSEO_Admin_Asset_Manager();
4✔
141
                $asset_manager->enqueue_style( 'scoring' );
4✔
142
                $asset_manager->enqueue_style( 'monorepo' );
4✔
143

144
                $tag_id = $this::get_tag_id();
4✔
145

146
                if (
147
                        self::is_term_edit( $pagenow )
4✔
148
                        && ! is_null( $tag_id )
4✔
149
                ) {
150
                        wp_enqueue_media(); // Enqueue files needed for upload functionality.
2✔
151

152
                        $asset_manager->enqueue_style( 'metabox-css' );
2✔
153
                        $asset_manager->enqueue_style( 'ai-generator' );
2✔
154
                        $asset_manager->enqueue_script( 'term-edit' );
2✔
155

156
                        /**
157
                         * Remove the emoji script as it is incompatible with both React and any
158
                         * contenteditable fields.
159
                         */
160
                        remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
2✔
161

162
                        $asset_manager->localize_script( 'term-edit', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
2✔
163

164
                        $script_data = [
2✔
165
                                'analysis'              => [
2✔
166
                                        'plugins' => [
2✔
167
                                                'replaceVars' => [
2✔
168
                                                        'replace_vars'             => $this->get_replace_vars(),
2✔
169
                                                        'recommended_replace_vars' => $this->get_recommended_replace_vars(),
2✔
170
                                                        'scope'                    => $this->determine_scope(),
2✔
171
                                                ],
2✔
172
                                                'shortcodes' => [
2✔
173
                                                        'wpseo_shortcode_tags'          => $this->get_valid_shortcode_tags(),
2✔
174
                                                        'wpseo_filter_shortcodes_nonce' => wp_create_nonce( 'wpseo-filter-shortcodes' ),
2✔
175
                                                ],
2✔
176
                                        ],
2✔
177
                                        'worker'  => [
2✔
178
                                                'url'                     => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
2✔
179
                                                'dependencies'            => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
2✔
180
                                                'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
2✔
181
                                                'log_level'               => WPSEO_Utils::get_analysis_worker_log_level(),
2✔
182
                                        ],
2✔
183
                                ],
2✔
184
                                'metabox'               => $this->localize_term_scraper_script( $tag_id ),
2✔
185
                                'isTerm'                => true,
2✔
186
                                'postId'                => $tag_id,
2✔
187
                                'termType'              => $this->get_taxonomy(),
2✔
188
                                'usedKeywordsNonce'     => wp_create_nonce( 'wpseo-keyword-usage' ),
2✔
189
                        ];
2✔
190

191
                        /**
192
                         * The website information repository.
193
                         *
194
                         * @var $repo Website_Information_Repository
195
                         */
196
                        $repo             = YoastSEO()->classes->get( Website_Information_Repository::class );
2✔
197
                        $term_information = $repo->get_term_site_information();
2✔
198
                        $term_information->set_term( get_term_by( 'id', $tag_id, $this::get_taxonomy() ) );
2✔
199
                        $script_data = array_merge_recursive( $term_information->get_legacy_site_information(), $script_data );
2✔
200

201
                        $asset_manager->localize_script( 'term-edit', 'wpseoScriptData', $script_data );
2✔
202
                        $asset_manager->enqueue_user_language_script();
2✔
203
                }
204

205
                if ( self::is_term_overview( $pagenow ) ) {
4✔
206
                        $asset_manager->enqueue_script( 'edit-page' );
2✔
207
                        $asset_manager->enqueue_style( 'edit-page' );
2✔
208
                }
209
        }
210

211
        /**
212
         * Update the taxonomy meta data on save.
213
         *
214
         * @param int    $term_id  ID of the term to save data for.
215
         * @param int    $tt_id    The taxonomy_term_id for the term.
216
         * @param string $taxonomy The taxonomy the term belongs to.
217
         *
218
         * @return void
219
         */
220
        public function update_term( $term_id, $tt_id, $taxonomy ) {
×
221
                // Bail if this is a multisite installation and the site has been switched.
222
                if ( is_multisite() && ms_is_switched() ) {
×
223
                        return;
×
224
                }
225

226
                /* Create post array with only our values. */
227
                $new_meta_data = [];
×
228
                foreach ( WPSEO_Taxonomy_Meta::$defaults_per_term as $key => $default ) {
×
229
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce is already checked by WordPress before executing this action.
230
                        if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
×
231
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $data is getting sanitized later.
232
                                $data                  = wp_unslash( $_POST[ $key ] );
×
233
                                $new_meta_data[ $key ] = ( $key !== 'wpseo_canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
×
234
                        }
235

236
                        // If analysis is disabled remove that analysis score value from the DB.
237
                        if ( $this->is_meta_value_disabled( $key ) ) {
×
238
                                $new_meta_data[ $key ] = '';
×
239
                        }
240
                }
241

242
                // Saving the values.
243
                WPSEO_Taxonomy_Meta::set_values( $term_id, $taxonomy, $new_meta_data );
×
244
        }
245

246
        /**
247
         * Determines if the given meta value key is disabled.
248
         *
249
         * @param string $key The key of the meta value.
250
         * @return bool Whether the given meta value key is disabled.
251
         */
252
        public function is_meta_value_disabled( $key ) {
×
253
                if ( $key === 'wpseo_linkdex' && ! $this->analysis_seo->is_enabled() ) {
×
254
                        return true;
×
255
                }
256

257
                if ( $key === 'wpseo_content_score' && ! $this->analysis_readability->is_enabled() ) {
×
258
                        return true;
×
259
                }
260

261
                if ( $key === 'wpseo_inclusive_language_score' && ! $this->analysis_inclusive_language->is_enabled() ) {
×
262
                        return true;
×
263
                }
264

265
                return false;
×
266
        }
267

268
        /**
269
         * Allows post-kses-filtered HTML in term descriptions.
270
         *
271
         * @return void
272
         */
273
        public function custom_category_descriptions_allow_html() {
×
274
                remove_filter( 'term_description', 'wp_kses_data' );
×
275
                remove_filter( 'pre_term_description', 'wp_filter_kses' );
×
276
                add_filter( 'term_description', 'wp_kses_post' );
×
277
                add_filter( 'pre_term_description', 'wp_filter_post_kses' );
×
278
        }
279

280
        /**
281
         * Output the WordPress editor.
282
         *
283
         * @return void
284
         */
285
        public function custom_category_description_editor() {
×
286
                wp_editor( '', 'description' );
×
287
        }
288

289
        /**
290
         * Pass variables to js for use with the term-scraper.
291
         *
292
         * @param int $term_id The ID of the term to localize the script for.
293
         *
294
         * @return array
295
         */
296
        public function localize_term_scraper_script( $term_id ) {
2✔
297
                $term     = get_term_by( 'id', $term_id, $this::get_taxonomy() );
2✔
298
                $taxonomy = get_taxonomy( $term->taxonomy );
2✔
299

300
                $term_formatter = new WPSEO_Metabox_Formatter(
2✔
301
                        new WPSEO_Term_Metabox_Formatter( $taxonomy, $term )
2✔
302
                );
2✔
303

304
                return $term_formatter->get_values();
2✔
305
        }
306

307
        /**
308
         * Pass some variables to js for replacing variables.
309
         *
310
         * @return array
311
         */
312
        public function localize_replace_vars_script() {
×
313
                return [
×
314
                        'replace_vars'             => $this->get_replace_vars(),
×
315
                        'recommended_replace_vars' => $this->get_recommended_replace_vars(),
×
316
                        'scope'                    => $this->determine_scope(),
×
317
                ];
×
318
        }
319

320
        /**
321
         * Determines the scope based on the current taxonomy.
322
         * This can be used by the replacevar plugin to determine if a replacement needs to be executed.
323
         *
324
         * @return string String decribing the current scope.
325
         */
326
        private function determine_scope() {
×
327
                $taxonomy = $this::get_taxonomy();
×
328

329
                if ( $taxonomy === 'category' ) {
×
330
                        return 'category';
×
331
                }
332

333
                if ( $taxonomy === 'post_tag' ) {
×
334
                        return 'tag';
×
335
                }
336

337
                return 'term';
×
338
        }
339

340
        /**
341
         * Determines if a given page is the term overview page.
342
         *
343
         * @param string $page The string to check for the term overview page.
344
         *
345
         * @return bool
346
         */
347
        public static function is_term_overview( $page ) {
2✔
348
                return $page === 'edit-tags.php';
2✔
349
        }
350

351
        /**
352
         * Determines if a given page is the term edit page.
353
         *
354
         * @param string $page The string to check for the term edit page.
355
         *
356
         * @return bool
357
         */
358
        public static function is_term_edit( $page ) {
2✔
359
                return $page === 'term.php';
2✔
360
        }
361

362
        /**
363
         * Function to get the labels for the current taxonomy.
364
         *
365
         * @return object|null Labels for the current taxonomy or null if the taxonomy is not set.
366
         */
367
        public static function get_labels() {
6✔
368
                $term = self::get_taxonomy();
6✔
369
                if ( $term !== '' ) {
6✔
370
                        $taxonomy = get_taxonomy( $term );
2✔
371
                        return $taxonomy->labels;
2✔
372
                }
373
                return null;
4✔
374
        }
375

376
        /**
377
         * Retrieves a template.
378
         * Check if metabox for current taxonomy should be displayed.
379
         *
380
         * @return bool
381
         */
382
        private function show_metabox() {
×
383
                $option_key = 'display-metabox-tax-' . $this->taxonomy;
×
384

385
                return WPSEO_Options::get( $option_key );
×
386
        }
387

388
        /**
389
         * Getting the taxonomy from the URL.
390
         *
391
         * @return string
392
         */
393
        private static function get_taxonomy() {
×
394
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
395
                if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
×
396
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
397
                        return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
×
398
                }
399
                return '';
×
400
        }
401

402
        /**
403
         * Get the current tag ID from the GET parameters.
404
         *
405
         * @return int|null the tag ID if it exists, null otherwise.
406
         */
407
        private static function get_tag_id() {
×
408
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
409
                if ( isset( $_GET['tag_ID'] ) && is_string( $_GET['tag_ID'] ) ) {
×
410
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
411
                        $tag_id = (int) wp_unslash( $_GET['tag_ID'] );
×
412
                        if ( $tag_id > 0 ) {
×
413
                                return $tag_id;
×
414
                        }
415
                }
416
                return null;
×
417
        }
418

419
        /**
420
         * Prepares the replace vars for localization.
421
         *
422
         * @return array<string, string> The replacement variables.
423
         */
424
        private function get_replace_vars() {
×
425
                $term_id = $this::get_tag_id();
×
426
                $term    = get_term_by( 'id', $term_id, $this::get_taxonomy() );
×
427

428
                $cached_replacement_vars = [];
×
429

430
                $vars_to_cache = [
×
431
                        'date',
×
432
                        'id',
×
433
                        'sitename',
×
434
                        'sitedesc',
×
435
                        'sep',
×
436
                        'page',
×
437
                        'term_title',
×
438
                        'term_description',
×
439
                        'term_hierarchy',
×
440
                        'category_description',
×
441
                        'tag_description',
×
442
                        'searchphrase',
×
443
                        'currentyear',
×
444
                ];
×
445

446
                foreach ( $vars_to_cache as $var ) {
×
447
                        $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $term );
×
448
                }
449

450
                return $cached_replacement_vars;
×
451
        }
452

453
        /**
454
         * Prepares the recommended replace vars for localization.
455
         *
456
         * @return array<string> The recommended replacement variables.
457
         */
458
        private function get_recommended_replace_vars() {
×
459
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
460
                $taxonomy                 = $this::get_taxonomy();
×
461

462
                if ( $taxonomy === '' ) {
×
463
                        return [];
×
464
                }
465

466
                // What is recommended depends on the current context.
467
                $page_type = $recommended_replace_vars->determine_for_term( $taxonomy );
×
468

469
                return $recommended_replace_vars->get_recommended_replacevars_for( $page_type );
×
470
        }
471

472
        /**
473
         * Returns an array with shortcode tags for all registered shortcodes.
474
         *
475
         * @return array<string> Array with shortcode tags.
476
         */
477
        private function get_valid_shortcode_tags() {
×
478
                $shortcode_tags = [];
×
479

480
                foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
×
481
                        $shortcode_tags[] = $tag;
×
482
                }
483

484
                return $shortcode_tags;
×
485
        }
486
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc