| @@ -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 | } |