PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
← All changes | admin/taxonomy/class-taxonomy.php +119 -50 18.5 → trunk View file →
@@ -4,8 +4,9 @@
4 4 *
5 5 * @package WPSEO\Admin
6 6 */
7 7
8 +use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository;
8 9 use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
9 10
10 11 /**
11 12 * Class that handles the edit boxes on taxonomy edit pages.
@@ -33,12 +34,19 @@
33 34 */
34 35 private $analysis_readability;
35 36
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 + /**
37 45 * Class constructor.
38 46 */
39 47 public function __construct() {
40 - $this->taxonomy = $this->get_taxonomy();
48 + $this->taxonomy = $this::get_taxonomy();
41 49
42 50 add_action( 'edit_term', [ $this, 'update_term' ], 99, 3 );
43 51 add_action( 'init', [ $this, 'custom_category_descriptions_allow_html' ] );
44 52 add_action( 'admin_init', [ $this, 'admin_init' ] );
@@ -45,14 +53,17 @@
45 53
46 54 if ( self::is_term_overview( $GLOBALS['pagenow'] ) ) {
47 55 new WPSEO_Taxonomy_Columns();
48 56 }
49 - $this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
50 - $this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
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();
51 60 }
52 61
53 62 /**
54 63 * Add hooks late enough for taxonomy object to be available for checks.
64 + *
65 + * @return void
55 66 */
56 67 public function admin_init() {
57 68
58 69 $taxonomy = get_taxonomy( $this->taxonomy );
@@ -60,9 +71,10 @@
60 71 if ( empty( $taxonomy ) || empty( $taxonomy->public ) || ! $this->show_metabox() ) {
61 72 return;
62 73 }
63 74
64 - $this->insert_description_field_editor();
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' ] );
65 77
66 78 add_action( sanitize_text_field( $this->taxonomy ) . '_edit_form', [ $this, 'term_metabox' ], 90, 1 );
67 79 add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
68 80 }
@@ -70,8 +82,10 @@
70 82 /**
71 83 * Show the SEO inputs for term.
72 84 *
73 85 * @param stdClass|WP_Term $term Term to show the edit boxes for.
86 + *
87 + * @return void
74 88 */
75 89 public function term_metabox( $term ) {
76 90 if ( WPSEO_Metabox::is_internet_explorer() ) {
77 91 $this->show_internet_explorer_notice();
@@ -99,9 +113,9 @@
99 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' ),
100 114 '<a href="https://www.mozilla.org/firefox/new/">',
101 115 '<a href="https://www.google.com/chrome/">',
102 116 '<a href="https://www.microsoft.com/windows/microsoft-edge">',
103 - '</a>'
117 + '</a>',
104 118 );
105 119 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
106 120 echo new Alert_Presenter( $content );
107 121
@@ -111,8 +125,10 @@
111 125 /**
112 126 * Queue assets for taxonomy screens.
113 127 *
114 128 * @since 1.5.0
129 + *
130 + * @return void
115 131 */
116 132 public function admin_enqueue_scripts() {
117 133
118 134 $pagenow = $GLOBALS['pagenow'];
@@ -121,20 +137,23 @@
121 137 return;
122 138 }
123 139
124 140 $asset_manager = new WPSEO_Admin_Asset_Manager();
125 - $asset_manager->enqueue_style( 'scoring' );
126 141 $asset_manager->enqueue_style( 'monorepo' );
127 142
128 - $tag_id = filter_input( INPUT_GET, 'tag_ID' );
143 + $tag_id = $this::get_tag_id();
144 +
129 145 if (
130 146 self::is_term_edit( $pagenow )
131 - && ! empty( $tag_id ) // After we drop support for <4.5 this can be removed.
147 + && $tag_id !== null
132 148 ) {
133 149 wp_enqueue_media(); // Enqueue files needed for upload functionality.
134 150
135 151 $asset_manager->enqueue_style( 'metabox-css' );
136 - $asset_manager->enqueue_style( 'scoring' );
152 + if ( $this->analysis_readability->is_enabled() ) {
153 + $asset_manager->enqueue_style( 'scoring' );
154 + }
155 + $asset_manager->enqueue_style( 'ai-generator' );
137 156 $asset_manager->enqueue_script( 'term-edit' );
138 157
139 158 /**
140 159 * Remove the emoji script as it is incompatible with both React and any
@@ -144,16 +163,19 @@
144 163
145 164 $asset_manager->localize_script( 'term-edit', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
146 165
147 166 $script_data = [
148 - 'analysis' => [
167 + 'analysis' => [
149 168 'plugins' => [
150 169 'replaceVars' => [
151 - 'no_parent_text' => __( '(no parent)', 'wordpress-seo' ),
152 170 'replace_vars' => $this->get_replace_vars(),
153 171 'recommended_replace_vars' => $this->get_recommended_replace_vars(),
154 172 'scope' => $this->determine_scope(),
155 173 ],
174 + 'shortcodes' => [
175 + 'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(),
176 + 'wpseo_filter_shortcodes_nonce' => wp_create_nonce( 'wpseo-filter-shortcodes' ),
177 + ],
156 178 ],
157 179 'worker' => [
158 180 'url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ),
159 181 'dependencies' => YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ),
@@ -160,22 +182,31 @@
160 182 'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
161 183 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(),
162 184 ],
163 185 ],
164 - 'media' => [
165 - // @todo replace this translation with JavaScript translations.
166 - 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
167 - ],
168 - 'metabox' => $this->localize_term_scraper_script(),
169 - 'userLanguageCode' => WPSEO_Language_Utils::get_language( \get_user_locale() ),
170 - 'isTerm' => true,
186 + 'metabox' => $this->localize_term_scraper_script( $tag_id ),
187 + 'isTerm' => true,
188 + 'postId' => $tag_id,
189 + 'postType' => $this->get_taxonomy(),
190 + 'usedKeywordsNonce' => wp_create_nonce( 'wpseo-keyword-usage' ),
171 191 ];
192 +
193 + /**
194 + * The website information repository.
195 + *
196 + * @var Website_Information_Repository $repo
197 + */
198 + $repo = YoastSEO()->classes->get( Website_Information_Repository::class );
199 + $term_information = $repo->get_term_site_information();
200 + $term_information->set_term( get_term_by( 'id', $tag_id, $this::get_taxonomy() ) );
201 + $script_data = array_merge_recursive( $term_information->get_legacy_site_information(), $script_data );
202 +
172 203 $asset_manager->localize_script( 'term-edit', 'wpseoScriptData', $script_data );
173 - $asset_manager->enqueue_user_language_script();
174 204 }
175 205
176 206 if ( self::is_term_overview( $pagenow ) ) {
177 207 $asset_manager->enqueue_script( 'edit-page' );
208 + $asset_manager->enqueue_style( 'edit-page' );
178 209 }
179 210 }
180 211
181 212 /**
@@ -183,8 +214,10 @@
183 214 *
184 215 * @param int $term_id ID of the term to save data for.
185 216 * @param int $tt_id The taxonomy_term_id for the term.
186 217 * @param string $taxonomy The taxonomy the term belongs to.
218 + *
219 + * @return void
187 220 */
188 221 public function update_term( $term_id, $tt_id, $taxonomy ) {
189 222 // Bail if this is a multisite installation and the site has been switched.
190 223 if ( is_multisite() && ms_is_switched() ) {
@@ -193,11 +226,13 @@
193 226
194 227 /* Create post array with only our values. */
195 228 $new_meta_data = [];
196 229 foreach ( WPSEO_Taxonomy_Meta::$defaults_per_term as $key => $default ) {
197 - $posted_value = filter_input( INPUT_POST, $key );
198 - if ( isset( $posted_value ) && $posted_value !== false ) {
199 - $new_meta_data[ $key ] = $posted_value;
230 + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: Nonce is already checked by WordPress before executing this action.
231 + if ( isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ) {
232 + // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: $data is getting sanitized later.
233 + $data = wp_unslash( $_POST[ $key ] );
234 + $new_meta_data[ $key ] = ( $key !== 'wpseo_canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data );
200 235 }
201 236
202 237 // If analysis is disabled remove that analysis score value from the DB.
203 238 if ( $this->is_meta_value_disabled( $key ) ) {
@@ -203,9 +238,8 @@
203 238 if ( $this->is_meta_value_disabled( $key ) ) {
204 239 $new_meta_data[ $key ] = '';
205 240 }
206 241 }
207 - unset( $key, $default );
208 242
209 243 // Saving the values.
210 244 WPSEO_Taxonomy_Meta::set_values( $term_id, $taxonomy, $new_meta_data );
211 245 }
@@ -224,13 +258,19 @@
224 258 if ( $key === 'wpseo_content_score' && ! $this->analysis_readability->is_enabled() ) {
225 259 return true;
226 260 }
227 261
262 + if ( $key === 'wpseo_inclusive_language_score' && ! $this->analysis_inclusive_language->is_enabled() ) {
263 + return true;
264 + }
265 +
228 266 return false;
229 267 }
230 268
231 269 /**
232 270 * Allows post-kses-filtered HTML in term descriptions.
271 + *
272 + * @return void
233 273 */
234 274 public function custom_category_descriptions_allow_html() {
235 275 remove_filter( 'term_description', 'wp_kses_data' );
236 276 remove_filter( 'pre_term_description', 'wp_filter_kses' );
@@ -239,8 +279,10 @@
239 279 }
240 280
241 281 /**
242 282 * Output the WordPress editor.
283 + *
284 + * @return void
243 285 */
244 286 public function custom_category_description_editor() {
245 287 wp_editor( '', 'description' );
246 288 }
@@ -247,17 +289,18 @@
247 289
248 290 /**
249 291 * Pass variables to js for use with the term-scraper.
250 292 *
293 + * @param int $term_id The ID of the term to localize the script for.
294 + *
251 295 * @return array
252 296 */
253 - public function localize_term_scraper_script() {
254 - $term_id = filter_input( INPUT_GET, 'tag_ID' );
255 - $term = get_term_by( 'id', $term_id, $this->get_taxonomy() );
297 + public function localize_term_scraper_script( $term_id ) {
298 + $term = get_term_by( 'id', $term_id, $this::get_taxonomy() );
256 299 $taxonomy = get_taxonomy( $term->taxonomy );
257 300
258 301 $term_formatter = new WPSEO_Metabox_Formatter(
259 - new WPSEO_Term_Metabox_Formatter( $taxonomy, $term )
302 + new WPSEO_Term_Metabox_Formatter( $taxonomy, $term ),
260 303 );
261 304
262 305 return $term_formatter->get_values();
263 306 }
@@ -268,9 +311,8 @@
268 311 * @return array
269 312 */
270 313 public function localize_replace_vars_script() {
271 314 return [
272 - 'no_parent_text' => __( '(no parent)', 'wordpress-seo' ),
273 315 'replace_vars' => $this->get_replace_vars(),
274 316 'recommended_replace_vars' => $this->get_recommended_replace_vars(),
275 317 'scope' => $this->determine_scope(),
276 318 ];
@@ -282,9 +324,9 @@
282 324 *
283 325 * @return string String decribing the current scope.
284 326 */
285 327 private function determine_scope() {
286 - $taxonomy = $this->get_taxonomy();
328 + $taxonomy = $this::get_taxonomy();
287 329
288 330 if ( $taxonomy === 'category' ) {
289 331 return 'category';
290 332 }
@@ -320,15 +362,17 @@
320 362
321 363 /**
322 364 * Function to get the labels for the current taxonomy.
323 365 *
324 - * @return object Labels for the current taxonomy.
366 + * @return object|null Labels for the current taxonomy or null if the taxonomy is not set.
325 367 */
326 368 public static function get_labels() {
327 - $term = filter_input( INPUT_GET, 'taxonomy', FILTER_DEFAULT, [ 'options' => [ 'default' => '' ] ] );
328 - $taxonomy = get_taxonomy( $term );
329 -
330 - return $taxonomy->labels;
369 + $term = self::get_taxonomy();
370 + if ( $term !== '' ) {
371 + $taxonomy = get_taxonomy( $term );
372 + return $taxonomy->labels;
373 + }
374 + return null;
331 375 }
332 376
333 377 /**
334 378 * Retrieves a template.
@@ -346,20 +390,42 @@
346 390 * Getting the taxonomy from the URL.
347 391 *
348 392 * @return string
349 393 */
350 - private function get_taxonomy() {
351 - return filter_input( INPUT_GET, 'taxonomy', FILTER_DEFAULT, [ 'options' => [ 'default' => '' ] ] );
394 + private static function get_taxonomy() {
395 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
396 + if ( isset( $_GET['taxonomy'] ) && is_string( $_GET['taxonomy'] ) ) {
397 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
398 + return sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) );
399 + }
400 + return '';
352 401 }
353 402
354 403 /**
404 + * Get the current tag ID from the GET parameters.
405 + *
406 + * @return int|null the tag ID if it exists, null otherwise.
407 + */
408 + private static function get_tag_id() {
409 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
410 + if ( isset( $_GET['tag_ID'] ) && is_string( $_GET['tag_ID'] ) ) {
411 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are casting to an integer.
412 + $tag_id = (int) wp_unslash( $_GET['tag_ID'] );
413 + if ( $tag_id > 0 ) {
414 + return $tag_id;
415 + }
416 + }
417 + return null;
418 + }
419 +
420 + /**
355 421 * Prepares the replace vars for localization.
356 422 *
357 - * @return array The replacement variables.
423 + * @return array<string, string> The replacement variables.
358 424 */
359 425 private function get_replace_vars() {
360 - $term_id = filter_input( INPUT_GET, 'tag_ID' );
361 - $term = get_term_by( 'id', $term_id, $this->get_taxonomy() );
426 + $term_id = $this::get_tag_id();
427 + $term = get_term_by( 'id', $term_id, $this::get_taxonomy() );
362 428
363 429 $cached_replacement_vars = [];
364 430
365 431 $vars_to_cache = [
@@ -387,14 +453,18 @@
387 453
388 454 /**
389 455 * Prepares the recommended replace vars for localization.
390 456 *
391 - * @return array The recommended replacement variables.
457 + * @return array<string> The recommended replacement variables.
392 458 */
393 459 private function get_recommended_replace_vars() {
394 460 $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
395 - $taxonomy = filter_input( INPUT_GET, 'taxonomy' );
461 + $taxonomy = $this::get_taxonomy();
396 462
463 + if ( $taxonomy === '' ) {
464 + return [];
465 + }
466 +
397 467 // What is recommended depends on the current context.
398 468 $page_type = $recommended_replace_vars->determine_for_term( $taxonomy );
399 469
400 470 return $recommended_replace_vars->get_recommended_replacevars_for( $page_type );
@@ -400,19 +470,18 @@
400 470 return $recommended_replace_vars->get_recommended_replacevars_for( $page_type );
401 471 }
402 472
403 473 /**
404 - * Adds custom category description editor.
405 - * Needs a hook that runs before the description field. Prior to WP version 4.5 we need to use edit_form as
406 - * term_edit_form_top was introduced in WP 4.5. This can be removed after <4.5 is no longer supported.
474 + * Returns an array with shortcode tags for all registered shortcodes.
407 475 *
408 - * @return void
476 + * @return array<string> Array with shortcode tags.
409 477 */
410 - private function insert_description_field_editor() {
411 - if ( version_compare( $GLOBALS['wp_version'], '4.5', '<' ) ) {
412 - add_action( "{$this->taxonomy}_edit_form", [ $this, 'custom_category_description_editor' ] );
413 - return;
478 + private function get_valid_shortcode_tags() {
479 + $shortcode_tags = [];
480 +
481 + foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) {
482 + $shortcode_tags[] = $tag;
414 483 }
415 484
416 - add_action( "{$this->taxonomy}_term_edit_form_top", [ $this, 'custom_category_description_editor' ] );
485 + return $shortcode_tags;
417 486 }
418 487 }