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-template-module.php

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

312 lines 13.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Reusable module adapter for the Booking Form Template setup editor.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Expose the template selector through the common setup-module contract.
14 */
15 final class WPBC_Setup_Wizard_Booking_Form_Template_Module implements WPBC_Setup_Wizard_Step_Module, WPBC_Setup_Wizard_Contextual_Values_Validator {
16
17 /** @var WPBC_Setup_Wizard_Booking_Form_Templates */
18 private $templates;
19
20 /** @var WPBC_Setup_Wizard_Booking_Form_Preview */
21 private $preview;
22
23 /** @var WPBC_Setup_Wizard_Booking_Form_Time_Options */
24 private $time_options;
25
26 /**
27 * Build the module around the read-only Form Builder registry adapter.
28 *
29 * @param WPBC_Setup_Wizard_Booking_Form_Templates|null $templates Optional registry adapter for testing or composition.
30 * @param WPBC_Setup_Wizard_Booking_Form_Preview|null $preview Optional preview coordinator for testing or composition.
31 * @param WPBC_Setup_Wizard_Booking_Form_Time_Options|null $time_options Optional time projector for testing or composition.
32 */
33 public function __construct( $templates = null, $preview = null, $time_options = null ) {
34 $this->templates = $templates instanceof WPBC_Setup_Wizard_Booking_Form_Templates
35 ? $templates
36 : new WPBC_Setup_Wizard_Booking_Form_Templates();
37 $this->time_options = $time_options instanceof WPBC_Setup_Wizard_Booking_Form_Time_Options
38 ? $time_options
39 : new WPBC_Setup_Wizard_Booking_Form_Time_Options();
40 $this->preview = $preview instanceof WPBC_Setup_Wizard_Booking_Form_Preview
41 ? $preview
42 : new WPBC_Setup_Wizard_Booking_Form_Preview( $this->templates, $this->time_options );
43 }
44
45 /**
46 * Return the stable module and step identifier.
47 *
48 * @return string Stable identifier.
49 */
50 public function get_step_id() {
51 return 'booking_form_template';
52 }
53
54 /**
55 * Return the rail and template metadata contributed by this module.
56 *
57 * @return array{id:string,label:string,template:string,footer_note:string} Step definition.
58 */
59 public function get_step_definition() {
60 return array(
61 'id' => $this->get_step_id(),
62 'label' => __( 'Booking form', 'booking' ),
63 'template' => 'step-booking-form-template',
64 'footer_note' => __( 'Create or update the selected Booking Form when you continue. Guided Appointment also assigns it to the saved Services.', 'booking' ),
65 );
66 }
67
68 /**
69 * Return the absolute server-owned template path.
70 *
71 * @return string Absolute template path.
72 */
73 public function get_template_path() {
74 return __DIR__ . '/template.php';
75 }
76
77 /**
78 * Return the fields accepted by the selector.
79 *
80 * @return string[] Field allow-list.
81 */
82 public function get_field_names() {
83 return array( 'booking_form_template', 'booking_form_usage' );
84 }
85
86 /**
87 * Return the required fields when advancing.
88 *
89 * @return string[] Required field identifiers.
90 */
91 public function get_required_fields() {
92 return array( 'booking_form_template', 'booking_form_usage' );
93 }
94
95 /**
96 * Return the preferred Form Builder template as a read-only suggestion.
97 *
98 * @return array<string,string> Initial field value.
99 */
100 public function get_initial_values() {
101 return array(
102 'booking_form_template' => $this->templates->get_initial_template_slug(),
103 'booking_form_usage' => $this->templates->get_initial_booking_form_usage(),
104 );
105 }
106
107 /**
108 * Return dependencies used for recommendations and time projection.
109 *
110 * @return string[] Dependency step identifiers.
111 */
112 public function get_context_dependencies() {
113 return array( 'customer_journey', 'start_end_times', 'start_duration_times', 'fixed_time_slots', 'date_time_formats' );
114 }
115
116 /**
117 * Build the template selector context and its initial Direct preview.
118 *
119 * Appointment Flow intentionally remains client-loaded because its later
120 * Service and Provider requests require the signed preview URL returned by
121 * the authenticated preview endpoint.
122 *
123 * @param array<string,mixed> $field_values Validated module field values.
124 * @param array<string,mixed> $consumer_context Read-only dependency values.
125 *
126 * @return array<string,mixed> Authorized presentation context.
127 */
128 public function get_context( array $field_values, array $consumer_context = array() ) {
129 $customer_journey = isset( $consumer_context['customer_journey']['customer_journey'] )
130 && is_scalar( $consumer_context['customer_journey']['customer_journey'] )
131 ? (string) $consumer_context['customer_journey']['customer_journey']
132 : '';
133 $has_saved_values = ! empty( $consumer_context['_step_state']['has_saved_values'] );
134 $is_journey_transition = WPBC_Setup_Wizard_Customer_Journey_Policy::has_pending_transition_for_step(
135 isset( $consumer_context['_step_results'] ) && is_array( $consumer_context['_step_results'] )
136 ? $consumer_context['_step_results']
137 : array(),
138 $this->get_step_id()
139 );
140
141 // A newly saved journey owns the next Booking Form recommendation. Preserve
142 // an explicitly saved template during ordinary revisits, but do not carry a
143 // stale template across a deliberate restart from Customer Journey.
144 if ( ! $has_saved_values || $is_journey_transition ) {
145 $field_values['booking_form_template'] = $this->templates->get_recommended_template_slug( $customer_journey );
146 }
147 $template_context = $this->templates->get_context( $field_values, $customer_journey );
148 $selected_slug = isset( $template_context['values']['booking_form_template'] )
149 ? (string) $template_context['values']['booking_form_template']
150 : '';
151 $selected_usage = isset( $template_context['values']['booking_form_usage'] )
152 ? (string) $template_context['values']['booking_form_usage']
153 : '';
154 $booking_form_context = $this->time_options->get_context_from_values( $consumer_context );
155 $preview_result = array();
156 $preview_error = '';
157
158 if ( '' !== $selected_slug && 'direct_booking_form' === $selected_usage ) {
159 $preview_result = $this->preview->build_preview(
160 $selected_slug,
161 $selected_usage,
162 WPBC_Setup_Wizard_Booking_Form_Preview::TARGET_EMBEDDED,
163 array(),
164 $booking_form_context
165 );
166
167 if ( is_wp_error( $preview_result ) ) {
168 $preview_error = $preview_result->get_error_message();
169 $preview_result = array();
170 }
171 }
172
173 $step_dialogs = array(
174 array(
175 'dialog_id' => 'wpbc-setup-wizard-booking-form-guidance-dialog',
176 'action' => 'booking-form-template-guidance',
177 'title' => __( 'Choose a starting template', 'booking' ),
178 'description' => __( 'This step selects a ready-made starting template for your booking form. It does not limit how you can customize the form.', 'booking' ),
179 'details' => array(
180 __( 'Review the interactive preview, then choose the template that best matches your booking flow.', 'booking' ),
181 __( 'After setup, open WP Booking Calendar > Settings > Forms Builder to add, remove, reorder, and configure your booking form fields.', 'booking' ),
182 ),
183 'note' => __( 'The selected template is applied only after you choose Update booking form & continue.', 'booking' ),
184 'cancel_label' => __( 'Close', 'booking' ),
185 'confirm_label' => __( 'Got it', 'booking' ),
186 ),
187 );
188
189 if ( 'guided_appointment_flow' === $customer_journey ) {
190 $step_dialogs[] = array(
191 'dialog_id' => 'wpbc-setup-wizard-appointment-flow-dialog',
192 'action' => 'booking-form-appointment-flow',
193 'title' => __( 'Use Appointment flow?', 'booking' ),
194 'description' => __( 'Before switching, review how Appointment flow uses the selected booking form:', 'booking' ),
195 'details' => array(
196 __( 'The selected booking form must contain a Start Time field.', 'booking' ),
197 __( 'Appointment flow uses the selected Service duration. Duration, End Time, and Time Range fields in the template are not shown, so customers cannot choose conflicting times.', 'booking' ),
198 __( 'This preview lists only Services already saved and active on your site, together with their assigned Providers. Services prepared earlier in this setup draft are not shown until setup is applied.', 'booking' ),
199 ),
200 'note' => __( 'This remains a private preview. Switching the entry point does not publish Services, import the template, or replace an existing booking form.', 'booking' ),
201 'cancel_label' => __( 'Keep direct booking form', 'booking' ),
202 'confirm_label' => __( 'Use Appointment flow', 'booking' ),
203 );
204 }
205
206 $template_context['preview_kind'] = isset( $preview_result['preview_kind'] ) ? (string) $preview_result['preview_kind'] : '';
207 $template_context['preview_html'] = isset( $preview_result['html'] ) ? (string) $preview_result['html'] : '';
208 $template_context['preview_bootstrap'] = isset( $preview_result['bootstrap'] ) && is_array( $preview_result['bootstrap'] ) ? $preview_result['bootstrap'] : array();
209 $template_context['preview_error'] = $preview_error;
210 $template_context['dialogs'] = $step_dialogs;
211
212 return $template_context;
213 }
214
215 /**
216 * Validate the selected stable Form Builder template slug.
217 *
218 * @param string $field_id Stable field identifier.
219 * @param mixed $raw_value Untrusted template slug.
220 * @param bool $is_required Whether an empty choice is invalid.
221 *
222 * @return string|WP_Error Normalized slug or an error.
223 */
224 public function validate_field( $field_id, $raw_value, $is_required ) {
225 if ( 'booking_form_template' === $field_id ) {
226 return $this->templates->validate_template_slug( $raw_value, $is_required );
227 }
228
229 if ( 'booking_form_usage' === $field_id ) {
230 return $this->templates->validate_booking_form_usage( $raw_value, $is_required );
231 }
232
233 return new WP_Error( 'wpbc_setup_wizard_booking_form_template_field_unknown', __( 'The Booking Form editor received an unsupported field.', 'booking' ) );
234 }
235
236 /**
237 * Revalidate the selected entry point against the active Customer Journey.
238 *
239 * Field validation owns the global usage allow-list. This contextual pass is
240 * the authoritative route boundary that prevents Appointment Flow from being
241 * submitted for time-based journeys even when a stale browser tab still
242 * contains the old radio value.
243 *
244 * @param array<string,mixed> $validated_values Validated module values.
245 * @param array<string,mixed> $consumer_context Read-only dependency values.
246 *
247 * @return array<string,mixed>|WP_Error Validated values or a field-addressable error.
248 */
249 public function validate_values_with_context( array $validated_values, array $consumer_context ) {
250 $customer_journey = isset( $consumer_context['customer_journey']['customer_journey'] )
251 ? sanitize_key( (string) $consumer_context['customer_journey']['customer_journey'] )
252 : '';
253 $booking_form_usage = isset( $validated_values['booking_form_usage'] )
254 ? $this->templates->validate_booking_form_usage_for_journey( $validated_values['booking_form_usage'], $customer_journey, true )
255 : new WP_Error( 'wpbc_setup_wizard_booking_form_usage_required', __( 'Choose how customers will open the booking form.', 'booking' ) );
256
257 if ( is_wp_error( $booking_form_usage ) ) {
258 $message = $booking_form_usage->get_error_message();
259
260 return new WP_Error(
261 $booking_form_usage->get_error_code(),
262 $message,
263 array( 'field_errors' => array( 'booking_form_usage' => $message ) )
264 );
265 }
266
267 $validated_values['booking_form_usage'] = $booking_form_usage;
268
269 return $validated_values;
270 }
271
272 /**
273 * Enqueue compiled selector assets and translations.
274 *
275 * @param string $module_url Absolute URL to the Setup Wizard module root.
276 * @param string|false $asset_version Plugin version used for cache busting.
277 * @param string $shared_style_handle Consumer shell style handle.
278 * @param string $shared_script_handle Consumer shell script handle.
279 *
280 * @return void
281 */
282 public function enqueue_assets( $module_url, $asset_version, $shared_style_handle, $shared_script_handle ) {
283 $module_url = trailingslashit( $module_url );
284 $script_dependencies = array( $shared_script_handle );
285
286 if ( class_exists( 'WPBC_BFB_Preview_Service' ) ) {
287 WPBC_BFB_Preview_Service::get_instance()->enqueue_inline_preview_assets();
288 $script_dependencies[] = 'wpbc-bfb-inline-preview';
289 }
290
291 wp_enqueue_style( 'wpbc-setup-wizard-booking-form-template', $module_url . 'step-booking-form-template/_out/step-booking-form-template.css', array( $shared_style_handle ), $asset_version );
292 wp_enqueue_script( 'wpbc-setup-wizard-booking-form-template', $module_url . 'step-booking-form-template/_out/step-booking-form-template.js', $script_dependencies, $asset_version, true );
293 wp_localize_script(
294 'wpbc-setup-wizard-booking-form-template',
295 'wpbc_setup_wizard_booking_form_template',
296 array(
297 'ajax_url' => admin_url( 'admin-ajax.php' ),
298 'nonce' => wp_create_nonce( WPBC_Setup_Wizard_Ajax::NONCE_ACTION ),
299 'preview_action' => WPBC_Setup_Wizard_Booking_Form_Preview_Ajax::ACTION,
300 'i18n' => array(
301 'template_required' => __( 'Choose a booking form template.', 'booking' ),
302 'usage_required' => __( 'Choose how customers will open the booking form.', 'booking' ),
303 'no_matching_templates' => __( 'No templates match this search and filter.', 'booking' ),
304 'preview_loading' => __( 'Loading the interactive booking form preview.', 'booking' ),
305 'preview_error' => __( 'The interactive preview could not be loaded. Try refreshing it.', 'booking' ),
306 'preview_open_error' => __( 'The full-site preview could not be opened. Try again.', 'booking' ),
307 ),
308 )
309 );
310 }
311 }
312