PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.1
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
wordpress-seo / admin / class-yoast-form.php

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

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