| @@ -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">×</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 | /** |