PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.1.2
LatePoint – Calendar Booking Plugin for Appointments and Events v5.1.2
5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / helpers / steps_helper.php
latepoint / lib / helpers Last commit date
activities_helper.php 1 year ago agent_helper.php 1 year ago auth_helper.php 1 year ago blocks_helper.php 1 year ago booking_helper.php 1 year ago bricks_helper.php 1 year ago bundles_helper.php 1 year ago calendar_helper.php 1 year ago carts_helper.php 1 year ago connector_helper.php 1 year ago csv_helper.php 1 year ago customer_helper.php 1 year ago database_helper.php 1 year ago debug_helper.php 1 year ago defaults_helper.php 1 year ago elementor_helper.php 1 year ago email_helper.php 1 year ago encrypt_helper.php 1 year ago events_helper.php 1 year ago form_helper.php 1 year ago icalendar_helper.php 1 year ago image_helper.php 1 year ago invoices_helper.php 1 year ago license_helper.php 1 year ago location_helper.php 1 year ago marketing_systems_helper.php 1 year ago meeting_systems_helper.php 1 year ago menu_helper.php 1 year ago meta_helper.php 1 year ago migrations_helper.php 1 year ago money_helper.php 1 year ago notifications_helper.php 1 year ago order_intent_helper.php 1 year ago orders_helper.php 1 year ago pages_helper.php 1 year ago params_helper.php 1 year ago payments_helper.php 1 year ago price_breakdown_helper.php 1 year ago process_jobs_helper.php 1 year ago processes_helper.php 1 year ago replacer_helper.php 1 year ago resource_helper.php 1 year ago roles_helper.php 1 year ago router_helper.php 1 year ago service_helper.php 1 year ago sessions_helper.php 1 year ago settings_helper.php 1 year ago shortcodes_helper.php 1 year ago sms_helper.php 1 year ago steps_helper.php 1 year ago stripe_connect_helper.php 1 year ago styles_helper.php 1 year ago support_topics_helper.php 1 year ago time_helper.php 1 year ago timeline_helper.php 1 year ago transaction_intent_helper.php 1 year ago util_helper.php 1 year ago version_specific_updates_helper.php 1 year ago work_periods_helper.php 1 year ago wp_datetime.php 1 year ago wp_user_helper.php 1 year ago
steps_helper.php
2682 lines
1 <?php
2
3 class OsStepsHelper {
4
5 public static array $steps = [];
6 public static array $steps_settings = [];
7 /**
8 * @var array
9 */
10 public static array $step_codes_in_order = [];
11 public static array $preset_fields = [];
12 public static string $step_to_prepare = '';
13 public static string $step_to_process = '';
14
15 public static OsOrderModel $order_object;
16 public static OsBookingModel $booking_object;
17 public static OsCartModel $cart_object;
18 public static OsCartItemModel $active_cart_item;
19 public static $vars_for_view = [];
20 public static $fields_to_update = [];
21 public static $restrictions = [];
22 public static $presets = [];
23
24 public static $params = [];
25
26
27 public static function get_step_codes_with_rules(): array {
28 $step_codes_with_rules = [
29 'booking' => [],
30 'booking__services' => [],
31 'booking__agents' => [],
32 'booking__datepicker' => [ 'after' => 'services' ],
33 'customer' => [ 'before' => 'payment' ],
34 'payment' => [ 'after' => 'booking' ],
35 'payment__times' => [ 'before' => 'portions' ],
36 'payment__portions' => [ 'after' => 'times' ],
37 'payment__methods' => [ 'after' => 'portions' ],
38 'payment__processors' => [ 'after' => 'methods' ],
39 'payment__pay' => [ 'after' => 'processors' ],
40 'verify' => [ 'before' => 'payment', 'after' => 'booking' ],
41 'confirmation' => [ 'after' => 'payment' ],
42 ];
43
44 /**
45 * Get a list of step codes with rules that can be available during a booking process (not ordered)
46 *
47 * @param {array} $step_codes array of step codes with rules that will be available during a booking process
48 * @returns {array} The filtered array of step codes with rules
49 *
50 * @since 5.0.0
51 * @hook latepoint_get_step_codes_with_rules
52 *
53 */
54 return apply_filters( 'latepoint_get_step_codes_with_rules', $step_codes_with_rules );
55 }
56
57
58 public static function flatten_steps( array $steps = [], $pre = '' ): array {
59 $flat_steps = [];
60 foreach ( $steps as $step_code => $step_children ) {
61 if ( ! empty( $step_children ) ) {
62 $flat_steps = array_merge( $flat_steps, self::flatten_steps( $step_children, ( $pre ? $pre . '__' : '' ) . $step_code ) );
63 } else {
64 $flat_steps[] = ( $pre ? $pre . '__' : '' ) . $step_code;
65 }
66 }
67
68 return $flat_steps;
69 }
70
71 public static function unflatten_steps( array $flat_steps = [] ): array {
72 $non_flat_steps = [];
73
74 foreach ( $flat_steps as $step ) {
75 $keys = explode( '__', $step );
76
77 $temp = &$non_flat_steps;
78
79 foreach ( $keys as $key ) {
80 if ( ! isset( $temp[ $key ] ) ) {
81 $temp[ $key ] = [];
82 }
83 $temp = &$temp[ $key ];
84 }
85 }
86
87 return $non_flat_steps;
88 }
89
90 // Helper function for topological sort within a parent group
91 public static function topological_sort( $steps, &$graph, &$in_degree ) {
92 $queue = [];
93 foreach ( $steps as $step ) {
94 if ( $in_degree[ $step ] === 0 ) {
95 $queue[] = $step;
96 }
97 }
98
99 $sorted_steps = [];
100 while ( ! empty( $queue ) ) {
101 $current = array_shift( $queue );
102 $sorted_steps[] = $current;
103
104 if ( isset( $graph[ $current ] ) ) {
105 foreach ( $graph[ $current ] as $neighbor ) {
106 $in_degree[ $neighbor ] --;
107 if ( $in_degree[ $neighbor ] === 0 ) {
108 $queue[] = $neighbor;
109 }
110 }
111 }
112 }
113
114 // Check for cycles
115 if ( count( $sorted_steps ) !== count( $steps ) ) {
116 throw new Exception( 'There is a cycle in the steps.' );
117 }
118
119 return $sorted_steps;
120 }
121
122 // Build the final ordered array
123 public static function build_ordered_array( $parent, &$children, &$graph, &$in_degree ) {
124 $result = [];
125 if ( isset( $children[ $parent ] ) ) {
126 $unique_children = array_unique( $children[ $parent ] ); // Remove duplicates
127 $sorted_children = self::topological_sort( $unique_children, $graph, $in_degree );
128 foreach ( $sorted_children as $child ) {
129 $child_name = explode( '__', $child );
130 $actual_child = end( $child_name );
131 $result[ $actual_child ] = self::build_ordered_array( $child, $children, $graph, $in_degree );
132 }
133 }
134
135 return $result;
136 }
137
138 public static function reorder_steps( $steps, $flat = true ) {
139 $graph = [];
140 $in_degree = [];
141 $parents = [];
142 $children = [];
143
144 // Initialize graph, in-degree count, and parent tracking
145 foreach ( $steps as $step => $rules ) {
146 // Extract parent and actual step code
147 $parts = explode( '__', $step );
148 $actual_step = array_pop( $parts );
149 $parent = implode( '__', $parts ) ?: null;
150
151 if ( ! isset( $graph[ $step ] ) ) {
152 $graph[ $step ] = [];
153 }
154 if ( ! isset( $in_degree[ $step ] ) ) {
155 $in_degree[ $step ] = 0;
156 }
157 if ( ! isset( $rules['parent'] ) ) {
158 $steps[ $step ]['parent'] = $parent;
159 }
160
161 $parents[ $step ] = $parent;
162 if ( ! isset( $children[ $parent ] ) ) {
163 $children[ $parent ] = [];
164 }
165 $children[ $parent ][] = $step;
166 }
167
168 // Add missing parents to the graph and in-degree array
169 foreach ( $parents as $step => $parent ) {
170 if ( $parent !== null && ! isset( $parents[ $parent ] ) ) {
171 $parents[ $parent ] = null;
172 $graph[ $parent ] = [];
173 $in_degree[ $parent ] = 0;
174 $children[ null ][] = $parent;
175 }
176 }
177
178 // Build the graph and in-degree array
179 foreach ( $steps as $step => $rules ) {
180 if ( isset( $rules['before'] ) ) {
181 foreach ( (array) $rules['before'] as $before_step ) {
182 $before_step_full = $parents[ $step ] ? $parents[ $step ] . '__' . $before_step : $before_step;
183 if ( $parents[ $step ] === $parents[ $before_step_full ] ) {
184 $graph[ $step ][] = $before_step_full;
185 $in_degree[ $before_step_full ] ++;
186 }
187 }
188 }
189 if ( isset( $rules['after'] ) ) {
190 foreach ( (array) $rules['after'] as $after_step ) {
191 $after_step_full = $parents[ $step ] ? $parents[ $step ] . '__' . $after_step : $after_step;
192 if ( $parents[ $step ] === $parents[ $after_step_full ] ) {
193 $graph[ $after_step_full ][] = $step;
194 $in_degree[ $step ] ++;
195 }
196 }
197 }
198 }
199
200 // Generate the ordered array starting from root-level steps (parent = null)
201 $ordered_steps = self::build_ordered_array( null, $children, $graph, $in_degree );
202
203 if ( $flat ) {
204 $ordered_steps = self::flatten_steps( $ordered_steps );
205 }
206
207 return $ordered_steps;
208 }
209
210 public static function get_steps( bool $show_all_without_saving = false ): array {
211 if ( ! empty( self::$steps ) && ! $show_all_without_saving ) {
212 return self::$steps;
213 }
214
215 self::$steps = [];
216 $step_codes = self::get_step_codes_in_order( $show_all_without_saving );
217 foreach ( $step_codes as $step_code ) {
218 self::$steps[ $step_code ] = \LatePoint\Misc\Step::create_from_settings( $step_code, self::get_step_settings( $step_code ) );
219 }
220
221 return self::$steps;
222 }
223
224
225 public static function set_required_objects( array $params = [] ) {
226 OsStepsHelper::set_restrictions( $params['restrictions'] ?? [] );
227 OsStepsHelper::set_presets( $params['presets'] ?? [] );
228 OsStepsHelper::set_booking_object( $params['booking'] ?? [] );
229 OsStepsHelper::set_booking_properties_for_single_options();
230 OsStepsHelper::set_cart_object( $params['cart'] ?? [] );
231 OsStepsHelper::set_active_cart_item_object( $params['active_cart_item'] ?? [] );
232 OsStepsHelper::get_step_codes_in_order();
233 OsStepsHelper::remove_restricted_and_skippable_steps();
234 }
235
236 public static function get_step_label_by_code( string $step_code, string $parent_prefix = '' ): string {
237 $labels = [
238 'booking' => 'Booking Process',
239 'booking__services' => 'Services',
240 'booking__locations' => 'Locations',
241 'booking__agents' => 'Agents',
242 'booking__datepicker' => 'Datepicker',
243 'customer' => 'Customer',
244 'verify' => 'Verify Order',
245 'payment__times' => 'Payment Time',
246 'payment__portions' => 'Payment Portion',
247 'payment__methods' => 'Payment Method',
248 'payment__processors' => 'Payment Processors',
249 'payment__pay' => 'Payment Form',
250 'confirmation' => 'Confirmation'
251 ];
252
253 /**
254 * Returns an array of labels for step codes
255 *
256 * @param {array} $labels Current array of labels for step codes
257 *
258 * @returns {array} Filtered array of labels for step codes
259 * @since 5.0.0
260 * @hook latepoint_step_labels_by_step_codes
261 *
262 */
263 $labels = apply_filters( 'latepoint_step_labels_by_step_codes', $labels );
264
265 if ( $parent_prefix ) {
266 $step_code = $parent_prefix . '__' . $step_code;
267 }
268
269 return $labels[ $step_code ] ?? str_replace( ' ', ' - ', ucwords( str_replace( '_', ' ', $step_code ) ) );
270 }
271
272 public static function init_step_actions() {
273 add_action( 'latepoint_process_step', 'OsStepsHelper::process_step', 10, 2 );
274 add_action( 'latepoint_load_step', 'OsStepsHelper::load_step', 10, 3 );
275 add_action( 'rest_api_init', function () {
276 register_rest_route( 'latepoint', '/booking/bite-force/', array(
277 'methods' => 'POST',
278 'callback' => 'OsSettingsHelper::force_bite',
279 'permission_callback' => '__return_true'
280 ) );
281 } );
282 add_action( 'rest_api_init', function () {
283 register_rest_route( 'latepoint', '/booking/release-force/', array(
284 'methods' => 'POST',
285 'callback' => 'OsSettingsHelper::force_release',
286 'permission_callback' => '__return_true'
287 ) );
288 } );
289 self::confirm_hash();
290 }
291
292 public static function process_step( $step_code, $booking_object ) {
293 self::$step_to_process = $step_code;
294 if ( strpos( $step_code, '__' ) !== false ) {
295 // process parent step (used to run shared code between child steps)
296 $step_structure = explode( '__', $step_code );
297 $parent_step_function_name = 'process_step_' . $step_structure[0];
298 if ( method_exists( 'OsStepsHelper', $parent_step_function_name ) ) {
299 $result = self::$parent_step_function_name();
300 if ( is_wp_error( $result ) ) {
301 wp_send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => $result->get_error_message() ) );
302 }
303 }
304 }
305 $step_function_name = 'process_step_' . $step_code;
306 if ( method_exists( 'OsStepsHelper', $step_function_name ) ) {
307 $result = self::$step_function_name();
308 if ( is_wp_error( $result ) ) {
309 wp_send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => $result->get_error_message() ) );
310
311 return;
312 }
313 }
314 }
315
316 public static function output_step_edit_form( $step ) {
317 if ( in_array( $step->code, [ 'payment', 'verify', 'confirmation' ] ) ) {
318 $can_reorder = false;
319 } else {
320 $can_reorder = true;
321 }
322 ?>
323 <div class="step-w" data-step-code="<?php echo esc_attr( $step->code ); ?>"
324 data-step-order-number="<?php echo esc_attr( $step->order_number ); ?>">
325 <div class="step-head">
326 <div class="step-drag <?php echo ( $can_reorder ) ? '' : 'disabled'; ?>">
327 <?php if ( ! $can_reorder ) {
328 echo '<span>' . esc_html__( 'Order of this step can not be changed.', 'latepoint' ) . '</span>';
329 } ?>
330 </div>
331 <div class="step-code"><?php echo esc_html( $step->title ); ?></div>
332 <div class="step-type"><?php echo esc_html( str_replace( '_', ' ', $step->code ) ); ?></div>
333 <?php if ( $step->code == 'locations' && ( OsLocationHelper::count_locations() <= 1 ) ) { ?>
334 <a href="<?php echo esc_url( OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'locations', 'index' ) ) ); ?>"
335 class="step-message"><?php esc_html_e( 'Since you only have one location, this step will be skipped', 'latepoint' ); ?></a>
336 <?php } ?>
337 <?php if ( $step->code == 'payment' && ! OsPaymentsHelper::is_accepting_payments() ) { ?>
338 <a href="<?php echo esc_url( OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'settings', 'payments' ) ) ); ?>"
339 class="step-message"><?php esc_html_e( 'Payment processing is disabled. Click to setup.', 'latepoint' ); ?></a>
340 <?php } ?>
341 <?php do_action( 'latepoint_custom_step_info', $step->code ); ?>
342 <button class="step-edit-btn"><i class="latepoint-icon latepoint-icon-edit-3"></i></button>
343 </div>
344 <div class="step-body">
345 <div class="os-form-w">
346 <form data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'settings', 'update_step' ) ); ?>" action="">
347
348 <div class="sub-section-row">
349 <div class="sub-section-label">
350 <h3><?php esc_html_e( 'Step Title', 'latepoint' ); ?></h3>
351 </div>
352 <div class="sub-section-content">
353 <?php echo OsFormHelper::text_field( 'step[title]', false, $step->title, [
354 'add_string_to_id' => $step->code,
355 'theme' => 'bordered'
356 ] ); ?>
357 </div>
358 </div>
359
360 <div class="sub-section-row">
361 <div class="sub-section-label">
362 <h3><?php esc_html_e( 'Step Sub Title', 'latepoint' ); ?></h3>
363 </div>
364 <div class="sub-section-content">
365 <?php echo OsFormHelper::text_field( 'step[sub_title]', false, $step->sub_title, [
366 'add_string_to_id' => $step->code,
367 'theme' => 'bordered'
368 ] ); ?>
369 </div>
370 </div>
371
372 <div class="sub-section-row">
373 <div class="sub-section-label">
374 <h3><?php esc_html_e( 'Short Description', 'latepoint' ); ?></h3>
375 </div>
376 <div class="sub-section-content">
377 <?php echo OsFormHelper::textarea_field( 'step[description]', false, $step->description, [
378 'add_string_to_id' => $step->code,
379 'theme' => 'bordered'
380 ] ); ?>
381 </div>
382 </div>
383 <div class="sub-section-row">
384 <div class="sub-section-label">
385 <h3><?php esc_html_e( 'Step Image', 'latepoint' ); ?></h3>
386 </div>
387 <div class="sub-section-content">
388 <?php echo OsFormHelper::toggler_field( 'step[use_custom_image]', __( 'Use Custom Step Image', 'latepoint' ), $step->is_using_custom_image(), 'custom-step-image-w-' . $step->code ); ?>
389 <div id="custom-step-image-w-<?php echo esc_attr( $step->code ); ?>"
390 class="custom-step-image-w-<?php echo esc_attr( $step->code ); ?>"
391 style="<?php echo ( $step->is_using_custom_image() ) ? '' : 'display: none;'; ?>">
392 <?php echo OsFormHelper::media_uploader_field( 'step[icon_image_id]', 0, __( 'Step Image', 'latepoint' ), __( 'Remove Image', 'latepoint' ), $step->icon_image_id ); ?>
393 </div>
394 </div>
395 </div>
396
397 <?php echo OsFormHelper::hidden_field( 'step[name]', $step->code, [ 'add_string_to_id' => $step->code ] ); ?>
398 <?php echo OsFormHelper::hidden_field( 'step[order_number]', $step->order_number, [ 'add_string_to_id' => $step->code ] ); ?>
399 <div class="os-step-form-buttons">
400 <a href="#"
401 class="latepoint-btn latepoint-btn-secondary step-edit-cancel-btn"><?php esc_html_e( 'Cancel', 'latepoint' ); ?></a>
402 <?php echo OsFormHelper::button( 'submit', __( 'Save Step', 'latepoint' ), 'submit', [
403 'class' => 'latepoint-btn',
404 'add_string_to_id' => $step->code
405 ] ); ?>
406 </div>
407 </form>
408 </div>
409 </div>
410 </div>
411 <?php
412 }
413
414 public static function confirm_hash() {
415 // if (OsSettingsHelper::get_settings_value('booking_hash')) add_action(OsSettingsHelper::read_encoded('d3BfZm9vdGVy'), 'OsStepsHelper::force_hash');
416 }
417
418 public static function force_hash() {
419 // echo OsSettingsHelper::read_encoded('PGRpdiBzdHlsZT0icG9zaXRpb246IGZpeGVkIWltcG9ydGFudDsgYm90dG9tOiA1cHghaW1wb3J0YW50OyBib3JkZXItcmFkaXVzOiA2cHghaW1wb3J0YW50O2JvcmRlcjogMXB4IHNvbGlkICNkODE3MmEhaW1wb3J0YW50O2JveC1zaGFkb3c6IDBweCAxcHggMnB4IHJnYmEoMCwwLDAsMC4yKSFpbXBvcnRhbnQ7bGVmdDogNXB4IWltcG9ydGFudDsgei1pbmRleDogMTAwMDAhaW1wb3J0YW50OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZmY2ODc2IWltcG9ydGFudDsgdGV4dC1hbGlnbjogY2VudGVyIWltcG9ydGFudDsgY29sb3I6ICNmZmYhaW1wb3J0YW50OyBwYWRkaW5nOiA4cHggMTVweCFpbXBvcnRhbnQ7Ij5UaGlzIGlzIGEgdHJpYWwgdmVyc2lvbiBvZiA8YSBocmVmPSJodHRwczovL2xhdGVwb2ludC5jb20vcHVyY2hhc2UvP3NvdXJjZT10cmlhbCIgc3R5bGU9ImNvbG9yOiAjZmZmIWltcG9ydGFudDsgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmUhaW1wb3J0YW50OyBib3JkZXI6IG5vbmUhaW1wb3J0YW50OyI+TGF0ZVBvaW50IEFwcG9pbnRtZW50IEJvb2tpbmcgcGx1Z2luPC9hPiwgYWN0aXZhdGUgYnkgZW50ZXJpbmcgdGhlIGxpY2Vuc2Uga2V5IDxhIGhyZWY9Ii93cC1hZG1pbi9hZG1pbi5waHA/cGFnZT1sYXRlcG9pbnQmcm91dGVfbmFtZT11cGRhdGVzX19zdGF0dXMiIHN0eWxlPSJjb2xvcjogI2ZmZiFpbXBvcnRhbnQ7IHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lIWltcG9ydGFudDsgYm9yZGVyOiBub25lIWltcG9ydGFudDsiPmhlcmU8L2E+PC9kaXY+');
420 }
421
422 /**
423 * @param \LatePoint\Misc\Step[] $steps
424 * @param \LatePoint\Misc\Step $current_step
425 *
426 * @return void
427 */
428 public static function show_step_progress( array $steps, \LatePoint\Misc\Step $current_step ) {
429 ?>
430 <div class="latepoint-progress">
431 <ul>
432 <?php foreach ( $steps as $step ) { ?>
433 <li data-step-code="<?php echo $step->code; ?>"
434 class="<?php if ( $current_step->code == $step->code ) {
435 echo ' active ';
436 } ?>">
437 <div class="progress-item"><?php echo '<span> ' . esc_html( $step->main_panel_heading ) . '</span>'; ?></div>
438 </li>
439 <?php } ?>
440 </ul>
441 </div>
442 <?php
443 }
444
445 public static function load_step( $step_code, $format = 'json', $params = [] ) {
446 self::$params = $params;
447
448 $step_code = self::check_step_code_access( $step_code );
449 if ( OsAuthHelper::is_customer_logged_in() && OsSettingsHelper::get_settings_value( 'max_future_bookings_per_customer' ) ) {
450 $customer = OsAuthHelper::get_logged_in_customer();
451 if ( $customer->get_future_bookings_count() >= OsSettingsHelper::get_settings_value( 'max_future_bookings_per_customer' ) ) {
452 $steps_controller = new OsStepsController();
453 $steps_controller->set_layout( 'none' );
454 $steps_controller->set_return_format( $format );
455 $steps_controller->format_render( 'partials/_limit_reached', [], [
456 'show_next_btn' => false,
457 'show_prev_btn' => false,
458 'is_first_step' => true,
459 'is_last_step' => true,
460 'is_pre_last_step' => false
461 ] );
462
463 return;
464 }
465 }
466
467 self::$step_to_prepare = $step_code;
468
469 if ( strpos( self::$step_to_prepare, '__' ) !== false ) {
470 // prepare parent step (used to run shared code between child steps)
471 $step_structure = explode( '__', self::$step_to_prepare );
472 $parent_step_function_name = 'prepare_step_' . $step_structure[0];
473 if ( method_exists( 'OsStepsHelper', $parent_step_function_name ) ) {
474 $result = self::$parent_step_function_name();
475 if ( is_wp_error( $result ) ) {
476 $error_data = $result->get_error_data();
477 $send_to_step = ( isset( $error_data['send_to_step'] ) && ! empty( $error_data['send_to_step'] ) ) ? $error_data['send_to_step'] : false;
478 wp_send_json( array(
479 'status' => LATEPOINT_STATUS_ERROR,
480 'message' => $result->get_error_message(),
481 'send_to_step' => $send_to_step
482 ) );
483
484 return;
485 }
486 }
487 }
488
489 // run prepare step function
490 $step_function_name = 'prepare_step_' . self::$step_to_prepare;
491 if ( method_exists( 'OsStepsHelper', $step_function_name ) ) {
492
493 $result = self::$step_function_name();
494 if ( is_wp_error( $result ) ) {
495 $error_data = $result->get_error_data();
496 $send_to_step = ( isset( $error_data['send_to_step'] ) && ! empty( $error_data['send_to_step'] ) ) ? $error_data['send_to_step'] : false;
497 wp_send_json( array(
498 'status' => LATEPOINT_STATUS_ERROR,
499 'message' => $result->get_error_message(),
500 'send_to_step' => $send_to_step
501 ) );
502
503 return;
504 }
505
506
507 $steps_controller = new OsStepsController();
508 self::$booking_object = apply_filters( 'latepoint_prepare_step_booking_object', self::$booking_object, self::$step_to_prepare );
509 self::$cart_object = apply_filters( 'latepoint_prepare_step_cart_object', self::$cart_object, self::$step_to_prepare );
510 self::$vars_for_view = apply_filters( 'latepoint_prepare_step_vars_for_view', self::$vars_for_view, self::$booking_object, self::$cart_object, self::$step_to_prepare );
511 $steps_controller->vars = self::$vars_for_view;
512 $steps_controller->vars['booking'] = self::$booking_object;
513 $steps_controller->vars['cart'] = self::$cart_object;
514 $steps_controller->vars['current_step_code'] = self::$step_to_prepare;
515 $steps_controller->vars['restrictions'] = self::$restrictions;
516 $steps_controller->vars['presets'] = self::$presets;
517 $steps_controller->set_layout( 'none' );
518 $steps_controller->set_return_format( $format );
519 $steps_controller->format_render( 'load_step', [], [
520 'fields_to_update' => self::$fields_to_update,
521 'step_code' => self::$step_to_prepare,
522 'show_next_btn' => self::can_step_show_next_btn( self::$step_to_prepare ),
523 'show_prev_btn' => self::can_step_show_prev_btn( self::$step_to_prepare ),
524 'is_first_step' => self::is_first_step( self::$step_to_prepare ),
525 'is_last_step' => self::is_last_step( self::$step_to_prepare ),
526 'is_pre_last_step' => self::is_pre_last_step( self::$step_to_prepare )
527 ] );
528 }
529 }
530
531 public static function retrieve_step_code( string $step_code ): string {
532 if ( empty( $step_code ) ) {
533 return false;
534 }
535 if ( in_array( $step_code, self::get_step_codes_in_order( true ) ) ) {
536 return $step_code;
537 } else {
538 // check if it's a parent step and return the first child
539 $step_codes = self::unflatten_steps( self::get_step_codes_in_order( true ) );
540 if ( ! empty( $step_codes[ $step_code ] ) ) {
541 return ( $step_code . '__' . array_key_first( $step_codes[ $step_code ] ) );
542 }
543 }
544
545 return '';
546 }
547
548 public static function remove_restricted_and_skippable_steps() {
549 self::remove_restricted_steps();
550 self::remove_preset_steps();
551 $steps = [];
552 foreach ( self::$step_codes_in_order as $step_code ) {
553 if ( ! self::should_step_be_skipped( $step_code ) ) {
554 $steps[] = $step_code;
555 }
556 }
557 self::$step_codes_in_order = $steps;
558 }
559
560 public static function remove_preset_steps(): void {
561
562 if (! empty( self::$presets['selected_bundle'] )) {
563 self::remove_steps_for_parent( 'booking' );
564 }else{
565 // if current step is agents or services selection and we have it preselected - skip to next step
566 if ( ! empty( self::$presets['selected_service'] ) ) {
567 $service = new OsServiceModel( self::$presets['selected_service'] );
568 if ( $service->id ) {
569 self::remove_step_by_name( 'booking__services' );
570 }
571 }
572 if ( ! empty( self::$presets['selected_location'] ) ) {
573 self::remove_step_by_name( 'booking__locations' );
574 }
575 if ( ! empty( self::$presets['selected_agent'] ) ) {
576 self::remove_step_by_name( 'booking__agents' );
577 }
578 if ( ! empty( self::$presets['selected_start_date'] ) && ! empty( self::$presets['selected_start_time'] ) ) {
579 self::remove_step_by_name( 'booking__datepicker' );
580 }
581 }
582
583 if ( self::is_bundle_scheduling() ) {
584 // booking a bundle that was already paid for, skip payment step
585 // TODO check if valid order item id
586 self::remove_step_by_name( 'payment__methods' );
587 self::remove_step_by_name( 'payment__times' );
588 self::remove_step_by_name( 'payment__portions' );
589 self::remove_step_by_name( 'payment__pay' );
590 self::remove_step_by_name( 'customer' );
591 }
592
593 /**
594 * Remove steps that should not be shown based on presets
595 *
596 * @param {array} $presets array of presets
597 * @param {OsCartItemModel} $active_cart_item instance of a current active cart item
598 * @param {OsBookingModel} $booking instance of current booking object
599 * @param {OsCartModel} $cart instance of current cart object
600 *
601 * @since 5.0.0
602 * @hook latepoint_remove_preset_steps
603 *
604 */
605 do_action( 'latepoint_remove_preset_steps', self::$presets, self::$active_cart_item, self::$booking_object, self::$cart_object );
606 }
607
608
609 public static function remove_restricted_steps(): void {
610 /**
611 * Remove steps that should not be shown based on restrictions
612 *
613 * @param {array} $restrictions array of restrictions
614 * @param {OsCartItemModel} $active_cart_item instance of a current active cart item
615 * @param {OsBookingModel} $booking instance of current booking object
616 * @param {OsCartModel} $cart instance of current cart object
617 *
618 * @since 5.0.0
619 * @hook latepoint_remove_restricted_steps
620 *
621 */
622 do_action( 'latepoint_remove_restricted_steps', self::$restrictions, self::$active_cart_item, self::$booking_object, self::$cart_object );
623 }
624
625
626 public static function remove_step_by_name( $step_code ) {
627 self::$step_codes_in_order = array_values( array_diff( self::$step_codes_in_order, [ $step_code ] ) );
628 }
629
630 public static function remove_steps_for_parent( $parent_step_code ) {
631 self::$step_codes_in_order = array_filter( self::$step_codes_in_order, function($step) use ($parent_step_code) { return strpos($step, $parent_step_code . '__') !== 0; });
632 }
633
634 public static function validate_presence( array $steps, array $rules ): array {
635
636 $errors = [];
637
638 // Check if each step in rules is present in steps
639 foreach ( $rules as $step_code => $conditions ) {
640 if ( ! in_array( $step_code, $steps ) ) {
641 // sometimes a rule is defined by the parent name, search for unflat list for parents
642 if ( ! in_array( $step_code, array_keys( self::unflatten_steps( $steps ) ) ) ) {
643 // translators: %s is the name of a step
644 $errors[] = sprintf( __( "Step %s is missing from steps array.", 'latepoint' ), $step_code );
645 }
646 }
647 }
648
649 // Check if each step in steps is present in rules
650 foreach ( $steps as $step_code ) {
651 if ( ! array_key_exists( $step_code, $rules ) ) {
652 // translators: %s is the name of a step
653 $errors[] = sprintf( __( "Step %s is not defined in the rules.", 'latepoint' ), $step_code );
654 }
655 }
656
657 return $errors;
658 }
659
660
661 public static function check_steps_for_errors( array $steps, array $steps_rules ): array {
662
663 $errors = [];
664
665 // check for step presence
666 $errors = array_merge( $errors, self::validate_presence( $steps, $steps_rules ) );
667
668 // check for correct order
669 $errors = array_merge( $errors, self::loop_step_rules_check( self::unflatten_steps( $steps ), $steps_rules ) );
670
671
672 /**
673 * Checks a list of steps for possible errors in order or existence and returns an array of errors if any
674 *
675 * @param {array} $errors list of errors found during a check
676 * @param {array} $steps list of steps that have to be checked
677 * @param {array} $role array of step rules to check against
678 * @returns {array} Filtered list of found errors
679 *
680 * @since 5.0.0
681 * @hook latepoint_check_steps_for_errors
682 *
683 */
684 return apply_filters( 'latepoint_check_steps_for_errors', $errors, $steps, $steps_rules );
685
686 }
687
688 public static function loop_step_rules_check( array $steps, array $steps_rules, string $parent = '' ): array {
689 $errors = [];
690 if ( empty( $steps ) ) {
691 return $errors;
692 }
693
694 $step_codes_to_validate = array_keys( $steps );
695
696 $errors = array_merge( $errors, self::validate_step_order( $step_codes_to_validate, $steps_rules, $parent ) );
697
698 foreach ( $steps as $parent_step_code => $step_children ) {
699 if ( ! empty( $step_children ) ) {
700 $errors = array_merge( $errors, self::loop_step_rules_check( $step_children, $steps_rules, $parent_step_code ) );
701 }
702 }
703
704 return $errors;
705 }
706
707 public static function validate_step_order( array $steps, array $rules, string $parent_code = '' ): array {
708 $errors = [];
709
710 foreach ( $steps as $step_code ) {
711 $rule_step_code = $parent_code ? $parent_code . '__' . $step_code : $step_code;
712
713 $current_index = array_search( $step_code, $steps );
714
715 if ( $current_index === false ) {
716 continue; // Skip if step is not in steps array
717 }
718
719 if ( isset( $rules[ $rule_step_code ]['after'] ) ) {
720 $after_index = array_search( $rules[ $rule_step_code ]['after'], $steps );
721 if ( $after_index === false || $after_index >= $current_index ) {
722 // translators: %1$s is step name with error, %2$s is step that it should come after
723 $errors[] = sprintf( __( 'Step "%1$s" has to come after "%2$s"', 'latepoint' ), self::get_step_label_by_code( $rule_step_code ), self::get_step_label_by_code( $rules[ $rule_step_code ]['after'], $parent_code ) );
724 }
725 }
726
727 if ( isset( $rules[ $rule_step_code ]['before'] ) ) {
728 $before_index = array_search( $rules[ $rule_step_code ]['before'], $steps );
729 if ( $before_index === false || $before_index <= $current_index ) {
730 // translators: %1$s is step name with error, %2$s is step that it should come before
731 $errors[] = sprintf( __( 'Step "%1$s" has to come before "%2$s"', 'latepoint' ), self::get_step_label_by_code( $rule_step_code ), self::get_step_label_by_code( $rules[ $rule_step_code ]['before'], $parent_code ) );
732 }
733 }
734 }
735
736 return $errors;
737 }
738
739 /**
740 *
741 * Returns a flat and ordered list of step codes
742 *
743 * @param bool $show_all_without_saving
744 *
745 * @return array
746 */
747 public static function get_step_codes_in_order( bool $show_all_without_saving = false ): array {
748 if ( $show_all_without_saving ) {
749 $steps_in_default_order = self::reorder_steps( self::get_step_codes_with_rules() );
750 $steps_in_saved_order = self::get_step_codes_in_order_from_db();
751
752 if ( empty( $steps_in_saved_order ) ) {
753 $step_codes_in_order = $steps_in_default_order;
754 } else {
755 $step_codes_in_order = self::cleanup_steps( $steps_in_saved_order, $steps_in_default_order );
756 }
757 } else {
758 if ( ! empty( self::$step_codes_in_order ) ) {
759 return self::$step_codes_in_order;
760 }
761 $steps_in_default_order = self::reorder_steps( self::get_step_codes_with_rules() );
762 $steps_in_saved_order = self::get_step_codes_in_order_from_db();
763
764 if ( empty( $steps_in_saved_order ) ) {
765 // save default active steps and order
766 $step_codes_in_order = $steps_in_default_order;
767 self::save_step_codes_in_order( $step_codes_in_order );
768 } else {
769 $step_codes_in_order = self::cleanup_steps( $steps_in_saved_order, $steps_in_default_order );
770 // save new order if different from what was saved before
771 if ( $step_codes_in_order != $steps_in_saved_order ) {
772 self::save_step_codes_in_order( $step_codes_in_order );
773 }
774 }
775 self::$step_codes_in_order = $step_codes_in_order;
776 }
777
778 return $step_codes_in_order;
779 }
780
781 public static function get_step_codes_in_order_from_db(): array {
782 $saved_order = OsSettingsHelper::get_settings_value( 'step_codes_in_order', '' );
783 if ( ! empty( $saved_order ) ) {
784 return explode( ',', $saved_order );
785 }
786
787 return [];
788 }
789
790 public static function insert_step( array $ordered_steps, string $new_step, array $new_step_rules ): array {
791 // Unflatten the ordered steps
792 $unflattened_steps = self::unflatten_steps( $ordered_steps );
793
794 // Insert the new step according to its rules
795 self::insert_step_recursive( $unflattened_steps, $new_step, $new_step_rules );
796
797 // Flatten the array again
798 $flattened_steps = self::flatten_steps( $unflattened_steps );
799
800 return $flattened_steps;
801 }
802
803 private static function insert_step_recursive( array &$steps, string $new_step, array $new_step_rules ) {
804 // Split the new step based on its parent structure
805 $parts = explode( '__', $new_step );
806 $actual_step = array_pop( $parts );
807 $parent = implode( '__', $parts ) ?: null;
808 $after = $new_step_rules['after'] ?? null;
809
810 // Insert the new step at the correct position in the unflattened steps
811 if ( $parent === null ) {
812 if ( $after === null ) {
813 // Insert at the beginning if no after rule
814 $steps = array_merge( [ $actual_step => [] ], $steps );
815 } else {
816 $position = array_search( $after, array_keys( $steps ) );
817 if ( $position !== false ) {
818 $steps = array_slice( $steps, 0, $position + 1, true ) + [ $actual_step => [] ] + array_slice( $steps, $position + 1, null, true );
819 }
820 }
821 } else {
822 // Recursively find the correct parent and insert
823 foreach ( $steps as $step_code => &$step_children ) {
824 if ( $step_code === $parent ) {
825 if ( $after === null ) {
826 $step_children = array_merge( [ $actual_step => [] ], $step_children );
827 } else {
828 $position = array_search( $after, array_keys( $step_children ) );
829 if ( $position !== false ) {
830 $step_children = array_slice( $step_children, 0, $position + 1, true ) + [ $actual_step => [] ] + array_slice( $step_children, $position + 1, null, true );
831 }
832 }
833
834 return;
835 } else {
836 self::insert_step_recursive( $step_children, $new_step, $new_step_rules );
837 }
838 }
839 }
840 }
841
842 public static function cleanup_steps( array $array_to_clean, array $reference_array ): array {
843 $filtered_array = [];
844 foreach ( $array_to_clean as $step_code ) {
845 if ( in_array( $step_code, $reference_array, true ) ) {
846 $filtered_array[] = $step_code;
847 }
848 }
849
850 $step_codes_with_rules = self::get_step_codes_with_rules();
851 foreach ( $reference_array as $step_code ) {
852 if ( ! in_array( $step_code, $filtered_array ) ) {
853 $step_rules = $step_codes_with_rules[ $step_code ] ?? [];
854 $filtered_array = self::insert_step( $filtered_array, $step_code, $step_rules );
855 }
856 }
857
858 return $filtered_array;
859 }
860
861 public static function get_step_name_without_parent( string $flat_step_name ): string {
862 $parts = explode( '__', $flat_step_name );
863
864 return end( $parts );
865 }
866
867
868 public static function set_default_presets(): array {
869 self::$presets = self::get_default_presets();
870
871 return self::$presets;
872 }
873
874 public static function get_default_presets(): array {
875 $default_presets = [
876 'selected_bundle' => false,
877 'selected_location' => false,
878 'selected_agent' => false,
879 'selected_service' => false,
880 'selected_duration' => false,
881 'selected_total_attendees' => false,
882 'selected_service_category' => false,
883 'selected_start_date' => false,
884 'selected_start_time' => false,
885 'order_item_id' => false,
886 'source_id' => false
887 ];
888
889 /**
890 * Sets default presets array of a StepHelper class
891 *
892 * @param {array} $presets Default array of presets set on StepHelper class
893 *
894 * @returns {array} Filtered array of presets
895 * @since 5.0.0
896 * @hook latepoint_get_default_presets
897 *
898 */
899 return apply_filters( 'latepoint_get_default_presets', $default_presets );
900 }
901
902 public static function set_default_restrictions(): array {
903 self::$restrictions = self::get_default_restrictions();
904
905 return self::$restrictions;
906 }
907
908 public static function get_default_restrictions(): array {
909 $default_restrictions = [
910 'show_locations' => false,
911 'show_agents' => false,
912 'show_services' => false,
913 'show_service_categories' => false,
914 'calendar_start_date' => false,
915 ];
916
917 /**
918 * Sets default restrictions array of a StepHelper class
919 *
920 * @param {array} $restrictions Default array of restrictions set on StepHelper class
921 *
922 * @returns {array} Filtered array of restrictions
923 * @since 5.0.0
924 * @hook latepoint_get_default_restrictions
925 *
926 */
927 return apply_filters( 'latepoint_get_default_restrictions', $default_restrictions );
928 }
929
930 public static function set_presets( array $presets = [] ): array {
931 self::set_default_presets();
932 // scheduling an item from existing order (bundle)
933 if ( isset( $presets['order_item_id'] ) ) {
934 self::$presets['order_item_id'] = $presets['order_item_id'];
935 }
936
937 // preselected service category
938 if ( isset( $presets['selected_service_category'] ) && is_numeric( $presets['selected_service_category'] ) ) {
939 self::$presets['selected_service_category'] = $presets['selected_service_category'];
940 }
941
942 // preselected location
943 if ( ! empty( $presets['selected_location'] ) && ( is_numeric( $presets['selected_location'] ) || ( $presets['selected_location'] == LATEPOINT_ANY_LOCATION ) ) ) {
944 self::$presets['selected_location'] = $presets['selected_location'];
945 }
946 // preselected agent
947 if ( ! empty( $presets['selected_agent'] ) && ( is_numeric( $presets['selected_agent'] ) || ( $presets['selected_agent'] == LATEPOINT_ANY_AGENT ) ) ) {
948 self::$presets['selected_agent'] = $presets['selected_agent'];
949 }
950
951 // preselected service
952 if ( isset( $presets['selected_service'] ) && is_numeric( $presets['selected_service'] ) ) {
953 self::$presets['selected_service'] = $presets['selected_service'];
954 }
955
956 // preselected bundle
957 if ( isset( $presets['selected_bundle'] ) && is_numeric( $presets['selected_bundle'] ) ) {
958 self::$presets['selected_bundle'] = $presets['selected_bundle'];
959 }
960
961 // preselected duration
962 if ( isset( $presets['selected_duration'] ) && is_numeric( $presets['selected_duration'] ) ) {
963 self::$presets['selected_duration'] = $presets['selected_duration'];
964 }
965
966 // preselected total attendees
967 if ( isset( $presets['selected_total_attendees'] ) && is_numeric( $presets['selected_total_attendees'] ) ) {
968 self::$presets['selected_total_attendees'] = $presets['selected_total_attendees'];
969 }
970
971 // preselected date
972 if ( isset( $presets['selected_start_date'] ) && OsTimeHelper::is_valid_date( $presets['selected_start_date'] ) ) {
973 self::$presets['selected_start_date'] = $presets['selected_start_date'];
974 }
975
976 // preselected time
977 if ( isset( $presets['selected_start_time'] ) && is_numeric( $presets['selected_start_time'] ) ) {
978 self::$presets['selected_start_time'] = $presets['selected_start_time'];
979 }
980
981 // set source id
982 if ( isset( $presets['source_id'] ) ) {
983 self::$presets['source_id'] = $presets['source_id'];
984 }
985
986 /**
987 * Sets presets array of a StepHelper class
988 *
989 * @param {array} $presets Array of presets set on StepHelper class
990 * @param {array} $presets Array of presets to be used to set presets on StepHelper class
991 *
992 * @returns {array} Filtered array of presets
993 * @since 5.0.0
994 * @hook latepoint_set_presets
995 *
996 */
997 return apply_filters( 'latepoint_set_presets', self::$presets, $presets );
998 }
999
1000
1001 public static function set_restrictions( array $restrictions = [] ): array {
1002 self::set_default_restrictions();
1003 if ( ! empty( $restrictions ) ) {
1004 // filter locations
1005 if ( isset( $restrictions['show_locations'] ) ) {
1006 self::$restrictions['show_locations'] = $restrictions['show_locations'];
1007 }
1008
1009 // filter agents
1010 if ( isset( $restrictions['show_agents'] ) ) {
1011 self::$restrictions['show_agents'] = $restrictions['show_agents'];
1012 }
1013
1014 // filter service category
1015 if ( isset( $restrictions['show_service_categories'] ) ) {
1016 self::$restrictions['show_service_categories'] = $restrictions['show_service_categories'];
1017 }
1018
1019 // filter services
1020 if ( isset( $restrictions['show_services'] ) ) {
1021 self::$restrictions['show_services'] = $restrictions['show_services'];
1022 }
1023
1024 // preselected calendar start date
1025 if ( isset( $restrictions['calendar_start_date'] ) && OsTimeHelper::is_valid_date( $restrictions['calendar_start_date'] ) ) {
1026 self::$restrictions['calendar_start_date'] = $restrictions['calendar_start_date'];
1027 }
1028
1029 // restriction in settings can override it
1030 if ( OsTimeHelper::is_valid_date( OsSettingsHelper::get_settings_value( 'earliest_possible_booking' ) ) ) {
1031 self::$restrictions['calendar_start_date'] = OsSettingsHelper::get_settings_value( 'earliest_possible_booking' );
1032 }
1033
1034
1035 }
1036
1037 /**
1038 * Sets restrictions array of a StepHelper class
1039 *
1040 * @param {array} $restrictions Array of restrictions set on StepHelper class
1041 * @param {array} $restrictions Array of restrictions to be used to set restrictions on StepHelper class
1042 *
1043 * @returns {array} Filtered array of restrictions
1044 * @since 5.0.0
1045 * @hook latepoint_set_restrictions
1046 *
1047 */
1048 return apply_filters( 'latepoint_set_restrictions', self::$restrictions, $restrictions );
1049 }
1050
1051 /**
1052 * Sets booking object properties when a single option is available
1053 *
1054 * If a booking object has a service selected and only one agent is offering that service -
1055 * that agent will be preselected. Same for location
1056 *
1057 * @return OsBookingModel
1058 */
1059 public static function set_booking_properties_for_single_options(): OsBookingModel {
1060
1061 // if only 1 location exists or assigned to selected agent - set it to this booking object
1062 if ( OsLocationHelper::count_locations() == 1 ) {
1063 self::$booking_object->location_id = OsLocationHelper::get_default_location_id();
1064 }
1065 // if only 1 agent exists - set it to this booking object
1066 if ( OsAgentHelper::count_agents() == 1 ) {
1067 self::$booking_object->agent_id = OsAgentHelper::get_default_agent_id();
1068 }
1069
1070 return self::$booking_object;
1071 }
1072
1073 public static function set_booking_object( $booking_object_params = [] ): OsBookingModel {
1074 self::$booking_object = new OsBookingModel();
1075 self::$booking_object->set_data( $booking_object_params );
1076 if ( ! empty( $booking_object_params['intent_key'] ) ) {
1077 self::$booking_object->intent_key = $booking_object_params['intent_key'];
1078 }
1079
1080 // set based on presets
1081
1082 // preselected service
1083 if ( isset( self::$presets['selected_service'] ) && is_numeric( self::$presets['selected_service'] ) ) {
1084 self::$booking_object->service_id = self::$presets['selected_service'];
1085 $service = new OsServiceModel( self::$booking_object->service_id );
1086 self::$booking_object->service = $service;
1087 if ( empty( $booking_object_params['duration'] ) ) {
1088 self::$booking_object->duration = $service->duration;
1089 }
1090 if ( empty( $booking_object_params['total_attendees'] ) ) {
1091 self::$booking_object->total_attendees = $service->capacity_min;
1092 }
1093 }
1094
1095 // preselected agent
1096 if ( ! empty( self::$presets['selected_agent'] ) && ( is_numeric( self::$presets['selected_agent'] ) || ( self::$presets['selected_agent'] == LATEPOINT_ANY_AGENT ) ) ) {
1097 self::$booking_object->agent_id = self::$presets['selected_agent'];
1098 }
1099
1100 // preselected location
1101 if ( ! empty( self::$presets['selected_location'] ) && ( is_numeric( self::$presets['selected_location'] ) || ( self::$presets['selected_location'] == LATEPOINT_ANY_LOCATION ) ) ) {
1102 self::$booking_object->location_id = self::$presets['selected_location'];
1103 }
1104
1105 // preselected duration
1106 if ( isset( self::$presets['selected_duration'] ) && is_numeric( self::$presets['selected_duration'] ) ) {
1107 self::$booking_object->duration = self::$presets['selected_duration'];
1108 }
1109 // preselected attendees
1110 if ( isset( self::$presets['selected_total_attendees'] ) && is_numeric( self::$presets['selected_total_attendees'] ) ) {
1111 self::$booking_object->total_attendees = self::$presets['selected_total_attendees'];
1112 }
1113 // preselected date
1114 if ( isset( self::$presets['selected_start_date'] ) && OsTimeHelper::is_valid_date( self::$presets['selected_start_date'] ) ) {
1115 self::$booking_object->start_date = self::$presets['selected_start_date'];
1116 }
1117 // preselected time
1118 if ( isset( self::$presets['selected_start_time'] ) && is_numeric( self::$presets['selected_start_time'] ) ) {
1119 self::$booking_object->start_time = self::$presets['selected_start_time'];
1120 }
1121 // preselected time
1122 if ( isset( self::$presets['order_item_id'] ) && is_numeric( self::$presets['order_item_id'] ) ) {
1123 self::$booking_object->order_item_id = self::$presets['order_item_id'];
1124 // TODO - move to pro
1125 // it's a bundle, preset values from a bundle
1126 $order_item = new OsOrderItemModel( self::$booking_object->order_item_id );
1127 $bundle = new OsBundleModel( $order_item->get_item_data_value_by_key( 'bundle_id' ) );
1128 self::$booking_object->total_attendees = $bundle->total_attendees_for_service( self::$booking_object->service_id );
1129 self::$booking_object->duration = $bundle->duration_for_service( self::$booking_object->service_id );
1130 }
1131
1132
1133 // get buffers from service and set to booking object
1134 self::$booking_object->set_buffers();
1135 self::$booking_object->calculate_end_date_and_time();
1136 self::$booking_object->customer_id = OsAuthHelper::get_logged_in_customer_id();
1137
1138 return self::$booking_object;
1139 }
1140
1141 public static function load_order_object( $order_id = false ) {
1142 if ( $order_id ) {
1143 self::$order_object = new OsOrderModel( $order_id );
1144 } else {
1145 self::$order_object = new OsOrderModel();
1146 }
1147 }
1148
1149 public static function is_bundle_scheduling() {
1150 return ! empty( self::$booking_object->order_item_id );
1151 }
1152
1153 /**
1154 * Checks if there were supposed to be some fields for this step - now they have to be carried over to next step, because this step is skipped
1155 *
1156 * @param string $current_step_code
1157 * @param string $next_step_code
1158 *
1159 * @return array
1160 */
1161 public static function carry_preset_fields_to_next_step( string $current_step_code, string $next_step_code ): void {
1162 if ( ! empty( self::$preset_fields[ $current_step_code ] ) ) {
1163 self::$preset_fields[ $next_step_code ] = array_merge( self::$preset_fields[ $next_step_code ], self::$preset_fields[ $current_step_code ] );
1164 }
1165 }
1166
1167 public static function should_step_be_skipped( string $step_code ): bool {
1168 $skip = false;
1169
1170 switch ( $step_code ) {
1171 case 'booking__agents':
1172 if ( OsAgentHelper::count_agents() == 1 ) {
1173 $skip = true;
1174 }
1175 if ( self::$active_cart_item->is_bundle() ) {
1176 $skip = true;
1177 }
1178 break;
1179 case 'booking__locations':
1180 if ( OsLocationHelper::count_locations() == 1 ) {
1181 $skip = true;
1182 }
1183 if ( self::$active_cart_item->is_bundle() ) {
1184 $skip = true;
1185 }
1186 break;
1187 case 'booking__datepicker':
1188 if ( self::$active_cart_item->is_bundle() ) {
1189 $skip = true;
1190 }
1191 break;
1192 case 'booking__services':
1193 if ( self::is_bundle_scheduling() ) {
1194 $skip = true;
1195 }
1196 break;
1197 case 'payment__times':
1198 case 'payment__portions':
1199 case 'payment__methods':
1200 case 'payment__processors':
1201 case 'payment__pay':
1202 if ( self::is_bundle_scheduling() || empty( OsPaymentsHelper::get_enabled_payment_times() ) ) {
1203 // scheduling a bundle or no enabled payment times
1204 $skip = true;
1205 self::set_zero_cost_payment_fields();
1206 } else {
1207 if(self::$cart_object->is_empty()){
1208 $skip = true;
1209 }else{
1210 $original_amount = self::$cart_object->get_subtotal();
1211 $after_coupons_amount = self::$cart_object->get_total();
1212 $deposit_amount = self::$cart_object->deposit_amount_to_charge();
1213 if ( $original_amount > 0 && $after_coupons_amount <= 0 ) {
1214 // original price was set, but coupon was applied and charge amount is now 0, we can skip step, even if deposit is not 0
1215 $is_zero_cost = true;
1216 } else {
1217 if ( $after_coupons_amount <= 0 && $deposit_amount <= 0 ) {
1218 $is_zero_cost = true;
1219 } else {
1220 $is_zero_cost = false;
1221 }
1222 }
1223 // if nothing to charge - don't show it, no matter what
1224 if ( $is_zero_cost && ! OsSettingsHelper::is_env_demo() ) {
1225 $skip = true;
1226 self::set_zero_cost_payment_fields();
1227 } else {
1228 if ( $step_code == 'payment__times' ) {
1229 if ( ! empty( self::$cart_object->payment_time ) ) {
1230 $skip = true;
1231 } else {
1232 // try to check if one only available and preset it
1233 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
1234 if ( count( $enabled_payment_times ) == 1 ) {
1235 $skip = true;
1236 self::$cart_object->payment_time = array_key_first( $enabled_payment_times );
1237 self::$preset_fields['verify']['cart[payment_time]'] = OsFormHelper::hidden_field( 'cart[payment_time]', self::$cart_object->payment_time, [ 'skip_id' => true ] );
1238 // assign preset field value for next step
1239 self::$preset_fields['payment__portions']['cart[payment_time]'] = OsFormHelper::hidden_field( 'cart[payment_time]', self::$cart_object->payment_time, [ 'skip_id' => true ] );
1240 self::carry_preset_fields_to_next_step( 'payment__times', 'payment__portions' );
1241 }
1242 }
1243 }
1244 if ( $step_code == 'payment__portions' ) {
1245 if ( ! empty( self::$cart_object->payment_portion ) ) {
1246 $skip = true;
1247 } else {
1248 if ( $is_zero_cost || ( self::$cart_object->payment_time == LATEPOINT_PAYMENT_TIME_LATER ) || ( $after_coupons_amount > 0 && $deposit_amount <= 0 ) ) {
1249 // zero cost, pay later or 0 deposit, means it's a full portion payment preset
1250 self::$cart_object->payment_portion = LATEPOINT_PAYMENT_PORTION_FULL;
1251 } elseif ( $deposit_amount > 0 && $after_coupons_amount <= 0 ) {
1252 self::$cart_object->payment_portion = LATEPOINT_PAYMENT_PORTION_DEPOSIT;
1253 }
1254
1255 if ( ! empty( self::$cart_object->payment_portion ) ) {
1256 $skip = true;
1257 self::$preset_fields['verify']['cart[payment_portion]'] = OsFormHelper::hidden_field( 'cart[payment_portion]', self::$cart_object->payment_portion, [ 'skip_id' => true ] );
1258 self::$preset_fields['payment__methods']['cart[payment_portion]'] = OsFormHelper::hidden_field( 'cart[payment_portion]', self::$cart_object->payment_portion, [ 'skip_id' => true ] );
1259
1260 self::carry_preset_fields_to_next_step( 'payment__portions', 'payment__methods' );
1261 }
1262 }
1263 }
1264 if ( $step_code == 'payment__methods' ) {
1265 if ( ! empty( self::$cart_object->payment_method ) ) {
1266 $skip = true;
1267 } else {
1268 if ( self::$cart_object->payment_time ) {
1269 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( self::$cart_object->payment_time );
1270 if ( count( $enabled_payment_methods ) <= 1 ) {
1271 $skip = true;
1272 self::$cart_object->payment_method = array_key_first( $enabled_payment_methods );
1273 self::$preset_fields['verify']['cart[payment_method]'] = OsFormHelper::hidden_field( 'cart[payment_method]', self::$cart_object->payment_method, [ 'skip_id' => true ] );
1274 self::$preset_fields['payment__processors']['cart[payment_method]'] = OsFormHelper::hidden_field( 'cart[payment_method]', self::$cart_object->payment_method, [ 'skip_id' => true ] );
1275
1276 self::carry_preset_fields_to_next_step( 'payment__methods', 'payment__processors' );
1277 }
1278 }
1279 }
1280 }
1281 if ( $step_code == 'payment__processors' ) {
1282 if ( ! empty( self::$cart_object->payment_processor ) ) {
1283 $skip = true;
1284 } else {
1285 if ( self::$cart_object->payment_time && self::$cart_object->payment_method ) {
1286 $enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( self::$cart_object->payment_time, self::$cart_object->payment_method );
1287 if ( count( $enabled_payment_processors ) <= 1 ) {
1288 $skip = true;
1289 self::$cart_object->payment_processor = array_key_first( $enabled_payment_processors );
1290 self::$preset_fields['verify']['cart[payment_processor]'] = OsFormHelper::hidden_field( 'cart[payment_processor]', self::$cart_object->payment_processor, [ 'skip_id' => true ] );
1291 self::$preset_fields['payment__pay']['cart[payment_processor]'] = OsFormHelper::hidden_field( 'cart[payment_processor]', self::$cart_object->payment_processor, [ 'skip_id' => true ] );
1292
1293 self::carry_preset_fields_to_next_step( 'payment__processors', 'payment__pay' );
1294 }
1295 }
1296 }
1297 }
1298 if ( $step_code == 'payment__pay' ) {
1299 if ( self::$cart_object->payment_time == LATEPOINT_PAYMENT_TIME_LATER || empty( OsPaymentsHelper::get_enabled_payment_times() ) ) {
1300 $skip = true;
1301 }
1302 }
1303 }
1304 }
1305 }
1306 break;
1307 }
1308
1309 $skip = apply_filters( 'latepoint_should_step_be_skipped', $skip, $step_code, self::$cart_object, self::$active_cart_item, self::$booking_object );
1310
1311 return $skip;
1312 }
1313
1314 public static function set_zero_cost_payment_fields() {
1315 self::$preset_fields['verify']['cart[payment_time]'] = OsFormHelper::hidden_field( 'cart[payment_time]', LATEPOINT_PAYMENT_TIME_LATER, [ 'skip_id' => true ] );
1316 self::$preset_fields['verify']['cart[payment_method]'] = OsFormHelper::hidden_field( 'cart[payment_method]', 'other', [ 'skip_id' => true ] );
1317 self::$preset_fields['verify']['cart[payment_processor]'] = OsFormHelper::hidden_field( 'cart[payment_processor]', 'other', [ 'skip_id' => true ] );
1318 self::$preset_fields['verify']['cart[payment_portion]'] = OsFormHelper::hidden_field( 'cart[payment_portion]', LATEPOINT_PAYMENT_PORTION_FULL, [ 'skip_id' => true ] );
1319 }
1320
1321 public static function output_preset_fields( string $step_code ) {
1322 if ( ! empty( self::$preset_fields[ $step_code ] ) ) {
1323 foreach ( self::$preset_fields[ $step_code ] as $preset_field_html ) {
1324 echo $preset_field_html;
1325 }
1326 }
1327 }
1328
1329 public static function get_next_step_code( $current_step_code ) {
1330 $all_step_codes = self::get_step_codes_in_order( true );
1331 $active_step_codes = self::get_step_codes_in_order();
1332 $current_step_index = array_search( $current_step_code, $all_step_codes );
1333 if ( $current_step_index === false || ( ( $current_step_index + 1 ) == count( $all_step_codes ) ) ) {
1334 // no more steps or not found
1335 return false;
1336 }
1337 $next_step_code = $all_step_codes[ $current_step_index + 1 ];
1338
1339 if ( ! in_array( $next_step_code, $active_step_codes ) ) {
1340 // if is skipped - get next step in order and try again
1341 $next_step_code = self::get_next_step_code( $next_step_code );
1342 }
1343
1344 /**
1345 * Get the next step code, based on a current step
1346 *
1347 * @param {string} $next_step_code The next step code
1348 * @param {string} $current_step_code The current step code
1349 * @param {array} $all_step_codes List of all step codes
1350 * @param {array} $active_step_codes List of active step codes
1351 * @returns {string} The filtered next step code
1352 *
1353 * @since 5.0.16
1354 * @hook latepoint_get_next_step_code
1355 *
1356 */
1357 return apply_filters('latepoint_get_next_step_code', $next_step_code, $current_step_code, $all_step_codes, $active_step_codes);
1358 }
1359
1360 public static function get_prev_step_code( $current_step_code ) {
1361 $all_step_codes = self::get_step_codes_in_order( true );
1362 $current_step_index = array_search( $current_step_code, $all_step_codes );
1363
1364 if ( ! $current_step_index ) {
1365 // first step or not found - return the same code
1366 return $current_step_code;
1367 }
1368 $prev_step_code = $all_step_codes[ $current_step_code - 1 ];
1369 if ( self::should_step_be_skipped( $prev_step_code ) ) {
1370 // if skipped - get previous in order and try again
1371 $prev_step_code = self::get_prev_step_code( $prev_step_code );
1372 }
1373
1374 /**
1375 * Get the next step code, based on a current step
1376 *
1377 * @param {string} $next_step_code The next step code
1378 * @param {string} $current_step_code The current step code
1379 * @param {array} $all_step_codes List of all step codes
1380 * @returns {string} The filtered next step code
1381 *
1382 * @since 5.0.16
1383 * @hook latepoint_get_previous_step_code
1384 *
1385 */
1386 return apply_filters('latepoint_get_previous_step_code', $prev_step_code, $current_step_code, $all_step_codes);
1387 }
1388
1389
1390 public static function is_first_step( $step_code ) {
1391 $step_index = array_search( $step_code, self::get_step_codes_in_order() );
1392
1393 return $step_index == 0;
1394 }
1395
1396 public static function is_last_step( $step_code ) {
1397 $step_index = array_search( $step_code, self::get_step_codes_in_order() );
1398
1399 return ( ( $step_index + 1 ) == count( self::get_step_codes_in_order() ) );
1400 }
1401
1402 public static function is_pre_last_step( $step_code ) {
1403 $next_step_code = self::get_next_step_code( $step_code );
1404 $step_index = array_search( $next_step_code, self::get_step_codes_in_order() );
1405
1406 return ( ( $step_index + 1 ) == count( self::get_step_codes_in_order() ) );
1407 }
1408
1409 public static function can_step_show_prev_btn( $step_code ) {
1410 $step_index = array_search( $step_code, self::get_step_codes_in_order() );
1411 // if first or last step
1412 if ( $step_index == 0 || ( ( $step_index + 1 ) == count( self::get_step_codes_in_order() ) ) ) {
1413 return false;
1414 } else {
1415 return true;
1416 }
1417 }
1418
1419 public static function get_next_btn_label_for_step( $step_code ) {
1420 $label = __( 'Next', 'latepoint' );
1421 $custom_labels = [
1422 'payment__pay' => __( 'Submit', 'latepoint' ),
1423 'verify' => OsStepsHelper::should_step_be_skipped( 'payment__pay' ) ? __( 'Submit', 'latepoint' ) : __( 'Checkout', 'latepoint' )
1424 ];
1425
1426
1427 /**
1428 * Returns an array of custom labels for "next" button with step codes as keys
1429 *
1430 * @param {array} $custom_labels Current array of labels for "next" button
1431 *
1432 * @returns {array} Filtered array of labels for "next" button
1433 * @since 4.7.0
1434 * @hook latepoint_next_btn_labels_for_steps
1435 *
1436 */
1437 $custom_labels = apply_filters( 'latepoint_next_btn_labels_for_steps', $custom_labels );
1438 if ( ! empty( $custom_labels[ $step_code ] ) ) {
1439 $label = $custom_labels[ $step_code ];
1440 }
1441
1442 return $label;
1443 }
1444
1445 public static function can_step_show_next_btn( $step_code ) {
1446 $step_show_btn_rules = [
1447 'booking__services' => false,
1448 'booking__agents' => false,
1449 'booking__datepicker' => false,
1450 'customer' => true,
1451 'payment__times' => false,
1452 'payment__portions' => false,
1453 'payment__methods' => false,
1454 'payment__pay' => false,
1455 'verify' => true,
1456 'confirmation' => false
1457 ];
1458
1459 /**
1460 * Returns an array of rules of whether to show a next button on not, step codes are keys in this array
1461 *
1462 * @param {array} $step_show_btn_rules Current array of labels for "next" button
1463 * @param {string} $step_code Current array of labels for "next" button
1464 *
1465 * @returns {array} Filtered array of labels for "next" button
1466 * @since 4.7.0
1467 * @hook latepoint_step_show_next_btn_rules
1468 *
1469 */
1470 $step_show_btn_rules = apply_filters( 'latepoint_step_show_next_btn_rules', $step_show_btn_rules, $step_code );
1471
1472 return $step_show_btn_rules[ $step_code ] ?? false;
1473 }
1474
1475 /**
1476 * @throws Exception
1477 */
1478 public static function add_current_item_to_cart() {
1479 if ( self::$active_cart_item->is_new_record() ) {
1480 if ( self::$active_cart_item->is_bundle() ) {
1481 self::$cart_object->add_item( self::$active_cart_item );
1482 self::$fields_to_update['active_cart_item[id]'] = self::$active_cart_item->id;
1483 } elseif ( self::$active_cart_item->is_booking() ) {
1484 // only do this for new cart item, if modifying existing one - then the set_active_cart_item method will take care of updating it
1485 if ( self::$booking_object->is_bookable( true, true ) ) {
1486 // set it again as booking object might have changed if agent or location were set to ANY, they are assigned now
1487 self::set_active_cart_item_object();
1488 if ( self::is_bundle_scheduling() ) {
1489 // we don't need to use a cart for bundle scheduling
1490 } else {
1491 self::$cart_object->add_item( self::$active_cart_item );
1492 self::$fields_to_update['active_cart_item[id]'] = self::$active_cart_item->id;
1493 }
1494 self::reset_booking_object();
1495
1496 return true;
1497 } else {
1498 throw new Exception( implode( ',', self::$booking_object->get_error_messages() ) );
1499 }
1500 }
1501 }
1502 }
1503
1504 public static function process_step_booking() {
1505
1506 if ( ! self::is_bundle_scheduling() ) {
1507 // check if we are processing the last step of a booking sequence
1508 $booking_steps = [];
1509 foreach ( self::$step_codes_in_order as $step_code ) {
1510 if ( strpos( $step_code, 'booking__' ) !== false ) {
1511 $booking_steps[] = $step_code;
1512 }
1513 }
1514 if ( end( $booking_steps ) == self::$step_to_process ) {
1515 try {
1516 self::add_current_item_to_cart();
1517 } catch ( Exception $e ) {
1518 return new WP_Error( 'booking_slot_not_available', $e->getMessage() );
1519 }
1520 }
1521 }
1522
1523
1524 }
1525
1526 public static function reset_booking_object() {
1527 self::set_booking_object( [] );
1528 }
1529
1530 public static function prepare_step_booking() {
1531
1532 }
1533
1534
1535 // SERVICES
1536
1537 public static function process_step_booking__services() {
1538 }
1539
1540 public static function prepare_step_booking__services() {
1541 $bundles_model = new OsBundleModel();
1542 $bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models();
1543
1544 $services_model = new OsServiceModel();
1545 $show_selected_services_arr = self::$restrictions['show_services'] ? explode( ',', self::$restrictions['show_services'] ) : false;
1546 $show_service_categories_arr = self::$restrictions['show_service_categories'] ? explode( ',', self::$restrictions['show_service_categories'] ) : false;
1547 $preselected_category = self::$presets['selected_service_category'];
1548 $preselected_duration = self::$presets['selected_duration'];
1549 $preselected_total_attendees = self::$presets['selected_total_attendees'];
1550
1551 $connected_ids = OsConnectorHelper::get_connected_object_ids( 'service_id', [
1552 'agent_id' => self::$booking_object->agent_id,
1553 'location_id' => self::$booking_object->location_id
1554 ] );
1555 // if "show only specific services" is selected (restrictions) - remove ids that are not found in connection
1556 $show_services_arr = ( ! empty( $show_selected_services_arr ) && ! empty( $connected_ids ) ) ? array_intersect( $connected_ids, $show_selected_services_arr ) : $connected_ids;
1557 if ( ! empty( $show_services_arr ) ) {
1558 $services_model->where_in( 'id', $show_services_arr );
1559 }
1560
1561 $services = $services_model->should_be_active()->should_not_be_hidden()->order_by( 'order_number asc' )->get_results_as_models();
1562
1563 self::$vars_for_view['show_services_arr'] = $show_services_arr;
1564 self::$vars_for_view['show_service_categories_arr'] = $show_service_categories_arr;
1565 self::$vars_for_view['preselected_category'] = $preselected_category;
1566 self::$vars_for_view['preselected_duration'] = $preselected_duration;
1567 self::$vars_for_view['preselected_total_attendees'] = $preselected_total_attendees;
1568 self::$vars_for_view['services'] = $services;
1569 self::$vars_for_view['bundles'] = $bundles;
1570 }
1571
1572 // AGENTS
1573
1574 public static function process_step_booking__agents() {
1575 }
1576
1577 public static function prepare_step_booking__agents() {
1578 $agents_model = new OsAgentModel();
1579
1580 $show_selected_agents_arr = ( self::$restrictions['show_agents'] ) ? explode( ',', self::$restrictions['show_agents'] ) : false;
1581 // Find agents that actually offer selected service (if selected) at selected location (if selected)
1582 $connected_ids = OsConnectorHelper::get_connected_object_ids( 'agent_id', [
1583 'service_id' => self::$booking_object->service_id,
1584 'location_id' => self::$booking_object->location_id
1585 ] );
1586
1587 // If date/time is selected - filter agents who are available at that time
1588 if ( self::$booking_object->start_date && self::$booking_object->start_time ) {
1589 $available_agent_ids = [];
1590 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( self::$booking_object );
1591 foreach ( $connected_ids as $agent_id ) {
1592 $booking_request->agent_id = $agent_id;
1593 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
1594 $available_agent_ids[] = $agent_id;
1595 }
1596 }
1597 $connected_ids = array_intersect( $available_agent_ids, $connected_ids );
1598 }
1599
1600
1601 // if show only specific agents are selected (restrictions) - remove ids that are not found in connection
1602 $show_agents_arr = ( $show_selected_agents_arr ) ? array_intersect( $connected_ids, $show_selected_agents_arr ) : $connected_ids;
1603 if ( ! empty( $show_agents_arr ) ) {
1604 $agents_model->where_in( 'id', $show_agents_arr );
1605 $agents = $agents_model->should_be_active()->get_results_as_models();
1606 self::$vars_for_view['agents'] = $agents;
1607 } else {
1608 // no available or connected agents
1609 self::$vars_for_view['agents'] = [];
1610 }
1611 }
1612
1613
1614 // DATEPICKER
1615
1616 public static function prepare_step_booking__datepicker() {
1617 if ( empty( self::$booking_object->agent_id ) ) {
1618 self::$booking_object->agent_id = LATEPOINT_ANY_AGENT;
1619 }
1620 self::$vars_for_view['calendar_start_date'] = self::$restrictions['calendar_start_date'] ? self::$restrictions['calendar_start_date'] : 'today';
1621 self::$vars_for_view['timeshift_minutes'] = OsTimeHelper::get_timezone_shift_in_minutes_from_session();
1622 self::$vars_for_view['timezone_name'] = OsTimeHelper::get_timezone_name_from_session();
1623 }
1624
1625 public static function process_step_booking__datepicker() {
1626 }
1627
1628
1629 // CONTACT
1630
1631
1632 public static function prepare_step_customer() {
1633
1634 if ( OsAuthHelper::is_customer_logged_in() ) {
1635 self::$booking_object->customer = OsAuthHelper::get_logged_in_customer();
1636 self::$booking_object->customer_id = self::$booking_object->customer->id;
1637 } else {
1638 self::$booking_object->customer = new OsCustomerModel();
1639 }
1640
1641 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1642 self::$vars_for_view['customer'] = self::$booking_object->customer;
1643 }
1644
1645 private static function customer_params(): array {
1646 $params = OsParamsHelper::get_param( 'customer' );
1647 if ( empty( $params ) ) {
1648 return [];
1649 }
1650
1651 $customer_params = OsParamsHelper::permit_params( $params, [
1652 'first_name',
1653 'last_name',
1654 'email',
1655 'phone',
1656 'notes',
1657 'password',
1658 'password_confirmation'
1659 ] );
1660
1661 if ( ! empty( $customer_params['first_name'] ) ) {
1662 $customer_params['first_name'] = sanitize_text_field( $customer_params['first_name'] );
1663 }
1664 if ( ! empty( $customer_params['last_name'] ) ) {
1665 $customer_params['last_name'] = sanitize_text_field( $customer_params['last_name'] );
1666 }
1667 if ( ! empty( $customer_params['email'] ) ) {
1668 $customer_params['email'] = sanitize_email( $customer_params['email'] );
1669 }
1670 if ( ! empty( $customer_params['phone'] ) ) {
1671 $customer_params['phone'] = sanitize_text_field( $customer_params['phone'] );
1672 }
1673 if ( ! empty( $customer_params['notes'] ) ) {
1674 $customer_params['notes'] = sanitize_textarea_field( $customer_params['notes'] );
1675 }
1676
1677 /**
1678 * Filtered customer params for steps
1679 *
1680 * @param {array} $customer_params a filtered array of customer params
1681 * @param {array} $params unfiltered 'customer' params
1682 * @returns {array} $customer_params a filtered array of customer params
1683 *
1684 * @since 5.0.14
1685 * @hook latepoint_customer_params_on_steps
1686 *
1687 */
1688 return apply_filters( 'latepoint_customer_params_on_steps', $customer_params, $params );
1689 }
1690
1691 public static function process_step_customer() {
1692 $status = LATEPOINT_STATUS_SUCCESS;
1693
1694 $customer_params = self::customer_params();
1695
1696 $logged_in_customer = OsAuthHelper::get_logged_in_customer();
1697
1698
1699 if ( $logged_in_customer ) {
1700 // LOGGED IN ALREADY
1701 // Check if they are changing the email on file
1702 if ( $logged_in_customer->email != $customer_params['email'] ) {
1703 // Check if other customer already has this email
1704 $customer = new OsCustomerModel();
1705 $customer_with_email_exist = $customer->where( array(
1706 'email' => $customer_params['email'],
1707 'id !=' => $logged_in_customer->id
1708 ) )->set_limit( 1 )->get_results_as_models();
1709 // check if another customer (or if wp user login enabled - another wp user) exists with the email that this user tries to update to
1710 if ( $customer_with_email_exist || ( OsAuthHelper::wp_users_as_customers() && email_exists( $customer_params['email'] ) ) ) {
1711 $status = LATEPOINT_STATUS_ERROR;
1712 $response_html = __( 'Another customer is registered with this email.', 'latepoint' );
1713 }
1714 }
1715 } else {
1716 // NEW REGISTRATION (NOT LOGGED IN)
1717 if ( OsAuthHelper::wp_users_as_customers() ) {
1718 // WP USERS AS CUSTOMERS
1719 if ( email_exists( $customer_params['email'] ) ) {
1720 // wordpress user with this email already exists, ask to login
1721 $status = LATEPOINT_STATUS_ERROR;
1722 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1723 } else {
1724 // wp user does not exist - search for latepoint customer
1725 $customer = new OsCustomerModel();
1726 $customer = $customer->where( array( 'email' => $customer_params['email'] ) )->set_limit( 1 )->get_results_as_models();
1727 if ( $customer ) {
1728 // latepoint customer with this email exits, create wp user for them
1729 $wp_user = OsCustomerHelper::create_wp_user_for_customer( $customer );
1730 $status = LATEPOINT_STATUS_ERROR;
1731 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1732 } else {
1733 // no latepoint customer or wp user with this email found, can proceed
1734 }
1735 }
1736 } else {
1737 // LATEPOINT CUSTOMERS
1738 $customer = new OsCustomerModel();
1739 $customer_exist = $customer->where( array( 'email' => $customer_params['email'] ) )->set_limit( 1 )->get_results_as_models();
1740 if ( $customer_exist ) {
1741 // customer with this email exists - check if current customer was registered as a guest
1742 if ( OsSettingsHelper::is_on( 'steps_hide_login_register_tabs' ) || ( $customer_exist->can_login_without_password() && ! OsSettingsHelper::is_on( 'steps_require_setting_password' ) ) ) {
1743 // guest account, login automatically
1744 $status == LATEPOINT_STATUS_SUCCESS;
1745 OsAuthHelper::authorize_customer( $customer_exist->id );
1746 } else {
1747 // Not a guest account, ask to login
1748 $status = LATEPOINT_STATUS_ERROR;
1749 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1750 }
1751 } else {
1752 // no latepoint customer with this email found, can proceed
1753 }
1754 }
1755 // if not logged in - check if password has to be set
1756 if ( ! OsAuthHelper::is_customer_logged_in() && OsSettingsHelper::is_on( 'steps_require_setting_password' ) ) {
1757 if ( ! empty( $customer_params['password'] ) && $customer_params['password'] == $customer_params['password_confirmation'] ) {
1758 $customer_params['password'] = OsAuthHelper::hash_password( $customer_params['password'] );
1759 $customer_params['is_guest'] = false;
1760 } else {
1761 // Password is blank or does not match the confirmation
1762 $status = LATEPOINT_STATUS_ERROR;
1763 $response_html = __( 'Setting password is required and should match password confirmation', 'latepoint' );
1764 }
1765 }
1766 }
1767 // If no errors, proceed
1768 if ( $status == LATEPOINT_STATUS_SUCCESS ) {
1769 if ( OsAuthHelper::is_customer_logged_in() ) {
1770 $customer = OsAuthHelper::get_logged_in_customer();
1771 $is_new_customer = $customer->is_new_record();
1772 } else {
1773 $customer = new OsCustomerModel();
1774 $is_new_customer = true;
1775 }
1776 $old_customer_data = $is_new_customer ? [] : $customer->get_data_vars();
1777 $customer->set_data( $customer_params, LATEPOINT_PARAMS_SCOPE_PUBLIC );
1778 if ( $customer->save() ) {
1779 if ( $is_new_customer ) {
1780 do_action( 'latepoint_customer_created', $customer );
1781 } else {
1782 do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
1783 }
1784
1785 self::$booking_object->customer_id = $customer->id;
1786 if ( ! OsAuthHelper::is_customer_logged_in() ) {
1787 OsAuthHelper::authorize_customer( $customer->id );
1788 }
1789 $customer->set_timezone_name();
1790 } else {
1791 $status = LATEPOINT_STATUS_ERROR;
1792 $response_html = $customer->get_error_messages();
1793 if ( is_array( $response_html ) ) {
1794 $response_html = implode( ', ', $response_html );
1795 }
1796 }
1797 }
1798 if ( $status == LATEPOINT_STATUS_ERROR ) {
1799 return new WP_Error( LATEPOINT_STATUS_ERROR, $response_html );
1800 }
1801
1802 }
1803
1804
1805 // VERIFICATION STEP
1806
1807 public static function process_step_verify() {
1808
1809 }
1810
1811 public static function prepare_step_verify() {
1812 $cart = OsCartsHelper::get_or_create_cart();
1813
1814 $cart->set_singular_payment_attributes();
1815
1816 self::$vars_for_view['cart'] = $cart;
1817 self::$vars_for_view['customer'] = OsAuthHelper::get_logged_in_customer();
1818 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1819 }
1820
1821 // PAYMENT
1822
1823 public static function process_step_payment__portions() {
1824 }
1825
1826 public static function prepare_step_payment__portions() {
1827 }
1828
1829 public static function process_step_payment__times() {
1830 }
1831
1832 public static function prepare_step_payment__times() {
1833 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
1834
1835 self::$vars_for_view['enabled_payment_times'] = $enabled_payment_times;
1836 }
1837
1838 public static function process_step_payment__methods() {
1839 }
1840
1841 public static function prepare_step_payment__methods() {
1842 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( self::$cart_object->payment_time );
1843 self::$vars_for_view['enabled_payment_methods'] = $enabled_payment_methods;
1844 }
1845
1846 public static function process_step_payment__processors() {
1847 }
1848
1849 public static function prepare_step_payment__processors() {
1850 $enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors();
1851 self::$vars_for_view['enabled_payment_processors'] = $enabled_payment_processors;
1852 }
1853
1854 public static function process_step_payment__pay() {
1855 }
1856
1857 public static function prepare_step_payment__pay() {
1858 $booking_form_page_url = self::$params['booking_form_page_url'] ?? wp_get_original_referer();
1859 $order_intent = OsOrderIntentHelper::create_or_update_order_intent( self::$cart_object, self::$restrictions, self::$presets, $booking_form_page_url );
1860 }
1861
1862
1863 // CONFIRMATION
1864
1865 public static function process_step_confirmation() {
1866 }
1867
1868 public static function prepare_step_confirmation() {
1869 self::$vars_for_view['customer'] = OsAuthHelper::get_logged_in_customer();
1870 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1871 if ( ! self::$order_object->is_new_record() ) {
1872 self::$vars_for_view['order'] = self::$order_object;
1873 self::$vars_for_view['order_bookings'] = self::$order_object->get_bookings_from_order_items();
1874 self::$vars_for_view['order_bundles'] = self::$order_object->get_bundles_from_order_items();
1875 self::$vars_for_view['price_breakdown_rows'] = self::$order_object->generate_price_breakdown_rows();
1876 self::$vars_for_view['is_bundle_scheduling'] = false;
1877 } else {
1878 // TRY SAVING BOOKING
1879 // check if it's a scheduling request for an existing order item, it means its a bundle
1880 $is_bundle_scheduling = self::is_bundle_scheduling();
1881 self::$vars_for_view['is_bundle_scheduling'] = $is_bundle_scheduling;
1882 if ( $is_bundle_scheduling ) {
1883 $order_item = new OsOrderItemModel( self::$booking_object->order_item_id );
1884 $order = new OsOrderModel( $order_item->order_id );
1885 self::$vars_for_view['order'] = $order;
1886 self::$vars_for_view['order_bookings'] = $order->get_bookings_from_order_items();
1887 self::$vars_for_view['order_bundles'] = $order->get_bundles_from_order_items();
1888 self::$vars_for_view['price_breakdown_rows'] = self::$cart_object->generate_price_breakdown_rows();
1889
1890 if ( self::$booking_object->is_bookable() ) {
1891 self::$booking_object->calculate_end_time();
1892 self::$booking_object->calculate_end_date();
1893 self::$booking_object->set_utc_datetimes();
1894 $service = new OsServiceModel( self::$booking_object->service_id );
1895 self::$booking_object->buffer_before = $service->buffer_before;
1896 self::$booking_object->buffer_after = $service->buffer_after;
1897
1898 if ( self::$booking_object->save() ) {
1899 do_action( 'latepoint_booking_created', self::$booking_object );
1900 } else {
1901 // error saving booking
1902 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1903 }
1904 } else {
1905 // is not bookable
1906 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1907 }
1908
1909 } else {
1910 $order_intent = OsOrderIntentHelper::create_or_update_order_intent( self::$cart_object, self::$restrictions, self::$presets );
1911 if ( $order_intent->is_processing() ) {
1912 return new WP_Error( LATEPOINT_STATUS_ERROR, __( 'Processing...', 'latepoint' ), [ 'send_to_step' => 'resubmit' ] );
1913 }
1914 if ( $order_intent->convert_to_order() ) {
1915 $order = new OsOrderModel( $order_intent->order_id );
1916 self::$cart_object->clear();
1917 self::$vars_for_view['order'] = $order;
1918 self::$vars_for_view['order_bookings'] = $order->get_bookings_from_order_items();
1919 self::$vars_for_view['order_bundles'] = $order->get_bundles_from_order_items();
1920 self::$vars_for_view['price_breakdown_rows'] = $order->generate_price_breakdown_rows();
1921 } else {
1922 // ERROR CONVERTING TO ORDER
1923 OsDebugHelper::log( 'Error saving order', 'order_error', $order_intent->get_error_messages() );
1924 $response_html = $order_intent->get_error_messages();
1925 $error_data = ( $order_intent->get_error_data( 'send_to_step' ) ) ? [ 'send_to_step' => $order_intent->get_error_data( 'send_to_step' ) ] : '';
1926
1927 return new WP_Error( LATEPOINT_STATUS_ERROR, $response_html, $error_data );
1928 }
1929 }
1930 }
1931 }
1932
1933 public static function output_list_option( $option ) {
1934 $html = '';
1935 $html .= '<div tabindex="0" class="lp-option ' . esc_attr( $option['css_class'] ) . '" ' . $option['attrs'] . '>';
1936 $html .= '<div class="lp-option-image-w"><div class="lp-option-image" style="background-image: url(' . esc_url( $option['image_url'] ) . ')"></div></div>';
1937 $html .= '<div class="lp-option-label">' . esc_html( $option['label'] ) . '</div>';
1938 $html .= '</div>';
1939
1940 return $html;
1941 }
1942
1943 public static function get_steps_for_select(): array {
1944 $steps = self::get_step_codes_in_order();
1945 $steps_with_labels = [];
1946 foreach ( $steps as $step_code ) {
1947 $steps_with_labels[ $step_code ] = self::get_step_label_by_code( $step_code );
1948 }
1949
1950 return $steps_with_labels;
1951 }
1952
1953
1954 public static function save_step_codes_in_order( array $step_codes_in_order ): bool {
1955 return OsSettingsHelper::save_setting_by_name( 'step_codes_in_order', implode( ',', $step_codes_in_order ) );
1956 }
1957
1958
1959 public static function save_steps_settings( $steps_settings ): bool {
1960 self::$steps_settings = $steps_settings;
1961
1962 return OsSettingsHelper::save_setting_by_name( 'steps_settings', self::$steps_settings );
1963 }
1964
1965
1966 public static function get_step_settings( string $step_code ): array {
1967 $settings = self::get_steps_settings();
1968
1969 return $settings[ $step_code ] ?? [];
1970 }
1971
1972 public static function get_steps_settings(): array {
1973 if ( ! empty( self::$steps_settings ) ) {
1974 return self::$steps_settings;
1975 }
1976
1977 $steps_settings_from_db = OsSettingsHelper::get_settings_value( 'steps_settings', [] );
1978 $step_codes = self::get_step_codes_in_order();
1979
1980
1981 if ( empty( $steps_settings_from_db ) ) {
1982 $steps_settings = [
1983 'shared' => [
1984 'steps_support_text' => '<h5>Questions?</h5><p>Call (858) 939-3746 for help</p>'
1985 ]
1986 ];
1987 foreach ( $step_codes as $step_code ) {
1988 $steps_settings[ $step_code ] = self::get_default_value_for_step_settings( $step_code );
1989 }
1990 OsSettingsHelper::save_setting_by_name( 'steps_settings', $steps_settings );
1991 self::$steps_settings = $steps_settings;
1992 } else {
1993 // iterate step codes to see if each has a setting
1994 $changed = false;
1995 foreach ( $step_codes as $step_code ) {
1996 if ( ! isset( $steps_settings_from_db[ $step_code ] ) ) {
1997 $steps_settings_from_db[ $step_code ] = self::get_default_value_for_step_settings( $step_code );
1998 $changed = true;
1999 }
2000 }
2001 if ( $changed ) {
2002 OsSettingsHelper::save_setting_by_name( 'steps_settings', $steps_settings_from_db );
2003 }
2004 self::$steps_settings = $steps_settings_from_db;
2005 }
2006
2007 return self::$steps_settings;
2008 }
2009
2010 /**
2011 * @param string $step_code
2012 * @param string $placement before, after
2013 *
2014 * @return string
2015 */
2016 public static function get_formatted_extra_step_content( string $step_code, string $placement ): string {
2017 $content = self::get_step_setting_value( $step_code, 'main_panel_content_' . $placement );
2018
2019 return ! empty( $content ) ? '<div class="latepoint-step-content-text-left">' . $content . '</div>' : '';
2020 }
2021
2022
2023 public static function get_step_setting_value( string $step_code, string $setting_key, $default = '' ) {
2024 $steps_settings = self::get_step_settings( $step_code );
2025
2026 return $steps_settings[ $setting_key ] ?? $default;
2027 }
2028
2029 public static function get_step_settings_edit_form_html( string $selected_step_code ): string {
2030 $step_settings_html = '';
2031 switch ( $selected_step_code ) {
2032 case 'booking__services':
2033 $step_settings_html .= OsFormHelper::toggler_field( 'settings[steps_show_service_categories]', __( 'Show service categories', 'latepoint' ), OsSettingsHelper::steps_show_service_categories(), false, false, [ 'sub_label' => __( 'If turned on, services will be displayed in categories', 'latepoint' ) ] );
2034 break;
2035 case 'booking__agents':
2036 $step_settings_html .= OsFormHelper::toggler_field( 'settings[steps_show_agent_bio]', __( 'Show Learn More about agents', 'latepoint' ), OsSettingsHelper::is_on( 'steps_show_agent_bio' ), false, false, [ 'sub_label' => __( 'A link to open information about agent will be added to each agent tile', 'latepoint' ) ] );
2037 $step_settings_html .= OsFormHelper::toggler_field( 'settings[steps_hide_agent_info]', __( 'Hide agent name from summary and confirmation', 'latepoint' ), OsSettingsHelper::is_on( 'steps_hide_agent_info' ), false, false, [ 'sub_label' => __( 'Check if you want to hide agent name from showing up', 'latepoint' ) ] );
2038 $step_settings_html .= OsFormHelper::toggler_field( 'settings[allow_any_agent]', __( 'Add "Any Agent" option to agent selection', 'latepoint' ), OsSettingsHelper::is_on( 'allow_any_agent' ), 'lp-any-agent-settings', false, [ 'sub_label' => __( 'Customers can pick "Any agent" and system will find a matching agent', 'latepoint' ) ] );
2039 $step_settings_html .= '<div class="control-under-toggler" id="lp-any-agent-settings" ' . ( OsSettingsHelper::is_on( 'allow_any_agent' ) ? '' : 'style="display: none;"' ) . '>';
2040 $step_settings_html .= OsFormHelper::select_field( 'settings[any_agent_order]', __( 'If "Any Agent" is selected then assign booking to', 'latepoint' ), OsSettingsHelper::get_order_types_list_for_any_agent_logic(), OsSettingsHelper::get_any_agent_order() );
2041 $step_settings_html .= '</div>';
2042 break;
2043 case 'booking__datepicker':
2044 $step_settings_html .= OsFormHelper::select_field( 'steps_settings[booking__datepicker][time_pick_style]', __( 'Show Time Slots as', 'latepoint' ), [
2045 'timebox' => 'Time Boxes',
2046 'timeline' => 'Timeline'
2047 ], OsStepsHelper::get_time_pick_style() );
2048 $step_settings_html .= OsFormHelper::toggler_field( 'steps_settings[booking__datepicker][hide_timepicker_when_one_slot_available]', __( 'Hide time picker if single slot', 'latepoint' ), OsUtilHelper::is_on( self::get_step_setting_value( $selected_step_code, 'hide_timepicker_when_one_slot_available' ) ), false, false, [ 'sub_label' => __( 'If a single slot is available in a day, it will be preselected.', 'latepoint' ) ] );
2049 $step_settings_html .= OsFormHelper::toggler_field( 'steps_settings[booking__datepicker][hide_slot_availability_count]', __( 'Hide slot availability count', 'latepoint' ), OsStepsHelper::hide_slot_availability_count(), false, false, [ 'sub_label' => __( 'Slot counter tooltip will not appear when hovering a day.', 'latepoint' ) ] );
2050 $step_settings_html .= OsFormHelper::toggler_field( 'steps_settings[booking__datepicker][hide_unavailable_slots]', __( 'Hide slots that are not available (time boxes)', 'latepoint' ), OsStepsHelper::hide_unavailable_slots(), false, false, [ 'sub_label' => __( 'Hides time boxes that are not available, instead of showing them in gray.', 'latepoint' ) ] );
2051 break;
2052 case 'confirmation':
2053 $step_settings_html .= OsFormHelper::select_field( 'steps_settings[confirmation][order_confirmation_message_style]', __( 'Message Style', 'latepoint' ), ['green' => __('Green', 'latepoint'), 'yellow' => __('Yellow', 'latepoint')], self::get_step_setting_value($selected_step_code, 'order_confirmation_message_style', 'green') );
2054 break;
2055 }
2056 /**
2057 * Generates HTML for step settings form in the preview
2058 *
2059 * @param {string} $step_settings_html html that is going to be output on the step settings form
2060 * @param {string} $selected_step_code step code that settings are requested for
2061 * @returns {string} $step_settings_html Filtered HTML of the settings form
2062 *
2063 * @since 5.0.0
2064 * @hook latepoint_get_step_settings_edit_form_html
2065 *
2066 */
2067 $step_settings_html = apply_filters( 'latepoint_get_step_settings_edit_form_html', $step_settings_html, $selected_step_code );
2068 if ( empty( $step_settings_html ) ) {
2069 $step_settings_html = '<div class="bf-step-no-settings-message">' . __( 'This step does not have any specific settings. You can use the selector above to check another step.', 'latepoint' ) . '</div>';
2070 }
2071
2072 return $step_settings_html;
2073 }
2074
2075 public static function get_default_value_for_step_settings( string $step_code ): array {
2076 $settings = [
2077 'booking__services' => [
2078 'side_panel_heading' => 'Service Selection',
2079 'side_panel_description' => 'Please select a service for which you want to schedule an appointment',
2080 'main_panel_heading' => 'Available Services'
2081 ],
2082 'booking__locations' => [
2083 'side_panel_heading' => 'Location Selection',
2084 'side_panel_description' => 'Please select a location where you want to schedule an appointment',
2085 'main_panel_heading' => 'Available Locations'
2086 ],
2087 'booking__agents' => [
2088 'side_panel_heading' => 'Agent Selection',
2089 'side_panel_description' => 'Please select an agent that will be providing you a service',
2090 'main_panel_heading' => 'Available Agents'
2091 ],
2092 'booking__datepicker' => [
2093 'side_panel_heading' => 'Select Date & Time',
2094 'side_panel_description' => 'Please select date and time for your appointment',
2095 'main_panel_heading' => 'Date & Time Selection'
2096 ],
2097 'customer' => [
2098 'side_panel_heading' => 'Enter Your Information',
2099 'side_panel_description' => 'Please enter your contact information',
2100 'main_panel_heading' => 'Customer Information'
2101 ],
2102 'verify' => [
2103 'side_panel_heading' => 'Verify Order Details',
2104 'side_panel_description' => 'Double check your reservation details and click submit button if everything is correct',
2105 'main_panel_heading' => 'Verify Order Details',
2106 ],
2107 'payment__times' => [
2108 'side_panel_heading' => 'Payment Time Selection',
2109 'side_panel_description' => 'Please choose when you would like to pay for your appointment',
2110 'main_panel_heading' => 'When would you like to pay?'
2111 ],
2112 'payment__portions' => [
2113 'side_panel_heading' => 'Payment Portion Selection',
2114 'side_panel_description' => 'Please select how much you would like to pay now',
2115 'main_panel_heading' => 'How much would you like to pay now?'
2116 ],
2117 'payment__methods' => [
2118 'side_panel_heading' => 'Payment Method Selection',
2119 'side_panel_description' => 'Please select a payment method you would like to make a payment with',
2120 'main_panel_heading' => 'Select payment method'
2121 ],
2122 'payment__processors' => [
2123 'side_panel_heading' => 'Payment Processor Selection',
2124 'side_panel_description' => 'Please select a payment processor you want to process the payment with',
2125 'main_panel_heading' => 'Select payment processor'
2126 ],
2127 'payment__pay' => [
2128 'side_panel_heading' => 'Make a Payment',
2129 'side_panel_description' => 'Please enter your payment information so we can process the payment',
2130 'main_panel_heading' => 'Enter your payment information'
2131 ],
2132 'confirmation' => [
2133 'side_panel_heading' => 'Confirmation',
2134 'side_panel_description' => 'Your order has been placed. Please retain this confirmation for your record.',
2135 'main_panel_heading' => 'Order Confirmation'
2136 ]
2137 ];
2138
2139
2140 $settings = apply_filters( 'latepoint_settings_for_step_codes', $settings );
2141
2142 return $settings[ $step_code ] ?? [];
2143 }
2144
2145
2146 public static function get_default_side_panel_image_html_for_step_code( string $step_code ): string {
2147 $svg = '';
2148 switch ( $step_code ) {
2149 case 'booking__locations':
2150 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2151 <path class="latepoint-step-svg-highlight" d="M60.3884583,4.85921c-2.8716431-0.2993164-5.8259277,0.557373-7.9927979,2.197998 c-1.0095825,0.6467285-1.8696899,1.4177246-2.4382935,2.2561035c-1.7146873,2.5291042-2.5220757,6.3280535-1.3348999,10.835206 c-5.2646828-1.1404552-4.7828903-1.0880737-4.9659424-1.052002l-2.1259766,0.4560547 c-18.4231091,3.9559402-16.4117718,3.5059223-16.6292133,3.5698242 C4.8973494,18.9566498,6.1634111,19.1396389,5.8543382,19.2293282c0.0001221-0.0048828,0.0001221-0.0097656,0.0002441-0.0146484 c-0.0184326,0.012207-0.0371094,0.0292969-0.055603,0.0419922c-0.2596664,0.100153-0.2317972,0.1285801-0.3178711,0.2409668 c-0.388855,0.3278809-0.7800293,0.7553711-1.1567383,1.2041016c-0.3962412,0.4718437-0.1706734-1.9064941,0.5690308,41.3483887 c0.0057373,0.3037109,0.1334229,0.597168,0.3482666,0.8115234c0.3456421,0.3449707,0.5272217,0.5529785,0.7957764,0.7592773 c0.0950928,0.2109375,0.2803345,0.3754883,0.5170288,0.4277306c20.0937347,4.4312515,18.6302357,4.2767105,19.0541992,3.9326172 c0.0049438-0.0039063,0.0066528-0.010498,0.0114746-0.0146484c0.10186-0.0230865,15.3084774-3.4694977,17.9484882-4.0644493 c0.0352173-0.0078125,0.0643921-0.0273438,0.0973511-0.0397949c19.0996971,4.4957237,18.2303658,4.3366661,18.4299927,4.3366661 c0.4144669,0,0.7473717-0.3352814,0.75-0.7451172c0.0791321-12.2700005,0.2286911-24.8520088,0.3359375-36.9809532 c3.2604828-5.2970676,7.2790756-13.97159,5.0361328-19.7866211C67.0105286,7.553546,63.8635559,5.2127256,60.3884583,4.85921z M24.2595501,66.4368439c-0.1054153-0.0233917-14.3338861-3.1805725-16.8095703-3.727047 C7.0617967,48.3806953,6.8420701,33.9500313,6.8132615,20.8670235c5.8759589,1.233469,11.3363876,2.3809967,17.2407227,3.6113281 C24.3160305,51.6952362,24.2979584,58.1465149,24.2595501,66.4368439z M42.6662903,62.5681953 c-2.7329216,0.6163788-16.6759109,3.7770119-16.7893696,3.8027306c-0.1231174-12.0390549-0.0782604-29.8359985-0.02948-41.9248009 c5.5739422-1.1885509,11.055666-2.3654537,17.2197285-3.6884766C43.0675392,20.8666286,42.96418,48.7001991,42.6662903,62.5681953z M61.3523254,66.5017853c-5.4633789-1.2939453-11.2871094-2.6728477-16.8710938-3.989254 c-0.1817551-17.4268951-0.0330315-7.6905823,0.1430664-41.7041016c1.5129585,0.33918,2.9774971,0.6543026,4.5148926,0.9870605 c1.2711296,3.5923672,4.1154442,8.24547,6.2368164,10.9348145c0.510498,0.6472168,1.4362793,1.4404297,2.2056885,1.7519531 c0.8912773,0.6281052,1.8476524,0.4962959,2.5943604-0.1904297c0.5303345-0.4863281,1.022644-1.03125,1.4845581-1.6137695 C61.5390205,45.8931503,61.4254494,55.6076279,61.3523254,66.5017853z M64.0022278,25.9051094 c-1.2943535,2.4604969-2.8116989,5.4206085-4.840332,7.28125c-0.1386719,0.1279297-0.296875,0.1855469-0.4130859,0.2011719 c-0.7806473-0.0199814-5.2463379-5.6790333-7.6728516-13.1708984c-0.5771484-1.7861328-1.190918-4.1210938-0.8085938-6.3457041 c0.3496094-2.03125,0.9931641-3.5849609,1.9125977-4.6152344c1.8496094-2.0751953,5.0126953-3.2119141,8.0566406-2.9042969 c2.9272461,0.2978516,5.5722656,2.2568359,6.5820313,4.8740234C68.454361,15.4667559,66.1138763,21.8956394,64.0022278,25.9051094z "/>
2152 <path class="latepoint-step-svg-base" d="M54.1091614,12.0506163c-2.088459,3.2326937,0.0606689,7.85254,4.3237305,7.85254 c3.6078873,0,5.8475189-3.5880222,4.8115234-6.6953135C61.9358063,9.2799187,56.3691139,8.5516081,54.1091614,12.0506163z M58.170929,18.3797188c-0.8803711-0.0610352-1.743103-0.4106445-2.3566895-1.0410156 c-1.1245117-1.1542969-1.3198242-3.1201181-0.4453125-4.4736338c0.8155251-1.2618265,2.428051-1.8824129,4.0743408-1.404541 c0.5652466,0.5754395,1.0892944,1.170166,1.3425903,1.8354492C61.5309181,15.2528019,60.553997,17.7360039,58.170929,18.3797188z" /></svg>';
2153 break;
2154 case 'booking__services':
2155 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2156 <path class="latepoint-step-svg-highlight" d="M12.4475956,46.2568436c-0.1044884,1.7254677-0.2875328,2.2941246,0.1235962,3.2275391 c0.2800293,1.0578613,1.2532349,2.0065918,2.4077148,2.4970703c2.5679932,1.0912819,3.8084583,0.576416,36.5757446,0.7905273 c1.5809326,0.0102539,4.2476807-0.1374512,5.786499-0.4538574c2.1460648-0.4416046,4.1996078-1.119503,4.6765137-3.3955078 c0.1690674-0.3930664,0.2585449-0.8137207,0.2453613-1.244873c-0.0195313-0.6503906-0.0566406-1.3046875-0.1044922-1.9511719 c-0.1210938-1.6845703-1.6621094-2.9892578-3.5175781-2.9892578c-0.015625,0-0.03125,0-0.046875,0l-42.6777344,0.5214844 C14.0725956,43.2812576,12.5491581,44.5976639,12.4475956,46.2568436z M58.6409569,44.2373123 c1.0712891,0,1.9560547,0.6972656,2.0214844,1.5976563c0.0458984,0.6259766,0.0830078,1.2587891,0.1005859,1.8876953 c0.0309868,1.0110512-0.9663086,1.7237892-2.0117188,1.7304688c-14.3534698,0.0823135-28.739151,0.728199-42.9609375,0.5419922 c-1.0929708-0.0137672-2.0631294-0.8028984-1.9785156-1.8085938c0.0527344-0.6113281,0.0957031-1.2294922,0.1337891-1.8378906 c0.0537109-0.8789063,0.9267578-1.5771484,1.9882813-1.5898438C16.0340576,44.757576,58.7426338,44.2373123,58.6409569,44.2373123z "/>
2157 <path class="latepoint-step-svg-base" d="M58.2141991,6.9736419l-0.5214844,4.9931645c-0.0457916,0.4391737,0.2963982,0.828125,0.7470703,0.828125 c0.3789063,0,0.7050781-0.2861328,0.7451172-0.671875l0.5214844-4.9931645 c0.0429688-0.4121094-0.2558594-0.78125-0.6679688-0.8242188C58.6360741,6.256845,58.2571678,6.5605559,58.2141991,6.9736419z"/>
2158 <path class="latepoint-step-svg-base" d="M65.2903671,8.9316502l-3.6796837,3.6767578c-0.4748344,0.4748325-0.1306915,1.2802734,0.5302734,1.2802734 c0.1914063,0,0.3837891-0.0732422,0.5302734-0.2197266L66.350914,9.992197c0.2929688-0.2929688,0.2929688-0.7675781,0-1.0605469 C66.0589218,8.639658,65.5843124,8.6377048,65.2903671,8.9316502z"/>
2159 <path class="latepoint-step-svg-base" d="M68.8108749,16.1767673c-0.1835938-0.3710938-0.6347656-0.5234375-1.0048828-0.3388672 c-1.1025391,0.5478516-2.3320313,0.7939453-3.5585938,0.7119141c-0.4033165-0.0234375-0.770504,0.2851563-0.7978477,0.6982422 s0.2851563,0.7705078,0.6982384,0.7978516c1.4586029,0.0992756,2.9659576-0.1902256,4.3242188-0.8642578 C68.8431015,16.9970798,68.9944687,16.5468845,68.8108749,16.1767673z"/>
2160 <path class="latepoint-step-svg-highlight" d="M7.0583744,24.3901463c1.7924805,0.6647949,3.8635864,0.6894531,5.857666,0.7006836 c12.414856,0.0710449,23.6358051,0.019043,36.0507202,0.0898438c1.8114014,0.0102539,4.8669434-0.1374512,6.630127-0.4538574 c1.7630615-0.3166504,3.4486084-0.7158203,4.5030518-1.8364258c0.5599365-0.5949707,0.8862305-1.326416,0.9301758-2.0551758 c0.1284103-0.495512,0.1391678-0.7500668-0.0229492-2.7072754c-0.125988-1.5260391-1.6530342-2.9814453-3.9726563-2.9814453 L8.1350956,15.6670017c-2.0859375,0.0224609-3.7490234,1.3085938-3.8671875,2.9931641 c-0.131978,1.8722496-0.2533808,2.0809135-0.0430298,2.7998047C4.332056,22.6867771,5.5573368,23.8335056,7.0583744,24.3901463z M5.7640018,18.764658c0.0615234-0.8681641,1.1318359-1.5849609,2.3867188-1.5976563l48.8994141-0.5205078 c1.2441406-0.0126953,2.3886719,0.7070313,2.4628906,1.6044922c0.0517578,0.625,0.09375,1.2558594,0.1142578,1.8818359 c0.0375061,1.0384789-1.2411385,1.7228012-2.4140625,1.7285156c-16.2836723,0.0816097-33.0511169,0.7308216-49.2275391,0.5429688 c-1.1799021-0.0141487-2.4750004-0.7440434-2.3740234-1.8007813C5.6712284,19.9912205,5.7220097,19.3730564,5.7640018,18.764658z" />
2161 <path class="latepoint-step-svg-highlight" d="M25.6985722,38.054451c1.9748383,1.0864716,2.6161232,0.5729103,28.2541523,0.7905273 c1.2214355,0.0102539,3.28125-0.1374512,4.4699707-0.4538574c1.6699829-0.4448471,2.8914299-1.0308228,3.4542236-2.7290039 c0.6960297-1.1023483,0.5326729-2.1277504,0.4388428-3.850584c-0.0966797-1.7070313-1.40625-3.0332031-2.9306641-3.0009766 l-32.9677734,0.5205078c-1.5166016,0.0253906-2.765625,1.3466797-2.8447266,3.0097637 c-0.0829926,1.7514267-0.3514214,2.8246078,0.5612793,4.0524902C24.4834843,37.0983963,25.0513554,37.698494,25.6985722,38.054451z M25.0706425,32.4111404c0.0419922-0.8740215,0.6445313-1.5683575,1.3710938-1.5800762l32.9667969-0.5205078 c0.0058594,0,0.0117188,0,0.0175781,0c0.7314453,0,1.3417969,0.6923828,1.3916016,1.5839844 c0.0351563,0.6289043,0.0634766,1.2646465,0.078125,1.8945293c0.0201225,0.8820457-0.556736,1.731514-1.3867188,1.7373047 c-10.9964714,0.0815811-22.1932869,0.7267456-33.1787109,0.5419922c-0.7375622-0.013092-1.4293518-0.7859573-1.3623047-1.8242188 C25.0081425,33.6347733,25.0423222,33.0185623,25.0706425,32.4111404z"/>
2162 <path class="latepoint-step-svg-highlight" d="M62.451992,63.2775955c0.5789719-1.0259094,0.4419289-1.8840179,0.3344727-3.6164551 c-0.1044922-1.6894531-1.4648438-2.9960938-3.1064453-2.9960938c-0.0146484,0-0.0302734,0-0.0449219,0l-36.3544922,0.5205078 c-1.6298828,0.0234375-2.9755859,1.3427734-3.0634766,3.0048828c-0.09375,1.795887-0.3370171,2.6628914,0.4232788,3.8208008 c0.3649292,0.8071289,1.0519409,1.5019531,1.8442383,1.8972168c2.1949348,1.0950089,3.3277054,0.5763168,31.1570454,0.7905273 c1.3469238,0.0102539,3.6184082-0.1374512,4.9293213-0.4538574C60.4500313,65.7912064,61.8896866,65.1745071,62.451992,63.2775955z M59.7708397,63.3798904c-12.1266251,0.0816307-24.4732285,0.7282944-36.5908203,0.5419922 c-0.9430161-0.0149651-1.6459942-0.8662491-1.578125-1.8183594c0.0439453-0.6103516,0.0820313-1.2265625,0.1132813-1.8339844 c0.0458984-0.8769531,0.7431641-1.5722656,1.5869141-1.5839844l36.3544922-0.5205078 c0.9013672-0.0332031,1.5761719,0.6855469,1.6328125,1.5888672c0.0390625,0.6289063,0.0693359,1.2617188,0.0859375,1.8916016 C61.4014854,62.6212692,60.6525688,63.3738251,59.7708397,63.3798904z"/>
2163 </svg>';
2164 break;
2165 case 'booking__agents':
2166 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2167 <path class="latepoint-step-svg-base" d="M53.4534083,0.0474242671 C53.0666895,-0.0961304329 52.6335841,0.0967406671 52.4866114,0.483947667 L50.3816309,6.05572497 C50.2351465,6.44342027 50.4309473,6.87603747 50.8181543,7.02252187 C51.2107248,7.16946117 51.6403055,6.96943747 51.7849512,6.58599847 L53.8899317,1.01422117 C54.0364161,0.626525867 53.8406153,0.193908667 53.4534083,0.0474242671 Z"></path>
2168 <path class="latepoint-step-svg-base" d="M55.1467677,9.54449457 L60.2917872,4.91949457 C60.5998927,4.64263907 60.624795,4.16851797 60.3479395,3.86041257 C60.0701075,3.55181877 59.5964747,3.52691647 59.2888575,3.80426027 L54.143838,8.42926027 C53.8357325,8.70611577 53.8108302,9.18023687 54.0876857,9.48834227 C54.3632441,9.79482267 54.8367587,9.82286737 55.1467677,9.54449457 Z"></path>
2169 <path class="latepoint-step-svg-base" d="M58.0530177,12.1817007 C58.1018458,12.5601187 58.4245997,12.8364859 58.7961818,12.8364859 C58.8279201,12.8364859 58.8601466,12.8345328 58.8923732,12.8306265 C60.810342,12.585021 62.7136623,11.9522085 64.3962795,11.0010376 C64.7566311,10.7974243 64.8840725,10.3399048 64.6799709,9.97906487 C64.4758693,9.61724847 64.0178615,9.49078357 63.6579982,9.69537347 C62.1428615,10.5518188 60.4289943,11.1211548 58.7019435,11.3423462 C58.2908106,11.3950796 58.0007716,11.7710562 58.0530177,12.1817007 Z"></path>
2170 <path class="latepoint-step-svg-base" d="M30.1647665,12.3430099 C34.8016087,11.2484035 39.4478623,14.1199381 40.5424644,18.7567618 C41.6370664,23.3935856 38.7655134,28.0398278 34.1286712,29.1344342 C29.491829,30.2290406 24.8455754,27.3575061 23.7509733,22.7206823 C22.6563712,18.0838585 25.5279243,13.4376163 30.1647665,12.3430099 Z M30.7048927,13.6876382 C26.8743165,14.5919117 24.5020759,18.4302508 25.406345,22.2608086 C26.3106141,26.0913663 30.1489646,28.4635885 33.9795408,27.5593151 C37.810117,26.6550416 40.1823577,22.8167025 39.2780886,18.9861448 C38.3738195,15.155587 34.535469,12.7833648 30.7048927,13.6876382 Z"></path>
2171 <path class="latepoint-step-svg-base" d="M21.9115992,61.4981718 C23.8270655,62.2352323 26.1083765,62.550601 28.0801173,62.8933134 C39.1328402,64.8145094 50.0195018,63.0462065 53.2110377,61.4772978 C54.3124781,60.935916 53.9811183,59.2539663 52.7560206,59.1805411 C50.270547,59.0314932 47.770608,59.1632071 45.3111353,59.5512114 C55.2235003,54.6875143 61.8597269,44.4488249 62.4270411,34.1118765 L62.4270411,34.1123648 C63.5544825,13.7695837 44.6203433,-0.201645833 26.3787013,3.15100097 C1.04216438,5.25931547 -5.22645982,35.1987143 4.08518218,48.907836 C7.82184888,54.4092207 14.728097,59.697505 21.9115992,61.4981718 Z M49.7043238,55.0174551 C38.1006632,64.1502943 22.8722105,61.8384047 13.4803858,53.7492056 C12.5408716,43.1234541 20.9689856,33.9107046 31.6687403,33.9107046 C42.9996081,33.9107046 51.4818011,44.1488142 49.7043238,55.0174551 Z M9.60721588,15.241271 C26.2435961,-6.79306413 62.4589091,6.43408397 60.9289942,34.029357 C60.8975687,34.1444121 60.8018961,44.9580946 51.3662501,53.6017447 C52.1936312,42.0003806 42.9873324,32.4107047 31.6687403,32.4107047 C20.7886057,32.4107047 11.8490992,41.2775069 11.9136133,52.293212 C2.00266698,42.3921652 1.59887988,25.849227 9.60721588,15.241271 Z"></path>
2172 </svg>';
2173 break;
2174 case 'booking__datepicker':
2175 case 'customer':
2176 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2177 <path class="latepoint-step-svg-highlight" d="M36.270771,27.7026501h16.8071289c0.4140625,0,0.75-0.3359375,0.75-0.75s-0.3359375-0.75-0.75-0.75H36.270771 c-0.4140625,0-0.75,0.3359375-0.75,0.75S35.8567085,27.7026501,36.270771,27.7026501z"/>
2178 <path class="latepoint-step-svg-highlight" d="M40.5549507,42.3081207c0,0.4140625,0.3359375,0.75,0.75,0.75h12.6015625c0.4140625,0,0.75-0.3359375,0.75-0.75 s-0.3359375-0.75-0.75-0.75H41.3049507C40.8908882,41.5581207,40.5549507,41.8940582,40.5549507,42.3081207z"/>
2179 <path class="latepoint-step-svg-highlight" d="M45.6980171,51.249527H29.9778023c-0.4140625,0-0.75,0.3359375-0.75,0.75s0.3359375,0.75,0.75,0.75h15.7202148 c0.4140625,0,0.75-0.3359375,0.75-0.75S46.1120796,51.249527,45.6980171,51.249527z"/>
2180 <path class="latepoint-step-svg-highlight" d="M62.1623726,11.5883932l0.3300781-3.3564453c0.0405273-0.4121094-0.2607422-0.7792969-0.6728516-0.8193359 c-0.4091797-0.0458984-0.77882,0.2597656-0.8203125,0.6728516l-0.3300781,3.3564453 c-0.0405273,0.4121094,0.2612305,0.7792969,0.6733398,0.8193359 C61.7317963,12.3070383,62.1204109,12.0155325,62.1623726,11.5883932z"/>
2181 <path class="latepoint-step-svg-highlight" d="M63.9743843,13.9233541c1.1010704-0.3369141,2.0717735-1.0410156,2.7333946-1.9814453 c0.2382813-0.3388672,0.1567383-0.8066406-0.1816406-1.0449219c-0.3383789-0.2392578-0.8066406-0.1572266-1.0449219,0.1816406 c-0.4711914,0.6699219-1.1621094,1.1708984-1.9462852,1.4111328c-0.3959961,0.1210938-0.6186523,0.5400391-0.4975586,0.9365234 C63.1588402,13.8212023,63.5774651,14.0450754,63.9743843,13.9233541z"/>
2182 <path class="latepoint-step-svg-highlight" d="M68.8601227,17.4516735c0.0356445-0.4121094-0.2695313-0.7763672-0.6826172-0.8115234l-3.859375-0.3349609 c-0.4072227-0.0390625-0.7758751,0.2695313-0.8115196,0.6826172c-0.0356445,0.4121094,0.2695313,0.7763672,0.6826134,0.8115234 l3.859375,0.3349609C68.4594727,18.1708145,68.8244781,17.8649578,68.8601227,17.4516735z"/>
2183 <path class="latepoint-step-svg-highlight" d="M4.7497134,18.4358044c1.0574932,1.9900436,1.9738078,2.5032253,13.2814941,11.7038574 c0.5604858,11.4355488,0.9589844,22.8789082,1.1829224,34.3259277c0.3128052,0.1918945,0.6256714,0.3835449,0.9384766,0.5751953 c0.1058846,0.3764038,0.416275,0.5851364,0.7949219,0.5466309c12.6464844-1.4892578,25.8935547-2.0419922,40.4916992-1.6767578 c0.4600639-0.0021172,0.763813-0.3514481,0.7685547-0.7421875c0.1805725-16.3819695-0.080349-32.8599472,0.0605469-49.1875 c0.003418-0.3740234-0.2685547-0.6923828-0.6376953-0.7480469c-14.1435547-2.140625-28.5092773-2.3291016-42.6953125-0.5664063 c-0.331604,0.0407715-0.5751953,0.2971191-0.6331177,0.6113281c-0.3464966,0.277832-0.6930542,0.5556641-1.0396118,0.8334961 c0.1156616,1.137207,0.0985718,2.392333,0.1765137,3.5629873c-2.2901011-1.8925772-4.5957651-3.8081045-6.9354258-5.7802725 c-0.7441406-0.6269531-1.6889648-0.9277344-2.683105-0.8378906C4.4105406,11.3600969,3.320657,15.7476349,4.7497134,18.4358044z M60.7629585,14.6196432c-0.1265907,15.9033155,0.1148987,31.8954544-0.046875,47.7734375 c-14.0498047-0.3193359-26.8598633,0.2099609-39.1044922,1.6074219c0.0154419-10.8208008-0.2228394-21.3803711-0.6828613-31.503418 c8.6963615,7.0753174,9.1210613,7.5400124,10.6517334,8.1962891c2.7804565,1.1923828,7.8590698,1.5974121,8.4487305,0.6987305 c0.0741577-0.0522461,0.1495361-0.1047363,0.2015381-0.1826172c0.1469727-0.2207031,0.1669922-0.5029297,0.0517578-0.7412109 c-1.0354347-2.1505203-2.3683548-6.0868149-3.1914063-6.7568359c-5.5252628-4.5023842-10.581501-8.5776329-16.84375-13.7214375 c-0.1300049-1.973877-0.2654419-3.9484863-0.4165039-5.9221182C33.4343452,12.4419088,47.1985054,12.6274557,60.7629585,14.6196432 z M9.5368834,13.0405416c9.0454321,7.6246099,17.5216217,14.4366217,26.5917969,21.8203125 c0.3883591,0.3987503,1.5395088,3.3786926,2.2700195,5.078125c-1.4580688-0.1650391-2.9936523-0.479248-4.7089233-0.8842773 c0.4859009-0.9790039,1.1461182-1.8769531,1.953064-2.6108398c0.3061523-0.2783203,0.3286133-0.7529297,0.0498047-1.0595703 c-0.2783203-0.3046875-0.7519531-0.328125-1.0595703-0.0498047c-0.9295654,0.8461914-1.6932373,1.8774414-2.2598877,3.0026855 c-8.9527779-7.1637478-17.1909065-14.1875877-25.8739014-21.1394062c-0.5556641-0.4443359-0.8725586-1.09375-0.8481445-1.7363272 C5.7526169,12.8167362,8.1288319,11.8543167,9.5368834,13.0405416z"/>
2184 </svg>';
2185 break;
2186 case 'payment__times':
2187 case 'payment__portions':
2188 case 'payment__methods':
2189 case 'payment__processors':
2190 case 'payment__pay':
2191 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2192 <path class="latepoint-step-svg-highlight" d="M58.6511116,6.1223307l-0.2675781,2.7509766c-0.0427284,0.4397869,0.3022537,0.8222656,0.7470703,0.8222656 c0.3818359,0,0.7080078-0.2900391,0.7451172-0.6777344l0.2675781-2.7509766 c0.0400391-0.4121094-0.2617188-0.7792969-0.6738281-0.8183594C59.0612679,5.3947916,58.6901741,5.7092447,58.6511116,6.1223307z" />
2193 <path class="latepoint-step-svg-highlight" d="M60.9724007,11.0764322c0.296711,0.2927561,0.7712784,0.2872667,1.0605469-0.0058594 c1.0693359-1.0820313,1.8466797-2.4306641,2.2470665-3.8984375c0.109375-0.3994141-0.1269531-0.8115234-0.5263634-0.9208984 c-0.4082031-0.1083984-0.8125,0.1269531-0.9208984,0.5263672c-0.3330078,1.2197266-0.9785156,2.3398438-1.8662109,3.2382813 C60.6755257,10.3108072,60.6774788,10.7854166,60.9724007,11.0764322z"/>
2194 <path class="latepoint-step-svg-highlight" d="M68.802475,10.2619791c-0.1806641-0.3710938-0.6279297-0.5253906-1.0029297-0.3466797l-4.2695274,2.0771484 c-0.3720703,0.1816406-0.5273438,0.6308594-0.3466797,1.0029297c0.1800232,0.3695202,0.6266098,0.5278702,1.0029259,0.3466797 l4.2695313-2.0771484C68.8278503,11.0832682,68.983139,10.6340494,68.802475,10.2619791z"/>
2195 <path class="latepoint-step-svg-highlight" d="M56.075428,39.6298981l-0.0135498,0.1000977c-1.02771,0.3820801-1.6018066,1.6784668-1.2001343,2.6987305 c0.4017334,1.0202637,1.6987915,1.5778809,2.7179565,1.173584c1.019165-0.404541,1.581665-1.692627,1.1917114-2.7172852 C58.3814583,39.8601227,57.1116829,39.2714996,56.075428,39.6298981z"/>
2196 <path class="latepoint-step-svg-highlight" d="M67.1153412,64.6347809c0.3217163-0.7180176-0.0892334-1.5942383-0.7265625-2.0559082 c-0.3763428-0.2724609-0.8133545-0.4296875-1.2661743-0.5449219c0.4932785-1.2028122,0.3154755,0.6508713,0.4796753-37.815918 c0.0175247-3.8000011-0.7661972-6.7081814-4.6874352-7.2695313c-0.3728027-0.1738281-0.7583618-0.3242188-1.1530762-0.456543 c0.0695915-1.4608269-0.0228233-2.4685307-0.0032349-3.5571299c0.0311775-1.7980299-1.4539566-3.2119141-3.1962891-3.2119141 c-0.0029297,0-0.0058594,0-0.0087891,0L17.7292366,9.8449869c-3.6554623,0.0112343-7.4443989,0.1655378-10.0129395,2.8173828 c-1.4490428,1.00739-2.4756026,2.9240465-2.9685669,4.6687021c-0.8636329,3.0560856-0.6394863,1.955822-0.4553223,44.1296387 c0.0185671,4.2640686,1.1058459,5.8280563,6.0576177,5.918457c18.1763916,0.3305664,36.4078979,0.4030762,54.4744225-1.6201172 C65.7114716,65.6596832,66.750412,65.4494781,67.1153412,64.6347809z M10.1530647,12.6457682 c2.2675781-1.2832031,5.0898438-1.2929688,7.5800781-1.3007813l38.8242188-0.1220703c0.0019531,0,0.0039063,0,0.0048828,0 c0.9442444,0,1.7127266,0.7628899,1.6962891,1.6855469c-0.0167885,0.973794,0.0510406,1.9935045,0.0214844,3.1801767 c-3.1493874-0.6768255-2.4396057-0.4888554-44.4998169-0.6098642c-0.5518799-0.0014648-5.0442505,0.4206543-6.5944219,1.3168955 C7.4678226,15.1682291,8.5861702,13.5339518,10.1530647,12.6457682z M64.0123749,45.5925446l-5.2597008,0.0493164 c-3.4698677,0.0267563-7.8461227-0.6362991-7.4550781-4.0878906c0.2425804-2.1451874,2.5993347-3.0465698,4.7382813-3.3955078 c2.6318359-0.4296875,5.3945313-0.3251953,7.9882774,0.3017578c0.0061646,0.0014648,0.012085-0.0004883,0.0182495,0.0007324 L64.0123749,45.5925446z M64.0487518,36.9409332c-2.6920738-0.6071777-5.5366783-0.7060547-8.2550621-0.2629395 c-2.8740196,0.470295-5.6615906,1.8131523-5.9863281,4.7080078c-0.5018425,4.4379425,4.47435,5.7899628,8.9589844,5.7558594 l5.2397423-0.0490723c-0.0889435,13.624691,0.1381378,14.0157204-0.5004845,14.7600098 c-0.4492188,0.5253906-2.2080078,1.0888672-3.2431641,1.1425781c-17.3261032,0.8932877-33.7187004,1.8238754-50.8261719,0.8164063 c-0.8339844-0.0488281-1.4882817-0.7509766-1.4912114-1.5986328C7.9190578,52.4376526,6.8739986,19.3938637,7.102283,19.0354176 c1.2720323,0,6.8894105-0.2661171,25.2783203-0.2939453c8.4413376-0.0108852,17.2458305-0.0266666,25.7978516-0.3779297 C65.4974823,18.0765209,64.0197983,20.7003078,64.0487518,36.9409332z"/>
2197 </svg>';
2198 break;
2199 case 'verify':
2200 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
2201 <path class="latepoint-step-svg-base" d="M14.1105938,17.6527386h21.4086933c0.4140625,0,0.75-0.3359375,0.75-0.75s-0.3359375-0.75-0.75-0.75H14.1105938 c-0.4140625,0-0.75,0.3359375-0.75,0.75S13.6965313,17.6527386,14.1105938,17.6527386z"/>
2202 <path class="latepoint-step-svg-base" d="M48.0480957,22.5179729c0.190918-4.6103516-0.2402344-8.1689453-1.3554688-11.2001953 c-1.9773369-5.3880882-10.6812592-6.6263709-16.4194965-6.88623c-2.2271042-0.3552918-3.4171219-0.4732823-23.8388062-0.9545901 C5.5955906,3.4306827,5.2978926,3.7840867,5.309813,4.2435594c0.4078836,15.8521996,0.3535037,38.6989517,0.1298828,54.6308594 c0.0489416,0.1005783,0.1066036,0.7338486,0.7416992,0.7373047c0.0014648,0,0.003418,0,0.0048828,0 c0.1726775,0,19.3874683-0.9524536,39.9575195,1.1923828c0.5861588,0.0651283,1.0673027-0.5827713,0.6965942-1.1501465 c-0.3957596-2.2545013-0.4755592-3.6757584-0.5795288-5.1481934c0.0477905-0.0227051,0.0947876-0.0480957,0.1424561-0.0710449 c2.0167389,2.6554184,8.5339165,10.8789749,11.3917847,12.6982422c0.7129517,0.4538574,1.5125732,0.8005371,2.3395996,0.9714355 c4.5379868,1.9745102,8.1917953-3.4511719,5.8001099-6.3081055c-4.0245361-4.8284912-8.767334-10.3620605-13.5692749-15.0280762 c1.0654297-2.1257324,1.6327515-4.5004883,1.6327515-6.911377c0-4.8347168-2.2924194-9.3981953-6.1298218-12.3183613 c0.0004272-0.0112305,0.0014648-0.0220947,0.0018921-0.0332031 C47.9866676,24.0398521,48.0113487,23.3549309,48.0480957,22.5179729z M45.2601929,59.2135315 c-12.4361572-1.2451172-25.3148212-1.6257324-38.3179321-1.1262207c0.02246-8.7914352,0.4327807-31.9077263-0.112915-53.0991211 c20.4045773,0.4872842,21.7616024,0.5873499,24.1508789,1.0756836c1.9755001,0.4037867,3.2904224,4.9198499,5.040041,6.5957026 c0.3312874,0.3179483,0.834362,0.2433729,1.1196289-0.0429688c1.8201218-1.8236427,4.0447845-4.2757235,6.2490234-3.3017578 c0.7670898,0.3339844,1.4047852,1.1816406,1.8959961,2.5205078c1.0449219,2.8398438,1.4467773,6.2138672,1.2641602,10.6191406 c-0.0358124,0.8280945-0.0610733,1.5315475-0.1461792,4.076416c-2.3810425-1.4171143-5.0792236-2.1643066-7.8845825-2.1643066 c-3.1671143,0-6.135437,0.9802246-8.6168232,2.6494141c-0.4119091-0.311924,0.2382946-0.0890408-15.7840576-0.3027344 c-0.0024414,0-0.0048828,0-0.0068359,0c-0.4111328,0-0.7460938,0.3310547-0.75,0.7431641 c-0.0039063,0.4140625,0.3291016,0.7529297,0.7431641,0.7568359l14.081665,0.1290283 c-2.8327827,2.5395775-5.5364246,7.2262096-5.8631592,11.064333l-10.6237793,0.2597656 c-0.4140625,0.0107422-0.7412109,0.3544922-0.7314453,0.7685547c0.0102539,0.4072266,0.34375,0.7314453,0.7495117,0.7314453 c0.0063477,0,0.0126953,0,0.019043,0l10.5239258-0.2573242c-0.0244522,3.6942863,0.6843319,7.0339737,3.2225342,10.0561523 l-11.5189209,0.1054688c-0.4140625,0.0039063-0.7470703,0.3427734-0.7431641,0.7568359 c0.0039063,0.4121094,0.3388672,0.7431641,0.75,0.7431641c0.0019531,0,0.0043945,0,0.0068359,0l12.9440308-0.1186523 c0.0007935,0.0007324,0.0015259,0.0014648,0.0023193,0.0021973c3.6866817,3.1902428,7.7025356,4.4405403,11.8752575,4.1297493 c1.9718208-0.146862,3.978672-0.6423225,6.0023689-1.4463997C44.890686,56.5292053,45.0510254,57.889801,45.2601929,59.2135315z M64.7839355,62.7582092c1.643486,1.9650421-1.8606987,5.9641113-4.7329102,3.5546875 c-0.2494545-0.2046814-7.4860306-8.2930336-12.2422485-14.1032715c1.5042725-1.1379395,2.7863159-2.5305176,3.7785034-4.102417 C56.248291,52.6703186,60.8580322,58.0475159,64.7839355,62.7582092z M52.498291,39.856842 c0,7.7039337-6.2337532,13.9804688-13.9799805,13.9804688c-7.7138691,0-13.989748-6.2714844-13.989748-13.9804688 c0-7.7516708,6.3275547-13.9902363,13.989748-13.9902363C46.3522835,25.8666058,52.498291,32.2686691,52.498291,39.856842z"/>
2203 <path class="latepoint-step-svg-base" d="M61.0549316,64.0072327c0.2964249,0.2864761,0.7709198,0.2816391,1.0605469-0.0175781 c0.2875977-0.2978516,0.2792969-0.7734375-0.0185547-1.0605469l-1.0400391-1.0039063 c-0.2978516-0.2880859-0.7734375-0.2773438-1.0605469,0.0195313c-0.2875977,0.2988281-0.2788086,0.7734375,0.0195313,1.0605469 L61.0549316,64.0072327z"/>
2204 <path class="latepoint-step-svg-base" d="M38.798584,28.5873089c-6.2089844,0-11.2602558,5.055666-11.2602558,11.2695332 c0,6.2089844,5.0512714,11.2597656,11.2602558,11.2597656c6.2009888,0,11.2597656-5.036171,11.2597656-11.2597656 C50.0583496,33.6183395,44.9775581,28.5873089,38.798584,28.5873089z M38.798584,49.6166077 c-5.3818359,0-9.7602558-4.3779297-9.7602558-9.7597656c0-5.3867188,4.3784199-9.7695332,9.7602558-9.7695332 c5.343029,0,9.7597656,4.3516827,9.7597656,9.7695332C48.5583496,45.2636604,44.1625519,49.6166077,38.798584,49.6166077z"/>
2205 <path class="latepoint-step-svg-base" d="M44.651123,39.0619202c-4.2592773-0.2041016-6.421875-0.2050781-10.8295898,0.1923828 c-0.4125977,0.0371094-0.7167969,0.4023438-0.6796875,0.8144531c0.0351563,0.3896484,0.3623047,0.6826172,0.7460938,0.6826172 c0.0229492,0,0.0454102-0.0009766,0.0683594-0.0029297c4.3188477-0.3916016,6.440918-0.3886719,10.6225586-0.1884766 c0.4106445,0.0498047,0.765625-0.2998047,0.7851563-0.7128906C45.3840332,39.4330139,45.0646973,39.0814514,44.651123,39.0619202z "/>
2206 </svg>';
2207 break;
2208 case 'confirmation':
2209 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
2210 <path class="latepoint-step-svg-base" d="M17.6552105,33.4646034C8.2132654,33.6182289,3.8646491,39.9382057,3.773782,46.3166199 C3.6704469,53.57024,9.073472,60.8994293,18.7539654,59.3212318c0.0535278,1.8059692,0.1070557,3.6119995,0.1605835,5.4179649 c0.4868374,0.7426834,0.9158726,1.2552795,1.3218193,1.5758286c0.7646008,0.6037445,1.4473019,0.5261841,2.2800751,0.0214233 c0.9628239-0.5835876,2.1262512-1.7382126,3.8487892-3.0711861c1.3595581,1.338192,2.7954102,3.2556725,3.8725586,4.7504234 c0.6969604,0.1324463,1.3938599,0.2648926,2.0908184,0.3973389c0.354744,0.2420731,0.7306252,0.1458817,0.9553833-0.0870972 c1.1480217-1.1914139,0.2770538-0.5825653,5.0960693-4.9796104c1.381897,1.3053551,3.0732422,3.0024986,4.1270752,4.464901 c2.8935661,0.5499954,2.7743301,0.7335205,3.1699219,0.4522095c0.2846146-0.2016754,0.2662773-0.1645584,0.3554688-0.2646484 c1.3665047-1.5280838,3.0428238-3.2071915,4.854248-5.0933189c1.8391113,1.4305992,3.5415039,2.966732,5.0125732,4.6672935 c0.8833618,0.1398926,1.7667236,0.2797241,2.6500854,0.4195557c0.3787956,0.0587921,0.647274-0.1178513,0.7819214-0.3831787 c0.6037369-1.1866455,1.2043419-2.4298172,1.9224854-3.9011192c1.3636475,1.03265,2.6345825,2.1318321,3.7449989,3.3383751 c0.520752,0.0775146,0.9672852,0.0211792,1.4367676,0.0062256c0.6980667,0.5534744,1.3601151,0.1294708,1.392334-0.4434814 c1.1637878-20.9316826-0.4478302-32.0234108-1.8408203-43.4101563 c-1.0667953-8.7491531-3.4310074-16.6642761-17.6171913-18.6894531 C37.5750961,2.9660594,18.2152557,2.0518365,10.3015718,9.4919462 c-3.7495093,3.4759312-5.6556306,13.6249208-5.8579102,18.3261719c-0.0175781,0.4130859,0.3032227,0.7636719,0.7167969,0.78125 c0.0008545,0,0.0019531-0.0001831,0.0028076-0.0001831c0.0002441,0,0.0003662,0.0001831,0.0006104,0.0001831 c0.0022583,0.0003052,0.0042114-0.0008545,0.0064697-0.0005493c1.7694812,0.0453014,8.2837915-2.8392754,13.4412851-1.0584106 c0.3204956,1.9219971,0.4412842,3.8793335,0.4950562,5.8326435 C18.6154156,33.3746986,18.1323223,33.4094276,17.6552105,33.4646034z M19.1414165,57.7614784 c-7.5994434,0-11.3555832-5.7171745-11.3348923-11.4369698c0.0206909-5.7197952,3.8182158-11.4422112,11.3261032-11.4526787 c0.0092773,0,0.0180664,0,0.0273438,0c6.2543888,0,11.4311523,5.0988808,11.4311523,11.4394531 C30.5911236,52.5667496,25.5261116,57.7614784,19.1414165,57.7614784z M48.1580162,5.9938989 c13.5598068,1.9365721,15.3743439,9.4665871,16.3403358,17.3867188c0.7182922,5.8958893,3.0389252,18.635561,1.8983765,41.6446533 c-1.2305298-1.1603355-2.6870155-2.8059044-4.0233803-4.5684776c-0.3519096-0.4632568-1.1312485-0.3892365-1.3088379,0.2573853 c-0.0006714,0.0013428-0.0020142,0.0020142-0.0026855,0.0033569c-0.829628,1.6306496-1.5776443,3.2193794-2.6342773,5.3439903 c-1.9974098-2.2269859-3.4938774-3.9506302-5.3305054-5.9934654c-0.1636276-0.8107109-1.4189148-0.82724-1.5952148-0.0100098 c-1.9148636,2.1023941-4.205822,4.3376503-6.1530762,6.4651451c-1.4751854-1.9926682-3.3123169-4.1955643-4.62323-6.0411949 c-0.2008209-0.5232658-0.8574333-0.635643-1.2301025-0.258606c-2.1993942,2.222168-4.5591049,4.0396156-6.7687988,6.4904747 c-1.3328838-1.4328613-3.3396587-3.9911461-4.4924297-5.7590294c-0.2881527-0.4409218-0.9600582-0.4756927-1.2632446,0.0197754 c-1.7325058,1.1738968-2.8503933,2.218853-4.8071289,3.6727867l0.09198-5.7758751 c5.7322388-1.4144287,9.8353252-6.5934448,9.8353252-12.5602417c0-5.9226074-4.0585918-11.0758057-9.8167706-12.5380249 c-0.1152134-4.2746181-0.3553181-14.4360523-1.6055908-18.5303345c-0.6845055-2.2400188-2.8216324-5.7650404-5.5857553-7.1168213 C21.5624371,4.8990502,34.3388634,4.0191674,48.1580162,5.9938989z M6.0422945,26.9650288 c0.2917447-3.411478,1.0564828-7.6568089,2.2514648-10.9311523c0.883728-0.4779043,1.4030762-0.8288565,1.9675293-0.7024527 c0.9700317,0.2299805,1.9000244,1.0199575,2.710022,1.5799551c2.9155273,2.0056763,4.5519419,5.618042,5.333375,8.9669189 C13.8285227,24.7062149,8.9758253,26.2891541,6.0422945,26.9650288z"/>
2211 <path class="latepoint-step-svg-base" d="M20.168272,46.12183c-1.4780273-0.424263-3.6082001-0.2521667-4.2836924-1.4824219 c-0.4052734-0.7392578,0.0585938-1.7636719,0.7285166-2.2216797c0.9785156-0.6708984,2.2700195-0.5273438,2.9526367-0.3837891 c0.4052734,0.0830078,0.8032227-0.1748047,0.8886719-0.5800781s-0.1738281-0.8027344-0.5791016-0.8886719 c-0.3931274-0.0823975-0.7782593-0.130127-1.1518555-0.1454468c-0.1039429-0.53302-0.0985718-1.0831909,0.0239258-1.6152954 c0.0927734-0.4033203-0.1591797-0.8066406-0.5629883-0.8994141c-0.4038086-0.0898438-0.8061523,0.1611328-0.8989258,0.5634766 c-0.1596069,0.6945801-0.1751709,1.4108276-0.0565796,2.1081543c-0.53479,0.1254883-1.0369263,0.3114624-1.4629526,0.6027832 c-1.3994141,0.9570313-1.9360352,2.8320313-1.1962891,4.1816406c1.1052847,2.0129051,3.8100004,1.8074532,5.1850595,2.2021484 c2.1161976,0.6054153,1.8197498,2.4342194,0.3833008,3.0107422c-1.0332031,0.4150391-2.2402344,0.0205078-2.8691406-0.2519531 c-0.3808594-0.1640625-0.8217773,0.0107422-0.9863281,0.390625s0.0102539,0.8212891,0.390625,0.9863281 c0.4503174,0.1948242,1.0012817,0.3755493,1.5961304,0.4760132l0.1016235,1.6411743 c0.0249023,0.3974609,0.3549805,0.703125,0.7480469,0.703125c0.4355659,0,0.7758923-0.3669624,0.7490234-0.796875 l-0.0942383-1.5200806c0.3078613-0.0443115,0.6169434-0.112915,0.9238281-0.2357788 C23.4494343,50.8599739,23.6716747,47.1243896,20.168272,46.12183z"/>
2212 <path class="latepoint-step-svg-base" d="M27.5291119,20.7048359h28.2197247c0.4140625,0,0.75-0.3359375,0.75-0.75s-0.3359375-0.75-0.75-0.75H27.5291119 c-0.4140625,0-0.75,0.3359375-0.75,0.75S27.1150494,20.7048359,27.5291119,20.7048359z"/>
2213 <path class="latepoint-step-svg-base" d="M32.607235,31.4577656c0,0.4140625,0.3359375,0.7500019,0.75,0.7500019h23.1582031 c0.4140625,0,0.75-0.3359394,0.75-0.7500019s-0.3359375-0.75-0.75-0.75H33.357235 C32.9431725,30.7077656,32.607235,31.0437031,32.607235,31.4577656z"/>
2214 <path class="latepoint-step-svg-base" d="M55.2888756,41.443119H38.4182701c-0.4140625,0-0.75,0.3359375-0.75,0.75s0.3359375,0.75,0.75,0.75h16.8706055 c0.4140625,0,0.75-0.3359375,0.75-0.75S55.7029381,41.443119,55.2888756,41.443119z"/>
2215 </svg>';
2216 break;
2217 }
2218
2219 /**
2220 * Generates an SVG image for step code, if there was no custom image set
2221 *
2222 * @param {string} $svg image svg code
2223 * @param {string} $step_code step name code
2224 *
2225 * @since 5.0.0
2226 * @hook latepoint_svg_for_step_code
2227 *
2228 */
2229 return apply_filters( 'latepoint_svg_for_step_code', $svg, $step_code );
2230 }
2231
2232
2233 public static function get_time_pick_style() {
2234 return OsStepsHelper::get_step_setting_value( 'booking__datepicker', 'time_pick_style', 'timebox' );
2235 }
2236
2237 /**
2238 * Generates a preview for a selected step to show on booking form preview in settings
2239 *
2240 * @param string $selected_step_code
2241 *
2242 * @return void
2243 */
2244 public static function get_step_content_preview( string $selected_step_code ) {
2245 switch ( $selected_step_code ) {
2246 case 'booking__services':
2247 OsBookingHelper::generate_services_bundles_and_categories_list();
2248 break;
2249 case 'booking__agents':
2250 $agents_model = new OsAgentModel();
2251 $agents = $agents_model->should_be_active()->get_results_as_models();
2252 OsAgentHelper::generate_agents_list( $agents );
2253 break;
2254 case 'booking__datepicker':
2255 $booking = new OsBookingModel();
2256 $services = new OsServiceModel();
2257 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2258 if ( $service ) {
2259 $booking->service_id = $service->id;
2260 ?>
2261 <div class="os-dates-w" data-time-pick-style="<?php echo esc_attr(OsStepsHelper::get_time_pick_style()); ?>">
2262 <?php OsCalendarHelper::generate_calendar_for_datepicker_step( \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking ), new OsWpDateTime( 'now' ) ); ?>
2263 </div>
2264 <div class="time-selector-w <?php echo OsStepsHelper::hide_unavailable_slots() ? 'hide-not-available-slots' : ''; ?> <?php echo 'time-system-' . esc_attr(OsTimeHelper::get_time_system()); ?> <?php echo ( OsSettingsHelper::is_on( 'show_booking_end_time' ) ) ? 'with-end-time' : 'without-end-time'; ?> style-<?php echo esc_attr(OsStepsHelper::get_time_pick_style()); ?>">
2265 <div class="times-header">
2266 <div class="th-line"></div>
2267 <div class="times-header-label">
2268 <?php esc_html_e( 'Pick a slot for', 'latepoint' ); ?> <span></span>
2269 <?php do_action( 'latepoint_step_datepicker_appointment_time_header_label', $booking ); ?>
2270 </div>
2271 <div class="th-line"></div>
2272 </div>
2273 <div class="os-times-w">
2274 <div class="timeslots"></div>
2275 </div>
2276 </div>
2277 <?php
2278 } else {
2279 echo 'You need to have an active service to generate the calendar';
2280 }
2281 break;
2282 case 'booking__locations':
2283 OsLocationHelper::generate_locations_and_categories_list();
2284 break;
2285 case 'customer':
2286 $booking = new OsBookingModel();
2287 $services = new OsServiceModel();
2288 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2289 $customer = new OsCustomerModel();
2290 $default_fields_for_customer = OsSettingsHelper::get_default_fields_for_customer();
2291
2292 $current_step_code = $selected_step_code;
2293
2294 include LATEPOINT_VIEWS_ABSPATH . 'booking_form_settings/previews/_customer.php';
2295 break;
2296 case 'payment__times':
2297 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "If you have both a payment processor and pay locally enabled, customer will make a selection here.", 'latepoint' ) . '</div>';
2298 break;
2299 case 'payment__portions':
2300 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "If selected service has both deposit and charge amount set, customer will have to pick how much they want to pay now.", 'latepoint' ) . '</div>';
2301 break;
2302 case 'payment__methods':
2303 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "If you have multiple payment processors enabled, customer will be able to select how they want to pay", 'latepoint' ) . '</div>';
2304 break;
2305 case 'payment__pay':
2306 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "Payment form generated by selected payment processor will appear here", 'latepoint' ) . '</div>';
2307 break;
2308 case 'confirmation':
2309 echo '<div class="summary-status-wrapper summary-status-style-'.esc_attr(OsStepsHelper::get_step_setting_value($selected_step_code, 'order_confirmation_message_style', 'green')).'">';
2310 echo '<div class="summary-status-inner">';
2311 echo '<div class="ss-icon"></div>';
2312 echo '<div class="ss-title bf-side-heading editable-setting" data-setting-key="['. esc_attr($selected_step_code).'][order_confirmation_message_title]" contenteditable="true">'.esc_html(OsStepsHelper::get_step_setting_value($selected_step_code, 'order_confirmation_message_title', __('Appointment Confirmed', 'latepoint'))).'</div>';
2313 echo '<div class="ss-description bf-side-heading editable-setting" data-setting-key="['. esc_attr($selected_step_code).'][order_confirmation_message_content]" contenteditable="true">'.esc_html(OsStepsHelper::get_step_setting_value($selected_step_code, 'order_confirmation_message_content', __('We look forward to seeing you.', 'latepoint'))).'</div>';
2314 echo '<div class="ss-confirmation-number"><span>'.esc_html__('Order #', 'latepoint').'</span><strong>KDFJ934K</strong></div>';
2315 echo '</div>';
2316 echo '</div>';
2317 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "Order information will appear here.", 'latepoint' ) . '</div>';
2318 break;
2319 }
2320 do_action( 'latepoint_get_step_content_preview', $selected_step_code );
2321 }
2322
2323 public static function hide_slot_availability_count(): bool {
2324 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_slot_availability_count' ) );
2325 }
2326
2327 public static function hide_timepicker_when_one_slot_available(): bool {
2328 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_timepicker_when_one_slot_available' ) );
2329 }
2330
2331 public static function build_booking_object_for_current_step_preview( string $current_step ): OsBookingModel {
2332 $booking = new OsBookingModel();
2333 $steps_in_order = self::get_step_codes_in_order();
2334
2335 $current_step_index = array_search( $current_step, $steps_in_order );
2336 if ( $current_step_index === false ) {
2337 return $booking;
2338 }
2339 $completed_steps = array_slice( $steps_in_order, 0, $current_step_index );
2340 foreach ( $completed_steps as $completed_step ) {
2341 self::set_booking_object_values_for_completed_step( $booking, $completed_step );
2342 }
2343
2344 return $booking;
2345 }
2346
2347 public static function set_booking_object_values_for_completed_step( OsBookingModel $booking, string $completed_step ): OsBookingModel {
2348 switch ( $completed_step ) {
2349 case 'booking__services':
2350 $services = new OsServiceModel();
2351 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2352 if ( $service ) {
2353 $booking->service_id = $service->id;
2354 }
2355 break;
2356 case 'booking__locations':
2357 $locations = new OsLocationModel();
2358 $location = $locations->should_be_active()->set_limit( 1 )->get_results_as_models();
2359 if ( $location ) {
2360 $booking->location_id = $location->id;
2361 }
2362 break;
2363 case 'booking__agents':
2364 $agents = new OsAgentModel();
2365 $agent = $agents->should_be_active()->set_limit( 1 )->get_results_as_models();
2366 if ( $agent ) {
2367 $booking->agent_id = $agent->id;
2368 }
2369 break;
2370 case 'customer':
2371 $customers = new OsCustomerModel();
2372 $customer = $customers->set_limit( 1 )->get_results_as_models();
2373 if ( $customer ) {
2374 $booking->customer_id = $customer->id;
2375 }
2376 break;
2377 case 'booking__datepicker':
2378 $tomorrow = new OsWpDateTime( 'tomorrow' );
2379 $booking->start_date = $tomorrow->format( 'Y-m-d' );
2380 $booking->start_time = 600;
2381
2382 break;
2383 }
2384
2385 /**
2386 * Sets values for booking object depending on a completed step code
2387 *
2388 * @param {OsBookingModel} $booking booking object
2389 * @param {string} $completed_step step code that was completed
2390 *
2391 * @since 5.0.0
2392 * @hook latepoint_set_booking_object_values_for_completed_step
2393 *
2394 */
2395 return apply_filters( 'latepoint_set_booking_object_values_for_completed_step', $booking, $completed_step );
2396 }
2397
2398 public static function generate_summary_key_value_pairs( OsBookingModel $booking ): string {
2399 $html = '';
2400
2401
2402 if ( $booking->location_id ) {
2403 $html .= '<div class="summary-box summary-box-location-info">
2404 <div class="summary-box-heading">
2405 <div class="sbh-item">' . __( 'Location', 'latepoint' ) . '</div>
2406 <div class="sbh-line"></div>
2407 </div>
2408 <div class="summary-box-content with-media">
2409 <div class="sbc-content-i">
2410 <div class="sbc-main-item">' . $booking->location->name . '</div>
2411 </div>
2412 </div>
2413 </div>';
2414 }
2415 if ( $booking->customer_id ) {
2416 $html .= '<div class="summary-box summary-box-customer-info">
2417 <div class="summary-box-heading">
2418 <div class="sbh-item">' . __( 'Customer', 'latepoint' ) . '</div>
2419 <div class="sbh-line"></div>
2420 </div>
2421 <div class="summary-box-content with-media">
2422 <div class="os-avatar-w">
2423 <div class="os-avatar"><span>' . esc_html( $booking->customer->get_initials() ) . '</span></div>
2424 </div>
2425 <div class="sbc-content-i">
2426 <div class="sbc-main-item">' . esc_html( $booking->customer->full_name ) . '</div>
2427 <div class="sbc-sub-item">' . esc_html( $booking->customer->email ) . '</div>
2428 </div>
2429 </div>';
2430 $customer_attributes = [];
2431 $customer_attributes = apply_filters( 'latepoint_booking_summary_customer_attributes', $customer_attributes, $booking->customer );
2432 if ( $customer_attributes ) {
2433 $html .= '<div class="summary-attributes sa-clean sa-hidden">';
2434 foreach ( $customer_attributes as $attribute ) {
2435 $html .= '<span>' . esc_html( $attribute['label'] ) . ': <strong>' . esc_html( $attribute['value'] ) . '</strong></span>';
2436 }
2437 $html .= '</div>';
2438 }
2439 $html .= '</div>';
2440 }
2441 if ( OsSettingsHelper::is_off( 'steps_hide_agent_info' ) && $booking->agent_id && $booking->agent_id != LATEPOINT_ANY_AGENT ) {
2442 $bio_html = '';
2443 if ( OsSettingsHelper::steps_show_agent_bio() ) {
2444 $bio_html .= '<div class="os-trigger-item-details-popup sbc-link-item" data-item-details-popup-id="osItemDetailsPopupAgent_' . $booking->agent_id . '">' . __( 'Learn More', 'latepoint' ) . '</div>';
2445 $bio_html .= OsAgentHelper::generate_bio( $booking->agent );
2446 }
2447 $html .= '<div class="summary-box summary-box-agent-info">
2448 <div class="summary-box-heading">
2449 <div class="sbh-item">' . __( 'Agent', 'latepoint' ) . '</div>
2450 <div class="sbh-line"></div>
2451 </div>
2452 <div class="summary-box-content with-media">
2453 <div class="os-avatar-w"
2454 style="background-image: url(' . ( ( $booking->agent->avatar_image_id ) ? $booking->agent->get_avatar_url() : '' ) . ')">
2455 ' . ( ( ! $booking->agent->avatar_image_id ) ? '<div class="os-avatar"><span>' . esc_html( $booking->agent->get_initials() ) . '</span></div>' : '' ) . '
2456 </div>
2457 <div class="sbc-content-i">
2458 <div class="sbc-main-item">' . esc_html( $booking->agent->full_name ) . '</div>
2459 ' . $bio_html . '
2460 </div>
2461 </div>
2462 </div>';
2463 }
2464
2465
2466 /**
2467 * Key value pairs of summary values for the booking summary panel
2468 *
2469 * @param {string} $html HTML of key value pairs
2470 * @param {OsBookingModel} $booking Booking object that is used to generate the summary
2471 * @returns {string} $html The filtered HTML of key value pairs
2472 *
2473 * @since 5.0.0
2474 * @hook latepoint_summary_key_value_pairs
2475 *
2476 */
2477 $html = apply_filters( 'latepoint_summary_key_value_pairs', $html, $booking );
2478
2479 if ( $html ) {
2480 $html = '<div class="summary-boxes-columns">' . $html . '</div>';
2481 }
2482
2483 return $html;
2484 }
2485
2486 public static function is_ready_for_summary() {
2487 if ( ! empty( self::$order_object ) && ! self::$order_object->is_new_record() ) {
2488 // order object is set - don't need to show summary anymore
2489 return false;
2490 }
2491 if ( ! self::$cart_object->is_empty() ) {
2492 // cart has items inside - show summary
2493 return true;
2494 }
2495 if ( self::$active_cart_item->is_bundle() ) {
2496 // bundle selected already - show summary
2497 return true;
2498 }
2499 if ( ! empty( self::$booking_object->service_id ) ) {
2500 // service is selected for a booking - show summary
2501 return true;
2502 }
2503
2504
2505 return false;
2506 }
2507
2508 public static function set_active_cart_item_object( array $cart_item_params = [] ): OsCartItemModel {
2509 self::$active_cart_item = new OsCartItemModel();
2510 if ( ! empty( $cart_item_params['id'] ) ) {
2511 self::$active_cart_item->id = $cart_item_params['id'];
2512 // try to find it in cart
2513 $cart_item = new OsCartItemModel( self::$active_cart_item->id );
2514 if ( $cart_item->is_new_record() ) {
2515 // not found, reset active cart item ID
2516 self::$active_cart_item = new OsCartItemModel();
2517 }
2518 }
2519 self::$active_cart_item->variant = ! empty( $cart_item_params['variant'] ) ? $cart_item_params['variant'] : (empty(self::$presets['selected_bundle']) ? LATEPOINT_ITEM_VARIANT_BOOKING : LATEPOINT_ITEM_VARIANT_BUNDLE);
2520 if ( self::$active_cart_item->is_bundle() ) {
2521 if(empty($cart_item_params['item_data'])){
2522 self::$active_cart_item->item_data = empty(self::$presets['selected_bundle']) ? '' : wp_json_encode(['bundle_id' => self::$presets['selected_bundle']]);
2523 }else{
2524 // bundle gets data from params
2525 self::$active_cart_item->item_data = is_array( $cart_item_params['item_data'] ) ? wp_json_encode( $cart_item_params['item_data'], true ) : $cart_item_params['item_data'];
2526 }
2527 } else {
2528 // booking gets data from booking object
2529 self::$active_cart_item->item_data = wp_json_encode( self::$booking_object->generate_params_for_booking_form(), true );
2530 }
2531
2532 return self::$active_cart_item;
2533 }
2534
2535 public static function get_cart_item_object() {
2536 return self::$active_cart_item;
2537 }
2538
2539
2540 /**
2541 *
2542 * Given a step code, returns the first sub step if found, or returns the parent step code if no children
2543 *
2544 * @param string $parent_code
2545 *
2546 * @return string
2547 */
2548 public static function get_first_step_for_parent_code( string $parent_code ): string {
2549 $first_step_code = '';
2550 $step_codes = self::$step_codes_in_order;
2551 foreach ( $step_codes as $step_code ) {
2552 $loop_parent_code = explode( '__', $step_code )[0];
2553 if ( $loop_parent_code == $parent_code ) {
2554 $first_step_code = $step_code;
2555 break;
2556 }
2557 }
2558
2559 return $first_step_code;
2560 }
2561
2562 public static function check_step_code_access( string $step_code_to_access ): string {
2563 if ( $step_code_to_access == 'confirmation' && ! self::$order_object->is_new_record() ) {
2564 return $step_code_to_access;
2565 }
2566 // loops through all steps and checks if they satisfy condition to be skipped
2567 for ( $i = 0; $i < count( self::$step_codes_in_order ); $i ++ ) {
2568 $code = self::$step_codes_in_order[ $i ];
2569 $parent_code = explode( '__', $code )[0];
2570
2571 $next_code = ( ( $i + 1 ) < count( self::$step_codes_in_order ) ) ? self::$step_codes_in_order[ $i + 1 ] : false;
2572 $next_parent_code = $next_code ? explode( '__', $next_code )[0] : false;
2573
2574 if ( $step_code_to_access == $code ) {
2575 break;
2576 }
2577 switch ( $parent_code ) {
2578 // even tho we are checking a parent code - make sure to assign to a $code, because it's a first one in order in that parent
2579 case 'customer':
2580 if ( ! OsAuthHelper::is_customer_logged_in() ) {
2581 $step_code_to_access = $code;
2582 break 2;
2583 }
2584 break;
2585 case 'booking':
2586 if ( $next_parent_code && $next_parent_code != $parent_code && self::$cart_object->is_empty() ) {
2587 // $step_code_to_access = self::get_first_step_for_parent_code($parent_code);
2588 // break 2;
2589 }
2590 break;
2591 }
2592 }
2593
2594 /**
2595 * Checks if a step code can be accessed, returns the step code that can be accessed
2596 *
2597 * @param {string} $step_code_to_access step code that needs to be checked for access
2598 * @returns {string} $step_code_to_access The filtered step code that can be accessed
2599 *
2600 * @since 5.0.0
2601 * @hook latepoint_check_step_code_access
2602 *
2603 */
2604 return apply_filters( 'latepoint_check_step_code_access', $step_code_to_access );
2605 }
2606
2607 public static function get_first_step_code( string $step_code, $step_codes = false ): string {
2608 if ( ! $step_codes ) {
2609 $step_codes = self::get_step_codes_in_order();
2610 }
2611 if ( isset( $step_codes[ $step_code ] ) ) {
2612 return $step_code;
2613 }
2614 $unflat_step_codes = self::unflatten_steps( $step_codes );
2615
2616 // TODO add support for more than 2 dimentional parent/child arrays
2617 if ( isset( $unflat_step_codes[ $step_code ] ) ) {
2618 return implode( '__', [ $step_code, array_key_first( $unflat_step_codes[ $step_code ] ) ] );
2619 }
2620
2621 return '';
2622 }
2623
2624 public static function build_cart_object(): OsCartModel {
2625 if ( ! isset( self::$cart_object ) ) {
2626 self::set_cart_object();
2627 }
2628
2629 return self::$cart_object;
2630 }
2631
2632 public static function set_order_object( array $params = [] ): OsOrderModel {
2633 self::$order_object = new OsOrderModel();
2634
2635 return self::$order_object;
2636 }
2637
2638 public static function set_cart_object( array $params = [] ): OsCartModel {
2639 self::$cart_object = OsCartsHelper::get_or_create_cart();
2640 if ( self::$cart_object->order_id ) {
2641 self::load_order_object( self::$cart_object->order_id );
2642 } else {
2643 self::load_order_object();
2644 self::$cart_object->set_data( $params );
2645
2646 // set source id
2647 if ( isset( self::$restrictions['source_id'] ) ) {
2648 self::$cart_object->source_id = self::$restrictions['source_id'];
2649 }
2650
2651 self::$cart_object->calculate_prices();
2652 }
2653
2654 return self::$cart_object;
2655 }
2656
2657 public static function set_cart_object_from_order_intent( OsOrderIntentModel $order_intent ): OsCartModel {
2658 OsCartsHelper::get_or_create_cart();
2659 self::$cart_object->clear();
2660
2661
2662 // add items from intent
2663 $intent_cart_items = json_decode( $order_intent->cart_items_data, true );
2664 foreach ( $intent_cart_items as $cart_item_data ) {
2665 OsCartsHelper::add_item_to_cart( OsCartsHelper::create_cart_item_from_item_data( $cart_item_data ) );
2666 }
2667
2668 // restore payment info
2669 $payment_data = json_decode( $order_intent->payment_data, true );
2670 self::$cart_object->payment_method = $payment_data['method'];
2671 self::$cart_object->payment_time = $payment_data['time'];
2672 self::$cart_object->payment_portion = $payment_data['portion'];
2673 self::$cart_object->payment_token = $payment_data['token'];
2674 self::$cart_object->payment_processor = $payment_data['processor'];
2675
2676 return self::$cart_object;
2677 }
2678
2679 public static function hide_unavailable_slots() {
2680 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_unavailable_slots' ) );
2681 }
2682 }