PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
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 19.1 All 128 releases
wordpress-seo / admin / class-yoast-form.php

class-yoast-form.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at admin/class-yoast-form.php

1,104 lines 35.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 use Yoast\WP\SEO\Presenters\Admin\Light_Switch_Presenter;
9 use Yoast\WP\SEO\Presenters\Admin\Sidebar_Presenter;
10
11 /**
12 * Admin form class.
13 *
14 * @since 2.0
15 */
16 class Yoast_Form {
17
18 /**
19 * Instance of this class
20 *
21 * @since 2.0
22 *
23 * @var Yoast_Form
24 */
25 public static $instance;
26
27 /**
28 * The short name of the option to use for the current page.
29 *
30 * @since 2.0
31 *
32 * @var string
33 */
34 public $option_name;
35
36 /**
37 * Option instance.
38 *
39 * @since 8.4
40 * @var WPSEO_Option|null
41 */
42 protected $option_instance = null;
43
44 /**
45 * Get the singleton instance of this class.
46 *
47 * @since 2.0
48 *
49 * @return Yoast_Form
50 */
51 public static function get_instance() {
52 if ( ! ( self::$instance instanceof self ) ) {
53 self::$instance = new self();
54 }
55
56 return self::$instance;
57 }
58
59 /**
60 * Generates the header for admin pages.
61 *
62 * @since 2.0
63 *
64 * @param bool $form Whether or not the form start tag should be included.
65 * @param string $option The short name of the option to use for the current page.
66 * @param bool $contains_files Whether the form should allow for file uploads.
67 * @param bool $option_long_name Group name of the option.
68 *
69 * @return void
70 */
71 public function admin_header( $form = true, $option = 'wpseo', $contains_files = false, $option_long_name = false ) {
72 if ( ! $option_long_name ) {
73 $option_long_name = WPSEO_Options::get_group_name( $option );
74 }
75 ?>
76 <div class="wrap yoast wpseo-admin-page <?php echo esc_attr( 'page-' . $option ); ?>">
77 <?php
78 /**
79 * Display the updated/error messages.
80 * Only needed as our settings page is not under options, otherwise it will automatically be included.
81 *
82 * @see settings_errors()
83 */
84 require_once ABSPATH . 'wp-admin/options-head.php';
85 ?>
86 <h1 id="wpseo-title"><?php echo esc_html( get_admin_page_title() ); ?></h1>
87 <div id="yst-settings-header-root"></div>
88 <div class="wpseo_content_wrapper">
89 <div class="wpseo_content_cell" id="wpseo_content_top">
90 <?php
91 if ( $form === true ) {
92 $enctype = ( $contains_files ) ? ' enctype="multipart/form-data"' : '';
93
94 $network_admin = new Yoast_Network_Admin();
95 if ( $network_admin->meets_requirements() ) {
96 $action_url = network_admin_url( 'settings.php' );
97 $hidden_fields_cb = [ $network_admin, 'settings_fields' ];
98 }
99 else {
100 $action_url = admin_url( 'options.php' );
101 $hidden_fields_cb = 'settings_fields';
102 }
103
104 echo '<form action="'
105 . esc_url( $action_url )
106 . '" method="post" id="wpseo-conf"'
107 . $enctype . ' accept-charset="' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- nothing to escape.
108 . esc_attr( get_bloginfo( 'charset' ) )
109 . '" novalidate="novalidate">';
110 call_user_func( $hidden_fields_cb, $option_long_name );
111 }
112 $this->set_option( $option );
113 }
114
115 /**
116 * Set the option used in output for form elements.
117 *
118 * @since 2.0
119 *
120 * @param string $option_name Option key.
121 *
122 * @return void
123 */
124 public function set_option( $option_name ) {
125 $this->option_name = $option_name;
126
127 $this->option_instance = WPSEO_Options::get_option_instance( $option_name );
128 if ( ! $this->option_instance ) {
129 $this->option_instance = null;
130 }
131 }
132
133 /**
134 * Generates the footer for admin pages.
135 *
136 * @since 2.0
137 *
138 * @param bool $submit Whether or not a submit button and form end tag should be shown.
139 * @param bool $show_sidebar Whether or not to show the banner sidebar - used by premium plugins to disable it.
140 *
141 * @return void
142 */
143 public function admin_footer( $submit = true, $show_sidebar = true ) {
144 if ( $submit ) {
145 $settings_changed_listener = new WPSEO_Admin_Settings_Changed_Listener();
146 echo '<div id="wpseo-submit-container">';
147
148 echo '<div id="wpseo-submit-container-float" class="wpseo-admin-submit">';
149 submit_button( __( 'Save changes', 'wordpress-seo' ) );
150 $settings_changed_listener->show_success_message();
151 echo '</div>';
152
153 echo '<div id="wpseo-submit-container-fixed" class="wpseo-admin-submit wpseo-admin-submit-fixed" style="display: none;">';
154 submit_button( __( 'Save changes', 'wordpress-seo' ) );
155 $settings_changed_listener->show_success_message();
156 echo '</div>';
157
158 echo '</div>';
159
160 echo '
161 </form>';
162 }
163
164 /**
165 * Apply general admin_footer hooks.
166 */
167 do_action( 'wpseo_admin_footer', $this );
168
169 /**
170 * Run possibly set actions to add for example an i18n box.
171 */
172 do_action( 'wpseo_admin_promo_footer' );
173
174 echo '
175 </div><!-- end of div wpseo_content_top -->';
176
177 if ( $show_sidebar ) {
178 $this->admin_sidebar();
179 }
180
181 echo '</div><!-- end of div wpseo_content_wrapper -->';
182
183 do_action( 'wpseo_admin_below_content', $this );
184
185 echo '
186 </div><!-- end of wrap -->';
187 }
188
189 /**
190 * Generates the sidebar for admin pages.
191 *
192 * @since 2.0
193 *
194 * @return void
195 */
196 public function admin_sidebar() {
197 // No banners in Premium.
198 $addon_manager = new WPSEO_Addon_Manager();
199 if ( YoastSEO()->helpers->product->is_premium() && $addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ) ) {
200 return;
201 }
202
203 $sidebar_presenter = new Sidebar_Presenter();
204 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in presenter.
205 echo $sidebar_presenter->present();
206 }
207
208 /**
209 * Output a label element.
210 *
211 * @since 2.0
212 *
213 * @param string $text Label text string, which can contain escaped html.
214 * @param array $attr HTML attributes set.
215 *
216 * @return void
217 */
218 public function label( $text, $attr ) {
219 $defaults = [
220 'class' => 'checkbox',
221 'close' => true,
222 'for' => '',
223 'aria_label' => '',
224 ];
225
226 $attr = wp_parse_args( $attr, $defaults );
227 $aria_label = '';
228 if ( $attr['aria_label'] !== '' ) {
229 $aria_label = ' aria-label="' . esc_attr( $attr['aria_label'] ) . '"';
230 }
231
232 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before. Specifically, the $text variable can contain escaped html.
233 echo "<label class='" . esc_attr( $attr['class'] ) . "' for='" . esc_attr( $attr['for'] ) . "'$aria_label>$text";
234 if ( $attr['close'] ) {
235 echo '</label>';
236 }
237 }
238
239 /**
240 * Output a legend element.
241 *
242 * @since 3.4
243 *
244 * @param string $text Legend text string.
245 * @param array $attr HTML attributes set.
246 *
247 * @return void
248 */
249 public function legend( $text, $attr ) {
250 $defaults = [
251 'id' => '',
252 'class' => '',
253 ];
254 $attr = wp_parse_args( $attr, $defaults );
255
256 $id = ( $attr['id'] === '' ) ? '' : ' id="' . esc_attr( $attr['id'] ) . '"';
257 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
258 echo '<legend class="' . esc_attr( 'yoast-form-legend ' . $attr['class'] ) . '"' . $id . '>' . $text . '</legend>';
259 }
260
261 /**
262 * Create a Checkbox input field.
263 *
264 * @since 2.0
265 *
266 * @param string $variable The variable within the option to create the checkbox for.
267 * @param string $label The label to show for the variable.
268 * @param bool $label_left Whether the label should be left (true) or right (false).
269 * @param array $attr Extra attributes to add to the checkbox.
270 *
271 * @return void
272 */
273 public function checkbox( $variable, $label, $label_left = false, $attr = [] ) {
274 $val = $this->get_field_value( $variable, false );
275
276 $defaults = [
277 'disabled' => false,
278 ];
279 $attr = wp_parse_args( $attr, $defaults );
280
281 if ( $val === true ) {
282 $val = 'on';
283 }
284
285 $class = '';
286 if ( $label_left !== false ) {
287 $this->label( $label_left, [ 'for' => $variable ] );
288 }
289 else {
290 $class = 'double';
291 }
292
293 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
294
295 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
296 echo '<input class="', esc_attr( 'checkbox ' . $class ), '" type="checkbox" id="', esc_attr( $variable ), '" name="', esc_attr( $this->option_name . '[' . $variable . ']' ), '" value="on"', checked( $val, 'on', false ), $disabled_attribute, '/>';
297
298 if ( ! empty( $label ) ) {
299 $this->label( $label, [ 'for' => $variable ] );
300 }
301
302 echo '<br class="clear" />';
303 }
304
305 /**
306 * Creates a Checkbox input field list.
307 *
308 * @since 12.8
309 *
310 * @param string $variable The variables within the option to create the checkbox list for.
311 * @param string $labels The labels to show for the variable.
312 * @param array $attr Extra attributes to add to the checkbox list.
313 *
314 * @return void
315 */
316 public function checkbox_list( $variable, $labels, $attr = [] ) {
317 $defaults = [
318 'disabled' => false,
319 ];
320 $attr = wp_parse_args( $attr, $defaults );
321
322 $values = $this->get_field_value( $variable, [] );
323
324 foreach ( $labels as $name => $label ) {
325 printf(
326 '<input class="checkbox double" id="%1$s" type="checkbox" name="%2$s" %3$s %5$s value="%4$s"/>',
327 esc_attr( $variable . '-' . $name ),
328 esc_attr( $this->option_name . '[' . $variable . '][' . $name . ']' ),
329 checked( ! empty( $values[ $name ] ), true, false ),
330 esc_attr( $name ),
331 disabled( ( isset( $attr['disabled'] ) && $attr['disabled'] ), true, false ),
332 );
333
334 printf(
335 '<label class="checkbox" for="%1$s">%2$s</label>',
336 esc_attr( $variable . '-' . $name ), // #1
337 esc_html( $label ),
338 );
339 echo '<br class="clear">';
340 }
341 }
342
343 /**
344 * Create a light switch input field using a single checkbox.
345 *
346 * @since 3.1
347 *
348 * @param string $variable The variable within the option to create the checkbox for.
349 * @param string $label The visual label text for the toggle.
350 * @param array $buttons Array of two visual labels for the buttons (defaults Disabled/Enabled).
351 * @param bool $reverse Reverse order of buttons (default true).
352 * @param string $help Inline Help that will be printed out before the toggle.
353 * @param bool $strong Whether the visual label is displayed in strong text. Default is false.
354 * Starting from Yoast SEO 16.5, the visual label is forced to bold via CSS.
355 * @param array $attr Extra attributes to add to the light switch.
356 *
357 * @return void
358 */
359 public function light_switch( $variable, $label, $buttons = [], $reverse = true, $help = '', $strong = false, $attr = [] ) {
360 $val = $this->get_field_value( $variable, false );
361
362 $defaults = [
363 'disabled' => false,
364 ];
365 $attr = wp_parse_args( $attr, $defaults );
366
367 if ( $val === true ) {
368 $val = 'on';
369 }
370
371 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
372
373 $output = new Light_Switch_Presenter(
374 $variable,
375 $label,
376 $buttons,
377 $this->option_name . '[' . $variable . ']',
378 $val,
379 $reverse,
380 $help,
381 $strong,
382 $disabled_attribute,
383 );
384
385 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: All output is properly escaped or hardcoded in the presenter.
386 echo $output;
387 }
388
389 /**
390 * Create a Text input field.
391 *
392 * @since 2.0
393 * @since 2.1 Introduced the `$attr` parameter.
394 *
395 * @param string $variable The variable within the option to create the text input field for.
396 * @param string $label The label to show for the variable.
397 * @param array|string $attr Extra attributes to add to the input field. Can be class, disabled, autocomplete.
398 *
399 * @return void
400 */
401 public function textinput( $variable, $label, $attr = [] ) {
402 $type = 'text';
403 if ( ! is_array( $attr ) ) {
404 $attr = [
405 'class' => $attr,
406 'disabled' => false,
407 ];
408 }
409
410 $defaults = [
411 'placeholder' => '',
412 'class' => '',
413 ];
414 $attr = wp_parse_args( $attr, $defaults );
415 $val = $this->get_field_value( $variable, '' );
416 if ( isset( $attr['type'] ) && $attr['type'] === 'url' ) {
417 $val = urldecode( $val );
418 $type = 'url';
419 }
420 $attributes = isset( $attr['autocomplete'] ) ? ' autocomplete="' . esc_attr( $attr['autocomplete'] ) . '"' : '';
421
422 $this->label(
423 $label,
424 [
425 'for' => $variable,
426 'class' => 'textinput',
427 ],
428 );
429
430 $aria_attributes = Yoast_Input_Validation::get_the_aria_invalid_attribute( $variable );
431
432 $aria_attributes .= Yoast_Input_Validation::get_the_aria_describedby_attribute( $variable );
433
434 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
435
436 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
437 echo '<input', $attributes, $aria_attributes, ' class="', esc_attr( 'textinput ' . $attr['class'] ), '" placeholder="', esc_attr( $attr['placeholder'] ), '" type="', $type, '" id="', esc_attr( $variable ), '" name="', esc_attr( $this->option_name . '[' . $variable . ']' ), '" value="', esc_attr( $val ), '"', $disabled_attribute, '/>', '<br class="clear" />';
438 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in getter.
439 echo Yoast_Input_Validation::get_the_error_description( $variable );
440 }
441
442 /**
443 * Create a Number input field.
444 *
445 * @param string $variable The variable within the option to create the text input field for.
446 * @param string $label The label to show for the variable.
447 * @param array|string $attr Extra attributes to add to the input field. Can be class, disabled, autocomplete.
448 *
449 * @return void
450 */
451 public function number( $variable, $label, $attr = [] ) {
452 $type = 'number';
453 $defaults = [
454 'placeholder' => '',
455 'class' => 'number',
456 'disabled' => false,
457 'min' => 0,
458 'max' => 100,
459 ];
460 $attr = wp_parse_args( $attr, $defaults );
461 $val = $this->get_field_value( $variable, 0 );
462
463 $this->label(
464 $label,
465 [
466 'for' => $variable,
467 'class' => 'textinput ' . $attr['class'],
468 ],
469 );
470
471 $aria_attributes = Yoast_Input_Validation::get_the_aria_invalid_attribute( $variable );
472 $aria_attributes .= Yoast_Input_Validation::get_the_aria_describedby_attribute( $variable );
473
474 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
475
476 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
477 echo '<input' . $aria_attributes . ' class="' . esc_attr( $attr['class'] ) . '" type="' . $type . '" id="', esc_attr( $variable ), '" min="', esc_attr( $attr['min'] ), '" max="', esc_attr( $attr['max'] ), '" name="', esc_attr( $this->option_name . '[' . $variable . ']' ), '" value="', esc_attr( $val ), '"', $disabled_attribute, '/>', '<br class="clear" />';
478 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in getter.
479 echo Yoast_Input_Validation::get_the_error_description( $variable );
480 }
481
482 /**
483 * Creates a text input field with with the ability to add content after the label.
484 *
485 * @param string $variable The variable within the option to create the text input field for.
486 * @param string $label The label to show for the variable.
487 * @param array $attr Extra attributes to add to the input field.
488 *
489 * @return void
490 */
491 public function textinput_extra_content( $variable, $label, $attr = [] ) {
492 $type = 'text';
493
494 $defaults = [
495 'class' => 'yoast-field-group__inputfield',
496 'disabled' => false,
497 ];
498
499 $attr = wp_parse_args( $attr, $defaults );
500 $val = $this->get_field_value( $variable, '' );
501
502 if ( isset( $attr['type'] ) && $attr['type'] === 'url' ) {
503 $val = urldecode( $val );
504 $type = 'url';
505 }
506
507 echo '<div class="yoast-field-group__title">';
508 $this->label(
509 $label,
510 [
511 'for' => $variable,
512 'class' => $attr['class'] . '--label',
513 ],
514 );
515
516 if ( isset( $attr['extra_content'] ) ) {
517 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: may contain HTML that should not be escaped.
518 echo $attr['extra_content'];
519 }
520 echo '</div>';
521
522 $aria_attributes = Yoast_Input_Validation::get_the_aria_invalid_attribute( $variable );
523 $aria_attributes .= Yoast_Input_Validation::get_the_aria_describedby_attribute( $variable );
524
525 // phpcs:disable WordPress.Security.EscapeOutput -- Reason: output is properly escaped or hardcoded.
526 printf(
527 '<input type="%1$s" name="%2$s" id="%3$s" class="%4$s"%5$s%6$s%7$s value="%8$s"%9$s>',
528 $type,
529 esc_attr( $this->option_name . '[' . $variable . ']' ),
530 esc_attr( $variable ),
531 esc_attr( $attr['class'] ),
532 isset( $attr['placeholder'] ) ? ' placeholder="' . esc_attr( $attr['placeholder'] ) . '"' : '',
533 isset( $attr['autocomplete'] ) ? ' autocomplete="' . esc_attr( $attr['autocomplete'] ) . '"' : '',
534 $aria_attributes,
535 esc_attr( $val ),
536 $this->get_disabled_attribute( $variable, $attr ),
537 );
538 // phpcs:enable
539 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: output is properly escaped.
540 echo Yoast_Input_Validation::get_the_error_description( $variable );
541 }
542
543 /**
544 * Create a textarea.
545 *
546 * @since 2.0
547 *
548 * @param string $variable The variable within the option to create the textarea for.
549 * @param string $label The label to show for the variable.
550 * @param string|array $attr The CSS class or an array of attributes to assign to the textarea.
551 *
552 * @return void
553 */
554 public function textarea( $variable, $label, $attr = [] ) {
555 if ( ! is_array( $attr ) ) {
556 $attr = [
557 'class' => $attr,
558 ];
559 }
560
561 $defaults = [
562 'cols' => '',
563 'rows' => '',
564 'class' => '',
565 'disabled' => false,
566 ];
567 $attr = wp_parse_args( $attr, $defaults );
568 $val = $this->get_field_value( $variable, '' );
569
570 $this->label(
571 $label,
572 [
573 'for' => $variable,
574 'class' => 'textinput',
575 ],
576 );
577
578 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
579
580 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
581 echo '<textarea cols="' . esc_attr( $attr['cols'] ) . '" rows="' . esc_attr( $attr['rows'] ) . '" class="' . esc_attr( 'textinput ' . $attr['class'] ) . '" id="' . esc_attr( $variable ) . '" name="' . esc_attr( $this->option_name . '[' . $variable . ']' ), '"', $disabled_attribute, '>' . esc_textarea( $val ) . '</textarea><br class="clear" />';
582 }
583
584 /**
585 * Create a hidden input field.
586 *
587 * @since 2.0
588 *
589 * @param string $variable The variable within the option to create the hidden input for.
590 * @param string $id The ID of the element.
591 * @param mixed $val Optional. The value to set in the input field. Otherwise the value from the options will be used.
592 *
593 * @return void
594 */
595 public function hidden( $variable, $id = '', $val = null ) {
596 $val ??= $this->get_field_value( $variable, '' );
597
598 if ( is_bool( $val ) ) {
599 $val = ( $val === true ) ? 'true' : 'false';
600 }
601
602 if ( $id === '' ) {
603 $id = 'hidden_' . $variable;
604 }
605
606 echo '<input type="hidden" id="' . esc_attr( $id ) . '" name="' . esc_attr( $this->option_name . '[' . $variable . ']' ), '" value="' . esc_attr( $val ) . '"/>';
607 }
608
609 /**
610 * Create a Select Box.
611 *
612 * @since 2.0
613 *
614 * @param string $variable The variable within the option to create the select for.
615 * @param string $label The label to show for the variable.
616 * @param array $select_options The select options to choose from.
617 * @param string $styled The select style. Use 'styled' to get a styled select. Default 'unstyled'.
618 * @param bool $show_label Whether or not to show the label, if not, it will be applied as an aria-label.
619 * @param array $attr Extra attributes to add to the select.
620 * @param string $help Optional. Inline Help HTML that will be printed after the label. Default is empty.
621 *
622 * @return void
623 */
624 public function select( $variable, $label, array $select_options, $styled = 'unstyled', $show_label = true, $attr = [], $help = '' ) {
625 if ( empty( $select_options ) ) {
626 return;
627 }
628
629 $defaults = [
630 'disabled' => false,
631 ];
632 $attr = wp_parse_args( $attr, $defaults );
633
634 if ( $show_label ) {
635 $this->label(
636 $label,
637 [
638 'for' => $variable,
639 'class' => 'select',
640 ],
641 );
642 echo $help; // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: The help contains HTML.
643 }
644
645 $select_name = esc_attr( $this->option_name ) . '[' . esc_attr( $variable ) . ']';
646 $active_option = $this->get_field_value( $variable, '' );
647 $wrapper_start_tag = '';
648 $wrapper_end_tag = '';
649
650 $select = new Yoast_Input_Select( $variable, $select_name, $select_options, $active_option );
651 $select->add_attribute( 'class', 'select' );
652
653 if ( $this->is_control_disabled( $variable )
654 || ( isset( $attr['disabled'] ) && $attr['disabled'] ) ) {
655 $select->add_attribute( 'disabled', 'disabled' );
656 }
657
658 if ( ! $show_label ) {
659 $select->add_attribute( 'aria-label', $label );
660 }
661
662 if ( $styled === 'styled' ) {
663 $wrapper_start_tag = '<span class="yoast-styled-select">';
664 $wrapper_end_tag = '</span>';
665 }
666
667 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
668 echo $wrapper_start_tag;
669 $select->output_html();
670 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
671 echo $wrapper_end_tag;
672 echo '<br class="clear"/>';
673 }
674
675 /**
676 * Create a File upload field.
677 *
678 * @since 2.0
679 *
680 * @param string $variable The variable within the option to create the file upload field for.
681 * @param string $label The label to show for the variable.
682 * @param array $attr Extra attributes to add to the file upload input.
683 *
684 * @return void
685 */
686 public function file_upload( $variable, $label, $attr = [] ) {
687 $val = $this->get_field_value( $variable, '' );
688 if ( is_array( $val ) ) {
689 $val = $val['url'];
690 }
691
692 $defaults = [
693 'disabled' => false,
694 ];
695 $attr = wp_parse_args( $attr, $defaults );
696
697 $var_esc = esc_attr( $variable );
698 $this->label(
699 $label,
700 [
701 'for' => $variable,
702 'class' => 'select',
703 ],
704 );
705
706 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
707
708 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
709 echo '<input type="file" value="' . esc_attr( $val ) . '" class="textinput" name="' . esc_attr( $this->option_name ) . '[' . $var_esc . ']" id="' . $var_esc . '"', $disabled_attribute, '/>';
710
711 // Need to save separate array items in hidden inputs, because empty file inputs type will be deleted by settings API.
712 if ( ! empty( $val ) ) {
713 $this->hidden( 'file', $this->option_name . '_file' );
714 $this->hidden( 'url', $this->option_name . '_url' );
715 $this->hidden( 'type', $this->option_name . '_type' );
716 }
717 echo '<br class="clear"/>';
718 }
719
720 /**
721 * Media input.
722 *
723 * @since 2.0
724 * @deprecated 23.5
725 * @codeCoverageIgnore
726 *
727 * @param string $variable Option name.
728 * @param string $label Label message.
729 * @param array $attr Extra attributes to add to the media input and buttons.
730 *
731 * @return void
732 */
733 public function media_input( $variable, $label, $attr = [] ) {
734
735 _deprecated_function( __METHOD__, 'Yoast SEO 23.5' );
736
737 $val = $this->get_field_value( $variable, '' );
738 $id_value = $this->get_field_value( $variable . '_id', '' );
739
740 $var_esc = esc_attr( $variable );
741
742 $defaults = [
743 'disabled' => false,
744 ];
745 $attr = wp_parse_args( $attr, $defaults );
746
747 $this->label(
748 $label,
749 [
750 'for' => 'wpseo_' . $variable,
751 'class' => 'select',
752 ],
753 );
754
755 $id_field_id = 'wpseo_' . $var_esc . '_id';
756
757 echo '<span>';
758 echo '<input',
759 ' class="textinput"',
760 ' id="wpseo_', $var_esc, '"', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
761 ' type="text" size="36"',
762 ' name="', esc_attr( $this->option_name ), '[', $var_esc, ']"', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
763 ' value="', esc_attr( $val ), '"',
764 ' readonly="readonly"',
765 ' /> ';
766 echo '<input',
767 ' type="hidden"',
768 ' id="', esc_attr( $id_field_id ), '"',
769 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
770 ' name="', esc_attr( $this->option_name ), '[', $var_esc, '_id]"',
771 ' value="', esc_attr( $id_value ), '"',
772 ' />';
773 echo '</span>';
774 echo '<br class="clear"/>';
775 }
776
777 /**
778 * Create a Radio input field.
779 *
780 * @since 2.0
781 *
782 * @param string $variable The variable within the option to create the radio button for.
783 * @param array $values The radio options to choose from.
784 * @param string $legend Optional. The legend to show for the field set, if any.
785 * @param array $legend_attr Optional. The attributes for the legend, if any.
786 * @param array $attr Extra attributes to add to the radio button.
787 *
788 * @return void
789 */
790 public function radio( $variable, $values, $legend = '', $legend_attr = [], $attr = [] ) {
791 if ( ! is_array( $values ) || $values === [] ) {
792 return;
793 }
794 $val = $this->get_field_value( $variable, false );
795
796 $var_esc = esc_attr( $variable );
797
798 $defaults = [
799 'disabled' => false,
800 ];
801 $attr = wp_parse_args( $attr, $defaults );
802
803 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
804 echo '<fieldset class="yoast-form-fieldset wpseo_radio_block" id="' . $var_esc . '">';
805
806 if ( is_string( $legend ) && $legend !== '' ) {
807
808 $legend_defaults = [
809 'id' => '',
810 'class' => 'radiogroup',
811 ];
812
813 $legend_attr = wp_parse_args( $legend_attr, $legend_defaults );
814
815 $this->legend( $legend, $legend_attr );
816 }
817
818 foreach ( $values as $key => $value ) {
819 $label = $value;
820 $aria_label = '';
821
822 if ( is_array( $value ) ) {
823 $label = ( $value['label'] ?? '' );
824 $aria_label = ( $value['aria_label'] ?? '' );
825 }
826
827 $key_esc = esc_attr( $key );
828
829 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
830
831 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
832 echo '<input type="radio" class="radio" id="' . $var_esc . '-' . $key_esc . '" name="' . esc_attr( $this->option_name ) . '[' . $var_esc . ']" value="' . $key_esc . '" ' . checked( $val, $key_esc, false ) . $disabled_attribute . ' />';
833 $this->label(
834 $label,
835 [
836 'for' => $var_esc . '-' . $key_esc,
837 'class' => 'radio',
838 'aria_label' => $aria_label,
839 ],
840 );
841 }
842 echo '</fieldset>';
843 }
844
845 /**
846 * Create a toggle switch input field using two radio buttons.
847 *
848 * @since 3.1
849 *
850 * @param string $variable The variable within the option to create the radio buttons for.
851 * @param array $values Associative array of on/off keys and their values to be used as
852 * the label elements text for the radio buttons. Optionally, each
853 * value can be an array of visible label text and screen reader text.
854 * @param string $label The visual label for the radio buttons group, used as the fieldset legend.
855 * @param string $help Inline Help that will be printed out before the visible toggles text.
856 * @param array $attr Extra attributes to add to the toggle switch.
857 *
858 * @return void
859 */
860 public function toggle_switch( $variable, $values, $label, $help = '', $attr = [] ) {
861 if ( ! is_array( $values ) || $values === [] ) {
862 return;
863 }
864
865 $defaults = [
866 'disabled' => false,
867 ];
868 $attr = wp_parse_args( $attr, $defaults );
869
870 if ( isset( $attr['preserve_disabled_value'] ) && $attr['preserve_disabled_value'] ) {
871 $this->hidden( $variable );
872 $variable .= '_disabled';
873 }
874
875 $val = $this->get_field_value( $variable, false );
876 if ( $val === true ) {
877 $val = 'on';
878 }
879 if ( $val === false ) {
880 $val = 'off';
881 }
882
883 $help_class = ! empty( $help ) ? ' switch-container__has-help' : '';
884
885 $has_premium_upsell = ( isset( $attr['show_premium_upsell'] ) && $attr['show_premium_upsell'] && isset( $attr['premium_upsell_url'] ) && ! empty( $attr['premium_upsell_url'] ) );
886 $upsell_class = ( $has_premium_upsell ) ? ' premium-upsell' : '';
887
888 $var_esc = esc_attr( $variable );
889
890 printf( '<div class="%s">', esc_attr( 'switch-container' . $help_class . $upsell_class ) );
891 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
892 echo '<fieldset id="', $var_esc, '" class="fieldset-switch-toggle"><legend>', $label, '</legend>', $help;
893
894 // Show disabled note if attribute does not exists or does exist and is set to true.
895 if ( ! isset( $attr['show_disabled_note'] ) || ( $attr['show_disabled_note'] === true ) ) {
896 if ( isset( $attr['note_when_disabled'] ) ) {
897 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
898 echo $this->get_disabled_note( $variable, $attr['note_when_disabled'] );
899 }
900 else {
901 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
902 echo $this->get_disabled_note( $variable );
903 }
904 }
905
906 echo '<div class="switch-toggle switch-candy switch-yoast-seo">';
907
908 foreach ( $values as $key => $value ) {
909 $screen_reader_text_html = '';
910
911 if ( is_array( $value ) ) {
912 $screen_reader_text = $value['screen_reader_text'];
913 $screen_reader_text_html = '<span class="screen-reader-text"> ' . esc_html( $screen_reader_text ) . '</span>';
914 $value = $value['text'];
915 }
916
917 $key_esc = esc_attr( $key );
918 $for = $var_esc . '-' . $key_esc;
919 $disabled_attribute = $this->get_disabled_attribute( $variable, $attr );
920
921 // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded and all other output is properly escaped.
922 echo '<input type="radio" id="' . $for . '" name="' . esc_attr( $this->option_name ) . '[' . $var_esc . ']" value="' . $key_esc . '" ' . checked( $val, $key_esc, false ) . $disabled_attribute . ' />',
923 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output escaped before.
924 '<label for="', $for, '">', esc_html( $value ), $screen_reader_text_html, '</label>';
925 }
926
927 $upsell_button = '';
928 if ( $has_premium_upsell ) {
929 $upsell_button = '<a class="yoast-button yoast-button--buy yoast-button--small" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2" href='
930 . esc_url( $attr['premium_upsell_url'] ) . ' target="_blank">'
931 . esc_html__( 'Unlock with Premium!', 'wordpress-seo' )
932 /* translators: Hidden accessibility text. */
933 . '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>'
934 . '<span aria-hidden="true" class="yoast-button--buy__caret"></span></a>';
935 }
936
937 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All variable output is escaped above.
938 echo '<a></a></div></fieldset><div class="clear"></div>' . $upsell_button . '</div>' . PHP_EOL . PHP_EOL;
939 }
940
941 /**
942 * Creates a toggle switch to define whether an indexable should be indexed or not.
943 *
944 * @param string $variable The variable within the option to create the radio buttons for.
945 * @param string $label The visual label for the radio buttons group, used as the fieldset legend.
946 * @param string $help Inline Help that will be printed out before the visible toggles text.
947 * @param array $attr Extra attributes to add to the index switch.
948 *
949 * @return void
950 */
951 public function index_switch( $variable, $label, $help = '', $attr = [] ) {
952 $defaults = [
953 'disabled' => false,
954 ];
955 $attr = wp_parse_args( $attr, $defaults );
956
957 $index_switch_values = [
958 'off' => __( 'On', 'wordpress-seo' ),
959 'on' => __( 'Off', 'wordpress-seo' ),
960 ];
961
962 $is_disabled = ( isset( $attr['disabled'] ) && $attr['disabled'] );
963
964 $this->toggle_switch(
965 $variable,
966 $index_switch_values,
967 sprintf(
968 /* translators: %s expands to an indexable object's name, like a post type or taxonomy */
969 esc_html__( 'Show %s in search results?', 'wordpress-seo' ),
970 $label,
971 ),
972 $help,
973 [ 'disabled' => $is_disabled ],
974 );
975 }
976
977 /**
978 * Creates a toggle switch to show hide certain options.
979 *
980 * @param string $variable The variable within the option to create the radio buttons for.
981 * @param string $label The visual label for the radio buttons group, used as the fieldset legend.
982 * @param bool $inverse_keys Whether or not the option keys need to be inverted to support older functions.
983 * @param string $help Inline Help that will be printed out before the visible toggles text.
984 * @param array $attr Extra attributes to add to the show-hide switch.
985 *
986 * @return void
987 */
988 public function show_hide_switch( $variable, $label, $inverse_keys = false, $help = '', $attr = [] ) {
989 $defaults = [
990 'disabled' => false,
991 ];
992 $attr = wp_parse_args( $attr, $defaults );
993
994 $on_key = ( $inverse_keys ) ? 'off' : 'on';
995 $off_key = ( $inverse_keys ) ? 'on' : 'off';
996
997 $show_hide_switch = [
998 $on_key => __( 'On', 'wordpress-seo' ),
999 $off_key => __( 'Off', 'wordpress-seo' ),
1000 ];
1001
1002 $is_disabled = ( isset( $attr['disabled'] ) && $attr['disabled'] );
1003
1004 $this->toggle_switch(
1005 $variable,
1006 $show_hide_switch,
1007 $label,
1008 $help,
1009 [ 'disabled' => $is_disabled ],
1010 );
1011 }
1012
1013 /**
1014 * Retrieves the value for the form field.
1015 *
1016 * @param string $field_name The field name to retrieve the value for.
1017 * @param string|null $default_value The default value, when field has no value.
1018 *
1019 * @return mixed|null The retrieved value.
1020 */
1021 protected function get_field_value( $field_name, $default_value = null ) {
1022 // On multisite subsites, the Usage tracking feature should always be set to Off.
1023 if ( $this->is_tracking_on_subsite( $field_name ) ) {
1024 return false;
1025 }
1026
1027 return WPSEO_Options::get( $field_name, $default_value );
1028 }
1029
1030 /**
1031 * Checks whether a given control should be disabled.
1032 *
1033 * @param string $variable The variable within the option to check whether its control should be disabled.
1034 *
1035 * @return bool True if control should be disabled, false otherwise.
1036 */
1037 protected function is_control_disabled( $variable ) {
1038 if ( $this->option_instance === null ) {
1039 return false;
1040 }
1041
1042 // Disable the Usage tracking feature for multisite subsites.
1043 if ( $this->is_tracking_on_subsite( $variable ) ) {
1044 return true;
1045 }
1046
1047 return $this->option_instance->is_disabled( $variable );
1048 }
1049
1050 /**
1051 * Gets the explanation note to print if a given control is disabled.
1052 *
1053 * @param string $variable The variable within the option to print a disabled note for.
1054 * @param string $custom_note An optional custom note to print instead.
1055 *
1056 * @return string Explanation note HTML string, or empty string if no note necessary.
1057 */
1058 protected function get_disabled_note( $variable, $custom_note = '' ) {
1059 if ( $custom_note === '' && ! $this->is_control_disabled( $variable ) ) {
1060 return '';
1061 }
1062 $disabled_message = esc_html__( 'This feature has been disabled by the network admin.', 'wordpress-seo' );
1063
1064 // The explanation to show when disabling the Usage tracking feature for multisite subsites.
1065 if ( $this->is_tracking_on_subsite( $variable ) ) {
1066 $disabled_message = esc_html__( 'This feature has been disabled since subsites never send tracking data.', 'wordpress-seo' );
1067 }
1068
1069 if ( $custom_note ) {
1070 $disabled_message = esc_html( $custom_note );
1071 }
1072
1073 return '<p class="disabled-note">' . $disabled_message . '</p>';
1074 }
1075
1076 /**
1077 * Determines whether we are dealing with the Usage tracking feature on a multisite subsite.
1078 * This feature requires specific behavior for the toggle switch.
1079 *
1080 * @param string $feature_setting The feature setting.
1081 *
1082 * @return bool True if we are dealing with the Usage tracking feature on a multisite subsite.
1083 */
1084 protected function is_tracking_on_subsite( $feature_setting ) {
1085 return ( $feature_setting === 'tracking' && ! is_network_admin() && ! is_main_site() );
1086 }
1087
1088 /**
1089 * Returns the disabled attribute HTML.
1090 *
1091 * @param string $variable The variable within the option of the related form element.
1092 * @param array $attr Extra attributes added to the form element.
1093 *
1094 * @return string The disabled attribute HTML.
1095 */
1096 protected function get_disabled_attribute( $variable, $attr ) {
1097 if ( $this->is_control_disabled( $variable ) || ( isset( $attr['disabled'] ) && $attr['disabled'] ) ) {
1098 return ' disabled';
1099 }
1100
1101 return '';
1102 }
1103 }
1104