PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
← All changes | includes/_front_end/class-fe-render-form-body.php +280 -118 10.15.7 → 11.9 View file →
@@ -1,12 +1,12 @@
1 1 <?php
2 2 /**
3 3 * Front-End Form Body Rendering
4 4 *
5 5 * - Calendar markup (or hidden selected-dates field)
6 - * - Form body source resolution (BFB / legacy / simple)
6 + * - Form body source resolution (BFB)
7 7 * - Legacy post-processing hooks (additional calendars, captcha, change-over times)
8 - * - Legacy wrapper (<form> container + nonce + hidden fields)
8 + * - Shared wrapper (<form> container + nonce + hidden fields)
9 9 * - Legacy inline scripts (duplicate-calendar warning, cost hints, autofill)
10 10 *
11 11 * @package Booking Calendar
12 12 * @since 11.0.x
@@ -19,9 +19,9 @@
19 19
20 20 /**
21 21 * Booking Form Body Renderer.
22 22 *
23 - * Orchestrates building of the booking form HTML while preserving legacy output and
23 + * Orchestrates building of the booking form HTML while preserving output and
24 24 * filter/action execution order.
25 25 *
26 26 * @since 11.0.x
27 27 */
@@ -34,11 +34,9 @@
34 34 * Expected flow:
35 35 * 1) Parse shortcode "options" into custom parameters (for BFB/simple form context).
36 36 * 2) Build calendar markup OR hidden textarea with preselected dates.
37 37 * 3) Resolve form body source:
38 - * - BFB source (if available) with filter override and shortcode-engine fallback.
39 - * - Legacy booking form (personal.php).
40 - * - Simple form fallback.
38 + * - BFB source with filter override and shortcode-engine fallback.
41 39 * 4) Post-process composed markup:
42 40 * - Insert calendar ([calendar] placeholder or prepend).
43 41 * - Replace additional calendars via legacy filter.
44 42 * - Replace [captcha] placeholder.
@@ -69,8 +67,9 @@
69 67 'form_status' => 'published',
70 68 'selected_dates_without_calendar' => '',
71 69 'cal_count' => 1,
72 70 'shortcode_param__options' => '',
71 + 'booking_hash' => '',
73 72 'legacy_instance' => null,
74 73 );
75 74
76 75 $args = wp_parse_args( $args, $defaults );
@@ -79,8 +78,9 @@
79 78 $custom_booking_form = (string) $args['custom_booking_form'];
80 79 $selected_dates_without_calendar = (string) $args['selected_dates_without_calendar'];
81 80 $cal_count = (int) $args['cal_count'];
82 81 $shortcode_param__options = $args['shortcode_param__options'];
82 + $booking_hash = sanitize_text_field( wp_unslash( (string) $args['booking_hash'] ) );
83 83 $legacy_instance = $args['legacy_instance'];
84 84 $form_status = isset( $args['form_status'] ) ? (string) $args['form_status'] : 'published';
85 85
86 86 $nl = '<div style="clear:both;height:10px;"></div>';
@@ -88,9 +88,9 @@
88 88 $custom_params = WPBC_FE_Options_Parser::parse_for_parameter__in_shortcode_options( $shortcode_param__options );
89 89
90 90 $calendar_html = WPBC_FE_Calendar_Markup::build( $resource_id, $cal_count, $shortcode_param__options, $selected_dates_without_calendar );
91 91
92 - $source_res = WPBC_FE_Form_Source::get_form_body_html( $resource_id, $custom_booking_form, $form_status, $custom_params, $legacy_instance );
92 + $source_res = WPBC_FE_Form_Source::get_form_body_html( $resource_id, $custom_booking_form, $form_status, $custom_params, $legacy_instance, $booking_hash );
93 93
94 94 $form_html = $source_res['body_html'];
95 95
96 96 // Preserve legacy: apply after-load filter ONLY for BFB and Simple form.
@@ -121,9 +121,35 @@
121 121
122 122 // 3. Postprocess Form Conent - regarding settings - e.g.: $source_res['bfb_settings'] = [ options = [ booking_form_theme = "wpbc_theme_dark_1", .... ], ...
123 123 $wrapped = apply_filters( 'wpbc_booking_form__wrapped_html__before_inline_scripts', $wrapped, $source_res['bfb_settings'], $resource_id, $custom_booking_form );
124 124
125 - $wrapped .= WPBC_FE_Inline_Scripts::collect( $resource_id );
125 + /**
126 + * Filters whether the current booking form render should collect legacy
127 + * page-level inline scripts.
128 + *
129 + * Request-local administration previews initialize their returned markup
130 + * through an explicit bootstrap payload. They must not queue callbacks on
131 + * the parent administration page because the preview form can be replaced
132 + * before those callbacks run.
133 + *
134 + * @since 11.9.0
135 + *
136 + * @param bool $should_collect Whether legacy inline scripts should be collected.
137 + * @param int $resource_id Booking resource ID used by the rendered form.
138 + * @param string $custom_booking_form Booking form slug requested by the renderer.
139 + * @param string $form_status Normalized renderer status such as published or preview.
140 + */
141 + $should_collect_inline_scripts = (bool) apply_filters(
142 + 'wpbc_booking_form__should_collect_inline_scripts',
143 + true,
144 + $resource_id,
145 + $custom_booking_form,
146 + $form_status
147 + );
148 +
149 + if ( $should_collect_inline_scripts ) {
150 + $wrapped .= WPBC_FE_Inline_Scripts::collect( $resource_id );
151 + }
126 152
127 153 // Info: Hook for addons. Postprocess Form Conent - regarding settings - e.g.: $source_res['bfb_settings'] = [ options = [ booking_form_theme = "wpbc_theme_dark_1", .... ], ...
128 154 $wrapped = apply_filters( 'wpbc_booking_form__wrapped_html__after_inline_scripts', $wrapped, $source_res['bfb_settings'], $resource_id, $custom_booking_form );
129 155
@@ -337,9 +363,9 @@
337 363 }
338 364
339 365
340 366 /**
341 - * Return the booking form body HTML, choosing BFB source if available, else legacy.
367 + * Return the booking form body HTML from BFB storage.
342 368 *
343 369 * @param int $resource_id
344 370 * @param string $custom_booking_form
345 371 * @param string $form_status 'published'|'preview'
@@ -344,17 +370,19 @@
344 370 * @param string $custom_booking_form
345 371 * @param string $form_status 'published'|'preview'
346 372 * @param array $custom_params
347 373 * @param wpdev_booking $legacy_instance
374 + * @param string $booking_hash
348 375 *
349 376 * @return array
350 377 */
351 - public static function get_form_body_html( $resource_id, $custom_booking_form, $form_status, $custom_params, $legacy_instance ) {
378 + public static function get_form_body_html( $resource_id, $custom_booking_form, $form_status, $custom_params, $legacy_instance, $booking_hash = '' ) {
352 379
353 380 $resource_id = (int) $resource_id;
354 381 $custom_booking_form = (string) $custom_booking_form;
355 382 $form_status = (string) $form_status;
356 383 $custom_params = ( is_array( $custom_params ) ) ? $custom_params : array();
384 + $booking_hash = sanitize_text_field( wp_unslash( (string) $booking_hash ) );
357 385
358 386 $req = array(
359 387 'resource_id' => $resource_id,
360 388 'form_slug' => $custom_booking_form,
@@ -370,9 +398,9 @@
370 398 // =============================================================================================================
371 399 // == If Booking Edit ? ==
372 400 // =============================================================================================================
373 401 // Maybe get the "custom booking form" and child "booking resource ID", if the booking edited. And this booking was created in custom booking form.
374 - $maybe_edited__booking_id__resource_id = self::maybe_check_if_booking_edit();
402 + $maybe_edited__booking_id__resource_id = self::maybe_check_if_booking_edit( $booking_hash );
375 403
376 404 if ( ! empty( $maybe_edited__booking_id__resource_id ) ) {
377 405
378 406 // == Edit Booking =========================================================================================
@@ -443,9 +471,9 @@
443 471 $resource_id = (int) $req['resource_id'];
444 472 $custom_booking_form = (string) $req['form_slug'];
445 473
446 474 /**
447 - * $resolved = array( 'engine' : 'bfb_db'|'legacy'|'simple'
475 + * $resolved = array( 'engine' : 'bfb_db'|'bfb_missing'
448 476 * 'apply_after_load_filter' : bool
449 477 * 'bfb_loader_args' : array()
450 478 * 'fallback_chain' : array()
451 479 */
@@ -462,33 +490,34 @@
462 490 return $booking_form_html__arr;
463 491 }
464 492 }
465 493
466 - // -----------------------------------------------------------------
467 - // Legacy engine (unchanged output).
468 - // -----------------------------------------------------------------
469 - if ( ( ! empty( $legacy_instance ) )
470 - && ( false !== $legacy_instance->wpdev_bk_personal )
471 - // && ( 'On' != get_bk_option( 'booking_is_use_simple_booking_form' ) ) // Important! in paid versions, if edited in "Simple mode" the compiled form, still saved as "Advanced form" content, so we need to get it from there!
472 - ) {
473 - return array(
474 - 'body_html' => $legacy_instance->wpdev_bk_personal->get_booking_form( $resource_id, $custom_booking_form, $custom_params ),
475 - 'apply_after_load_filter' => false,
476 - 'bfb_settings' => array(),
477 - );
478 - }
494 + return array(
495 + 'body_html' => self::get_missing_bfb_form_html( $resolved ),
496 + 'apply_after_load_filter' => false,
497 + 'bfb_settings' => array(),
498 + );
499 + }
500 +
501 + /**
502 + * Render a controlled empty state when no BFB form row exists.
503 + *
504 + * @param array $resolved Resolver result.
505 + *
506 + * @return string
507 + */
508 + private static function get_missing_bfb_form_html( $resolved ) {
509 +
510 + $reason = ( is_array( $resolved ) && ! empty( $resolved['reason'] ) ) ? (string) $resolved['reason'] : 'bfb_form_not_found';
511 +
512 + if ( is_admin() || current_user_can( 'manage_options' ) ) {
513 + return '<div class="wpbc_bfb_missing_form wpbc_alert_warning">' . esc_html__( 'Booking form configuration was not found. Please open the Booking Form Builder and save the form once.', 'booking' ) . ' <span class="wpbc_bfb_missing_reason">' . esc_html( $reason ) . '</span></div>';
514 + }
515 +
516 + return '<div class="wpbc_bfb_missing_form" style="display:none;"></div>';
517 + }
479 518
480 - // -----------------------------------------------------------------
481 - // Simple fallback. - ONLY for the Booking Calendar Free version !
482 - // -----------------------------------------------------------------
483 - return array(
484 - 'body_html' => wpbc_simple_form__get_booking_form__as_html( $resource_id, $custom_booking_form, $custom_params ),
485 - 'apply_after_load_filter' => true,
486 - 'bfb_settings' => array(),
487 - );
488 - }
489 519
490 -
491 520 /**
492 521 * Get BFB booking form content array. Helper function.
493 522 *
494 523 * @param array $req - array.
@@ -567,9 +596,17 @@
567 596 if ( '' === $bfb_form_html ) {
568 597
569 598 $render_args = array_merge( $custom_params, array( 'booking_type' => (int) $resource_id ) );
570 599
571 - $bfb_source = wpbc_lang( $bfb_source );
600 + /*
601 + * Do not pass the complete Builder form through wpbc_lang(). Builder
602 + * labels and option values may contain legacy [lang=LOCALE] markers.
603 + * Parsing one of those inline markers as a whole-form language block
604 + * truncates everything before/after the matching field and can leave an
605 + * incomplete form on the front end. BFB multilingual forms are separate
606 + * custom forms and are selected by their form slug.
607 + */
608 + $bfb_source = (string) $bfb_source;
572 609
573 610 // Replace custom params in the source (legacy behavior).
574 611 if ( ! empty( $custom_params ) ) {
575 612 foreach ( $custom_params as $custom_params_key => $custom_params_value ) {
@@ -701,15 +738,18 @@
701 738 * @return string Wrapped HTML.
702 739 */
703 740 public static function wrap( $form_html, $resource_id ) {
704 741
705 - $resource_id = (int) $resource_id;
706 - $wpbc_nonce = wp_nonce_field( 'CALCULATE_THE_COST', ( 'wpbc_nonceCALCULATE_THE_COST' . $resource_id ), true, false );
742 + $resource_id = (int) $resource_id;
743 + $wpbc_nonce = wp_nonce_field( 'CALCULATE_THE_COST', ( 'wpbc_nonceCALCULATE_THE_COST' . $resource_id ), true, false );
744 +
745 + $booking_form_is_using_bs_css = get_bk_option( 'booking_form_is_using_bs_css' );
746 + $booking_form_format_type = get_bk_option( 'booking_form_format_type' );
747 + $form_style_preset = function_exists( 'wpbc_bfb_settings__get_form_style_preset' )
748 + ? wpbc_bfb_settings__get_form_style_preset( wpbc_bfb_settings__get_current_form_style() )
749 + : array();
750 + $booking_form_theme = isset( $form_style_preset['theme_class'] ) ? sanitize_html_class( $form_style_preset['theme_class'] ) : '';
707 751
708 - $booking_form_is_using_bs_css = get_bk_option( 'booking_form_is_using_bs_css' );
709 - $booking_form_format_type = get_bk_option( 'booking_form_format_type' );
710 - $booking_form_theme = get_bk_option( 'booking_form_theme' );
711 -
712 752 $form_container_random_id = 'form_id' . ( time() * wp_rand( 0, 1000 ) );
713 753 $form_container_css = 'wpbc_container wpbc_form wpbc_container_booking_form ' . ( ( 'On' === wpbc_is_this_demo() ) ? ' wpbc_demo_site ' : '' ) .
714 754 $booking_form_theme . ( ( 'On' === $booking_form_is_using_bs_css ) ? ' wpdevelop ' : '' );
715 755
@@ -763,9 +803,15 @@
763 803 // == Autofill (legacy conditions preserved) ==
764 804 $autofill_js = self::build_autofill_js_body( $resource_id );
765 805 if ( '' !== $autofill_js ) {
766 806 $assets_key = 'wpbc:autofill:' . intval( $resource_id );
767 - $added = WPBC_FE_Assets::add_jq_ready_js_to_wp_script( 'wpbc_all', $autofill_js, $assets_key );
807 + $added = false;
808 + if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) {
809 + $html .= WPBC_FE_Assets::add_inline_js( $autofill_js, $assets_key . ':ajax' );
810 + $added = true;
811 + } else {
812 + $added = WPBC_FE_Assets::add_jq_ready_js_to_wp_script( 'wpbc_all', $autofill_js, $assets_key );
813 + }
768 814 if ( ! $added ) {
769 815 $html .= WPBC_FE_Assets::add_inline_js( $autofill_js, $assets_key ); // Fallback to direct inline JS, if previosly not edded script.
770 816 }
771 817 }
@@ -832,72 +878,82 @@
832 878 * @param array $css_vars Map: '--var-name' => 'value'
833 879 *
834 880 * @return string
835 881 */
836 - public static function inject_css_vars_into_bfb_root( $html, $css_vars ) {
882 + public static function inject_css_vars_into_bfb_root( $html, $css_vars ) {
883 +
884 + return self::inject_css_vars_into_first_tag_with_classes( $html, $css_vars, array( 'wpbc_bfb_form' ) );
885 + }
886 +
887 + /**
888 + * Inject CSS variables into the FIRST outer booking form wrapper.
889 + *
890 + * @param string $html
891 + * @param array $css_vars
892 + *
893 + * @return string
894 + */
895 + public static function inject_css_vars_into_form_wrapper( $html, $css_vars ) {
896 +
897 + return self::inject_css_vars_into_first_tag_with_classes( $html, $css_vars, array( 'wpbc_container', 'wpbc_form' ) );
898 + }
899 +
900 + /**
901 + * Add a class to the FIRST <div> with all required class tokens.
902 + *
903 + * @param string $html
904 + * @param array $required_classes
905 + * @param string $class_name
906 + *
907 + * @return string
908 + */
909 + public static function add_class_to_first_tag_with_classes( $html, $required_classes, $class_name ) {
910 +
911 + $html = (string) $html;
912 + $required_classes = is_array( $required_classes ) ? $required_classes : array();
913 + $class_name = sanitize_html_class( (string) $class_name );
914 +
915 + if ( '' === $class_name || empty( $required_classes ) ) {
916 + return $html;
917 + }
918 +
919 + $pattern = self::get_first_div_with_classes_pattern( $required_classes );
920 + if ( '' === $pattern ) {
921 + return $html;
922 + }
923 +
924 + $done = false;
925 + $result = preg_replace_callback(
926 + $pattern,
927 + function( $m ) use ( $required_classes, $class_name, &$done ) {
928 +
929 + $tag = $m[0];
930 +
931 + if ( ! empty( $done ) || ! self::tag_has_required_classes( $tag, $required_classes ) ) {
932 + return $tag;
933 + }
934 +
935 + if ( ! preg_match( '/\bclass\s*=\s*(["\'])(.*?)\1/i', $tag, $cm ) ) {
936 + return $tag;
937 + }
938 +
939 + $quote = $cm[1];
940 + $classes = trim( preg_replace( '/\s+/', ' ', (string) $cm[2] ) );
941 +
942 + if ( false === strpos( ' ' . $classes . ' ', ' ' . $class_name . ' ' ) ) {
943 + $classes = trim( $classes . ' ' . $class_name );
944 + }
945 +
946 + $done = true;
947 +
948 + return preg_replace( '/\bclass\s*=\s*(["\'])(.*?)\1/i', 'class=' . $quote . esc_attr( $classes ) . $quote, $tag, 1 );
949 + },
950 + $html
951 + );
952 +
953 + return ( null === $result ) ? $html : $result;
954 + }
837 955
838 - $html = (string) $html;
839 - $css_vars = is_array( $css_vars ) ? $css_vars : array();
840 -
841 - if ( empty( $css_vars ) ) {
842 - return $html;
843 - }
844 - if ( false === strpos( $html, 'wpbc_bfb_form' ) ) {
845 - return $html;
846 - }
847 -
848 - $style_append = self::build_css_vars_style_fragment( $css_vars );
849 - if ( '' === $style_append ) {
850 - return $html;
851 - }
852 -
853 - // Match FIRST <div ... class="... wpbc_bfb_form ..."> tag.
854 - $pattern = '/<div\b[^>]*\bclass\s*=\s*(["\'])(?:(?!\1).)*\bwpbc_bfb_form\b(?:(?!\1).)*\1[^>]*>/i';
855 -
856 - $result = preg_replace_callback(
857 - $pattern,
858 - function( $m ) use ( $style_append ) {
859 -
860 - $tag = $m[0];
861 -
862 - // If style already exists: append.
863 - if ( preg_match( '/\bstyle\s*=\s*(["\'])(.*?)\1/i', $tag, $sm ) ) {
864 -
865 - $quote = $sm[1];
866 - $existing = (string) $sm[2];
867 -
868 - // Avoid double-escaping if the tag already contains entities.
869 - $existing = html_entity_decode( $existing, ENT_QUOTES, 'UTF-8' );
870 -
871 - $merged = trim( $existing );
872 - if ( ( '' !== $merged ) && ( ';' !== substr( $merged, -1 ) ) ) {
873 - $merged .= ';';
874 - }
875 - $merged .= $style_append;
876 -
877 - $tag = preg_replace(
878 - '/\bstyle\s*=\s*(["\'])(.*?)\1/i',
879 - 'style=' . $quote . esc_attr( $merged ) . $quote,
880 - $tag,
881 - 1
882 - );
883 -
884 - return $tag;
885 - }
886 -
887 - // No style attr: add it.
888 - $tag = rtrim( $tag, '>' ) . ' style="' . esc_attr( $style_append ) . '">';
889 -
890 - return $tag;
891 - },
892 - $html,
893 - 1
894 - );
895 -
896 - // preg_replace_callback can return null on regex error.
897 - return ( null === $result ) ? $html : $result;
898 - }
899 -
900 956 /**
901 957 * Build CSS vars fragment: "--a:1;--b:2;"
902 958 *
903 959 * @param array $css_vars
@@ -903,9 +959,9 @@
903 959 * @param array $css_vars
904 960 *
905 961 * @return string
906 962 */
907 - private static function build_css_vars_style_fragment( $css_vars ) {
963 + private static function build_css_vars_style_fragment( $css_vars ) {
908 964
909 965 $out = '';
910 966
911 967 foreach ( $css_vars as $name => $value ) {
@@ -935,17 +991,17 @@
935 991 if ( ! preg_match( '/^--[a-z0-9\-_]+$/i', $name ) ) {
936 992 return '';
937 993 }
938 994
939 - // Optional: enforce prefix to avoid abusing other vars (case-insensitive).
940 - $lower = strtolower( $name );
941 - if ( 0 !== strpos( $lower, '--wpbc-' ) && 0 !== strpos( $lower, '--wpbc_bfb-' ) ) {
942 - return '';
943 - }
995 + // Optional: enforce prefix to avoid abusing other vars (case-insensitive).
996 + $lower = strtolower( $name );
997 + if ( 0 !== strpos( $lower, '--wpbc-' ) && 0 !== strpos( $lower, '--wpbc_bfb-' ) && 0 !== strpos( $lower, '--wpbc_form-' ) ) {
998 + return '';
999 + }
1000 +
1001 + return $name;
1002 + }
944 1003
945 - return $name;
946 - }
947 -
948 1004 private static function sanitize_css_var_value( $value ) {
949 1005
950 1006 $value = is_scalar( $value ) ? trim( (string) $value ) : '';
951 1007 if ( '' === $value ) {
@@ -964,7 +1020,113 @@
964 1020 '',
965 1021 $value
966 1022 );
967 1023
968 - return trim( $value );
969 - }
970 -}
1024 + return trim( $value );
1025 + }
1026 +
1027 + private static function inject_css_vars_into_first_tag_with_classes( $html, $css_vars, $required_classes ) {
1028 +
1029 + $html = (string) $html;
1030 + $css_vars = is_array( $css_vars ) ? $css_vars : array();
1031 +
1032 + if ( empty( $css_vars ) ) {
1033 + return $html;
1034 + }
1035 +
1036 + $style_append = self::build_css_vars_style_fragment( $css_vars );
1037 + if ( '' === $style_append ) {
1038 + return $html;
1039 + }
1040 +
1041 + $pattern = self::get_first_div_with_classes_pattern( $required_classes );
1042 + if ( '' === $pattern ) {
1043 + return $html;
1044 + }
1045 +
1046 + $done = false;
1047 + $result = preg_replace_callback(
1048 + $pattern,
1049 + function( $m ) use ( $required_classes, $style_append, &$done ) {
1050 +
1051 + $tag = $m[0];
1052 +
1053 + if ( ! empty( $done ) || ! self::tag_has_required_classes( $tag, $required_classes ) ) {
1054 + return $tag;
1055 + }
1056 +
1057 + // If style already exists: append.
1058 + if ( preg_match( '/\bstyle\s*=\s*(["\'])(.*?)\1/i', $tag, $sm ) ) {
1059 +
1060 + $quote = $sm[1];
1061 + $existing = (string) $sm[2];
1062 +
1063 + // Avoid double-escaping if the tag already contains entities.
1064 + $existing = html_entity_decode( $existing, ENT_QUOTES, 'UTF-8' );
1065 +
1066 + $merged = trim( $existing );
1067 + if ( ( '' !== $merged ) && ( ';' !== substr( $merged, -1 ) ) ) {
1068 + $merged .= ';';
1069 + }
1070 + $merged .= $style_append;
1071 +
1072 + $done = true;
1073 +
1074 + return preg_replace(
1075 + '/\bstyle\s*=\s*(["\'])(.*?)\1/i',
1076 + 'style=' . $quote . esc_attr( $merged ) . $quote,
1077 + $tag,
1078 + 1
1079 + );
1080 + }
1081 +
1082 + // No style attr: add it.
1083 + $done = true;
1084 + return rtrim( $tag, '>' ) . ' style="' . esc_attr( $style_append ) . '">';
1085 + },
1086 + $html
1087 + );
1088 +
1089 + return ( null === $result ) ? $html : $result;
1090 + }
1091 +
1092 + private static function get_first_div_with_classes_pattern( $required_classes ) {
1093 +
1094 + $required_classes = is_array( $required_classes ) ? $required_classes : array();
1095 + $has_class = false;
1096 +
1097 + foreach ( $required_classes as $class_name ) {
1098 + $class_name = sanitize_html_class( (string) $class_name );
1099 + if ( '' === $class_name ) {
1100 + continue;
1101 + }
1102 + $has_class = true;
1103 + }
1104 +
1105 + if ( ! $has_class ) {
1106 + return '';
1107 + }
1108 +
1109 + return '/<div\b[^>]*\bclass\s*=\s*(["\'])(.*?)\1[^>]*>/i';
1110 + }
1111 +
1112 + private static function tag_has_required_classes( $tag, $required_classes ) {
1113 +
1114 + if ( ! preg_match( '/\bclass\s*=\s*(["\'])(.*?)\1/i', (string) $tag, $cm ) ) {
1115 + return false;
1116 + }
1117 +
1118 + $classes = ' ' . preg_replace( '/\s+/', ' ', (string) $cm[2] ) . ' ';
1119 +
1120 + foreach ( (array) $required_classes as $class_name ) {
1121 + $class_name = sanitize_html_class( (string) $class_name );
1122 + if ( '' === $class_name ) {
1123 + continue;
1124 + }
1125 + if ( false === strpos( $classes, ' ' . $class_name . ' ' ) ) {
1126 + return false;
1127 + }
1128 + }
1129 +
1130 + return true;
1131 + }
1132 +}