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

Yoast / wordpress-seo / 6638054992

25 Oct 2023 08:55AM UTC coverage: 49.106%. First build
6638054992

Pull #20653

github

vraja-pro
Merge remote-tracking branch 'origin/feature/upgrade-react-and-tests' into feature/upgrade-react-and-tests
Pull Request #20653: Feature/upgrade react and tests

64 of 157 new or added lines in 34 files covered. (40.76%)

13135 of 26748 relevant lines covered (49.11%)

3.96 hits per line

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

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

8
use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
9

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

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

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

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

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

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

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

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

61
        /**
62
         * Add hooks late enough for taxonomy object to be available for checks.
63
         */
64
        public function admin_init() {
65

66
                $taxonomy = get_taxonomy( $this->taxonomy );
×
67

68
                if ( empty( $taxonomy ) || empty( $taxonomy->public ) || ! $this->show_metabox() ) {
×
69
                        return;
×
70
                }
71

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

75
                add_action( sanitize_text_field( $this->taxonomy ) . '_edit_form', [ $this, 'term_metabox' ], 90, 1 );
×
76
                add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
×
77
        }
78

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

90
                $metabox = new WPSEO_Taxonomy_Metabox( $this->taxonomy, $term );
×
91
                $metabox->display();
×
92
        }
93

94
        /**
95
         * Renders the content for the internet explorer metabox.
96
         *
97
         * @return void
98
         */
99
        private function show_internet_explorer_notice() {
100
                $product_title = YoastSEO()->helpers->product->get_product_name();
×
101

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

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

117
                echo '</div></div>';
×
118
        }
119

120
        /**
121
         * Queue assets for taxonomy screens.
122
         *
123
         * @since 1.5.0
124
         */
125
        public function admin_enqueue_scripts() {
126

127
                $pagenow = $GLOBALS['pagenow'];
×
128

129
                if ( ! ( self::is_term_edit( $pagenow ) || self::is_term_overview( $pagenow ) ) ) {
×
130
                        return;
×
131
                }
132

133
                $asset_manager = new WPSEO_Admin_Asset_Manager();
×
134
                $asset_manager->enqueue_style( 'scoring' );
×
135
                $asset_manager->enqueue_style( 'monorepo' );
×
136

137
                $tag_id = $this::get_tag_id();
×
138

139
                if (
140
                        self::is_term_edit( $pagenow )
×
141
                        && ! is_null( $tag_id )
×
142
                ) {
143
                        wp_enqueue_media(); // Enqueue files needed for upload functionality.
×
144

145
                        $asset_manager->enqueue_style( 'metabox-css' );
×
146
                        $asset_manager->enqueue_style( 'scoring' );
×
147
                        $asset_manager->enqueue_script( 'term-edit' );
×
148

149
                        /**
150
                         * Remove the emoji script as it is incompatible with both React and any
151
                         * contenteditable fields.
152
                         */
153
                        remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
×
154

155
                        $asset_manager->localize_script( 'term-edit', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
×
156

157
                        $script_data = [
158
                                'analysis'          => [
×
159
                                        'plugins' => [
160
                                                'replaceVars' => [
161
                                                        'no_parent_text'           => __( '(no parent)', 'wordpress-seo' ),
×
162
                                                        'replace_vars'             => $this->get_replace_vars(),
×
163
                                                        'recommended_replace_vars' => $this->get_recommended_replace_vars(),
×
164
                                                        'scope'                    => $this->determine_scope(),
×
165
                                                ],
166
                                                'shortcodes' => [
NEW
167
                                                        'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(),
×
168
                                                ],
169
                                        ],
170
                                        'worker'  => [
171
                                                'url'                     => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
×
172
                                                'dependencies'            => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
×
173
                                                'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
×
174
                                                'log_level'               => WPSEO_Utils::get_analysis_worker_log_level(),
×
175
                                        ],
176
                                ],
177
                                'media'             => [
178
                                        // @todo replace this translation with JavaScript translations.
179
                                        'choose_image' => __( 'Use Image', 'wordpress-seo' ),
×
180
                                ],
181
                                'metabox'           => $this->localize_term_scraper_script( $tag_id ),
×
182
                                'userLanguageCode'  => WPSEO_Language_Utils::get_language( \get_user_locale() ),
×
183
                                'isTerm'            => true,
184
                                'postId'            => $tag_id,
×
185
                                'usedKeywordsNonce' => \wp_create_nonce( 'wpseo-keyword-usage' ),
×
186
                                'linkParams'        => WPSEO_Shortlinker::get_query_params(),
×
187
                        ];
188
                        $asset_manager->localize_script( 'term-edit', 'wpseoScriptData', $script_data );
×
189
                        $asset_manager->enqueue_user_language_script();
×
190
                }
191

192
                if ( self::is_term_overview( $pagenow ) ) {
×
193
                        $asset_manager->enqueue_script( 'edit-page' );
×
194
                }
195
        }
196

197
        /**
198
         * Update the taxonomy meta data on save.
199
         *
200
         * @param int    $term_id  ID of the term to save data for.
201
         * @param int    $tt_id    The taxonomy_term_id for the term.
202
         * @param string $taxonomy The taxonomy the term belongs to.
203
         */
204
        public function update_term( $term_id, $tt_id, $taxonomy ) {
205
                // Bail if this is a multisite installation and the site has been switched.
206
                if ( is_multisite() && ms_is_switched() ) {
×
207
                        return;
×
208
                }
209

210
                /* Create post array with only our values. */
211
                $new_meta_data = [];
×
212
                foreach ( WPSEO_Taxonomy_Meta::$defaults_per_term as $key => $default ) {
×
213
                        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce is already checked by WordPress before executing this action.
214
                        if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
×
215
                                // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $data is getting sanitized later.
NEW
216
                                $data                  = \wp_unslash( $_POST[ $key ] );
×
NEW
217
                                $new_meta_data[ $key ] = ( $key !== 'wpseo_canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
×
218
                        }
219

220
                        // If analysis is disabled remove that analysis score value from the DB.
221
                        if ( $this->is_meta_value_disabled( $key ) ) {
×
222
                                $new_meta_data[ $key ] = '';
×
223
                        }
224
                }
225

226
                // Saving the values.
227
                WPSEO_Taxonomy_Meta::set_values( $term_id, $taxonomy, $new_meta_data );
×
228
        }
229

230
        /**
231
         * Determines if the given meta value key is disabled.
232
         *
233
         * @param string $key The key of the meta value.
234
         * @return bool Whether the given meta value key is disabled.
235
         */
236
        public function is_meta_value_disabled( $key ) {
237
                if ( $key === 'wpseo_linkdex' && ! $this->analysis_seo->is_enabled() ) {
×
238
                        return true;
×
239
                }
240

241
                if ( $key === 'wpseo_content_score' && ! $this->analysis_readability->is_enabled() ) {
×
242
                        return true;
×
243
                }
244

245
                if ( $key === 'wpseo_inclusive_language_score' && ! $this->analysis_inclusive_language->is_enabled() ) {
×
246
                        return true;
×
247
                }
248

249
                return false;
×
250
        }
251

252
        /**
253
         * Allows post-kses-filtered HTML in term descriptions.
254
         */
255
        public function custom_category_descriptions_allow_html() {
256
                remove_filter( 'term_description', 'wp_kses_data' );
×
257
                remove_filter( 'pre_term_description', 'wp_filter_kses' );
×
258
                add_filter( 'term_description', 'wp_kses_post' );
×
259
                add_filter( 'pre_term_description', 'wp_filter_post_kses' );
×
260
        }
261

262
        /**
263
         * Output the WordPress editor.
264
         */
265
        public function custom_category_description_editor() {
266
                wp_editor( '', 'description' );
×
267
        }
268

269
        /**
270
         * Pass variables to js for use with the term-scraper.
271
         *
272
         * @param int $term_id The ID of the term to localize the script for.
273
         *
274
         * @return array
275
         */
276
        public function localize_term_scraper_script( $term_id ) {
277
                $term     = get_term_by( 'id', $term_id, $this::get_taxonomy() );
×
278
                $taxonomy = get_taxonomy( $term->taxonomy );
×
279

280
                $term_formatter = new WPSEO_Metabox_Formatter(
×
281
                        new WPSEO_Term_Metabox_Formatter( $taxonomy, $term )
×
282
                );
283

284
                return $term_formatter->get_values();
×
285
        }
286

287
        /**
288
         * Pass some variables to js for replacing variables.
289
         *
290
         * @return array
291
         */
292
        public function localize_replace_vars_script() {
293
                return [
294
                        'no_parent_text'           => __( '(no parent)', 'wordpress-seo' ),
×
295
                        'replace_vars'             => $this->get_replace_vars(),
×
296
                        'recommended_replace_vars' => $this->get_recommended_replace_vars(),
×
297
                        'scope'                    => $this->determine_scope(),
×
298
                ];
299
        }
300

301
        /**
302
         * Determines the scope based on the current taxonomy.
303
         * This can be used by the replacevar plugin to determine if a replacement needs to be executed.
304
         *
305
         * @return string String decribing the current scope.
306
         */
307
        private function determine_scope() {
308
                $taxonomy = $this::get_taxonomy();
×
309

310
                if ( $taxonomy === 'category' ) {
×
311
                        return 'category';
×
312
                }
313

314
                if ( $taxonomy === 'post_tag' ) {
×
315
                        return 'tag';
×
316
                }
317

318
                return 'term';
×
319
        }
320

321
        /**
322
         * Determines if a given page is the term overview page.
323
         *
324
         * @param string $page The string to check for the term overview page.
325
         *
326
         * @return bool
327
         */
328
        public static function is_term_overview( $page ) {
329
                return $page === 'edit-tags.php';
4✔
330
        }
331

332
        /**
333
         * Determines if a given page is the term edit page.
334
         *
335
         * @param string $page The string to check for the term edit page.
336
         *
337
         * @return bool
338
         */
339
        public static function is_term_edit( $page ) {
340
                return $page === 'term.php';
4✔
341
        }
342

343
        /**
344
         * Function to get the labels for the current taxonomy.
345
         *
346
         * @return object|null Labels for the current taxonomy or null if the taxonomy is not set.
347
         */
348
        public static function get_labels() {
349
                $term = self::get_taxonomy();
12✔
350
                if ( $term !== '' ) {
12✔
351
                        $taxonomy = get_taxonomy( $term );
4✔
352
                        return $taxonomy->labels;
4✔
353
                }
354
                return null;
8✔
355
        }
356

357
        /**
358
         * Retrieves a template.
359
         * Check if metabox for current taxonomy should be displayed.
360
         *
361
         * @return bool
362
         */
363
        private function show_metabox() {
364
                $option_key = 'display-metabox-tax-' . $this->taxonomy;
×
365

366
                return WPSEO_Options::get( $option_key );
×
367
        }
368

369
        /**
370
         * Getting the taxonomy from the URL.
371
         *
372
         * @return string
373
         */
374
        private static function get_taxonomy() {
375
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
376
                if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
×
377
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
378
                        return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
×
379
                }
380
                return '';
×
381
        }
382

383
        /**
384
         * Get the current tag ID from the GET parameters.
385
         *
386
         * @return int|null the tag ID if it exists, null otherwise.
387
         */
388
        private static function get_tag_id() {
389
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
390
                if ( isset( $_GET['tag_ID'] ) && is_string( $_GET['tag_ID'] ) ) {
×
391
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
392
                        $tag_id = (int) wp_unslash( $_GET['tag_ID'] );
×
393
                        if ( $tag_id > 0 ) {
×
394
                                return $tag_id;
×
395
                        }
396
                }
397
                return null;
×
398
        }
399

400
        /**
401
         * Prepares the replace vars for localization.
402
         *
403
         * @return array The replacement variables.
404
         */
405
        private function get_replace_vars() {
406
                $term_id = $this::get_tag_id();
×
407
                $term    = get_term_by( 'id', $term_id, $this::get_taxonomy() );
×
408

409
                $cached_replacement_vars = [];
×
410

411
                $vars_to_cache = [
412
                        'date',
×
413
                        'id',
414
                        'sitename',
415
                        'sitedesc',
416
                        'sep',
417
                        'page',
418
                        'term_title',
419
                        'term_description',
420
                        'term_hierarchy',
421
                        'category_description',
422
                        'tag_description',
423
                        'searchphrase',
424
                        'currentyear',
425
                ];
426

427
                foreach ( $vars_to_cache as $var ) {
×
428
                        $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $term );
×
429
                }
430

431
                return $cached_replacement_vars;
×
432
        }
433

434
        /**
435
         * Prepares the recommended replace vars for localization.
436
         *
437
         * @return array The recommended replacement variables.
438
         */
439
        private function get_recommended_replace_vars() {
440
                $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
×
441
                $taxonomy                 = $this::get_taxonomy();
×
442

443
                if ( $taxonomy === '' ) {
×
444
                        return [];
×
445
                }
446

447
                // What is recommended depends on the current context.
448
                $page_type = $recommended_replace_vars->determine_for_term( $taxonomy );
×
449

450
                return $recommended_replace_vars->get_recommended_replacevars_for( $page_type );
×
451
        }
452

453
        /**
454
         * Returns an array with shortcode tags for all registered shortcodes.
455
         *
456
         * @return array
457
         */
458
        private function get_valid_shortcode_tags() {
NEW
459
                $shortcode_tags = [];
×
460

NEW
461
                foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
×
NEW
462
                        $shortcode_tags[] = $tag;
×
463
                }
464

NEW
465
                return $shortcode_tags;
×
466
        }
467
}
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