PluginProbe
Booking Calendar / 11.8.3
Booking Calendar v11.8.3
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 10.11 10.11.2 All 203 releases
← All changes | includes/_front_end/class-fe-shortcodes.php +216 -0 11.011.8.3 View file →
@@ -37,8 +37,9 @@
37 37 * - wpbc_render_booking_form_shortcodes()
38 38 * - WPBC_BFB_FormShortcodeEngine->render().
39 39 */
40 40 add_shortcode( 'booking', array( __CLASS__, 'booking_shortcode' ) );
41 + add_shortcode( 'booking_popup', array( __CLASS__, 'booking_popup_shortcode' ) );
41 42 add_shortcode( 'bookingcalendar', array( __CLASS__, 'booking_calendar_only_shortcode' ) );
42 43 add_shortcode( 'bookingform', array( __CLASS__, 'bookingform_shortcode' ) );
43 44 }
44 45
@@ -57,8 +58,200 @@
57 58
58 59 return is_array( $attr ) ? $attr : array();
59 60 }
60 61
62 + /**
63 + * Check shortcode parameter for a boolean-like true value.
64 + *
65 + * @param array $attr Shortcode attributes.
66 + * @param string $key Attribute key.
67 + *
68 + * @return bool
69 + */
70 + private static function is_truthy_attr( $attr, $key ) {
71 +
72 + if ( ! isset( $attr[ $key ] ) ) {
73 + return false;
74 + }
75 +
76 + $value = strtolower( trim( (string) $attr[ $key ] ) );
77 +
78 + return in_array( $value, array( '1', 'true', 'yes', 'on' ), true );
79 + }
80 +
81 + /**
82 + * Check if the current render is happening inside Elementor editor/preview.
83 + *
84 + * Elementor can render widgets through admin AJAX, where is_admin() is true
85 + * even though the output is displayed in the front-end canvas iframe.
86 + *
87 + * @return bool
88 + */
89 + private static function is_elementor_render_context() {
90 +
91 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
92 + $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';
93 + if ( in_array( $action, array( 'elementor', 'elementor_ajax' ), true ) ) {
94 + return true;
95 + }
96 +
97 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
98 + if ( isset( $_GET['elementor-preview'] ) ) {
99 + return true;
100 + }
101 +
102 + if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance ) ) {
103 + if ( isset( \Elementor\Plugin::$instance->editor ) && method_exists( \Elementor\Plugin::$instance->editor, 'is_edit_mode' ) && \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
104 + return true;
105 + }
106 + if ( isset( \Elementor\Plugin::$instance->preview ) && method_exists( \Elementor\Plugin::$instance->preview, 'is_preview_mode' ) && \Elementor\Plugin::$instance->preview->is_preview_mode() ) {
107 + return true;
108 + }
109 + }
110 +
111 + return false;
112 + }
113 +
114 + /**
115 + * Check if popup wrapping is safe for the current render context.
116 + *
117 + * @return bool
118 + */
119 + private static function is_popup_render_allowed() {
120 +
121 + return ( ! is_admin() ) || self::is_elementor_render_context();
122 + }
123 +
124 + /**
125 + * Sanitize a space-separated HTML class list.
126 + *
127 + * @param string $class_list Class list.
128 + * @param string $default_value Fallback classes.
129 + *
130 + * @return string
131 + */
132 + private static function sanitize_class_list( $class_list, $default_value = '' ) {
133 +
134 + $class_list = is_scalar( $class_list ) ? trim( (string) $class_list ) : '';
135 +
136 + if ( '' === $class_list ) {
137 + $class_list = (string) $default_value;
138 + }
139 +
140 + $classes = preg_split( '/\s+/', $class_list );
141 + $classes = array_filter( array_map( 'sanitize_html_class', $classes ) );
142 +
143 + return implode( ' ', array_unique( $classes ) );
144 + }
145 +
146 + /**
147 + * Get normalized popup parameters from shortcode attributes.
148 + *
149 + * @param array $attr Shortcode attributes.
150 + *
151 + * @return array
152 + */
153 + private static function get_popup_params_from_attr( $attr ) {
154 +
155 + $popup_title = isset( $attr['popup_title'] ) ? sanitize_text_field( wp_unslash( $attr['popup_title'] ) ) : '';
156 + if ( '' === $popup_title ) {
157 + $popup_title = __( 'Booking Form', 'booking' );
158 + }
159 +
160 + $button_title = isset( $attr['popup_button_title'] ) ? sanitize_text_field( wp_unslash( $attr['popup_button_title'] ) ) : '';
161 + if ( '' === $button_title ) {
162 + $button_title = __( 'Book now', 'booking' );
163 + }
164 +
165 + $button_class = isset( $attr['popup_button_class'] ) ? $attr['popup_button_class'] : 'wp-element-button';
166 + $modal_class = isset( $attr['popup_modal_class'] ) ? $attr['popup_modal_class'] : '';
167 +
168 + $popup_size = isset( $attr['popup_size'] ) ? sanitize_key( wp_unslash( $attr['popup_size'] ) ) : 'lg';
169 + if ( ! in_array( $popup_size, array( 'lg', 'sm', 'default' ), true ) ) {
170 + $popup_size = 'lg';
171 + }
172 +
173 + return array(
174 + 'title' => $popup_title,
175 + 'button_title' => $button_title,
176 + 'button_class' => self::sanitize_class_list( $button_class, 'wp-element-button' ),
177 + 'modal_class' => self::sanitize_class_list( $modal_class ),
178 + 'size' => $popup_size,
179 + );
180 + }
181 +
182 + /**
183 + * Enqueue front-end assets needed for popup booking forms.
184 + *
185 + * @return string Fallback inline assets if they must be printed at the shortcode location.
186 + */
187 + private static function enqueue_popup_assets() {
188 +
189 + if ( function_exists( 'wpbc_load_js__required_for_front_end_modals' ) ) {
190 + wpbc_load_js__required_for_front_end_modals();
191 + } else {
192 + wp_enqueue_script( 'wpbc-modal', wpbc_plugin_url( '/vendors/_custom/dropdown_modal/_out/dropdown_modal.js' ), array( 'jquery' ), WP_BK_VERSION_NUM, array( 'in_footer' => WPBC_JS_IN_FOOTER ) );
193 + wp_enqueue_style( 'wpbc-admin-modal-popups', wpbc_plugin_url( '/css/modal.css' ), array(), WP_BK_VERSION_NUM );
194 + }
195 +
196 + $js_body = "jQuery( document ).on( 'shown.wpbc.modal', '.wpbc_booking_form_popup_modal', function(){ jQuery( window ).trigger( 'resize' ); jQuery( this ).trigger( 'wpbc_booking_popup_opened' ); } );";
197 + $added = WPBC_FE_Assets::add_jq_ready_js_to_wp_script( 'wpbc-modal', $js_body, 'wpbc-booking-popup-modal-shown' );
198 + if ( ! $added ) {
199 + return WPBC_FE_Assets::add_inline_js( wpbc_jq_ready_start() . "\n" . $js_body . "\n" . wpbc_jq_ready_end(), 'wpbc-booking-popup-modal-shown' );
200 + }
201 +
202 + return '';
203 + }
204 +
205 + /**
206 + * Wrap already-rendered booking form HTML into a front-end modal.
207 + *
208 + * @param string $booking_form_html Rendered booking form.
209 + * @param array $popup_params Normalized popup params.
210 + * @param int $resource_id Primary booking resource ID.
211 + *
212 + * @return string
213 + */
214 + private static function render_booking_popup( $booking_form_html, $popup_params, $resource_id ) {
215 +
216 + static $popup_counter = 0;
217 + $popup_counter++;
218 +
219 + $fallback_assets = self::enqueue_popup_assets();
220 +
221 + $resource_id = absint( $resource_id );
222 + $modal_id = 'wpbc_booking_form_popup_modal_' . $resource_id . '_' . $popup_counter;
223 +
224 + $modal_size_class = '';
225 + if ( 'default' !== $popup_params['size'] ) {
226 + $modal_size_class = ' modal-' . $popup_params['size'];
227 + }
228 +
229 + $modal_extra_class = ( '' !== $popup_params['modal_class'] ) ? ' ' . $popup_params['modal_class'] : '';
230 +
231 + $html = $fallback_assets;
232 + $html .= '<div class="wpbc_booking_form_popup">';
233 + $html .= '<a href="#' . esc_attr( $modal_id ) . '" class="' . esc_attr( $popup_params['button_class'] ) . '" data-toggle="wpbc_my_modal" data-target="#' . esc_attr( $modal_id ) . '" aria-haspopup="dialog" aria-controls="' . esc_attr( $modal_id ) . '">' . esc_html( $popup_params['button_title'] ) . '</a>';
234 + $html .= '<div class="wpdevelop">';
235 + $html .= '<div id="' . esc_attr( $modal_id ) . '" class="modal wpbc_popup_modal wpbc_booking_form_popup_modal' . esc_attr( $modal_extra_class ) . '" tabindex="-1" role="dialog" aria-hidden="true" data-keyboard="false" data-backdrop="true" data-resource-id="' . esc_attr( $resource_id ) . '" style="display:none;">';
236 + $html .= '<div class="modal-dialog' . esc_attr( $modal_size_class ) . '" role="document">';
237 + $html .= '<div class="modal-content">';
238 + $html .= '<div class="modal-header">';
239 + $html .= '<button type="button" class="close" data-dismiss="modal" aria-label="' . esc_attr__( 'Close', 'booking' ) . '"><span aria-hidden="true">&times;</span></button>';
240 + $html .= '<h4 class="modal-title">' . esc_html( $popup_params['title'] ) . '</h4>';
241 + $html .= '</div>';
242 + $html .= '<div class="modal-body">';
243 + $html .= $booking_form_html;
244 + $html .= '</div>';
245 + $html .= '</div>';
246 + $html .= '</div>';
247 + $html .= '</div>';
248 + $html .= '</div>';
249 + $html .= '</div>';
250 +
251 + return $html;
252 + }
253 +
61 254 // ---------------------------------------------------------------------
62 255 // Shortcodes
63 256 // ---------------------------------------------------------------------
64 257
@@ -98,9 +291,32 @@
98 291 );
99 292
100 293 $shortcode_result = WPBC_FE_Render::render_booking_form( $shortcode_params );
101 294
295 + if ( self::is_popup_render_allowed() && self::is_truthy_attr( $attr, 'popup' ) ) {
296 + $resource_id_for_popup = WPBC_FE_Shortcode_Params::get_from_attr__primary_resource_id( $attr, WPBC_FE_Attr_Postprocessor::get_default_booking_resource_id() );
297 + $shortcode_result = self::render_booking_popup( $shortcode_result, self::get_popup_params_from_attr( $attr ), $resource_id_for_popup );
298 + }
299 +
102 300 return $shortcode_result;
301 + }
302 +
303 + /**
304 + * Shortcode [booking_popup ...]
305 + *
306 + * Convenience alias for [booking ... popup=1].
307 + *
308 + * @param array $attr
309 + *
310 + * @return string
311 + */
312 + public static function booking_popup_shortcode( $attr ) {
313 +
314 + $attr = self::force_attr_array( $attr );
315 +
316 + $attr['popup'] = '1';
317 +
318 + return self::booking_shortcode( $attr );
103 319 }
104 320
105 321
106 322 /**