@@ -135,6 +135,105 @@ protected function get_field_attributes( $args ) {
135135 'field_name ' => $ field_name ,
136136 );
137137 }
138+
139+ /**
140+ * Returns the allowed HTML tags and attributes for settings form output.
141+ *
142+ * Use the `{prefix}_settings_form_allowed_html` filter to add extra tags or
143+ * attributes needed by custom field types or `field_attributes` entries.
144+ *
145+ * @return array<string, array<string, bool>>
146+ */
147+ protected function get_allowed_html (): array {
148+ $ allowed = wp_kses_allowed_html ( 'post ' );
149+
150+ $ form_tags = array (
151+ 'input ' => array (
152+ 'type ' => true ,
153+ 'name ' => true ,
154+ 'id ' => true ,
155+ 'value ' => true ,
156+ 'class ' => true ,
157+ 'style ' => true ,
158+ 'checked ' => true ,
159+ 'disabled ' => true ,
160+ 'readonly ' => true ,
161+ 'required ' => true ,
162+ 'placeholder ' => true ,
163+ 'size ' => true ,
164+ 'min ' => true ,
165+ 'max ' => true ,
166+ 'step ' => true ,
167+ 'multiple ' => true ,
168+ 'autocomplete ' => true ,
169+ // Tom Select data attributes (built-in Settings API feature).
170+ 'data-wp-prefix ' => true ,
171+ 'data-wp-endpoint ' => true ,
172+ 'data-ts-config ' => true ,
173+ ),
174+ 'select ' => array (
175+ 'id ' => true ,
176+ 'name ' => true ,
177+ 'class ' => true ,
178+ 'style ' => true ,
179+ 'disabled ' => true ,
180+ 'multiple ' => true ,
181+ 'size ' => true ,
182+ 'autocomplete ' => true ,
183+ // Tom Select data attributes (built-in Settings API feature).
184+ 'data-wp-prefix ' => true ,
185+ 'data-wp-endpoint ' => true ,
186+ 'data-ts-config ' => true ,
187+ ),
188+ 'option ' => array (
189+ 'value ' => true ,
190+ 'selected ' => true ,
191+ 'disabled ' => true ,
192+ ),
193+ 'optgroup ' => array (
194+ 'label ' => true ,
195+ 'disabled ' => true ,
196+ ),
197+ 'textarea ' => array (
198+ 'id ' => true ,
199+ 'name ' => true ,
200+ 'class ' => true ,
201+ 'style ' => true ,
202+ 'rows ' => true ,
203+ 'cols ' => true ,
204+ 'disabled ' => true ,
205+ 'readonly ' => true ,
206+ 'required ' => true ,
207+ 'placeholder ' => true ,
208+ 'autocomplete ' => true ,
209+ ),
210+ 'label ' => array (
211+ 'for ' => true ,
212+ 'class ' => true ,
213+ 'style ' => true ,
214+ ),
215+ 'button ' => array (
216+ 'type ' => true ,
217+ 'id ' => true ,
218+ 'class ' => true ,
219+ 'style ' => true ,
220+ 'disabled ' => true ,
221+ ),
222+ );
223+
224+ $ allowed = array_replace_recursive ( $ allowed , $ form_tags );
225+
226+ /**
227+ * Filters the allowed HTML tags and attributes for settings form output.
228+ *
229+ * Use this filter to add data attributes or custom elements required by
230+ * field_attributes entries in your registered settings.
231+ *
232+ * @param array<string, array<string, bool>> $allowed Allowed HTML tags/attributes.
233+ */
234+ return apply_filters ( $ this ->prefix . '_settings_form_allowed_html ' , $ allowed ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
235+ }
236+
138237 /**
139238 * Miscellaneous callback funcion
140239 *
@@ -163,7 +262,7 @@ public function callback_header( $args ) {
163262 * @param string $html HTML string.
164263 * @param array $args Arguments array.
165264 */
166- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
265+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
167266 }
168267
169268 /**
@@ -211,7 +310,7 @@ public function callback_text( $args ) {
211310 $ html .= $ this ->get_field_description ( $ args );
212311
213312 /** This filter has been defined in class-settings-api.php */
214- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
313+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
215314 }
216315
217316 /**
@@ -291,7 +390,7 @@ public function callback_textarea( $args ) {
291390 $ html .= $ this ->get_field_description ( $ args );
292391
293392 /** This filter has been defined in class-settings-api.php */
294- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
393+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
295394 }
296395
297396 /**
@@ -345,7 +444,7 @@ public function callback_checkbox( $args ) {
345444 $ html .= $ this ->get_field_description ( $ args );
346445
347446 /** This filter has been defined in class-settings-api.php */
348- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
447+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
349448 }
350449
351450 /**
@@ -399,7 +498,7 @@ public function callback_multicheck( $args ) {
399498 $ html .= $ this ->get_field_description ( $ args );
400499
401500 /** This filter has been defined in class-settings-api.php */
402- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
501+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
403502 }
404503
405504 /**
@@ -439,7 +538,7 @@ public function callback_radio( $args ) {
439538 $ html .= $ this ->get_field_description ( $ args );
440539
441540 /** This filter has been defined in class-settings-api.php */
442- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
541+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
443542 }
444543
445544 /**
@@ -482,7 +581,7 @@ public function callback_radiodesc( $args ) {
482581 $ html .= $ this ->get_field_description ( $ args );
483582
484583 /** This filter has been defined in class-settings-api.php */
485- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
584+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
486585 }
487586
488587 /**
@@ -534,7 +633,7 @@ public function callback_thumbsizes( $args ) {
534633 $ html .= $ this ->get_field_description ( $ args );
535634
536635 /** This filter has been defined in class-settings-api.php */
537- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
636+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
538637 }
539638
540639 /**
@@ -574,7 +673,7 @@ public function callback_number( $args ) {
574673 $ html .= $ this ->get_field_description ( $ args );
575674
576675 /** This filter has been defined in class-settings-api.php */
577- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
676+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
578677 }
579678
580679 /**
@@ -618,7 +717,7 @@ public function callback_select( $args ) {
618717 $ html .= $ this ->get_field_description ( $ args );
619718
620719 /** This filter has been defined in class-settings-api.php */
621- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
720+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
622721 }
623722
624723 /**
@@ -675,7 +774,7 @@ public function callback_posttypes( $args ) {
675774 $ html .= $ this ->get_field_description ( $ args );
676775
677776 /** This filter has been defined in class-settings-api.php */
678- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
777+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
679778 }
680779
681780
@@ -729,7 +828,7 @@ public function callback_taxonomies( $args ) {
729828 $ html .= $ this ->get_field_description ( $ args );
730829
731830 /** This filter has been defined in class-settings-api.php */
732- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
831+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
733832 }
734833
735834
@@ -792,7 +891,7 @@ public function callback_file( $args ) {
792891 $ html .= $ this ->get_field_description ( $ args );
793892
794893 /** This filter has been defined in class-settings-api.php */
795- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
894+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
796895 }
797896
798897 /**
@@ -819,7 +918,7 @@ public function callback_password( $args ) {
819918 $ html .= $ this ->get_field_description ( $ args );
820919
821920 /** This filter has been defined in class-settings-api.php */
822- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
921+ echo wp_kses ( apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ), $ this -> get_allowed_html () ) ; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
823922 }
824923
825924 /**
@@ -867,7 +966,7 @@ public function callback_repeater( $args ) {
867966 $ html .= $ this ->get_field_description ( $ args );
868967
869968 /** This filter has been defined in class-settings-api.php */
870- echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped
969+ echo apply_filters ( $ this ->prefix . '_after_setting_output ' , $ html , $ args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound,WordPress.Security.EscapeOutput.OutputNotEscaped -- Contains <script type="text/template"> which wp_kses would strip.
871970 }
872971
873972 /**
0 commit comments