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/metabox/class-metabox.php +159 -342 18.5.1 → trunk View file →
@@ -4,11 +4,9 @@
4 4 *
5 5 * @package WPSEO\Admin
6 6 */
7 7
8 -use Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional;
9 -use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional;
10 -use Yoast\WP\SEO\Helpers\Input_Helper;
8 +use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository;
11 9 use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
12 10 use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter;
13 11
14 12 /**
@@ -16,9 +14,9 @@
16 14 */
17 15 class WPSEO_Metabox extends WPSEO_Meta {
18 16
19 17 /**
20 - * Whether or not the social tab is enabled.
18 + * Whether the social tab is enabled.
21 19 *
22 20 * @var bool
23 21 */
24 22 private $social_is_enabled;
@@ -23,9 +21,9 @@
23 21 */
24 22 private $social_is_enabled;
25 23
26 24 /**
27 - * Helper to determine whether or not the SEO analysis is enabled.
25 + * Helper to determine whether the SEO analysis is enabled.
28 26 *
29 27 * @var WPSEO_Metabox_Analysis_SEO
30 28 */
31 29 protected $seo_analysis;
@@ -30,9 +28,9 @@
30 28 */
31 29 protected $seo_analysis;
32 30
33 31 /**
34 - * Helper to determine whether or not the readability analysis is enabled.
32 + * Helper to determine whether the readability analysis is enabled.
35 33 *
36 34 * @var WPSEO_Metabox_Analysis_Readability
37 35 */
38 36 protected $readability_analysis;
@@ -37,8 +35,15 @@
37 35 */
38 36 protected $readability_analysis;
39 37
40 38 /**
39 + * Helper to determine whether the inclusive language analysis is enabled.
40 + *
41 + * @var WPSEO_Metabox_Analysis_Inclusive_Language
42 + */
43 + protected $inclusive_language_analysis;
44 +
45 + /**
41 46 * The metabox editor object.
42 47 *
43 48 * @var WPSEO_Metabox_Editor
44 49 */
@@ -46,14 +51,14 @@
46 51
47 52 /**
48 53 * The Metabox post.
49 54 *
50 - * @var WP_Post
55 + * @var WP_Post|null
51 56 */
52 57 protected $post = null;
53 58
54 59 /**
55 - * Whether or not the advanced metadata is enabled.
60 + * Whether the advanced metadata is enabled.
56 61 *
57 62 * @var bool
58 63 */
59 64 protected $is_advanced_metadata_enabled;
@@ -58,20 +63,14 @@
58 63 */
59 64 protected $is_advanced_metadata_enabled;
60 65
61 66 /**
62 - * Represents the estimated_reading_time_conditional.
63 - *
64 - * @var Estimated_Reading_Time_Conditional
65 - */
66 - protected $estimated_reading_time_conditional;
67 -
68 - /**
69 67 * Class constructor.
70 68 */
71 69 public function __construct() {
72 70 if ( $this->is_internet_explorer() ) {
73 71 add_action( 'add_meta_boxes', [ $this, 'internet_explorer_metabox' ] );
72 +
74 73 return;
75 74 }
76 75
77 76 add_action( 'add_meta_boxes', [ $this, 'add_meta_box' ] );
@@ -78,23 +77,15 @@
78 77 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
79 78 add_action( 'wp_insert_post', [ $this, 'save_postdata' ] );
80 79 add_action( 'edit_attachment', [ $this, 'save_postdata' ] );
81 80 add_action( 'add_attachment', [ $this, 'save_postdata' ] );
82 - add_action( 'admin_init', [ $this, 'translate_meta_boxes' ] );
83 81
84 - $this->editor = new WPSEO_Metabox_Editor();
85 - $this->editor->register_hooks();
82 + $this->social_is_enabled = WPSEO_Options::get( 'opengraph', false, [ 'wpseo_social' ] ) || WPSEO_Options::get( 'twitter', false, [ 'wpseo_social' ] );
83 + $this->is_advanced_metadata_enabled = WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta', null, [ 'wpseo' ] ) === false;
86 84
87 - $this->social_is_enabled = WPSEO_Options::get( 'opengraph', false ) || WPSEO_Options::get( 'twitter', false );
88 - $this->is_advanced_metadata_enabled = WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta' ) === false;
89 -
90 - $this->estimated_reading_time_conditional = new Estimated_Reading_Time_Conditional(
91 - new Post_Conditional(),
92 - new Input_Helper()
93 - );
94 -
95 - $this->seo_analysis = new WPSEO_Metabox_Analysis_SEO();
96 - $this->readability_analysis = new WPSEO_Metabox_Analysis_Readability();
85 + $this->seo_analysis = new WPSEO_Metabox_Analysis_SEO();
86 + $this->readability_analysis = new WPSEO_Metabox_Analysis_Readability();
87 + $this->inclusive_language_analysis = new WPSEO_Metabox_Analysis_Inclusive_Language();
97 88 }
98 89
99 90 /**
100 91 * Checks whether the request comes from an IE 11 browser.
@@ -116,8 +107,10 @@
116 107 }
117 108
118 109 /**
119 110 * Adds an alternative metabox for internet explorer users.
111 + *
112 + * @return void
120 113 */
121 114 public function internet_explorer_metabox() {
122 115 $post_types = WPSEO_Post_Type::get_accessible_post_types();
123 116 $post_types = array_filter( $post_types, [ $this, 'display_metabox' ] );
@@ -137,9 +130,9 @@
137 130 [ $this, 'render_internet_explorer_notice' ],
138 131 $post_type,
139 132 'normal',
140 133 apply_filters( 'wpseo_metabox_prio', 'high' ),
141 - [ '__block_editor_compatible_meta_box' => true ]
134 + [ '__block_editor_compatible_meta_box' => true ],
142 135 );
143 136 }
144 137 }
145 138
@@ -154,9 +147,9 @@
154 147 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' ),
155 148 '<a href="https://www.mozilla.org/firefox/new/">',
156 149 '<a href="https://www.google.com/chrome/">',
157 150 '<a href="https://www.microsoft.com/windows/microsoft-edge">',
158 - '</a>'
151 + '</a>',
159 152 );
160 153
161 154 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above.
162 155 echo new Alert_Presenter( $content );
@@ -162,57 +155,8 @@
162 155 echo new Alert_Presenter( $content );
163 156 }
164 157
165 158 /**
166 - * Translates text strings for use in the meta box.
167 - *
168 - * IMPORTANT: if you want to add a new string (option) somewhere, make sure you add that array key to
169 - * the main meta box definition array in the class WPSEO_Meta() as well!!!!
170 - */
171 - public static function translate_meta_boxes() {
172 - WPSEO_Meta::$meta_fields['general']['title']['title'] = __( 'SEO title', 'wordpress-seo' );
173 - WPSEO_Meta::$meta_fields['general']['metadesc']['title'] = __( 'Meta description', 'wordpress-seo' );
174 -
175 - /* translators: %s expands to the post type name. */
176 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['title'] = __( 'Allow search engines to show this %s in search results?', 'wordpress-seo' );
177 - if ( (string) get_option( 'blog_public' ) === '0' ) {
178 - 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>';
179 - }
180 - /* translators: %1$s expands to Yes or No, %2$s expands to the post type name.*/
181 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['0'] = __( 'Default for %2$s, currently: %1$s', 'wordpress-seo' );
182 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['2'] = __( 'Yes', 'wordpress-seo' );
183 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-noindex']['options']['1'] = __( 'No', 'wordpress-seo' );
184 -
185 - /* translators: %1$s expands to the post type name.*/
186 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['title'] = __( 'Should search engines follow links on this %1$s?', 'wordpress-seo' );
187 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['options']['0'] = __( 'Yes', 'wordpress-seo' );
188 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-nofollow']['options']['1'] = __( 'No', 'wordpress-seo' );
189 -
190 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['title'] = __( 'Meta robots advanced', 'wordpress-seo' );
191 - 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' );
192 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['noimageindex'] = __( 'No Image Index', 'wordpress-seo' );
193 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['noarchive'] = __( 'No Archive', 'wordpress-seo' );
194 - WPSEO_Meta::$meta_fields['advanced']['meta-robots-adv']['options']['nosnippet'] = __( 'No Snippet', 'wordpress-seo' );
195 -
196 - WPSEO_Meta::$meta_fields['advanced']['bctitle']['title'] = __( 'Breadcrumbs Title', 'wordpress-seo' );
197 - WPSEO_Meta::$meta_fields['advanced']['bctitle']['description'] = __( 'Title to use for this page in breadcrumb paths', 'wordpress-seo' );
198 -
199 - WPSEO_Meta::$meta_fields['advanced']['canonical']['title'] = __( 'Canonical URL', 'wordpress-seo' );
200 -
201 - WPSEO_Meta::$meta_fields['advanced']['canonical']['description'] = sprintf(
202 - /* translators: 1: link open tag; 2: link close tag. */
203 - __( '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' ),
204 - '<a href="https://googlewebmastercentral.blogspot.com/2009/12/handling-legitimate-cross-domain.html" target="_blank" rel="noopener">',
205 - WPSEO_Admin_Utils::get_new_tab_message() . '</a>'
206 - );
207 -
208 - WPSEO_Meta::$meta_fields['advanced']['redirect']['title'] = __( '301 Redirect', 'wordpress-seo' );
209 - WPSEO_Meta::$meta_fields['advanced']['redirect']['description'] = __( 'The URL that this page should redirect to.', 'wordpress-seo' );
210 -
211 - do_action( 'wpseo_tab_translate' );
212 - }
213 -
214 - /**
215 159 * Determines whether the metabox should be shown for the passed identifier.
216 160 *
217 161 * By default the check is done for post types, but can also be used for taxonomies.
218 162 *
@@ -250,9 +194,9 @@
250 194 [ $this, 'meta_box' ],
251 195 $post_type,
252 196 'normal',
253 197 apply_filters( 'wpseo_metabox_prio', 'high' ),
254 - [ '__block_editor_compatible_meta_box' => true ]
198 + [ '__block_editor_compatible_meta_box' => true ],
255 199 );
256 200 }
257 201 }
258 202
@@ -258,11 +202,11 @@
258 202
259 203 /**
260 204 * Adds CSS classes to the meta box.
261 205 *
262 - * @param array $classes An array of postbox CSS classes.
206 + * @param string[] $classes An array of postbox CSS classes.
263 207 *
264 - * @return array List of classes that will be applied to the editbox container.
208 + * @return string[] List of classes that will be applied to the editbox container.
265 209 */
266 210 public function wpseo_metabox_class( $classes ) {
267 211 $classes[] = 'yoast wpseo-metabox';
268 212
@@ -271,24 +215,18 @@
271 215
272 216 /**
273 217 * Passes variables to js for use with the post-scraper.
274 218 *
275 - * @return array
219 + * @return array<string, string|array<string|int|bool>|bool|int>
276 220 */
277 221 public function get_metabox_script_data() {
278 - $permalink = '';
222 + $permalink = $this->get_permalink();
279 223
280 - if ( is_object( $this->get_metabox_post() ) ) {
281 - $permalink = get_sample_permalink( $this->get_metabox_post()->ID );
282 - $permalink = $permalink[0];
283 - }
284 -
285 224 $post_formatter = new WPSEO_Metabox_Formatter(
286 - new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink )
225 + new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink ),
287 226 );
288 227
289 228 $values = $post_formatter->get_values();
290 -
291 229 /** This filter is documented in admin/filters/class-cornerstone-filter.php. */
292 230 $post_types = apply_filters( 'wpseo_cornerstone_post_types', WPSEO_Post_Type::get_accessible_post_types() );
293 231 if ( $values['cornerstoneActive'] && ! in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) {
294 232 $values['cornerstoneActive'] = false;
@@ -331,8 +269,10 @@
331 269 }
332 270
333 271 /**
334 272 * Outputs the meta box.
273 + *
274 + * @return void
335 275 */
336 276 public function meta_box() {
337 277 $this->render_hidden_fields();
338 278 $this->render_tabs();
@@ -361,12 +301,27 @@
361 301 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class.
362 302 echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' );
363 303 }
364 304
305 + $is_block_editor = YoastSEO()->helpers->current_page->is_block_editor();
306 + if ( $is_block_editor && $this->get_metabox_post()->post_type === 'post' ) {
307 + /**
308 + * Filter: 'wpseo_enable_ai_content_planner_inline_banner' - Allows hiding the AI Content Planner inline banner site-wide.
309 + *
310 + * Returning false stops the hidden meta inputs from being rendered, which the editor JS treats as "banner disabled".
311 + *
312 + * @param bool $enabled Whether the inline banner should be available in the editor. Default true.
313 + */
314 + if ( apply_filters( 'wpseo_enable_ai_content_planner_inline_banner', true ) ) {
315 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in class.
316 + echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'content_planner' );
317 + }
318 + }
319 +
365 320 /**
366 321 * Filter: 'wpseo_content_meta_section_content' - Allow filtering the metabox content before outputting.
367 322 *
368 - * @api string $post_content The metabox content string.
323 + * @param string $post_content The metabox content string.
369 324 */
370 325 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output should be escaped in the filter.
371 326 echo apply_filters( 'wpseo_content_meta_section_content', '' );
372 327 }
@@ -417,13 +372,17 @@
417 372 if ( $this->readability_analysis->is_enabled() ) {
418 373 $tabs[] = new WPSEO_Metabox_Section_Readability();
419 374 }
420 375
376 + if ( $this->inclusive_language_analysis->is_enabled() ) {
377 + $tabs[] = new WPSEO_Metabox_Section_Inclusive_Language();
378 + }
379 +
421 380 if ( $this->is_advanced_metadata_enabled ) {
422 381 $tabs[] = new WPSEO_Metabox_Section_React(
423 382 'schema',
424 383 '<span class="wpseo-schema-icon"></span>' . __( 'Schema', 'wordpress-seo' ),
425 - ''
384 + '',
426 385 );
427 386 }
428 387
429 388 if ( $this->social_is_enabled ) {
@@ -432,9 +391,9 @@
432 391 '<span class="dashicons dashicons-share"></span>' . __( 'Social', 'wordpress-seo' ),
433 392 '',
434 393 [
435 394 'html_after' => '<div id="wpseo-section-social"></div>',
436 - ]
395 + ],
437 396 );
438 397 }
439 398
440 399 $tabs = array_merge( $tabs, $this->get_additional_tabs() );
@@ -483,9 +442,9 @@
483 442 $tabs[] = new WPSEO_Metabox_Section_Additional(
484 443 $tab['name'],
485 444 $tab['link_content'],
486 445 $tab['content'],
487 - $options
446 + $options,
488 447 );
489 448 }
490 449 }
491 450
@@ -492,208 +451,8 @@
492 451 return $tabs;
493 452 }
494 453
495 454 /**
496 - * Adds a line in the meta box.
497 - *
498 - * @todo [JRF] Check if $class is added appropriately everywhere.
499 - *
500 - * @param array $meta_field_def Contains the vars based on which output is generated.
501 - * @param string $key Internal key (without prefix).
502 - *
503 - * @return string
504 - */
505 - public function do_meta_box( $meta_field_def, $key = '' ) {
506 - $content = '';
507 - $esc_form_key = esc_attr( WPSEO_Meta::$form_prefix . $key );
508 - $meta_value = WPSEO_Meta::get_value( $key, $this->get_metabox_post()->ID );
509 -
510 - $class = '';
511 - if ( isset( $meta_field_def['class'] ) && $meta_field_def['class'] !== '' ) {
512 - $class = ' ' . $meta_field_def['class'];
513 - }
514 -
515 - $placeholder = '';
516 - if ( isset( $meta_field_def['placeholder'] ) && $meta_field_def['placeholder'] !== '' ) {
517 - $placeholder = $meta_field_def['placeholder'];
518 - }
519 -
520 - $aria_describedby = '';
521 - $description = '';
522 - if ( isset( $meta_field_def['description'] ) ) {
523 - $aria_describedby = ' aria-describedby="' . $esc_form_key . '-desc"';
524 - $description = '<p id="' . $esc_form_key . '-desc" class="yoast-metabox__description">' . $meta_field_def['description'] . '</p>';
525 - }
526 -
527 - // Add a hide_on_pages option that returns nothing when the field is rendered on a page.
528 - if ( isset( $meta_field_def['hide_on_pages'] ) && $meta_field_def['hide_on_pages'] && get_post_type() === 'page' ) {
529 - return '';
530 - }
531 -
532 - switch ( $meta_field_def['type'] ) {
533 - case 'text':
534 - $ac = '';
535 - if ( isset( $meta_field_def['autocomplete'] ) && $meta_field_def['autocomplete'] === false ) {
536 - $ac = 'autocomplete="off" ';
537 - }
538 - if ( $placeholder !== '' ) {
539 - $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"';
540 - }
541 - $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 . '/>';
542 - break;
543 -
544 - case 'url':
545 - if ( $placeholder !== '' ) {
546 - $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"';
547 - }
548 - $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 . '/>';
549 - break;
550 -
551 - case 'textarea':
552 - $rows = 3;
553 - if ( isset( $meta_field_def['rows'] ) && $meta_field_def['rows'] > 0 ) {
554 - $rows = $meta_field_def['rows'];
555 - }
556 - $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>';
557 - break;
558 -
559 - case 'hidden':
560 - $default = '';
561 - if ( isset( $meta_field_def['default'] ) ) {
562 - $default = sprintf( ' data-default="%s"', esc_attr( $meta_field_def['default'] ) );
563 - }
564 - $content .= '<input type="hidden" id="' . $esc_form_key . '" name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '"' . $default . '/>' . "\n";
565 - break;
566 - case 'select':
567 - if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) {
568 - $content .= '<select name="' . $esc_form_key . '" id="' . $esc_form_key . '" class="yoast' . $class . '">';
569 - foreach ( $meta_field_def['options'] as $val => $option ) {
570 - $selected = selected( $meta_value, $val, false );
571 - $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
572 - }
573 - unset( $val, $option, $selected );
574 - $content .= '</select>';
575 - }
576 - break;
577 -
578 - case 'multiselect':
579 - if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) {
580 -
581 - // Set $meta_value as $selected_arr.
582 - $selected_arr = $meta_value;
583 -
584 - // If the multiselect field is 'meta-robots-adv' we should explode on ,.
585 - if ( $key === 'meta-robots-adv' ) {
586 - $selected_arr = explode( ',', $meta_value );
587 - }
588 -
589 - if ( ! is_array( $selected_arr ) ) {
590 - $selected_arr = (array) $selected_arr;
591 - }
592 -
593 - $options_count = count( $meta_field_def['options'] );
594 -
595 - // This select now uses Select2.
596 - $content .= '<select multiple="multiple" size="' . esc_attr( $options_count ) . '" name="' . $esc_form_key . '[]" id="' . $esc_form_key . '" class="yoast' . $class . '"' . $aria_describedby . '>';
597 - foreach ( $meta_field_def['options'] as $val => $option ) {
598 - $selected = '';
599 - if ( in_array( $val, $selected_arr, true ) ) {
600 - $selected = ' selected="selected"';
601 - }
602 - $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
603 - }
604 - $content .= '</select>';
605 - unset( $val, $option, $selected, $selected_arr, $options_count );
606 - }
607 - break;
608 -
609 - case 'checkbox':
610 - $checked = checked( $meta_value, 'on', false );
611 - $expl = ( isset( $meta_field_def['expl'] ) ) ? esc_html( $meta_field_def['expl'] ) : '';
612 - $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>';
613 - unset( $checked, $expl );
614 - break;
615 -
616 - case 'radio':
617 - if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== [] ) {
618 - foreach ( $meta_field_def['options'] as $val => $option ) {
619 - $checked = checked( $meta_value, $val, false );
620 - $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> ';
621 - }
622 - unset( $val, $option, $checked );
623 - }
624 - break;
625 -
626 - case 'upload':
627 - $content .= '<input' .
628 - ' id="' . $esc_form_key . '"' .
629 - ' type="text"' .
630 - ' size="36"' .
631 - ' class="' . $class . '"' .
632 - ' name="' . $esc_form_key . '"' .
633 - ' value="' . esc_attr( $meta_value ) . '"' . $aria_describedby .
634 - ' readonly="readonly"' .
635 - ' /> ';
636 - $content .= '<input' .
637 - ' id="' . esc_attr( $esc_form_key ) . '_button"' .
638 - ' class="wpseo_image_upload_button button"' .
639 - ' data-target="' . esc_attr( $esc_form_key ) . '"' .
640 - ' data-target-id="' . esc_attr( $esc_form_key ) . '-id"' .
641 - ' type="button"' .
642 - ' value="' . esc_attr__( 'Upload Image', 'wordpress-seo' ) . '"' .
643 - ' /> ';
644 - $content .= '<input' .
645 - ' class="wpseo_image_remove_button button"' .
646 - ' type="button"' .
647 - ' value="' . esc_attr__( 'Clear Image', 'wordpress-seo' ) . '"' .
648 - ' />';
649 - break;
650 - }
651 -
652 - $html = '';
653 - if ( $content === '' ) {
654 - $content = apply_filters( 'wpseo_do_meta_box_field_' . $key, $content, $meta_value, $esc_form_key, $meta_field_def, $key );
655 - }
656 -
657 - if ( $content !== '' ) {
658 -
659 - $title = esc_html( $meta_field_def['title'] );
660 -
661 - // By default, use the field title as a label element.
662 - $label = '<label for="' . $esc_form_key . '">' . $title . '</label>';
663 -
664 - // Set the inline help and help panel, if any.
665 - $help_button = '';
666 - $help_panel = '';
667 - if ( isset( $meta_field_def['help'] ) && $meta_field_def['help'] !== '' ) {
668 - $help = new WPSEO_Admin_Help_Panel( $key, $meta_field_def['help-button'], $meta_field_def['help'] );
669 - $help_button = $help->get_button_html();
670 - $help_panel = $help->get_panel_html();
671 - }
672 -
673 - // If it's a set of radio buttons, output proper fieldset and legend.
674 - if ( $meta_field_def['type'] === 'radio' ) {
675 - return '<fieldset><legend>' . $title . '</legend>' . $help_button . $help_panel . $content . $description . '</fieldset>';
676 - }
677 -
678 - // If it's a single checkbox, ignore the title.
679 - if ( $meta_field_def['type'] === 'checkbox' ) {
680 - $label = '';
681 - }
682 -
683 - // Other meta box content or form fields.
684 - if ( $meta_field_def['type'] === 'hidden' ) {
685 - $html = $content;
686 - }
687 - else {
688 - $html = $label . $description . $help_button . $help_panel . $content;
689 - }
690 - }
691 -
692 - return $html;
693 - }
694 -
695 - /**
696 455 * Saves the WP SEO metadata for posts.
697 456 *
698 457 * {@internal $_POST parameters are validated via sanitize_post_meta().}}
699 458 *
@@ -748,11 +507,16 @@
748 507 $meta_boxes,
749 508 WPSEO_Meta::get_meta_field_defs( 'general', $post->post_type ),
750 509 WPSEO_Meta::get_meta_field_defs( 'advanced' ),
751 510 $social_fields,
752 - WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type )
511 + WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type ),
753 512 );
754 513
514 + // We can't detect in save_postdata whether the request is coming from the block editor, so we gate the content_planner fields on post type only.
515 + if ( $post->post_type === 'post' ) {
516 + $meta_boxes = array_merge( $meta_boxes, WPSEO_Meta::get_meta_field_defs( 'content_planner' ) );
517 + }
518 +
755 519 foreach ( $meta_boxes as $key => $meta_box ) {
756 520
757 521 // If analysis is disabled remove that analysis score value from the DB.
758 522 if ( $this->is_meta_value_disabled( $key ) ) {
@@ -802,9 +566,9 @@
802 566 *
803 567 * @return bool Whether the given meta value key is disabled.
804 568 */
805 569 public function is_meta_value_disabled( $key ) {
806 - if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) {
570 + if ( in_array( $key, [ 'linkdex', 'seo_title_score', 'meta_description_score' ], true ) && ! $this->seo_analysis->is_enabled() ) {
807 571 return true;
808 572 }
809 573
810 574 if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) {
@@ -810,8 +574,12 @@
810 574 if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) {
811 575 return true;
812 576 }
813 577
578 + if ( $key === 'inclusive_language_score' && ! $this->inclusive_language_analysis->is_enabled() ) {
579 + return true;
580 + }
581 +
814 582 return false;
815 583 }
816 584
817 585 /**
@@ -817,32 +585,35 @@
817 585 /**
818 586 * Enqueues all the needed JS and CSS.
819 587 *
820 588 * @todo [JRF => whomever] Create css/metabox-mp6.css file and add it to the below allowed colors array when done.
589 + *
590 + * @return void
821 591 */
822 592 public function enqueue() {
823 593 global $pagenow;
824 594
595 + if ( $this->readability_analysis->is_enabled() ) {
596 + $this->editor = new WPSEO_Metabox_Editor();
597 + $this->editor->register_hooks();
598 + }
599 +
825 600 $asset_manager = new WPSEO_Admin_Asset_Manager();
826 601
827 - $is_editor = self::is_post_overview( $pagenow ) || self::is_post_edit( $pagenow );
828 -
829 602 if ( self::is_post_overview( $pagenow ) ) {
830 - $asset_manager->enqueue_style( 'edit-page' );
831 - $asset_manager->enqueue_script( 'edit-page' );
832 -
833 603 return;
834 604 }
835 605
836 606 /* Filter 'wpseo_always_register_metaboxes_on_admin' documented in wpseo-main.php */
837 - if ( ( $is_editor === false && apply_filters( 'wpseo_always_register_metaboxes_on_admin', false ) === false ) || $this->display_metabox() === false ) {
607 + if ( ( self::is_post_edit( $pagenow ) === false && apply_filters( 'wpseo_always_register_metaboxes_on_admin', false ) === false ) || $this->display_metabox() === false ) {
838 608 return;
839 609 }
840 610
841 611 $post_id = get_queried_object_id();
842 - // phpcs:ignore WordPress.Security.NonceVerification.Recommended
843 - if ( empty( $post_id ) && isset( $_GET['post'] ) ) {
844 - $post_id = sanitize_text_field( filter_input( INPUT_GET, 'post' ) );
612 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
613 + if ( empty( $post_id ) && isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) {
614 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
615 + $post_id = sanitize_text_field( wp_unslash( $_GET['post'] ) );
845 616 }
846 617
847 618 if ( $post_id !== 0 ) {
848 619 // Enqueue files needed for upload functionality.
@@ -849,13 +620,16 @@
849 620 wp_enqueue_media( [ 'post' => $post_id ] );
850 621 }
851 622
852 623 $asset_manager->enqueue_style( 'metabox-css' );
853 - $asset_manager->enqueue_style( 'scoring' );
854 - $asset_manager->enqueue_style( 'select2' );
624 + if ( $this->readability_analysis->is_enabled() ) {
625 + $asset_manager->enqueue_style( 'scoring' );
626 + }
855 627 $asset_manager->enqueue_style( 'monorepo' );
628 + $asset_manager->enqueue_style( 'ai-generator' );
629 + $asset_manager->enqueue_style( 'ai-fix-assessments' );
856 630
857 - $is_block_editor = WP_Screen::get()->is_block_editor();
631 + $is_block_editor = YoastSEO()->helpers->current_page->is_block_editor();
858 632 $post_edit_handle = 'post-edit';
859 633 if ( ! $is_block_editor ) {
860 634 $post_edit_handle = 'post-edit-classic';
861 635 }
@@ -871,9 +645,8 @@
871 645 $asset_manager->localize_script( $post_edit_handle, 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() );
872 646
873 647 $plugins_script_data = [
874 648 'replaceVars' => [
875 - 'no_parent_text' => __( '(no parent)', 'wordpress-seo' ),
876 649 'replace_vars' => $this->get_replace_vars(),
877 650 'hidden_replace_vars' => $this->get_hidden_replace_vars(),
878 651 'recommended_replace_vars' => $this->get_recommended_replace_vars(),
879 652 'scope' => $this->determine_scope(),
@@ -879,10 +652,10 @@
879 652 'scope' => $this->determine_scope(),
880 653 'has_taxonomies' => $this->current_post_type_has_taxonomies(),
881 654 ],
882 655 'shortcodes' => [
656 + 'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(),
883 657 'wpseo_filter_shortcodes_nonce' => wp_create_nonce( 'wpseo-filter-shortcodes' ),
884 - 'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(),
885 658 ],
886 659 ];
887 660
888 661 $worker_script_data = [
@@ -891,44 +664,49 @@
891 664 'keywords_assessment_url' => YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ),
892 665 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(),
893 666 ];
894 667
895 - $alert_dismissal_action = YoastSEO()->classes->get( \Yoast\WP\SEO\Actions\Alert_Dismissal_Action::class );
896 - $dismissed_alerts = $alert_dismissal_action->all_dismissed();
668 + $page_on_front = (int) get_option( 'page_on_front' );
669 + $homepage_is_page = get_option( 'show_on_front' ) === 'page';
670 + $is_front_page = $homepage_is_page && $page_on_front === (int) $post_id;
897 671
898 672 $script_data = [
899 - // @todo replace this translation with JavaScript translations.
900 - 'media' => [ 'choose_image' => __( 'Use Image', 'wordpress-seo' ) ],
901 - 'metabox' => $this->get_metabox_script_data(),
902 - 'userLanguageCode' => WPSEO_Language_Utils::get_language( \get_user_locale() ),
903 - 'isPost' => true,
904 - 'isBlockEditor' => $is_block_editor,
905 - 'postStatus' => get_post_status( $post_id ),
906 - 'analysis' => [
907 - 'plugins' => $plugins_script_data,
908 - 'worker' => $worker_script_data,
909 - 'estimatedReadingTimeEnabled' => $this->estimated_reading_time_conditional->is_met(),
673 + 'metabox' => $this->get_metabox_script_data(),
674 + 'isPost' => true,
675 + 'isBlockEditor' => $is_block_editor,
676 + 'postId' => $post_id,
677 + 'postStatus' => get_post_status( $post_id ),
678 + 'postType' => get_post_type( $post_id ),
679 + 'isPage' => get_post_type( $post_id ) === 'page',
680 + 'usedKeywordsNonce' => wp_create_nonce( 'wpseo-keyword-usage-and-post-types' ),
681 + 'analysis' => [
682 + 'plugins' => $plugins_script_data,
683 + 'worker' => $worker_script_data,
910 684 ],
911 - 'dismissedAlerts' => $dismissed_alerts,
685 + 'isFrontPage' => $is_front_page,
912 686 ];
913 687
914 - if ( post_type_supports( get_post_type(), 'thumbnail' ) ) {
688 + /**
689 + * The website information repository.
690 + *
691 + * @var Website_Information_Repository $repo
692 + */
693 + $repo = YoastSEO()->classes->get( Website_Information_Repository::class );
694 + $site_information = $repo->get_post_site_information();
695 + $site_information->set_permalink( $this->get_permalink() );
696 + $script_data = array_merge_recursive( $site_information->get_legacy_site_information(), $script_data );
697 +
698 + if ( ! $is_block_editor && post_type_supports( get_post_type(), 'thumbnail' ) ) {
915 699 $asset_manager->enqueue_style( 'featured-image' );
916 -
917 - // @todo replace this translation with JavaScript translations.
918 - $script_data['featuredImage'] = [
919 - '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' ),
920 - ];
921 700 }
922 701
923 702 $asset_manager->localize_script( $post_edit_handle, 'wpseoScriptData', $script_data );
924 - $asset_manager->enqueue_user_language_script();
925 703 }
926 704
927 705 /**
928 706 * Returns post in metabox context.
929 707 *
930 - * @return WP_Post|array
708 + * @return WP_Post|array<string|int|bool>
931 709 */
932 710 protected function get_metabox_post() {
933 711 if ( $this->post !== null ) {
934 712 return $this->post;
@@ -933,11 +711,12 @@
933 711 if ( $this->post !== null ) {
934 712 return $this->post;
935 713 }
936 714
937 - $post = filter_input( INPUT_GET, 'post' );
938 - if ( ! empty( $post ) ) {
939 - $post_id = (int) WPSEO_Utils::validate_int( $post );
715 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
716 + if ( isset( $_GET['post'] ) && is_string( $_GET['post'] ) ) {
717 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, Sanitization happens in the validate_int function.
718 + $post_id = (int) WPSEO_Utils::validate_int( wp_unslash( $_GET['post'] ) );
940 719
941 720 $this->post = get_post( $post_id );
942 721
943 722 return $this->post;
@@ -954,9 +733,9 @@
954 733
955 734 /**
956 735 * Returns an array with shortcode tags for all registered shortcodes.
957 736 *
958 - * @return array
737 + * @return string[]
959 738 */
960 739 private function get_valid_shortcode_tags() {
961 740 $shortcode_tags = [];
962 741
@@ -969,9 +748,9 @@
969 748
970 749 /**
971 750 * Prepares the replace vars for localization.
972 751 *
973 - * @return array Replace vars.
752 + * @return string[] Replace vars.
974 753 */
975 754 private function get_replace_vars() {
976 755 $cached_replacement_vars = [];
977 756
@@ -1018,9 +797,9 @@
1018 797
1019 798 /**
1020 799 * Prepares the recommended replace vars for localization.
1021 800 *
1022 - * @return array Recommended replacement variables.
801 + * @return array<string[]> Recommended replacement variables.
1023 802 */
1024 803 private function get_recommended_replace_vars() {
1025 804 $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
1026 805
@@ -1034,9 +813,9 @@
1034 813 * Gets the custom replace variables for custom taxonomies and fields.
1035 814 *
1036 815 * @param WP_Post $post The post to check for custom taxonomies and fields.
1037 816 *
1038 - * @return array Array containing all the replacement variables.
817 + * @return array<string[]> Array containing all the replacement variables.
1039 818 */
1040 819 private function get_custom_replace_vars( $post ) {
1041 820 return [
1042 821 'custom_fields' => $this->get_custom_fields_replace_vars( $post ),
@@ -1048,9 +827,9 @@
1048 827 * Gets the custom replace variables for custom taxonomies.
1049 828 *
1050 829 * @param WP_Post $post The post to check for custom taxonomies.
1051 830 *
1052 - * @return array Array containing all the replacement variables.
831 + * @return array<string[]> Array containing all the replacement variables.
1053 832 */
1054 833 private function get_custom_taxonomies_replace_vars( $post ) {
1055 834 $taxonomies = get_object_taxonomies( $post, 'objects' );
1056 835 $custom_replace_vars = [];
@@ -1079,9 +858,9 @@
1079 858 * Gets the custom replace variables for custom fields.
1080 859 *
1081 860 * @param WP_Post $post The post to check for custom fields.
1082 861 *
1083 - * @return array Array containing all the replacement variables.
862 + * @return array<string[]> Array containing all the replacement variables.
1084 863 */
1085 864 private function get_custom_fields_replace_vars( $post ) {
1086 865 $custom_replace_vars = [];
1087 866
@@ -1096,8 +875,25 @@
1096 875 if ( ! is_array( $custom_fields ) ) {
1097 876 return $custom_replace_vars;
1098 877 }
1099 878
879 + $meta = YoastSEO()->meta->for_post( $post->ID );
880 +
881 + if ( ! $meta ) {
882 + return $custom_replace_vars;
883 + }
884 +
885 + // Simply concatenate all fields containing replace vars so we can handle them all with a single regex find.
886 + $replace_vars_fields = implode(
887 + ' ',
888 + [
889 + $meta->presentation->title,
890 + $meta->presentation->meta_description,
891 + ],
892 + );
893 +
894 + preg_match_all( '/%%cf_([A-Za-z0-9_]+)%%/', $replace_vars_fields, $matches );
895 + $fields_to_include = $matches[1];
1100 896 foreach ( $custom_fields as $custom_field_name => $custom_field ) {
1101 897 // Skip private custom fields.
1102 898 if ( substr( $custom_field_name, 0, 1 ) === '_' ) {
1103 899 continue;
@@ -1102,8 +898,13 @@
1102 898 if ( substr( $custom_field_name, 0, 1 ) === '_' ) {
1103 899 continue;
1104 900 }
1105 901
902 + // Skip custom fields that are not used, new ones will be fetched dynamically.
903 + if ( ! in_array( $custom_field_name, $fields_to_include, true ) ) {
904 + continue;
905 + }
906 +
1106 907 // Skip custom field values that are serialized.
1107 908 if ( is_serialized( $custom_field[0] ) ) {
1108 909 continue;
1109 910 }
@@ -1143,6 +944,22 @@
1143 944 * @return string The product title.
1144 945 */
1145 946 protected function get_product_title() {
1146 947 return YoastSEO()->helpers->product->get_product_name();
948 + }
949 +
950 + /**
951 + * Gets the permalink.
952 + *
953 + * @return string
954 + */
955 + protected function get_permalink() {
956 + $permalink = '';
957 +
958 + if ( is_object( $this->get_metabox_post() ) ) {
959 + $permalink = get_sample_permalink( $this->get_metabox_post()->ID );
960 + $permalink = $permalink[0];
961 + }
962 +
963 + return $permalink;
1147 964 }
1148 965 }