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
booking / includes / page-setup-wizard / step-booking-form-template / class-wpbc-setup-wizard-booking-form-preview.php

class-wpbc-setup-wizard-booking-form-preview.php in Booking Calendar 11.9, at includes/page-setup-wizard/step-booking-form-template/class-wpbc-setup-wizard-booking-form-preview.php

250 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Reusable Setup Wizard Booking Form preview coordinator.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Resolve server-owned templates into inline or signed URL previews.
14 *
15 * Authorization remains the caller's responsibility. This service validates
16 * every template, usage, and target against server-owned allow-lists, performs
17 * no canonical Form Builder writes, and returns only normalized preview data.
18 */
19 final class WPBC_Setup_Wizard_Booking_Form_Preview {
20
21 const TARGET_EMBEDDED = 'embedded';
22 const TARGET_WINDOW = 'window';
23
24 /** @var WPBC_Setup_Wizard_Booking_Form_Templates */
25 private $templates;
26
27 /** @var WPBC_Setup_Wizard_Booking_Form_Time_Options */
28 private $time_options;
29
30 /**
31 * Build the coordinator around the current Form Builder template registry.
32 *
33 * @param WPBC_Setup_Wizard_Booking_Form_Templates|null $templates Optional registry adapter for testing.
34 * @param WPBC_Setup_Wizard_Booking_Form_Time_Options|null $time_options Optional time-option projector for testing.
35 */
36 public function __construct( $templates = null, $time_options = null ) {
37 $this->templates = $templates instanceof WPBC_Setup_Wizard_Booking_Form_Templates
38 ? $templates
39 : new WPBC_Setup_Wizard_Booking_Form_Templates();
40 $this->time_options = $time_options instanceof WPBC_Setup_Wizard_Booking_Form_Time_Options
41 ? $time_options
42 : new WPBC_Setup_Wizard_Booking_Form_Time_Options();
43 }
44
45 /**
46 * Build an authorized inline or signed URL preview result.
47 *
48 * Direct embedded previews render in the current request. Appointment Flow
49 * and explicit new-window previews use the existing signed transient because
50 * later front-end requests must recover the unsaved template snapshot.
51 *
52 * @param mixed $raw_template_slug Untrusted or stored template slug.
53 * @param mixed $raw_booking_form_usage Untrusted or stored usage identifier.
54 * @param mixed $raw_preview_target Untrusted or stored preview target.
55 * @param array $preview_configuration Server-owned request-local preview configuration.
56 * @param array $booking_form_context Server-owned Customer Journey and time proposal context.
57 *
58 * @return array<string,mixed>|WP_Error Normalized preview result or validation error.
59 */
60 public function build_preview( $raw_template_slug, $raw_booking_form_usage, $raw_preview_target, array $preview_configuration = array(), array $booking_form_context = array() ) {
61 $has_journey_context = array_key_exists( 'customer_journey', $booking_form_context );
62 $customer_journey = isset( $booking_form_context['customer_journey'] )
63 ? sanitize_key( (string) $booking_form_context['customer_journey'] )
64 : '';
65 $booking_form_usage = $has_journey_context
66 ? $this->templates->validate_booking_form_usage_for_journey( $raw_booking_form_usage, $customer_journey, true )
67 : $this->templates->validate_booking_form_usage( $raw_booking_form_usage, true );
68 $render_mode = $this->templates->get_preview_render_mode(
69 $raw_booking_form_usage,
70 $has_journey_context ? $customer_journey : null
71 );
72 $preview_target = $this->validate_preview_target( $raw_preview_target );
73
74 foreach ( array( $booking_form_usage, $render_mode, $preview_target ) as $validated_value ) {
75 if ( is_wp_error( $validated_value ) ) {
76 return $validated_value;
77 }
78 }
79
80 $is_inline_preview = self::TARGET_EMBEDDED === $preview_target && 'direct_booking_form' === $booking_form_usage;
81 $preview_payload = $is_inline_preview
82 ? $this->templates->get_inline_preview_payload( $raw_template_slug )
83 : $this->templates->get_preview_payload( $raw_template_slug );
84 if ( is_wp_error( $preview_payload ) ) {
85 return $preview_payload;
86 }
87
88 $preview_payload = $this->time_options->apply_to_preview_payload( $preview_payload, $booking_form_context );
89 if ( is_wp_error( $preview_payload ) ) {
90 return $preview_payload;
91 }
92
93 if ( ! class_exists( 'WPBC_BFB_Preview_Service' ) ) {
94 return new WP_Error(
95 'wpbc_setup_wizard_booking_form_preview_unavailable',
96 __( 'The interactive booking form preview is unavailable.', 'booking' ),
97 array( 'status' => 503 )
98 );
99 }
100
101 if ( $is_inline_preview ) {
102 return $this->build_inline_preview( $preview_payload, $booking_form_usage, $preview_target, $render_mode, $preview_configuration );
103 }
104
105 return $this->build_url_preview( $preview_payload, $booking_form_usage, $preview_target, $render_mode, $preview_configuration );
106 }
107
108 /**
109 * Validate the requested presentation target against the exact allow-list.
110 *
111 * @param mixed $raw_preview_target Untrusted or stored preview target.
112 *
113 * @return string|WP_Error Normalized target or validation error.
114 */
115 public function validate_preview_target( $raw_preview_target ) {
116 if ( ! is_scalar( $raw_preview_target ) ) {
117 return new WP_Error(
118 'wpbc_setup_wizard_booking_form_preview_target_invalid',
119 __( 'Choose a valid booking form preview target.', 'booking' ),
120 array( 'status' => 400 )
121 );
122 }
123
124 $raw_preview_target = trim( (string) $raw_preview_target );
125 $preview_target = sanitize_key( $raw_preview_target );
126
127 if (
128 $preview_target !== $raw_preview_target
129 || ! in_array( $preview_target, array( self::TARGET_EMBEDDED, self::TARGET_WINDOW ), true )
130 ) {
131 return new WP_Error(
132 'wpbc_setup_wizard_booking_form_preview_target_unknown',
133 __( 'Choose a valid booking form preview target.', 'booking' ),
134 array( 'status' => 400 )
135 );
136 }
137
138 return $preview_target;
139 }
140
141 /**
142 * Render one request-local Direct booking form.
143 *
144 * @param array<string,mixed> $preview_payload Validated server-owned template payload.
145 * @param string $booking_form_usage Validated usage identifier.
146 * @param string $preview_target Validated target identifier.
147 * @param string $render_mode Validated Form Builder render mode.
148 * @param array<string,mixed> $preview_configuration Server-owned preview configuration.
149 *
150 * @return array<string,mixed>|WP_Error Normalized inline preview or error.
151 */
152 private function build_inline_preview( array $preview_payload, $booking_form_usage, $preview_target, $render_mode, array $preview_configuration ) {
153 $preview_configuration['render_mode'] = $render_mode;
154 try {
155 $inline_preview = WPBC_BFB_Preview_Service::get_instance()->render_inline_preview_payload(
156 $this->get_default_resource_id(),
157 $preview_payload['structure'],
158 $preview_payload['form_name'],
159 $preview_payload['advanced_form'],
160 $preview_payload['content_form'],
161 array(),
162 $preview_configuration
163 );
164 } catch ( Throwable $preview_exception ) {
165 $inline_preview = false;
166 }
167
168 if (
169 ! is_array( $inline_preview )
170 || empty( $inline_preview['html'] )
171 || ! is_string( $inline_preview['html'] )
172 || empty( $inline_preview['bootstrap'] )
173 || ! is_array( $inline_preview['bootstrap'] )
174 ) {
175 return new WP_Error(
176 'wpbc_setup_wizard_booking_form_preview_render_failed',
177 __( 'The selected booking form could not be rendered.', 'booking' ),
178 array( 'status' => 500 )
179 );
180 }
181
182 return array(
183 'preview_kind' => 'inline',
184 'preview_target' => $preview_target,
185 'html' => $inline_preview['html'],
186 'bootstrap' => $inline_preview['bootstrap'],
187 'template_slug' => $preview_payload['template_slug'],
188 'booking_form_usage' => $booking_form_usage,
189 );
190 }
191
192 /**
193 * Create one signed short-lived preview URL.
194 *
195 * @param array<string,mixed> $preview_payload Validated server-owned template payload.
196 * @param string $booking_form_usage Validated usage identifier.
197 * @param string $preview_target Validated target identifier.
198 * @param string $render_mode Validated Form Builder render mode.
199 * @param array<string,mixed> $preview_configuration Server-owned preview configuration.
200 *
201 * @return array<string,mixed>|WP_Error Normalized URL preview or error.
202 */
203 private function build_url_preview( array $preview_payload, $booking_form_usage, $preview_target, $render_mode, array $preview_configuration ) {
204 $preview_configuration['render_mode'] = $render_mode;
205 try {
206 $preview = WPBC_BFB_Preview_Service::get_instance()->create_preview_session(
207 $this->get_default_resource_id(),
208 get_current_user_id(),
209 $preview_payload['structure'],
210 $preview_payload['form_name'],
211 $preview_payload['advanced_form'],
212 $preview_payload['content_form'],
213 array(),
214 $preview_configuration
215 );
216 } catch ( Throwable $preview_exception ) {
217 $preview = false;
218 }
219
220 if ( ! is_array( $preview ) || empty( $preview['preview_url'] ) ) {
221 return new WP_Error(
222 'wpbc_setup_wizard_booking_form_preview_session_failed',
223 __( 'The interactive booking form preview could not be created.', 'booking' ),
224 array( 'status' => 500 )
225 );
226 }
227
228 return array(
229 'preview_kind' => 'url',
230 'preview_target' => $preview_target,
231 'preview_url' => esc_url_raw( $preview['preview_url'] ),
232 'template_slug' => $preview_payload['template_slug'],
233 'booking_form_usage' => $booking_form_usage,
234 );
235 }
236
237 /**
238 * Return the current user's default authorized booking resource.
239 *
240 * @return int Positive booking resource ID.
241 */
242 private function get_default_resource_id() {
243 $resource_id = class_exists( 'WPBC_FE_Attr_Postprocessor' )
244 ? absint( WPBC_FE_Attr_Postprocessor::get_default_booking_resource_id() )
245 : 1;
246
247 return $resource_id > 0 ? $resource_id : 1;
248 }
249 }
250