| @@ -17,9 +17,10 @@ | ||
| 17 | 17 | |
| 18 | 18 | /** |
| 19 | 19 | * Settings API wrapper class |
| 20 | 20 | * |
| 21 | - * @version 2.9.0 | |
| 21 | + * @version 3.0.0 | |
| 22 | + * @since 4.0.0 | |
| 22 | 23 | */ |
| 23 | 24 | class Settings_API { |
| 24 | 25 | |
| 25 | 26 | /** |
| @@ -26,9 +27,9 @@ | ||
| 26 | 27 | * Current version number |
| 27 | 28 | * |
| 28 | 29 | * @var string |
| 29 | 30 | */ |
| 30 | - public const VERSION = '2.9.0'; | |
| 31 | + public const VERSION = '3.0.0'; | |
| 31 | 32 | |
| 32 | 33 | /** |
| 33 | 34 | * Settings Key. |
| 34 | 35 | * |
| @@ -73,8 +74,16 @@ | ||
| 73 | 74 | */ |
| 74 | 75 | protected $default_tab; |
| 75 | 76 | |
| 76 | 77 | /** |
| 78 | + * Version used for cache-busting enqueued assets. Pass the plugin's own | |
| 79 | + * version via props so every plugin release refreshes browser caches. | |
| 80 | + * | |
| 81 | + * @var string | |
| 82 | + */ | |
| 83 | + protected $version = self::VERSION; | |
| 84 | + | |
| 85 | + /** | |
| 77 | 86 | * Settings page. |
| 78 | 87 | * |
| 79 | 88 | * @var string Settings page. |
| 80 | 89 | */ |
| @@ -124,10 +133,8 @@ | ||
| 124 | 133 | |
| 125 | 134 | /** |
| 126 | 135 | * Settings form. |
| 127 | 136 | * |
| 128 | - * @since 2.0.0 | |
| 129 | - * | |
| 130 | 137 | * @var object Settings form. |
| 131 | 138 | */ |
| 132 | 139 | public $settings_form; |
| 133 | 140 | |
| @@ -217,8 +224,9 @@ | ||
| 217 | 224 | 'default_tab' => 'general', |
| 218 | 225 | 'admin_footer_text' => '', |
| 219 | 226 | 'help_sidebar' => '', |
| 220 | 227 | 'help_tabs' => array(), |
| 228 | + 'version' => self::VERSION, | |
| 221 | 229 | ); |
| 222 | 230 | |
| 223 | 231 | $args = wp_parse_args( $args, $defaults ); |
| 224 | 232 | |
| @@ -240,9 +248,8 @@ | ||
| 240 | 248 | * @type string $success_message Success message. |
| 241 | 249 | * @type string $save_changes Save changes button label. |
| 242 | 250 | * @type string $reset_settings Reset settings button label. |
| 243 | 251 | * @type string $reset_button_confirm Reset button confirmation message. |
| 244 | - * @type string $checkbox_modified Checkbox modified label. | |
| 245 | 252 | * } |
| 246 | 253 | * |
| 247 | 254 | * @return void |
| 248 | 255 | */ |
| @@ -255,14 +262,19 @@ | ||
| 255 | 262 | 'success_message' => 'Settings updated.', |
| 256 | 263 | 'save_changes' => 'Save Changes', |
| 257 | 264 | 'reset_settings' => 'Reset all settings', |
| 258 | 265 | 'reset_button_confirm' => 'Do you really want to reset all these settings to their default values?', |
| 259 | - 'checkbox_modified' => 'Modified from default setting', | |
| 260 | 266 | 'button_label' => 'Choose File', |
| 261 | 267 | 'previous_saved' => 'Previously saved', |
| 262 | 268 | 'repeater_new_item' => 'New Item', |
| 263 | 269 | 'required_label' => 'Required', |
| 264 | 270 | 'tom_select_no_results' => 'No results found for "%s"', |
| 271 | + 'search_placeholder' => 'Search settings', | |
| 272 | + 'search_no_results' => 'No settings found. Try a different search term.', | |
| 273 | + 'search_clear' => 'Clear search', | |
| 274 | + 'search_results_single' => '%d setting found.', | |
| 275 | + 'search_results_plural' => '%d settings found.', | |
| 276 | + 'search_matches_label' => 'matching settings', | |
| 265 | 277 | ); |
| 266 | 278 | |
| 267 | 279 | $strings = wp_parse_args( $strings, $defaults ); |
| 268 | 280 | |
| @@ -483,9 +495,9 @@ | ||
| 483 | 495 | wp_register_script( |
| 484 | 496 | 'wz-' . $this->prefix . '-admin', |
| 485 | 497 | plugins_url( 'js/settings-admin-scripts' . $minimize . '.js', __FILE__ ), |
| 486 | 498 | array( 'jquery', 'wp-color-picker', 'jquery-ui-tabs' ), |
| 487 | - self::VERSION, | |
| 499 | + $this->version, | |
| 488 | 500 | true |
| 489 | 501 | ); |
| 490 | 502 | wp_register_script( |
| 491 | 503 | 'wz-' . $this->prefix . '-codemirror', |
| @@ -490,9 +502,9 @@ | ||
| 490 | 502 | wp_register_script( |
| 491 | 503 | 'wz-' . $this->prefix . '-codemirror', |
| 492 | 504 | plugins_url( 'js/apply-cm' . $minimize . '.js', __FILE__ ), |
| 493 | 505 | array( 'jquery', 'underscore', 'code-editor' ), |
| 494 | - self::VERSION, | |
| 506 | + $this->version, | |
| 495 | 507 | true |
| 496 | 508 | ); |
| 497 | 509 | wp_register_script( |
| 498 | 510 | 'wz-' . $this->prefix . '-media-selector', |
| @@ -497,9 +509,9 @@ | ||
| 497 | 509 | wp_register_script( |
| 498 | 510 | 'wz-' . $this->prefix . '-media-selector', |
| 499 | 511 | plugins_url( 'js/media-selector' . $minimize . '.js', __FILE__ ), |
| 500 | 512 | array( 'jquery', 'media-editor', 'media-views' ), |
| 501 | - self::VERSION, | |
| 513 | + $this->version, | |
| 502 | 514 | true |
| 503 | 515 | ); |
| 504 | 516 | wp_register_style( |
| 505 | 517 | 'wz-' . $this->prefix . '-admin', |
| @@ -504,9 +516,9 @@ | ||
| 504 | 516 | wp_register_style( |
| 505 | 517 | 'wz-' . $this->prefix . '-admin', |
| 506 | 518 | plugins_url( 'css/admin-style' . $minimize . '.css', __FILE__ ), |
| 507 | 519 | array( 'wp-color-picker' ), |
| 508 | - self::VERSION | |
| 520 | + $this->version | |
| 509 | 521 | ); |
| 510 | 522 | |
| 511 | 523 | // Tom Select scripts and styles. |
| 512 | 524 | wp_register_style( |
| @@ -512,15 +524,15 @@ | ||
| 512 | 524 | wp_register_style( |
| 513 | 525 | 'wz-' . $this->prefix . '-tom-select', |
| 514 | 526 | plugins_url( 'css/tom-select.min.css', __FILE__ ), |
| 515 | 527 | array(), |
| 516 | - self::VERSION | |
| 528 | + $this->version | |
| 517 | 529 | ); |
| 518 | 530 | wp_register_script( |
| 519 | 531 | 'wz-' . $this->prefix . '-tom-select', |
| 520 | 532 | plugins_url( 'js/tom-select.complete.min.js', __FILE__ ), |
| 521 | 533 | array( 'jquery' ), |
| 522 | - self::VERSION, | |
| 534 | + $this->version, | |
| 523 | 535 | true |
| 524 | 536 | ); |
| 525 | 537 | wp_register_script( |
| 526 | 538 | 'wz-' . $this->prefix . '-tom-select-init', |
| @@ -525,17 +537,23 @@ | ||
| 525 | 537 | wp_register_script( |
| 526 | 538 | 'wz-' . $this->prefix . '-tom-select-init', |
| 527 | 539 | plugin_dir_url( __FILE__ ) . 'js/tom-select-init' . $minimize . '.js', |
| 528 | 540 | array( 'jquery', 'wz-' . $this->prefix . '-tom-select' ), |
| 529 | - self::VERSION, | |
| 541 | + $this->version, | |
| 530 | 542 | true |
| 531 | 543 | ); |
| 532 | 544 | wp_localize_script( |
| 533 | 545 | "wz-{$this->prefix}-admin", |
| 534 | - 'WZSettingsAdmin', | |
| 546 | + 'WebberSettingsAdmin', | |
| 535 | 547 | array( |
| 536 | 548 | 'prefix' => $this->prefix, |
| 537 | 549 | 'settings_key' => $this->settings_key, |
| 550 | + 'strings' => array( | |
| 551 | + 'search_no_results' => esc_html( $this->translation_strings['search_no_results'] ?? 'No settings found. Try a different search term.' ), | |
| 552 | + 'search_results_single' => esc_html( $this->translation_strings['search_results_single'] ?? '%d setting found.' ), | |
| 553 | + 'search_results_plural' => esc_html( $this->translation_strings['search_results_plural'] ?? '%d settings found.' ), | |
| 554 | + 'search_matches_label' => esc_html( $this->translation_strings['search_matches_label'] ?? 'matching settings' ), | |
| 555 | + ), | |
| 538 | 556 | ) |
| 539 | 557 | ); |
| 540 | 558 | |
| 541 | 559 | if ( $hook === $this->settings_page ) { |
| @@ -587,9 +605,9 @@ | ||
| 587 | 605 | |
| 588 | 606 | // Localize Tom Select settings. |
| 589 | 607 | wp_localize_script( |
| 590 | 608 | "wz-{$prefix}-tom-select-init", |
| 591 | - 'WZTomSelectSettings', | |
| 609 | + 'WebberTomSelectSettings', | |
| 592 | 610 | $args |
| 593 | 611 | ); |
| 594 | 612 | wp_enqueue_script( "wz-{$prefix}-tom-select-init" ); |
| 595 | 613 | |
| @@ -637,8 +655,13 @@ | ||
| 637 | 655 | $name = $args['name']; |
| 638 | 656 | $type = isset( $args['type'] ) ? $args['type'] : 'text'; |
| 639 | 657 | $callback = method_exists( $this->settings_form, "callback_{$type}" ) ? array( $this->settings_form, "callback_{$type}" ) : array( $this->settings_form, 'callback_missing' ); |
| 640 | 658 | |
| 659 | + // Tag header rows so the settings search can group fields under them. | |
| 660 | + if ( 'header' === $type ) { | |
| 661 | + $args['class'] = trim( ( $args['class'] ?? '' ) . ' wz-settings-header-row', " \t\n\r\0\x0B" ); | |
| 662 | + } | |
| 663 | + | |
| 641 | 664 | add_settings_field( |
| 642 | 665 | "{$settings_key}[{$id}]", // ID of the settings field. We save it within the settings array. |
| 643 | 666 | $name, // Label of the setting. |
| 644 | 667 | $callback, // Function to handle the setting. |
| @@ -653,10 +676,13 @@ | ||
| 653 | 676 | register_setting( |
| 654 | 677 | $settings_key, |
| 655 | 678 | $settings_key, |
| 656 | 679 | array( |
| 680 | + 'type' => 'object', | |
| 681 | + 'default' => $this->settings_defaults(), | |
| 657 | 682 | 'sanitize_callback' => array( $this, 'settings_sanitize' ), |
| 658 | - 'show_in_rest' => true, | |
| 683 | + // The value is an open-ended map with no REST schema, and settings_sanitize() expects a form submission. | |
| 684 | + 'show_in_rest' => false, | |
| 659 | 685 | ) |
| 660 | 686 | ); |
| 661 | 687 | } |
| 662 | 688 | |
| @@ -791,28 +817,23 @@ | ||
| 791 | 817 | if ( $setting['id'] !== $key ) { |
| 792 | 818 | continue; |
| 793 | 819 | } |
| 794 | 820 | |
| 795 | - // Return the callback name. | |
| 796 | - $sanitize_callback = false; | |
| 797 | - | |
| 798 | 821 | if ( isset( $setting['sanitize_callback'] ) && is_callable( $setting['sanitize_callback'] ) ) { |
| 799 | - $sanitize_callback = $setting['sanitize_callback']; | |
| 800 | - return $sanitize_callback; | |
| 822 | + return $setting['sanitize_callback']; | |
| 801 | 823 | } |
| 802 | 824 | |
| 803 | - if ( is_callable( array( $settings_sanitize, 'sanitize_' . $setting['type'] . '_field' ) ) ) { | |
| 804 | - // For repeater fields, create a closure to pass the field configuration. | |
| 805 | - if ( 'repeater' === $setting['type'] ) { | |
| 806 | - return function ( $value ) use ( $settings_sanitize, $setting ) { | |
| 807 | - return $settings_sanitize->sanitize_repeater_field( $value, $setting ); | |
| 808 | - }; | |
| 809 | - } | |
| 810 | - $sanitize_callback = array( $settings_sanitize, 'sanitize_' . $setting['type'] . '_field' ); | |
| 811 | - return $sanitize_callback; | |
| 825 | + $method = 'sanitize_' . $setting['type'] . '_field'; | |
| 826 | + | |
| 827 | + // Field types with no callback of their own must still not store raw input. | |
| 828 | + if ( ! is_callable( array( $settings_sanitize, $method ) ) ) { | |
| 829 | + $method = 'sanitize_missing'; | |
| 812 | 830 | } |
| 813 | 831 | |
| 814 | - return $sanitize_callback; | |
| 832 | + // Every callback receives the field configuration so choice fields can validate against their own options. | |
| 833 | + return function ( $value ) use ( $settings_sanitize, $method, $setting ) { | |
| 834 | + return $settings_sanitize->$method( $value, $setting ); | |
| 835 | + }; | |
| 815 | 836 | } |
| 816 | 837 | } |
| 817 | 838 | |
| 818 | 839 | return false; |
| @@ -818,21 +839,40 @@ | ||
| 818 | 839 | return false; |
| 819 | 840 | } |
| 820 | 841 | |
| 821 | 842 | /** |
| 843 | + * Get the settings keys that are rendered locked (disabled or pro-gated). | |
| 844 | + * | |
| 845 | + * @return array Map of settings key => true for each locked setting. | |
| 846 | + */ | |
| 847 | + public function get_locked_settings() { | |
| 848 | + $locked = array(); | |
| 849 | + | |
| 850 | + foreach ( $this->registered_settings as $settings ) { | |
| 851 | + foreach ( $settings as $setting ) { | |
| 852 | + if ( isset( $setting['id'] ) && ( ! empty( $setting['disabled'] ) || ! empty( $setting['pro'] ) ) ) { | |
| 853 | + $locked[ $setting['id'] ] = true; | |
| 854 | + } | |
| 855 | + } | |
| 856 | + } | |
| 857 | + | |
| 858 | + return $locked; | |
| 859 | + } | |
| 860 | + | |
| 861 | + /** | |
| 822 | 862 | * Sanitize the form data being submitted. |
| 823 | 863 | * |
| 824 | - * @param array $input Input unclean array. | |
| 864 | + * @param mixed $input Unsanitized input. An array for form submissions, but REST and WP-CLI may pass anything. | |
| 825 | 865 | * @return array Sanitized array |
| 826 | 866 | */ |
| 827 | 867 | public function settings_sanitize( $input ) { |
| 828 | - // This should be set if a form is submitted, so let's save it in the $referrer variable. | |
| 829 | - if ( empty( $_POST['_wp_http_referer'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 830 | - return $input; | |
| 868 | + // Set when a classic form is submitted; used only to pick the active tab below. | |
| 869 | + $referrer = array(); | |
| 870 | + | |
| 871 | + if ( ! empty( $_POST['_wp_http_referer'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 872 | + parse_str( sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ), $referrer ); // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 831 | 873 | } |
| 832 | 874 | |
| 833 | - parse_str( sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ), $referrer ); // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 834 | - | |
| 835 | 875 | // Check if we need to set to defaults. |
| 836 | 876 | $reset = isset( $_POST['settings_reset'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing |
| 837 | 877 | |
| 838 | 878 | if ( $reset ) { |
| @@ -847,13 +887,14 @@ | ||
| 847 | 887 | // Get the various settings we've registered. |
| 848 | 888 | $settings = get_option( $this->settings_key ); |
| 849 | 889 | $settings = is_array( $settings ) ? $settings : array(); |
| 850 | 890 | $settings_types = $this->get_registered_settings_types(); |
| 891 | + $locked = $this->get_locked_settings(); | |
| 851 | 892 | |
| 852 | 893 | // Get the tab. This is also our settings' section. |
| 853 | 894 | $tab = $referrer['tab'] ?? $this->default_tab; |
| 854 | 895 | |
| 855 | - $input = $input ? $input : array(); | |
| 896 | + $input = is_array( $input ) ? $input : array(); | |
| 856 | 897 | |
| 857 | 898 | /** |
| 858 | 899 | * Filter the settings for the tab. e.g. prefix_settings_general_sanitize. |
| 859 | 900 | * |
| @@ -860,10 +901,10 @@ | ||
| 860 | 901 | * @param array $input Input unclean array |
| 861 | 902 | */ |
| 862 | 903 | $input = apply_filters( $this->prefix . '_settings_' . $tab . '_sanitize', $input ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound |
| 863 | 904 | |
| 864 | - // Create an output array by merging the existing settings with the ones submitted. | |
| 865 | - $output = array_merge( $settings, $input ); | |
| 905 | + // Start from what is stored. Submitted values are merged back in below, once sanitized. | |
| 906 | + $output = $settings; | |
| 866 | 907 | |
| 867 | 908 | // Loop through each setting being saved and pass it through a sanitization filter. |
| 868 | 909 | foreach ( $settings_types as $key => $type ) { |
| 869 | 910 | /** |
| @@ -881,13 +922,9 @@ | ||
| 881 | 922 | $sanitize_callback = $this->get_sanitize_callback( $key ); |
| 882 | 923 | |
| 883 | 924 | // If callback is set, call it. |
| 884 | 925 | if ( $sanitize_callback ) { |
| 885 | - if ( 'sensitive' === $type ) { | |
| 886 | - $output[ $key ] = call_user_func( $sanitize_callback, $input[ $key ], $key ); | |
| 887 | - } else { | |
| 888 | - $output[ $key ] = call_user_func( $sanitize_callback, $input[ $key ] ); | |
| 889 | - } | |
| 926 | + $output[ $key ] = call_user_func( $sanitize_callback, $input[ $key ] ); | |
| 890 | 927 | continue; |
| 891 | 928 | } |
| 892 | 929 | } |
| 893 | 930 | |
| @@ -892,15 +929,25 @@ | ||
| 892 | 929 | } |
| 893 | 930 | |
| 894 | 931 | // Delete any key that is not present when we submit the input array. |
| 895 | 932 | if ( ! isset( $input[ $key ] ) ) { |
| 896 | - unset( $output[ $key ] ); | |
| 933 | + // Disabled fields are never submitted, so a missing key must not delete them. | |
| 934 | + if ( ! isset( $locked[ $key ] ) ) { | |
| 935 | + unset( $output[ $key ] ); | |
| 936 | + } | |
| 897 | 937 | } |
| 938 | + } | |
| 898 | 939 | |
| 899 | - // Delete any settings that are no longer part of our registered settings. | |
| 900 | - if ( array_key_exists( $key, $output ) && ! array_key_exists( $key, $settings_types ) ) { | |
| 901 | - unset( $output[ $key ] ); | |
| 902 | - } | |
| 940 | + // Keys added by the tab filter are not registered settings, but must not be stored raw either. | |
| 941 | + $settings_sanitize = new Settings_Sanitize( | |
| 942 | + array( | |
| 943 | + 'settings_key' => $this->settings_key, | |
| 944 | + 'prefix' => $this->prefix, | |
| 945 | + ) | |
| 946 | + ); | |
| 947 | + | |
| 948 | + foreach ( array_diff_key( $input, $settings_types ) as $key => $value ) { | |
| 949 | + $output[ sanitize_text_field( (string) $key ) ] = $settings_sanitize->sanitize_missing( $value ); | |
| 903 | 950 | } |
| 904 | 951 | |
| 905 | 952 | add_settings_error( $this->prefix . '-notices', '', $this->translation_strings['success_message'], 'updated' ); |
| 906 | 953 | |
| @@ -932,10 +979,10 @@ | ||
| 932 | 979 | } |
| 933 | 980 | ?> |
| 934 | 981 | |
| 935 | 982 | <div id="poststuff"> |
| 936 | - <div id="post-body" class="metabox-holder columns-2"> | |
| 937 | - <div id="post-body-content"> | |
| 983 | + <div id="post-body" class="metabox-holder columns-2 wz-settings-post-body"> | |
| 984 | + <div id="post-body-content" class="wz-vertical-tabs"> | |
| 938 | 985 | |
| 939 | 986 | <?php $this->show_navigation(); ?> |
| 940 | 987 | <?php $this->show_form(); ?> |
| 941 | 988 | |
| @@ -969,10 +1016,8 @@ | ||
| 969 | 1016 | */ |
| 970 | 1017 | public function show_navigation() { |
| 971 | 1018 | $active_tab = isset( $_GET['tab'] ) && array_key_exists( sanitize_key( wp_unslash( $_GET['tab'] ) ), $this->settings_sections ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : $this->default_tab; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended |
| 972 | 1019 | |
| 973 | - $html = '<ul class="nav-tab-wrapper" style="padding:0">'; | |
| 974 | - | |
| 975 | 1020 | $count = count( $this->settings_sections ); |
| 976 | 1021 | |
| 977 | 1022 | // Don't show the navigation if only one section exists. |
| 978 | 1023 | if ( 1 === $count ) { |
| @@ -978,14 +1023,32 @@ | ||
| 978 | 1023 | if ( 1 === $count ) { |
| 979 | 1024 | return; |
| 980 | 1025 | } |
| 981 | 1026 | |
| 1027 | + $html = '<ul class="nav-tab-wrapper">'; | |
| 1028 | + | |
| 1029 | + // Settings search box. Rendered via wp_kses() with an extended allowed list below as | |
| 1030 | + // wp_kses_post() strips input and button tags. | |
| 1031 | + $search_box = sprintf( | |
| 1032 | + '<li class="wz-settings-search-wrap">' . | |
| 1033 | + '<label class="screen-reader-text" for="%1$s">%2$s</label>' . | |
| 1034 | + '<span class="wz-settings-search-box">' . | |
| 1035 | + '<input type="search" id="%1$s" class="wz-settings-search" placeholder="%2$s" autocomplete="off" />' . | |
| 1036 | + '<button type="button" class="wz-settings-search-clear" aria-label="%3$s" hidden><span aria-hidden="true">×</span></button>' . | |
| 1037 | + '</span>' . | |
| 1038 | + '<span class="wz-settings-search-status screen-reader-text" role="status" aria-live="polite"></span>' . | |
| 1039 | + '</li>', | |
| 1040 | + esc_attr( "{$this->prefix}-settings-search" ), | |
| 1041 | + esc_attr( $this->translation_strings['search_placeholder'] ?? 'Search settings' ), | |
| 1042 | + esc_attr( $this->translation_strings['search_clear'] ?? 'Clear search' ) | |
| 1043 | + ); | |
| 1044 | + | |
| 982 | 1045 | foreach ( $this->settings_sections as $tab_id => $tab_name ) { |
| 983 | 1046 | |
| 984 | 1047 | $active = $active_tab === $tab_id ? ' ' : ''; |
| 985 | 1048 | |
| 986 | 1049 | $html .= sprintf( |
| 987 | - '<li style="padding:0; border:0; margin:0;"><a href="#%s" title="%s" class="nav-tab %s">%s</a></li>', | |
| 1050 | + '<li><a href="#%s" title="%s" class="nav-tab %s">%s</a></li>', | |
| 988 | 1051 | esc_attr( $tab_id ), |
| 989 | 1052 | esc_attr( $tab_name ), |
| 990 | 1053 | sanitize_html_class( $active ), |
| 991 | 1054 | esc_html( $tab_name ) |
| @@ -994,9 +1057,35 @@ | ||
| 994 | 1057 | } |
| 995 | 1058 | |
| 996 | 1059 | $html .= '</ul>'; |
| 997 | 1060 | |
| 998 | - echo wp_kses_post( $html ); | |
| 1061 | + $allowed_html = wp_kses_allowed_html( 'post' ); | |
| 1062 | + $allowed_html['input'] = array( | |
| 1063 | + 'type' => true, | |
| 1064 | + 'id' => true, | |
| 1065 | + 'class' => true, | |
| 1066 | + 'placeholder' => true, | |
| 1067 | + 'autocomplete' => true, | |
| 1068 | + ); | |
| 1069 | + $allowed_html['button'] = array( | |
| 1070 | + 'type' => true, | |
| 1071 | + 'class' => true, | |
| 1072 | + 'aria-label' => true, | |
| 1073 | + 'hidden' => true, | |
| 1074 | + ); | |
| 1075 | + $allowed_html['span'] = array_merge( | |
| 1076 | + (array) ( $allowed_html['span'] ?? array() ), | |
| 1077 | + array( | |
| 1078 | + 'class' => true, | |
| 1079 | + 'role' => true, | |
| 1080 | + 'aria-live' => true, | |
| 1081 | + 'aria-hidden' => true, | |
| 1082 | + ) | |
| 1083 | + ); | |
| 1084 | + | |
| 1085 | + $html = str_replace( '<ul class="nav-tab-wrapper">', '<ul class="nav-tab-wrapper">' . $search_box, $html ); | |
| 1086 | + | |
| 1087 | + echo wp_kses( $html, $allowed_html ); | |
| 999 | 1088 | } |
| 1000 | 1089 | |
| 1001 | 1090 | /** |
| 1002 | 1091 | * Show the section settings forms |
| @@ -1012,8 +1101,9 @@ | ||
| 1012 | 1101 | |
| 1013 | 1102 | <?php foreach ( $this->settings_sections as $tab_id => $tab_name ) : ?> |
| 1014 | 1103 | |
| 1015 | 1104 | <div id="<?php echo esc_attr( $tab_id ); ?>"> |
| 1105 | + <h2 class="wz-section-title" tabindex="-1"><?php echo esc_html( $tab_name ); ?></h2> | |
| 1016 | 1106 | <table class="form-table"> |
| 1017 | 1107 | <?php |
| 1018 | 1108 | do_settings_fields( $this->prefix . '_settings_' . $tab_id, $this->prefix . '_settings_' . $tab_id ); |
| 1019 | 1109 | ?> |
| @@ -1053,8 +1143,9 @@ | ||
| 1053 | 1143 | */ |
| 1054 | 1144 | do_action( $this->prefix . '_settings_form_buttons', $tab_id, $tab_name, $this->settings_sections ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound |
| 1055 | 1145 | ?> |
| 1056 | 1146 | </p> |
| 1147 | + <p class="wz-modified-legend"><span class="wz-modified-dot"></span><?php echo esc_html( $this->translation_strings['modified_legend'] ?? 'Setting modified from its default value' ); ?></p> | |
| 1057 | 1148 | </div><!-- /#tab_id--> |
| 1058 | 1149 | |
| 1059 | 1150 | <?php endforeach; ?> |
| 1060 | 1151 | |