PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.1.8
LatePoint – Calendar Booking Plugin for Appointments and Events v5.1.8
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_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 whatsapp_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
2760 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_recurring_booking_properties( $params );
231 OsStepsHelper::set_cart_object( $params['cart'] ?? [] );
232 OsStepsHelper::set_active_cart_item_object( $params['active_cart_item'] ?? [] );
233 OsStepsHelper::get_step_codes_in_order();
234 OsStepsHelper::remove_restricted_and_skippable_steps();
235 }
236
237 public static function get_step_label_by_code( string $step_code, string $parent_prefix = '' ): string {
238 $labels = [
239 'booking' => 'Booking Process',
240 'booking__services' => 'Services',
241 'booking__locations' => 'Locations',
242 'booking__agents' => 'Agents',
243 'booking__datepicker' => 'Datepicker',
244 'customer' => 'Customer',
245 'verify' => 'Verify Order',
246 'payment__times' => 'Payment Time',
247 'payment__portions' => 'Payment Portion',
248 'payment__methods' => 'Payment Method',
249 'payment__processors' => 'Payment Processors',
250 'payment__pay' => 'Payment Form',
251 'confirmation' => 'Confirmation'
252 ];
253
254 /**
255 * Returns an array of labels for step codes
256 *
257 * @param {array} $labels Current array of labels for step codes
258 *
259 * @returns {array} Filtered array of labels for step codes
260 * @since 5.0.0
261 * @hook latepoint_step_labels_by_step_codes
262 *
263 */
264 $labels = apply_filters( 'latepoint_step_labels_by_step_codes', $labels );
265
266 if ( $parent_prefix ) {
267 $step_code = $parent_prefix . '__' . $step_code;
268 }
269
270 return $labels[ $step_code ] ?? str_replace( ' ', ' - ', ucwords( str_replace( '_', ' ', $step_code ) ) );
271 }
272
273 public static function init_step_actions() {
274 add_action( 'latepoint_process_step', 'OsStepsHelper::process_step', 10, 2 );
275 add_action( 'latepoint_load_step', 'OsStepsHelper::load_step', 10, 3 );
276 add_action( 'rest_api_init', function () {
277 register_rest_route( 'latepoint', '/booking/bite-force/', array(
278 'methods' => 'POST',
279 'callback' => 'OsSettingsHelper::force_bite',
280 'permission_callback' => '__return_true'
281 ) );
282 } );
283 add_action( 'rest_api_init', function () {
284 register_rest_route( 'latepoint', '/booking/release-force/', array(
285 'methods' => 'POST',
286 'callback' => 'OsSettingsHelper::force_release',
287 'permission_callback' => '__return_true'
288 ) );
289 } );
290 self::confirm_hash();
291 }
292
293 public static function process_step( $step_code, $booking_object ) {
294 self::$step_to_process = $step_code;
295 if ( strpos( $step_code, '__' ) !== false ) {
296 // process parent step (used to run shared code between child steps)
297 $step_structure = explode( '__', $step_code );
298 $parent_step_function_name = 'process_step_' . $step_structure[0];
299 if ( method_exists( 'OsStepsHelper', $parent_step_function_name ) ) {
300 $result = self::$parent_step_function_name();
301 if ( is_wp_error( $result ) ) {
302 wp_send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => $result->get_error_message() ) );
303 }
304 }
305 }
306 $step_function_name = 'process_step_' . $step_code;
307 if ( method_exists( 'OsStepsHelper', $step_function_name ) ) {
308 $result = self::$step_function_name();
309 if ( is_wp_error( $result ) ) {
310 wp_send_json( array( 'status' => LATEPOINT_STATUS_ERROR, 'message' => $result->get_error_message() ) );
311
312 return;
313 }
314 }
315 }
316
317 public static function output_step_edit_form( $step ) {
318 if ( in_array( $step->code, [ 'payment', 'verify', 'confirmation' ] ) ) {
319 $can_reorder = false;
320 } else {
321 $can_reorder = true;
322 }
323 ?>
324 <div class="step-w" data-step-code="<?php echo esc_attr( $step->code ); ?>"
325 data-step-order-number="<?php echo esc_attr( $step->order_number ); ?>">
326 <div class="step-head">
327 <div class="step-drag <?php echo ( $can_reorder ) ? '' : 'disabled'; ?>">
328 <?php if ( ! $can_reorder ) {
329 echo '<span>' . esc_html__( 'Order of this step can not be changed.', 'latepoint' ) . '</span>';
330 } ?>
331 </div>
332 <div class="step-code"><?php echo esc_html( $step->title ); ?></div>
333 <div class="step-type"><?php echo esc_html( str_replace( '_', ' ', $step->code ) ); ?></div>
334 <?php if ( $step->code == 'locations' && ( OsLocationHelper::count_locations() <= 1 ) ) { ?>
335 <a href="<?php echo esc_url( OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'locations', 'index' ) ) ); ?>"
336 class="step-message"><?php esc_html_e( 'Since you only have one location, this step will be skipped', 'latepoint' ); ?></a>
337 <?php } ?>
338 <?php if ( $step->code == 'payment' && ! OsPaymentsHelper::is_accepting_payments() ) { ?>
339 <a href="<?php echo esc_url( OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'settings', 'payments' ) ) ); ?>"
340 class="step-message"><?php esc_html_e( 'Payment processing is disabled. Click to setup.', 'latepoint' ); ?></a>
341 <?php } ?>
342 <?php do_action( 'latepoint_custom_step_info', $step->code ); ?>
343 <button class="step-edit-btn"><i class="latepoint-icon latepoint-icon-edit-3"></i></button>
344 </div>
345 <div class="step-body">
346 <div class="os-form-w">
347 <form data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'settings', 'update_step' ) ); ?>" action="">
348
349 <div class="sub-section-row">
350 <div class="sub-section-label">
351 <h3><?php esc_html_e( 'Step Title', 'latepoint' ); ?></h3>
352 </div>
353 <div class="sub-section-content">
354 <?php echo OsFormHelper::text_field( 'step[title]', false, $step->title, [
355 'add_string_to_id' => $step->code,
356 'theme' => 'bordered'
357 ] ); ?>
358 </div>
359 </div>
360
361 <div class="sub-section-row">
362 <div class="sub-section-label">
363 <h3><?php esc_html_e( 'Step Sub Title', 'latepoint' ); ?></h3>
364 </div>
365 <div class="sub-section-content">
366 <?php echo OsFormHelper::text_field( 'step[sub_title]', false, $step->sub_title, [
367 'add_string_to_id' => $step->code,
368 'theme' => 'bordered'
369 ] ); ?>
370 </div>
371 </div>
372
373 <div class="sub-section-row">
374 <div class="sub-section-label">
375 <h3><?php esc_html_e( 'Short Description', 'latepoint' ); ?></h3>
376 </div>
377 <div class="sub-section-content">
378 <?php echo OsFormHelper::textarea_field( 'step[description]', false, $step->description, [
379 'add_string_to_id' => $step->code,
380 'theme' => 'bordered'
381 ] ); ?>
382 </div>
383 </div>
384 <div class="sub-section-row">
385 <div class="sub-section-label">
386 <h3><?php esc_html_e( 'Step Image', 'latepoint' ); ?></h3>
387 </div>
388 <div class="sub-section-content">
389 <?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 ); ?>
390 <div id="custom-step-image-w-<?php echo esc_attr( $step->code ); ?>"
391 class="custom-step-image-w-<?php echo esc_attr( $step->code ); ?>"
392 style="<?php echo ( $step->is_using_custom_image() ) ? '' : 'display: none;'; ?>">
393 <?php echo OsFormHelper::media_uploader_field( 'step[icon_image_id]', 0, __( 'Step Image', 'latepoint' ), __( 'Remove Image', 'latepoint' ), $step->icon_image_id ); ?>
394 </div>
395 </div>
396 </div>
397
398 <?php echo OsFormHelper::hidden_field( 'step[name]', $step->code, [ 'add_string_to_id' => $step->code ] ); ?>
399 <?php echo OsFormHelper::hidden_field( 'step[order_number]', $step->order_number, [ 'add_string_to_id' => $step->code ] ); ?>
400 <div class="os-step-form-buttons">
401 <a href="#"
402 class="latepoint-btn latepoint-btn-secondary step-edit-cancel-btn"><?php esc_html_e( 'Cancel', 'latepoint' ); ?></a>
403 <?php echo OsFormHelper::button( 'submit', __( 'Save Step', 'latepoint' ), 'submit', [
404 'class' => 'latepoint-btn',
405 'add_string_to_id' => $step->code
406 ] ); ?>
407 </div>
408 </form>
409 </div>
410 </div>
411 </div>
412 <?php
413 }
414
415 public static function confirm_hash() {
416 // if (OsSettingsHelper::get_settings_value('booking_hash')) add_action(OsSettingsHelper::read_encoded('d3BfZm9vdGVy'), 'OsStepsHelper::force_hash');
417 }
418
419 public static function force_hash() {
420 // echo OsSettingsHelper::read_encoded('PGRpdiBzdHlsZT0icG9zaXRpb246IGZpeGVkIWltcG9ydGFudDsgYm90dG9tOiA1cHghaW1wb3J0YW50OyBib3JkZXItcmFkaXVzOiA2cHghaW1wb3J0YW50O2JvcmRlcjogMXB4IHNvbGlkICNkODE3MmEhaW1wb3J0YW50O2JveC1zaGFkb3c6IDBweCAxcHggMnB4IHJnYmEoMCwwLDAsMC4yKSFpbXBvcnRhbnQ7bGVmdDogNXB4IWltcG9ydGFudDsgei1pbmRleDogMTAwMDAhaW1wb3J0YW50OyBiYWNrZ3JvdW5kLWNvbG9yOiAjZmY2ODc2IWltcG9ydGFudDsgdGV4dC1hbGlnbjogY2VudGVyIWltcG9ydGFudDsgY29sb3I6ICNmZmYhaW1wb3J0YW50OyBwYWRkaW5nOiA4cHggMTVweCFpbXBvcnRhbnQ7Ij5UaGlzIGlzIGEgdHJpYWwgdmVyc2lvbiBvZiA8YSBocmVmPSJodHRwczovL2xhdGVwb2ludC5jb20vcHVyY2hhc2UvP3NvdXJjZT10cmlhbCIgc3R5bGU9ImNvbG9yOiAjZmZmIWltcG9ydGFudDsgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmUhaW1wb3J0YW50OyBib3JkZXI6IG5vbmUhaW1wb3J0YW50OyI+TGF0ZVBvaW50IEFwcG9pbnRtZW50IEJvb2tpbmcgcGx1Z2luPC9hPiwgYWN0aXZhdGUgYnkgZW50ZXJpbmcgdGhlIGxpY2Vuc2Uga2V5IDxhIGhyZWY9Ii93cC1hZG1pbi9hZG1pbi5waHA/cGFnZT1sYXRlcG9pbnQmcm91dGVfbmFtZT11cGRhdGVzX19zdGF0dXMiIHN0eWxlPSJjb2xvcjogI2ZmZiFpbXBvcnRhbnQ7IHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lIWltcG9ydGFudDsgYm9yZGVyOiBub25lIWltcG9ydGFudDsiPmhlcmU8L2E+PC9kaXY+');
421 }
422
423 /**
424 * @param \LatePoint\Misc\Step[] $steps
425 * @param \LatePoint\Misc\Step $current_step
426 *
427 * @return void
428 */
429 public static function show_step_progress( array $steps, \LatePoint\Misc\Step $current_step ) {
430 ?>
431 <div class="latepoint-progress">
432 <ul>
433 <?php foreach ( $steps as $step ) { ?>
434 <li data-step-code="<?php echo $step->code; ?>"
435 class="<?php if ( $current_step->code == $step->code ) {
436 echo ' active ';
437 } ?>">
438 <div class="progress-item"><?php echo '<span> ' . esc_html( $step->main_panel_heading ) . '</span>'; ?></div>
439 </li>
440 <?php } ?>
441 </ul>
442 </div>
443 <?php
444 }
445
446 public static function load_step( $step_code, $format = 'json', $params = [] ) {
447 self::$params = $params;
448
449 $step_code = self::check_step_code_access( $step_code );
450 if ( OsAuthHelper::is_customer_logged_in() && OsSettingsHelper::get_settings_value( 'max_future_bookings_per_customer' ) ) {
451 $customer = OsAuthHelper::get_logged_in_customer();
452 if ( $customer->get_future_bookings_count() >= OsSettingsHelper::get_settings_value( 'max_future_bookings_per_customer' ) ) {
453 $steps_controller = new OsStepsController();
454 $steps_controller->set_layout( 'none' );
455 $steps_controller->set_return_format( $format );
456 $steps_controller->format_render( 'partials/_limit_reached', [], [
457 'show_next_btn' => false,
458 'show_prev_btn' => false,
459 'is_first_step' => true,
460 'is_last_step' => true,
461 'is_pre_last_step' => false
462 ] );
463
464 return;
465 }
466 }
467
468 self::$step_to_prepare = $step_code;
469
470 if ( strpos( self::$step_to_prepare, '__' ) !== false ) {
471 // prepare parent step (used to run shared code between child steps)
472 $step_structure = explode( '__', self::$step_to_prepare );
473 $parent_step_function_name = 'prepare_step_' . $step_structure[0];
474 if ( method_exists( 'OsStepsHelper', $parent_step_function_name ) ) {
475 $result = self::$parent_step_function_name();
476 if ( is_wp_error( $result ) ) {
477 $error_data = $result->get_error_data();
478 $send_to_step = ( isset( $error_data['send_to_step'] ) && ! empty( $error_data['send_to_step'] ) ) ? $error_data['send_to_step'] : false;
479 wp_send_json( array(
480 'status' => LATEPOINT_STATUS_ERROR,
481 'message' => $result->get_error_message(),
482 'send_to_step' => $send_to_step
483 ) );
484
485 return;
486 }
487 }
488 }
489
490 // run prepare step function
491 $step_function_name = 'prepare_step_' . self::$step_to_prepare;
492 if ( method_exists( 'OsStepsHelper', $step_function_name ) ) {
493
494 $result = self::$step_function_name();
495 if ( is_wp_error( $result ) ) {
496 $error_data = $result->get_error_data();
497 $send_to_step = ( isset( $error_data['send_to_step'] ) && ! empty( $error_data['send_to_step'] ) ) ? $error_data['send_to_step'] : false;
498 wp_send_json( array(
499 'status' => LATEPOINT_STATUS_ERROR,
500 'message' => $result->get_error_message(),
501 'send_to_step' => $send_to_step
502 ) );
503
504 return;
505 }
506
507
508 $steps_controller = new OsStepsController();
509 self::$booking_object = apply_filters( 'latepoint_prepare_step_booking_object', self::$booking_object, self::$step_to_prepare );
510 self::$cart_object = apply_filters( 'latepoint_prepare_step_cart_object', self::$cart_object, self::$step_to_prepare );
511 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 );
512 $steps_controller->vars = self::$vars_for_view;
513 $steps_controller->vars['booking'] = self::$booking_object;
514 $steps_controller->vars['cart'] = self::$cart_object;
515 $steps_controller->vars['current_step_code'] = self::$step_to_prepare;
516 $steps_controller->vars['restrictions'] = self::$restrictions;
517 $steps_controller->vars['presets'] = self::$presets;
518 $steps_controller->set_layout( 'none' );
519 $steps_controller->set_return_format( $format );
520 $steps_controller->format_render( 'load_step', [], [
521 'fields_to_update' => self::$fields_to_update,
522 'step_code' => self::$step_to_prepare,
523 'show_next_btn' => self::can_step_show_next_btn( self::$step_to_prepare ),
524 'show_prev_btn' => self::can_step_show_prev_btn( self::$step_to_prepare ),
525 'is_first_step' => self::is_first_step( self::$step_to_prepare ),
526 'is_last_step' => self::is_last_step( self::$step_to_prepare ),
527 'is_pre_last_step' => self::is_pre_last_step( self::$step_to_prepare )
528 ] );
529 }
530 }
531
532 public static function retrieve_step_code( string $step_code ): string {
533 if ( empty( $step_code ) ) {
534 return false;
535 }
536 if ( in_array( $step_code, self::get_step_codes_in_order( true ) ) ) {
537 return $step_code;
538 } else {
539 // check if it's a parent step and return the first child
540 $step_codes = self::unflatten_steps( self::get_step_codes_in_order( true ) );
541 if ( ! empty( $step_codes[ $step_code ] ) ) {
542 return ( $step_code . '__' . array_key_first( $step_codes[ $step_code ] ) );
543 }
544 }
545
546 return '';
547 }
548
549 public static function remove_restricted_and_skippable_steps() {
550 self::remove_restricted_steps();
551 self::remove_preset_steps();
552 $steps = [];
553 foreach ( self::$step_codes_in_order as $step_code ) {
554 if ( ! self::should_step_be_skipped( $step_code ) ) {
555 $steps[] = $step_code;
556 }
557 }
558 self::$step_codes_in_order = $steps;
559 }
560
561 public static function remove_preset_steps(): void {
562
563 if ( ! empty( self::$presets['selected_bundle'] ) ) {
564 self::remove_steps_for_parent( 'booking' );
565 } else {
566 // if current step is agents or services selection and we have it preselected - skip to next step
567 if ( ! empty( self::$presets['selected_service'] ) ) {
568 $service = new OsServiceModel( self::$presets['selected_service'] );
569 if ( $service->id ) {
570 self::remove_step_by_name( 'booking__services' );
571 }
572 }
573 if ( ! empty( self::$presets['selected_location'] ) ) {
574 self::remove_step_by_name( 'booking__locations' );
575 }
576 if ( ! empty( self::$presets['selected_agent'] ) ) {
577 self::remove_step_by_name( 'booking__agents' );
578 }
579 if ( ! empty( self::$presets['selected_start_date'] ) && ! empty( self::$presets['selected_start_time'] ) ) {
580 self::remove_step_by_name( 'booking__datepicker' );
581 }
582 }
583
584 if ( self::is_bundle_scheduling() ) {
585 // booking a bundle that was already paid for, skip payment step
586 // TODO check if valid order item id
587 self::remove_step_by_name( 'payment__methods' );
588 self::remove_step_by_name( 'payment__times' );
589 self::remove_step_by_name( 'payment__portions' );
590 self::remove_step_by_name( 'payment__pay' );
591 self::remove_step_by_name( 'customer' );
592 }
593
594 /**
595 * Remove steps that should not be shown based on presets
596 *
597 * @param {array} $presets array of presets
598 * @param {OsCartItemModel} $active_cart_item instance of a current active cart item
599 * @param {OsBookingModel} $booking instance of current booking object
600 * @param {OsCartModel} $cart instance of current cart object
601 *
602 * @since 5.0.0
603 * @hook latepoint_remove_preset_steps
604 *
605 */
606 do_action( 'latepoint_remove_preset_steps', self::$presets, self::$active_cart_item, self::$booking_object, self::$cart_object );
607 }
608
609
610 public static function remove_restricted_steps(): void {
611 /**
612 * Remove steps that should not be shown based on restrictions
613 *
614 * @param {array} $restrictions array of restrictions
615 * @param {OsCartItemModel} $active_cart_item instance of a current active cart item
616 * @param {OsBookingModel} $booking instance of current booking object
617 * @param {OsCartModel} $cart instance of current cart object
618 *
619 * @since 5.0.0
620 * @hook latepoint_remove_restricted_steps
621 *
622 */
623 do_action( 'latepoint_remove_restricted_steps', self::$restrictions, self::$active_cart_item, self::$booking_object, self::$cart_object );
624 }
625
626
627 public static function remove_step_by_name( $step_code ) {
628 self::$step_codes_in_order = array_values( array_diff( self::$step_codes_in_order, [ $step_code ] ) );
629 }
630
631 public static function remove_steps_for_parent( $parent_step_code ) {
632 self::$step_codes_in_order = array_filter( self::$step_codes_in_order, function ( $step ) use ( $parent_step_code ) {
633 return strpos( $step, $parent_step_code . '__' ) !== 0;
634 } );
635 }
636
637 public static function validate_presence( array $steps, array $rules ): array {
638
639 $errors = [];
640
641 // Check if each step in rules is present in steps
642 foreach ( $rules as $step_code => $conditions ) {
643 if ( ! in_array( $step_code, $steps ) ) {
644 // sometimes a rule is defined by the parent name, search for unflat list for parents
645 if ( ! in_array( $step_code, array_keys( self::unflatten_steps( $steps ) ) ) ) {
646 // translators: %s is the name of a step
647 $errors[] = sprintf( __( "Step %s is missing from steps array.", 'latepoint' ), $step_code );
648 }
649 }
650 }
651
652 // Check if each step in steps is present in rules
653 foreach ( $steps as $step_code ) {
654 if ( ! array_key_exists( $step_code, $rules ) ) {
655 // translators: %s is the name of a step
656 $errors[] = sprintf( __( "Step %s is not defined in the rules.", 'latepoint' ), $step_code );
657 }
658 }
659
660 return $errors;
661 }
662
663
664 public static function check_steps_for_errors( array $steps, array $steps_rules ): array {
665
666 $errors = [];
667
668 // check for step presence
669 $errors = array_merge( $errors, self::validate_presence( $steps, $steps_rules ) );
670
671 // check for correct order
672 $errors = array_merge( $errors, self::loop_step_rules_check( self::unflatten_steps( $steps ), $steps_rules ) );
673
674
675 /**
676 * Checks a list of steps for possible errors in order or existence and returns an array of errors if any
677 *
678 * @param {array} $errors list of errors found during a check
679 * @param {array} $steps list of steps that have to be checked
680 * @param {array} $role array of step rules to check against
681 * @returns {array} Filtered list of found errors
682 *
683 * @since 5.0.0
684 * @hook latepoint_check_steps_for_errors
685 *
686 */
687 return apply_filters( 'latepoint_check_steps_for_errors', $errors, $steps, $steps_rules );
688
689 }
690
691 public static function loop_step_rules_check( array $steps, array $steps_rules, string $parent = '' ): array {
692 $errors = [];
693 if ( empty( $steps ) ) {
694 return $errors;
695 }
696
697 $step_codes_to_validate = array_keys( $steps );
698
699 $errors = array_merge( $errors, self::validate_step_order( $step_codes_to_validate, $steps_rules, $parent ) );
700
701 foreach ( $steps as $parent_step_code => $step_children ) {
702 if ( ! empty( $step_children ) ) {
703 $errors = array_merge( $errors, self::loop_step_rules_check( $step_children, $steps_rules, $parent_step_code ) );
704 }
705 }
706
707 return $errors;
708 }
709
710 public static function validate_step_order( array $steps, array $rules, string $parent_code = '' ): array {
711 $errors = [];
712
713 foreach ( $steps as $step_code ) {
714 $rule_step_code = $parent_code ? $parent_code . '__' . $step_code : $step_code;
715
716 $current_index = array_search( $step_code, $steps );
717
718 if ( $current_index === false ) {
719 continue; // Skip if step is not in steps array
720 }
721
722 if ( isset( $rules[ $rule_step_code ]['after'] ) ) {
723 $after_index = array_search( $rules[ $rule_step_code ]['after'], $steps );
724 if ( $after_index === false || $after_index >= $current_index ) {
725 // translators: %1$s is step name with error, %2$s is step that it should come after
726 $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 ) );
727 }
728 }
729
730 if ( isset( $rules[ $rule_step_code ]['before'] ) ) {
731 $before_index = array_search( $rules[ $rule_step_code ]['before'], $steps );
732 if ( $before_index === false || $before_index <= $current_index ) {
733 // translators: %1$s is step name with error, %2$s is step that it should come before
734 $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 ) );
735 }
736 }
737 }
738
739 return $errors;
740 }
741
742 /**
743 *
744 * Returns a flat and ordered list of step codes
745 *
746 * @param bool $show_all_without_saving
747 *
748 * @return array
749 */
750 public static function get_step_codes_in_order( bool $show_all_without_saving = false ): array {
751 if ( $show_all_without_saving ) {
752 $steps_in_default_order = self::reorder_steps( self::get_step_codes_with_rules() );
753 $steps_in_saved_order = self::get_step_codes_in_order_from_db();
754
755 if ( empty( $steps_in_saved_order ) ) {
756 $step_codes_in_order = $steps_in_default_order;
757 } else {
758 $step_codes_in_order = self::cleanup_steps( $steps_in_saved_order, $steps_in_default_order );
759 }
760 } else {
761 if ( ! empty( self::$step_codes_in_order ) ) {
762 return self::$step_codes_in_order;
763 }
764 $steps_in_default_order = self::reorder_steps( self::get_step_codes_with_rules() );
765 $steps_in_saved_order = self::get_step_codes_in_order_from_db();
766
767 if ( empty( $steps_in_saved_order ) ) {
768 // save default active steps and order
769 $step_codes_in_order = $steps_in_default_order;
770 self::save_step_codes_in_order( $step_codes_in_order );
771 } else {
772 $step_codes_in_order = self::cleanup_steps( $steps_in_saved_order, $steps_in_default_order );
773 // save new order if different from what was saved before
774 if ( $step_codes_in_order != $steps_in_saved_order ) {
775 self::save_step_codes_in_order( $step_codes_in_order );
776 }
777 }
778 self::$step_codes_in_order = $step_codes_in_order;
779 }
780
781 return $step_codes_in_order;
782 }
783
784 public static function get_step_codes_in_order_from_db(): array {
785 $saved_order = OsSettingsHelper::get_settings_value( 'step_codes_in_order', '' );
786 if ( ! empty( $saved_order ) ) {
787 return explode( ',', $saved_order );
788 }
789
790 return [];
791 }
792
793 public static function insert_step( array $ordered_steps, string $new_step, array $new_step_rules ): array {
794 // Unflatten the ordered steps
795 $unflattened_steps = self::unflatten_steps( $ordered_steps );
796
797 // Insert the new step according to its rules
798 self::insert_step_recursive( $unflattened_steps, $new_step, $new_step_rules );
799
800 // Flatten the array again
801 $flattened_steps = self::flatten_steps( $unflattened_steps );
802
803 return $flattened_steps;
804 }
805
806 private static function insert_step_recursive( array &$steps, string $new_step, array $new_step_rules ) {
807 // Split the new step based on its parent structure
808 $parts = explode( '__', $new_step );
809 $actual_step = array_pop( $parts );
810 $parent = implode( '__', $parts ) ?: null;
811 $after = $new_step_rules['after'] ?? null;
812
813 // Insert the new step at the correct position in the unflattened steps
814 if ( $parent === null ) {
815 if ( $after === null ) {
816 // Insert at the beginning if no after rule
817 $steps = array_merge( [ $actual_step => [] ], $steps );
818 } else {
819 $position = array_search( $after, array_keys( $steps ) );
820 if ( $position !== false ) {
821 $steps = array_slice( $steps, 0, $position + 1, true ) + [ $actual_step => [] ] + array_slice( $steps, $position + 1, null, true );
822 }
823 }
824 } else {
825 // Recursively find the correct parent and insert
826 foreach ( $steps as $step_code => &$step_children ) {
827 if ( $step_code === $parent ) {
828 if ( $after === null ) {
829 $step_children = array_merge( [ $actual_step => [] ], $step_children );
830 } else {
831 $position = array_search( $after, array_keys( $step_children ) );
832 if ( $position !== false ) {
833 $step_children = array_slice( $step_children, 0, $position + 1, true ) + [ $actual_step => [] ] + array_slice( $step_children, $position + 1, null, true );
834 }
835 }
836
837 return;
838 } else {
839 self::insert_step_recursive( $step_children, $new_step, $new_step_rules );
840 }
841 }
842 }
843 }
844
845 public static function cleanup_steps( array $array_to_clean, array $reference_array ): array {
846 $filtered_array = [];
847 foreach ( $array_to_clean as $step_code ) {
848 if ( in_array( $step_code, $reference_array, true ) ) {
849 $filtered_array[] = $step_code;
850 }
851 }
852
853 $step_codes_with_rules = self::get_step_codes_with_rules();
854 foreach ( $reference_array as $step_code ) {
855 if ( ! in_array( $step_code, $filtered_array ) ) {
856 $step_rules = $step_codes_with_rules[ $step_code ] ?? [];
857 $filtered_array = self::insert_step( $filtered_array, $step_code, $step_rules );
858 }
859 }
860
861 return $filtered_array;
862 }
863
864 public static function get_step_name_without_parent( string $flat_step_name ): string {
865 $parts = explode( '__', $flat_step_name );
866
867 return end( $parts );
868 }
869
870
871 public static function set_default_presets(): array {
872 self::$presets = self::get_default_presets();
873
874 return self::$presets;
875 }
876
877 public static function get_default_presets(): array {
878 $default_presets = [
879 'selected_bundle' => false,
880 'selected_location' => false,
881 'selected_agent' => false,
882 'selected_service' => false,
883 'selected_duration' => false,
884 'selected_total_attendees' => false,
885 'selected_service_category' => false,
886 'selected_start_date' => false,
887 'selected_start_time' => false,
888 'order_item_id' => false,
889 'source_id' => false
890 ];
891
892 /**
893 * Sets default presets array of a StepHelper class
894 *
895 * @param {array} $presets Default array of presets set on StepHelper class
896 *
897 * @returns {array} Filtered array of presets
898 * @since 5.0.0
899 * @hook latepoint_get_default_presets
900 *
901 */
902 return apply_filters( 'latepoint_get_default_presets', $default_presets );
903 }
904
905 public static function set_default_restrictions(): array {
906 self::$restrictions = self::get_default_restrictions();
907
908 return self::$restrictions;
909 }
910
911 public static function get_default_restrictions(): array {
912 $default_restrictions = [
913 'show_locations' => false,
914 'show_agents' => false,
915 'show_services' => false,
916 'show_service_categories' => false,
917 'calendar_start_date' => false,
918 ];
919
920 /**
921 * Sets default restrictions array of a StepHelper class
922 *
923 * @param {array} $restrictions Default array of restrictions set on StepHelper class
924 *
925 * @returns {array} Filtered array of restrictions
926 * @since 5.0.0
927 * @hook latepoint_get_default_restrictions
928 *
929 */
930 return apply_filters( 'latepoint_get_default_restrictions', $default_restrictions );
931 }
932
933 public static function set_presets( array $presets = [] ): array {
934 self::set_default_presets();
935 // scheduling an item from existing order (bundle)
936 if ( isset( $presets['order_item_id'] ) ) {
937 self::$presets['order_item_id'] = $presets['order_item_id'];
938 }
939
940 // preselected service category
941 if ( isset( $presets['selected_service_category'] ) && is_numeric( $presets['selected_service_category'] ) ) {
942 self::$presets['selected_service_category'] = $presets['selected_service_category'];
943 }
944
945 // preselected location
946 if ( ! empty( $presets['selected_location'] ) && ( is_numeric( $presets['selected_location'] ) || ( $presets['selected_location'] == LATEPOINT_ANY_LOCATION ) ) ) {
947 self::$presets['selected_location'] = $presets['selected_location'];
948 }
949 // preselected agent
950 if ( ! empty( $presets['selected_agent'] ) && ( is_numeric( $presets['selected_agent'] ) || ( $presets['selected_agent'] == LATEPOINT_ANY_AGENT ) ) ) {
951 self::$presets['selected_agent'] = $presets['selected_agent'];
952 }
953
954 // preselected service
955 if ( isset( $presets['selected_service'] ) && is_numeric( $presets['selected_service'] ) ) {
956 self::$presets['selected_service'] = $presets['selected_service'];
957 }
958
959 // preselected bundle
960 if ( isset( $presets['selected_bundle'] ) && is_numeric( $presets['selected_bundle'] ) ) {
961 self::$presets['selected_bundle'] = $presets['selected_bundle'];
962 }
963
964 // preselected duration
965 if ( isset( $presets['selected_duration'] ) && is_numeric( $presets['selected_duration'] ) ) {
966 self::$presets['selected_duration'] = $presets['selected_duration'];
967 }
968
969 // preselected total attendees
970 if ( isset( $presets['selected_total_attendees'] ) && is_numeric( $presets['selected_total_attendees'] ) ) {
971 self::$presets['selected_total_attendees'] = $presets['selected_total_attendees'];
972 }
973
974 // preselected date
975 if ( isset( $presets['selected_start_date'] ) && OsTimeHelper::is_valid_date( $presets['selected_start_date'] ) ) {
976 self::$presets['selected_start_date'] = $presets['selected_start_date'];
977 }
978
979 // preselected time
980 if ( isset( $presets['selected_start_time'] ) && is_numeric( $presets['selected_start_time'] ) ) {
981 self::$presets['selected_start_time'] = $presets['selected_start_time'];
982 }
983
984 // set source id
985 if ( isset( $presets['source_id'] ) ) {
986 self::$presets['source_id'] = $presets['source_id'];
987 }
988
989 /**
990 * Sets presets array of a StepHelper class
991 *
992 * @param {array} $presets Array of presets set on StepHelper class
993 * @param {array} $presets Array of presets to be used to set presets on StepHelper class
994 *
995 * @returns {array} Filtered array of presets
996 * @since 5.0.0
997 * @hook latepoint_set_presets
998 *
999 */
1000 return apply_filters( 'latepoint_set_presets', self::$presets, $presets );
1001 }
1002
1003
1004 public static function set_restrictions( array $restrictions = [] ): array {
1005 self::set_default_restrictions();
1006 if ( ! empty( $restrictions ) ) {
1007 // filter locations
1008 if ( isset( $restrictions['show_locations'] ) ) {
1009 self::$restrictions['show_locations'] = $restrictions['show_locations'];
1010 }
1011
1012 // filter agents
1013 if ( isset( $restrictions['show_agents'] ) ) {
1014 self::$restrictions['show_agents'] = $restrictions['show_agents'];
1015 }
1016
1017 // filter service category
1018 if ( isset( $restrictions['show_service_categories'] ) ) {
1019 self::$restrictions['show_service_categories'] = $restrictions['show_service_categories'];
1020 }
1021
1022 // filter services
1023 if ( isset( $restrictions['show_services'] ) ) {
1024 self::$restrictions['show_services'] = $restrictions['show_services'];
1025 }
1026
1027 // preselected calendar start date
1028 if ( isset( $restrictions['calendar_start_date'] ) && OsTimeHelper::is_valid_date( $restrictions['calendar_start_date'] ) ) {
1029 self::$restrictions['calendar_start_date'] = $restrictions['calendar_start_date'];
1030 }
1031
1032 // restriction in settings can override it
1033 if ( OsTimeHelper::is_valid_date( OsSettingsHelper::get_settings_value( 'earliest_possible_booking' ) ) ) {
1034 self::$restrictions['calendar_start_date'] = OsSettingsHelper::get_settings_value( 'earliest_possible_booking' );
1035 }
1036
1037
1038 }
1039
1040 /**
1041 * Sets restrictions array of a StepHelper class
1042 *
1043 * @param {array} $restrictions Array of restrictions set on StepHelper class
1044 * @param {array} $restrictions Array of restrictions to be used to set restrictions on StepHelper class
1045 *
1046 * @returns {array} Filtered array of restrictions
1047 * @since 5.0.0
1048 * @hook latepoint_set_restrictions
1049 *
1050 */
1051 return apply_filters( 'latepoint_set_restrictions', self::$restrictions, $restrictions );
1052 }
1053
1054 /**
1055 * Sets booking object properties when a single option is available
1056 *
1057 * If a booking object has a service selected and only one agent is offering that service -
1058 * that agent will be preselected. Same for location
1059 *
1060 * @return OsBookingModel
1061 */
1062 public static function set_booking_properties_for_single_options(): OsBookingModel {
1063
1064 // if only 1 location exists or assigned to selected agent - set it to this booking object
1065 if ( OsLocationHelper::count_locations() == 1 ) {
1066 self::$booking_object->location_id = OsLocationHelper::get_default_location_id();
1067 }
1068 // if only 1 agent exists - set it to this booking object
1069 if ( OsAgentHelper::count_agents() == 1 ) {
1070 self::$booking_object->agent_id = OsAgentHelper::get_default_agent_id();
1071 }
1072
1073 return self::$booking_object;
1074 }
1075
1076 public static function set_booking_object( $booking_object_params = [] ): OsBookingModel {
1077 self::$booking_object = new OsBookingModel();
1078 self::$booking_object->set_data( $booking_object_params );
1079
1080 self::$booking_object->convert_start_datetime_into_server_timezone(OsTimeHelper::get_timezone_name_from_session());
1081
1082 if ( ! empty( $booking_object_params['intent_key'] ) ) {
1083 self::$booking_object->intent_key = $booking_object_params['intent_key'];
1084 }
1085
1086 // set based on presets
1087
1088 // preselected service
1089 if ( isset( self::$presets['selected_service'] ) && is_numeric( self::$presets['selected_service'] ) ) {
1090 self::$booking_object->service_id = self::$presets['selected_service'];
1091 $service = new OsServiceModel( self::$booking_object->service_id );
1092 self::$booking_object->service = $service;
1093 if ( empty( $booking_object_params['duration'] ) ) {
1094 self::$booking_object->duration = $service->duration;
1095 }
1096 if ( empty( $booking_object_params['total_attendees'] ) ) {
1097 self::$booking_object->total_attendees = $service->capacity_min;
1098 }
1099 }
1100
1101 // preselected agent
1102 if ( ! empty( self::$presets['selected_agent'] ) && ( is_numeric( self::$presets['selected_agent'] ) || ( self::$presets['selected_agent'] == LATEPOINT_ANY_AGENT ) ) ) {
1103 self::$booking_object->agent_id = self::$presets['selected_agent'];
1104 }
1105
1106 // preselected location
1107 if ( ! empty( self::$presets['selected_location'] ) && ( is_numeric( self::$presets['selected_location'] ) || ( self::$presets['selected_location'] == LATEPOINT_ANY_LOCATION ) ) ) {
1108 self::$booking_object->location_id = self::$presets['selected_location'];
1109 }
1110
1111 // preselected duration
1112 if ( isset( self::$presets['selected_duration'] ) && is_numeric( self::$presets['selected_duration'] ) ) {
1113 self::$booking_object->duration = self::$presets['selected_duration'];
1114 }
1115 // preselected attendees
1116 if ( isset( self::$presets['selected_total_attendees'] ) && is_numeric( self::$presets['selected_total_attendees'] ) ) {
1117 self::$booking_object->total_attendees = self::$presets['selected_total_attendees'];
1118 }
1119 // preselected date
1120 if ( isset( self::$presets['selected_start_date'] ) && OsTimeHelper::is_valid_date( self::$presets['selected_start_date'] ) ) {
1121 self::$booking_object->start_date = self::$presets['selected_start_date'];
1122 }
1123 // preselected time
1124 if ( isset( self::$presets['selected_start_time'] ) && is_numeric( self::$presets['selected_start_time'] ) ) {
1125 self::$booking_object->start_time = self::$presets['selected_start_time'];
1126 }
1127 // preselected time
1128 if ( isset( self::$presets['order_item_id'] ) && is_numeric( self::$presets['order_item_id'] ) ) {
1129 self::$booking_object->order_item_id = self::$presets['order_item_id'];
1130 // TODO - move to pro
1131 // it's a bundle, preset values from a bundle
1132 $order_item = new OsOrderItemModel( self::$booking_object->order_item_id );
1133 $bundle = new OsBundleModel( $order_item->get_item_data_value_by_key( 'bundle_id' ) );
1134 self::$booking_object->total_attendees = $bundle->total_attendees_for_service( self::$booking_object->service_id );
1135 self::$booking_object->duration = $bundle->duration_for_service( self::$booking_object->service_id );
1136 }
1137
1138
1139 // get buffers from service and set to booking object
1140 self::$booking_object->set_buffers();
1141 if ( self::$booking_object->is_start_date_and_time_set() ) {
1142 self::$booking_object->calculate_end_date_and_time();
1143 self::$booking_object->set_utc_datetimes();
1144 }
1145 self::$booking_object->customer_id = OsAuthHelper::get_logged_in_customer_id();
1146
1147 return self::$booking_object;
1148 }
1149
1150 public static function load_order_object( $order_id = false ) {
1151 if ( $order_id ) {
1152 self::$order_object = new OsOrderModel( $order_id );
1153 } else {
1154 self::$order_object = new OsOrderModel();
1155 }
1156 }
1157
1158 public static function is_bundle_scheduling() : bool {
1159 return self::$booking_object->is_bundle_scheduling();
1160 }
1161
1162 /**
1163 * 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
1164 *
1165 * @param string $current_step_code
1166 * @param string $next_step_code
1167 *
1168 * @return array
1169 */
1170 public static function carry_preset_fields_to_next_step( string $current_step_code, string $next_step_code ): void {
1171 if ( ! empty( self::$preset_fields[ $current_step_code ] ) ) {
1172 self::$preset_fields[ $next_step_code ] = array_merge( self::$preset_fields[ $next_step_code ], self::$preset_fields[ $current_step_code ] );
1173 }
1174 }
1175
1176 public static function should_step_be_skipped( string $step_code ): bool {
1177 $skip = false;
1178
1179 switch ( $step_code ) {
1180 case 'booking__agents':
1181 if ( OsAgentHelper::count_agents() == 1 ) {
1182 $skip = true;
1183 }
1184 if ( self::$active_cart_item->is_bundle() ) {
1185 $skip = true;
1186 }
1187 break;
1188 case 'booking__locations':
1189 if ( OsLocationHelper::count_locations() == 1 ) {
1190 $skip = true;
1191 }
1192 if ( self::$active_cart_item->is_bundle() ) {
1193 $skip = true;
1194 }
1195 break;
1196 case 'booking__datepicker':
1197 if ( self::$active_cart_item->is_bundle() ) {
1198 $skip = true;
1199 }
1200 break;
1201 case 'booking__services':
1202 if ( self::is_bundle_scheduling() ) {
1203 $skip = true;
1204 }
1205 break;
1206 case 'payment__times':
1207 case 'payment__portions':
1208 case 'payment__methods':
1209 case 'payment__processors':
1210 case 'payment__pay':
1211 if ( self::is_bundle_scheduling() || empty( OsPaymentsHelper::get_enabled_payment_times() ) ) {
1212 // scheduling a bundle or no enabled payment times
1213 $skip = true;
1214 self::set_zero_cost_payment_fields();
1215 } else {
1216 if ( self::$cart_object->is_empty() ) {
1217 $skip = true;
1218 } else {
1219 $original_amount = self::$cart_object->get_subtotal();
1220 $after_coupons_amount = self::$cart_object->get_total();
1221 $deposit_amount = self::$cart_object->deposit_amount_to_charge();
1222 if ( $original_amount > 0 && $after_coupons_amount <= 0 ) {
1223 // original price was set, but coupon was applied and charge amount is now 0, we can skip step, even if deposit is not 0
1224 $is_zero_cost = true;
1225 } else {
1226 if ( $after_coupons_amount <= 0 && $deposit_amount <= 0 ) {
1227 $is_zero_cost = true;
1228 } else {
1229 $is_zero_cost = false;
1230 }
1231 }
1232 // if nothing to charge - don't show it, no matter what
1233 if ( $is_zero_cost && ! OsSettingsHelper::is_env_demo() ) {
1234 $skip = true;
1235 self::set_zero_cost_payment_fields();
1236 } else {
1237 if ( $step_code == 'payment__times' ) {
1238 if ( ! empty( self::$cart_object->payment_time ) ) {
1239 $skip = true;
1240 } else {
1241 // try to check if one only available and preset it
1242 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
1243 if ( count( $enabled_payment_times ) == 1 ) {
1244 $skip = true;
1245 self::$cart_object->payment_time = array_key_first( $enabled_payment_times );
1246 self::$preset_fields['verify']['cart[payment_time]'] = OsFormHelper::hidden_field( 'cart[payment_time]', self::$cart_object->payment_time, [ 'skip_id' => true ] );
1247 // assign preset field value for next step
1248 self::$preset_fields['payment__portions']['cart[payment_time]'] = OsFormHelper::hidden_field( 'cart[payment_time]', self::$cart_object->payment_time, [ 'skip_id' => true ] );
1249 self::carry_preset_fields_to_next_step( 'payment__times', 'payment__portions' );
1250 }
1251 }
1252 }
1253 if ( $step_code == 'payment__portions' ) {
1254 if ( ! empty( self::$cart_object->payment_portion ) ) {
1255 $skip = true;
1256 } else {
1257 if ( $is_zero_cost || ( self::$cart_object->payment_time == LATEPOINT_PAYMENT_TIME_LATER ) || ( $after_coupons_amount > 0 && $deposit_amount <= 0 ) ) {
1258 // zero cost, pay later or 0 deposit, means it's a full portion payment preset
1259 self::$cart_object->payment_portion = LATEPOINT_PAYMENT_PORTION_FULL;
1260 } elseif ( $deposit_amount > 0 && $after_coupons_amount <= 0 ) {
1261 self::$cart_object->payment_portion = LATEPOINT_PAYMENT_PORTION_DEPOSIT;
1262 }
1263
1264 if ( ! empty( self::$cart_object->payment_portion ) ) {
1265 $skip = true;
1266 self::$preset_fields['verify']['cart[payment_portion]'] = OsFormHelper::hidden_field( 'cart[payment_portion]', self::$cart_object->payment_portion, [ 'skip_id' => true ] );
1267 self::$preset_fields['payment__methods']['cart[payment_portion]'] = OsFormHelper::hidden_field( 'cart[payment_portion]', self::$cart_object->payment_portion, [ 'skip_id' => true ] );
1268
1269 self::carry_preset_fields_to_next_step( 'payment__portions', 'payment__methods' );
1270 }
1271 }
1272 }
1273 if ( $step_code == 'payment__methods' ) {
1274 if ( ! empty( self::$cart_object->payment_method ) ) {
1275 $skip = true;
1276 } else {
1277 if ( self::$cart_object->payment_time ) {
1278 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( self::$cart_object->payment_time );
1279 if ( count( $enabled_payment_methods ) <= 1 ) {
1280 $skip = true;
1281 self::$cart_object->payment_method = array_key_first( $enabled_payment_methods );
1282 self::$preset_fields['verify']['cart[payment_method]'] = OsFormHelper::hidden_field( 'cart[payment_method]', self::$cart_object->payment_method, [ 'skip_id' => true ] );
1283 self::$preset_fields['payment__processors']['cart[payment_method]'] = OsFormHelper::hidden_field( 'cart[payment_method]', self::$cart_object->payment_method, [ 'skip_id' => true ] );
1284
1285 self::carry_preset_fields_to_next_step( 'payment__methods', 'payment__processors' );
1286 }
1287 }
1288 }
1289 }
1290 if ( $step_code == 'payment__processors' ) {
1291 if ( ! empty( self::$cart_object->payment_processor ) ) {
1292 $skip = true;
1293 } else {
1294 if ( self::$cart_object->payment_time && self::$cart_object->payment_method ) {
1295 $enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors_for_payment_time_and_method( self::$cart_object->payment_time, self::$cart_object->payment_method );
1296 if ( count( $enabled_payment_processors ) <= 1 ) {
1297 $skip = true;
1298 self::$cart_object->payment_processor = array_key_first( $enabled_payment_processors );
1299 self::$preset_fields['verify']['cart[payment_processor]'] = OsFormHelper::hidden_field( 'cart[payment_processor]', self::$cart_object->payment_processor, [ 'skip_id' => true ] );
1300 self::$preset_fields['payment__pay']['cart[payment_processor]'] = OsFormHelper::hidden_field( 'cart[payment_processor]', self::$cart_object->payment_processor, [ 'skip_id' => true ] );
1301
1302 self::carry_preset_fields_to_next_step( 'payment__processors', 'payment__pay' );
1303 }
1304 }
1305 }
1306 }
1307 if ( $step_code == 'payment__pay' ) {
1308 if ( self::$cart_object->payment_time == LATEPOINT_PAYMENT_TIME_LATER || empty( OsPaymentsHelper::get_enabled_payment_times() ) ) {
1309 $skip = true;
1310 }
1311 }
1312 }
1313 }
1314 }
1315 break;
1316 }
1317
1318 $skip = apply_filters( 'latepoint_should_step_be_skipped', $skip, $step_code, self::$cart_object, self::$active_cart_item, self::$booking_object );
1319
1320 return $skip;
1321 }
1322
1323 public static function set_zero_cost_payment_fields() {
1324 self::$preset_fields['verify']['cart[payment_time]'] = OsFormHelper::hidden_field( 'cart[payment_time]', LATEPOINT_PAYMENT_TIME_LATER, [ 'skip_id' => true ] );
1325 self::$preset_fields['verify']['cart[payment_method]'] = OsFormHelper::hidden_field( 'cart[payment_method]', 'other', [ 'skip_id' => true ] );
1326 self::$preset_fields['verify']['cart[payment_processor]'] = OsFormHelper::hidden_field( 'cart[payment_processor]', 'other', [ 'skip_id' => true ] );
1327 self::$preset_fields['verify']['cart[payment_portion]'] = OsFormHelper::hidden_field( 'cart[payment_portion]', LATEPOINT_PAYMENT_PORTION_FULL, [ 'skip_id' => true ] );
1328 }
1329
1330 public static function output_preset_fields( string $step_code ) {
1331 if ( ! empty( self::$preset_fields[ $step_code ] ) ) {
1332 foreach ( self::$preset_fields[ $step_code ] as $preset_field_html ) {
1333 echo $preset_field_html;
1334 }
1335 }
1336 }
1337
1338 public static function get_next_step_code( $current_step_code ) {
1339 $all_step_codes = self::get_step_codes_in_order( true );
1340 $active_step_codes = self::get_step_codes_in_order();
1341 $current_step_index = array_search( $current_step_code, $all_step_codes );
1342 if ( $current_step_index === false || ( ( $current_step_index + 1 ) == count( $all_step_codes ) ) ) {
1343 // no more steps or not found
1344 return false;
1345 }
1346 $next_step_code = $all_step_codes[ $current_step_index + 1 ];
1347
1348 if ( ! in_array( $next_step_code, $active_step_codes ) ) {
1349 // if is skipped - get next step in order and try again
1350 $next_step_code = self::get_next_step_code( $next_step_code );
1351 }
1352
1353 /**
1354 * Get the next step code, based on a current step
1355 *
1356 * @param {string} $next_step_code The next step code
1357 * @param {string} $current_step_code The current step code
1358 * @param {array} $all_step_codes List of all step codes
1359 * @param {array} $active_step_codes List of active step codes
1360 * @returns {string} The filtered next step code
1361 *
1362 * @since 5.0.16
1363 * @hook latepoint_get_next_step_code
1364 *
1365 */
1366 return apply_filters( 'latepoint_get_next_step_code', $next_step_code, $current_step_code, $all_step_codes, $active_step_codes );
1367 }
1368
1369 public static function get_prev_step_code( $current_step_code ) {
1370 $all_step_codes = self::get_step_codes_in_order( true );
1371 $current_step_index = array_search( $current_step_code, $all_step_codes );
1372
1373 if ( ! $current_step_index ) {
1374 // first step or not found - return the same code
1375 return $current_step_code;
1376 }
1377 $prev_step_code = $all_step_codes[ $current_step_code - 1 ];
1378 if ( self::should_step_be_skipped( $prev_step_code ) ) {
1379 // if skipped - get previous in order and try again
1380 $prev_step_code = self::get_prev_step_code( $prev_step_code );
1381 }
1382
1383 /**
1384 * Get the next step code, based on a current step
1385 *
1386 * @param {string} $next_step_code The next step code
1387 * @param {string} $current_step_code The current step code
1388 * @param {array} $all_step_codes List of all step codes
1389 * @returns {string} The filtered next step code
1390 *
1391 * @since 5.0.16
1392 * @hook latepoint_get_previous_step_code
1393 *
1394 */
1395 return apply_filters( 'latepoint_get_previous_step_code', $prev_step_code, $current_step_code, $all_step_codes );
1396 }
1397
1398
1399 public static function is_first_step( $step_code ) {
1400 $step_index = array_search( $step_code, self::get_step_codes_in_order() );
1401
1402 return $step_index == 0;
1403 }
1404
1405 public static function is_last_step( $step_code ) {
1406 $step_index = array_search( $step_code, self::get_step_codes_in_order() );
1407
1408 return ( ( $step_index + 1 ) == count( self::get_step_codes_in_order() ) );
1409 }
1410
1411 public static function is_pre_last_step( $step_code ) {
1412 $next_step_code = self::get_next_step_code( $step_code );
1413 $step_index = array_search( $next_step_code, self::get_step_codes_in_order() );
1414
1415 return ( ( $step_index + 1 ) == count( self::get_step_codes_in_order() ) );
1416 }
1417
1418 public static function can_step_show_prev_btn( $step_code ) {
1419 $step_index = array_search( $step_code, self::get_step_codes_in_order() );
1420 // if first or last step
1421 if ( $step_index == 0 || ( ( $step_index + 1 ) == count( self::get_step_codes_in_order() ) ) ) {
1422 return false;
1423 } else {
1424 return true;
1425 }
1426 }
1427
1428 public static function get_next_btn_label_for_step( $step_code ) {
1429 $label = __( 'Next', 'latepoint' );
1430 $custom_labels = [
1431 'payment__pay' => __( 'Submit', 'latepoint' ),
1432 'verify' => OsStepsHelper::should_step_be_skipped( 'payment__pay' ) ? __( 'Submit', 'latepoint' ) : __( 'Checkout', 'latepoint' )
1433 ];
1434
1435
1436 /**
1437 * Returns an array of custom labels for "next" button with step codes as keys
1438 *
1439 * @param {array} $custom_labels Current array of labels for "next" button
1440 *
1441 * @returns {array} Filtered array of labels for "next" button
1442 * @since 4.7.0
1443 * @hook latepoint_next_btn_labels_for_steps
1444 *
1445 */
1446 $custom_labels = apply_filters( 'latepoint_next_btn_labels_for_steps', $custom_labels );
1447 if ( ! empty( $custom_labels[ $step_code ] ) ) {
1448 $label = $custom_labels[ $step_code ];
1449 }
1450
1451 return $label;
1452 }
1453
1454 public static function can_step_show_next_btn( $step_code ) {
1455 $step_show_btn_rules = [
1456 'booking__services' => false,
1457 'booking__agents' => false,
1458 'booking__datepicker' => false,
1459 'customer' => true,
1460 'payment__times' => false,
1461 'payment__portions' => false,
1462 'payment__methods' => false,
1463 'payment__pay' => false,
1464 'verify' => true,
1465 'confirmation' => false
1466 ];
1467
1468 /**
1469 * Returns an array of rules of whether to show a next button on not, step codes are keys in this array
1470 *
1471 * @param {array} $step_show_btn_rules Current array of labels for "next" button
1472 * @param {string} $step_code Current array of labels for "next" button
1473 *
1474 * @returns {array} Filtered array of labels for "next" button
1475 * @since 4.7.0
1476 * @hook latepoint_step_show_next_btn_rules
1477 *
1478 */
1479 $step_show_btn_rules = apply_filters( 'latepoint_step_show_next_btn_rules', $step_show_btn_rules, $step_code );
1480
1481 return $step_show_btn_rules[ $step_code ] ?? false;
1482 }
1483
1484 /**
1485 * @throws Exception
1486 */
1487 public static function add_current_item_to_cart() {
1488 if ( self::$active_cart_item->is_new_record() ) {
1489 if ( self::$active_cart_item->is_bundle() ) {
1490 self::$cart_object->add_item( self::$active_cart_item );
1491 self::$fields_to_update['active_cart_item[id]'] = self::$active_cart_item->id;
1492 } elseif ( self::$active_cart_item->is_booking() ) {
1493 // only do this for new cart item, if modifying existing one - then the set_active_cart_item method will take care of updating it
1494 if ( self::$booking_object->is_bookable( [ 'skip_customer_check' => true ] ) ) {
1495 // create recurring record and assign it to this booking
1496 if ( ! empty( self::$booking_object->generate_recurrent_sequence ) ) {
1497 // Recurring booking
1498 $recurrence = new OsRecurrenceModel();
1499 $recurrence->rules = wp_json_encode( self::$booking_object->generate_recurrent_sequence['rules'] );
1500 $recurrence->overrides = wp_json_encode( self::$booking_object->generate_recurrent_sequence['overrides'] );
1501 if ( $recurrence->save() ) {
1502 self::$booking_object->recurrence_id = $recurrence->id;
1503 // we don't need these attributes anymore as we will get them from the recurrence model by ID
1504 self::$booking_object->generate_recurrent_sequence = [];
1505 $customer_timezone = self::$booking_object->get_customer_timezone();
1506 $recurring_bookings_data_and_errors = OsFeatureRecurringBookingsHelper::generate_recurring_bookings_data( self::$booking_object, $recurrence->get_rules(), $recurrence->get_overrides(), $customer_timezone );
1507 $main_cart_item_id = false;
1508 foreach ( $recurring_bookings_data_and_errors['bookings_data'] as $recurrence_bookings_datum ) {
1509 if ( $recurrence_bookings_datum['unchecked'] == 'yes' || !$recurrence_bookings_datum['is_bookable'] ) {
1510 continue;
1511 }
1512 self::$booking_object = $recurrence_bookings_datum['booking'];
1513 // set it again as booking object might have changed if agent or location were set to ANY, they are assigned now
1514 self::set_active_cart_item_object();
1515 if(!empty($main_cart_item_id)){
1516 self::$active_cart_item->connected_cart_item_id = $main_cart_item_id;
1517 }
1518 self::$cart_object->add_item( self::$active_cart_item );
1519 self::$fields_to_update['active_cart_item[id]'] = self::$active_cart_item->id;
1520 if(empty($main_cart_item_id)) $main_cart_item_id = self::$active_cart_item->id;
1521 }
1522 }
1523 } else {
1524 // Single time booking
1525 // set it again as booking object might have changed if agent or location were set to ANY, they are assigned now
1526 self::set_active_cart_item_object();
1527 if ( self::is_bundle_scheduling() ) {
1528 // we don't need to use a cart for bundle scheduling
1529 } else {
1530 self::$cart_object->add_item( self::$active_cart_item );
1531 self::$fields_to_update['active_cart_item[id]'] = self::$active_cart_item->id;
1532 }
1533 }
1534 self::reset_booking_object();
1535
1536 return true;
1537 } else {
1538 throw new Exception( implode( ',', self::$booking_object->get_error_messages() ) );
1539 }
1540 }
1541 }
1542 }
1543
1544 public static function process_step_booking() {
1545
1546 if ( ! self::is_bundle_scheduling() ) {
1547 // check if we are processing the last step of a booking sequence
1548 $booking_steps = [];
1549 foreach ( self::$step_codes_in_order as $step_code ) {
1550 if ( strpos( $step_code, 'booking__' ) !== false ) {
1551 $booking_steps[] = $step_code;
1552 }
1553 }
1554 if ( end( $booking_steps ) == self::$step_to_process ) {
1555 try {
1556 self::add_current_item_to_cart();
1557 } catch ( Exception $e ) {
1558 return new WP_Error( 'booking_slot_not_available', $e->getMessage() );
1559 }
1560 }
1561 }
1562
1563
1564 }
1565
1566 public static function reset_booking_object() {
1567 self::set_booking_object( [] );
1568 }
1569
1570 public static function prepare_step_booking() {
1571
1572 }
1573
1574
1575 // SERVICES
1576
1577 public static function process_step_booking__services() {
1578 }
1579
1580 public static function prepare_step_booking__services() {
1581 $bundles_model = new OsBundleModel();
1582 $bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models();
1583
1584 $services_model = new OsServiceModel();
1585 $show_selected_services_arr = self::$restrictions['show_services'] ? explode( ',', self::$restrictions['show_services'] ) : false;
1586 $show_service_categories_arr = self::$restrictions['show_service_categories'] ? explode( ',', self::$restrictions['show_service_categories'] ) : false;
1587 $preselected_category = self::$presets['selected_service_category'];
1588 $preselected_duration = self::$presets['selected_duration'];
1589 $preselected_total_attendees = self::$presets['selected_total_attendees'];
1590
1591 $connected_ids = OsConnectorHelper::get_connected_object_ids( 'service_id', [
1592 'agent_id' => self::$booking_object->agent_id,
1593 'location_id' => self::$booking_object->location_id
1594 ] );
1595 // if "show only specific services" is selected (restrictions) - remove ids that are not found in connection
1596 $show_services_arr = ( ! empty( $show_selected_services_arr ) && ! empty( $connected_ids ) ) ? array_intersect( $connected_ids, $show_selected_services_arr ) : $connected_ids;
1597 if ( ! empty( $show_services_arr ) ) {
1598 $services_model->where_in( 'id', $show_services_arr );
1599 }
1600
1601 $services = $services_model->should_be_active()->should_not_be_hidden()->order_by( 'order_number asc' )->get_results_as_models();
1602
1603 self::$vars_for_view['show_services_arr'] = $show_services_arr;
1604 self::$vars_for_view['show_service_categories_arr'] = $show_service_categories_arr;
1605 self::$vars_for_view['preselected_category'] = $preselected_category;
1606 self::$vars_for_view['preselected_duration'] = $preselected_duration;
1607 self::$vars_for_view['preselected_total_attendees'] = $preselected_total_attendees;
1608 self::$vars_for_view['services'] = $services;
1609 self::$vars_for_view['bundles'] = $bundles;
1610 }
1611
1612 // AGENTS
1613
1614 public static function process_step_booking__agents() {
1615 }
1616
1617 public static function prepare_step_booking__agents() {
1618 $agents_model = new OsAgentModel();
1619
1620 $show_selected_agents_arr = ( self::$restrictions['show_agents'] ) ? explode( ',', self::$restrictions['show_agents'] ) : false;
1621 // Find agents that actually offer selected service (if selected) at selected location (if selected)
1622 $connected_ids = OsConnectorHelper::get_connected_object_ids( 'agent_id', [
1623 'service_id' => self::$booking_object->service_id,
1624 'location_id' => self::$booking_object->location_id
1625 ] );
1626
1627 // If date/time is selected - filter agents who are available at that time
1628 if ( self::$booking_object->start_date && self::$booking_object->start_time ) {
1629 $available_agent_ids = [];
1630 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( self::$booking_object );
1631 foreach ( $connected_ids as $agent_id ) {
1632 $booking_request->agent_id = $agent_id;
1633 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
1634 $available_agent_ids[] = $agent_id;
1635 }
1636 }
1637 $connected_ids = array_intersect( $available_agent_ids, $connected_ids );
1638 }
1639
1640
1641 // if show only specific agents are selected (restrictions) - remove ids that are not found in connection
1642 $show_agents_arr = ( $show_selected_agents_arr ) ? array_intersect( $connected_ids, $show_selected_agents_arr ) : $connected_ids;
1643 if ( ! empty( $show_agents_arr ) ) {
1644 $agents_model->where_in( 'id', $show_agents_arr );
1645 $agents = $agents_model->should_be_active()->get_results_as_models();
1646 self::$vars_for_view['agents'] = $agents;
1647 } else {
1648 // no available or connected agents
1649 self::$vars_for_view['agents'] = [];
1650 }
1651 }
1652
1653
1654 // DATEPICKER
1655
1656 public static function prepare_step_booking__datepicker() {
1657 if ( empty( self::$booking_object->agent_id ) ) {
1658 self::$booking_object->agent_id = LATEPOINT_ANY_AGENT;
1659 }
1660 self::$vars_for_view['calendar_start_date'] = self::$restrictions['calendar_start_date'] ? self::$restrictions['calendar_start_date'] : 'today';
1661 }
1662
1663 public static function process_step_booking__datepicker() {
1664 }
1665
1666
1667 // CONTACT
1668
1669
1670 public static function prepare_step_customer() {
1671
1672 if ( OsAuthHelper::is_customer_logged_in() ) {
1673 self::$booking_object->customer = OsAuthHelper::get_logged_in_customer();
1674 self::$booking_object->customer_id = self::$booking_object->customer->id;
1675 } else {
1676 self::$booking_object->customer = new OsCustomerModel();
1677 }
1678
1679 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1680 self::$vars_for_view['customer'] = self::$booking_object->customer;
1681 }
1682
1683 private static function customer_params(): array {
1684 $params = OsParamsHelper::get_param( 'customer' );
1685 if ( empty( $params ) ) {
1686 return [];
1687 }
1688
1689 $customer_params = OsParamsHelper::permit_params( $params, [
1690 'first_name',
1691 'last_name',
1692 'email',
1693 'phone',
1694 'notes',
1695 'password',
1696 'password_confirmation'
1697 ] );
1698
1699 if ( ! empty( $customer_params['first_name'] ) ) {
1700 $customer_params['first_name'] = sanitize_text_field( $customer_params['first_name'] );
1701 }
1702 if ( ! empty( $customer_params['last_name'] ) ) {
1703 $customer_params['last_name'] = sanitize_text_field( $customer_params['last_name'] );
1704 }
1705 if ( ! empty( $customer_params['email'] ) ) {
1706 $customer_params['email'] = sanitize_email( $customer_params['email'] );
1707 }
1708 if ( ! empty( $customer_params['phone'] ) ) {
1709 $customer_params['phone'] = sanitize_text_field( $customer_params['phone'] );
1710 }
1711 if ( ! empty( $customer_params['notes'] ) ) {
1712 $customer_params['notes'] = sanitize_textarea_field( $customer_params['notes'] );
1713 }
1714
1715 /**
1716 * Filtered customer params for steps
1717 *
1718 * @param {array} $customer_params a filtered array of customer params
1719 * @param {array} $params unfiltered 'customer' params
1720 * @returns {array} $customer_params a filtered array of customer params
1721 *
1722 * @since 5.0.14
1723 * @hook latepoint_customer_params_on_steps
1724 *
1725 */
1726 return apply_filters( 'latepoint_customer_params_on_steps', $customer_params, $params );
1727 }
1728
1729 public static function process_step_customer() {
1730 $status = LATEPOINT_STATUS_SUCCESS;
1731
1732 $customer_params = self::customer_params();
1733
1734 $logged_in_customer = OsAuthHelper::get_logged_in_customer();
1735
1736
1737 if ( $logged_in_customer ) {
1738 // LOGGED IN ALREADY
1739 // Check if they are changing the email on file
1740 if ( $logged_in_customer->email != $customer_params['email'] ) {
1741 // Check if other customer already has this email
1742 $customer = new OsCustomerModel();
1743 $customer_with_email_exist = $customer->where( array(
1744 'email' => $customer_params['email'],
1745 'id !=' => $logged_in_customer->id
1746 ) )->set_limit( 1 )->get_results_as_models();
1747 // check if another customer (or if wp user login enabled - another wp user) exists with the email that this user tries to update to
1748 if ( $customer_with_email_exist || ( OsAuthHelper::wp_users_as_customers() && email_exists( $customer_params['email'] ) ) ) {
1749 $status = LATEPOINT_STATUS_ERROR;
1750 $response_html = __( 'Another customer is registered with this email.', 'latepoint' );
1751 }
1752 }
1753 } else {
1754 // NEW REGISTRATION (NOT LOGGED IN)
1755 if ( OsAuthHelper::wp_users_as_customers() ) {
1756 // WP USERS AS CUSTOMERS
1757 if ( email_exists( $customer_params['email'] ) ) {
1758 // wordpress user with this email already exists, ask to login
1759 $status = LATEPOINT_STATUS_ERROR;
1760 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1761 } else {
1762 // wp user does not exist - search for latepoint customer
1763 $customer = new OsCustomerModel();
1764 $customer = $customer->where( array( 'email' => $customer_params['email'] ) )->set_limit( 1 )->get_results_as_models();
1765 if ( $customer ) {
1766 // latepoint customer with this email exits, create wp user for them
1767 $wp_user = OsCustomerHelper::create_wp_user_for_customer( $customer );
1768 $status = LATEPOINT_STATUS_ERROR;
1769 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1770 } else {
1771 // no latepoint customer or wp user with this email found, can proceed
1772 }
1773 }
1774 } else {
1775 // LATEPOINT CUSTOMERS
1776 $customer = new OsCustomerModel();
1777 $customer_exist = $customer->where( array( 'email' => $customer_params['email'] ) )->set_limit( 1 )->get_results_as_models();
1778 if ( $customer_exist ) {
1779 // customer with this email exists - check if current customer was registered as a guest
1780 if ( OsSettingsHelper::is_on( 'steps_hide_login_register_tabs' ) || ( $customer_exist->can_login_without_password() && ! OsSettingsHelper::is_on( 'steps_require_setting_password' ) ) ) {
1781 // guest account, login automatically
1782 $status == LATEPOINT_STATUS_SUCCESS;
1783 OsAuthHelper::authorize_customer( $customer_exist->id );
1784 } else {
1785 // Not a guest account, ask to login
1786 $status = LATEPOINT_STATUS_ERROR;
1787 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1788 }
1789 } else {
1790 // no latepoint customer with this email found, can proceed
1791 }
1792 }
1793 // if not logged in - check if password has to be set
1794 if ( ! OsAuthHelper::is_customer_logged_in() && OsSettingsHelper::is_on( 'steps_require_setting_password' ) ) {
1795 if ( ! empty( $customer_params['password'] ) && $customer_params['password'] == $customer_params['password_confirmation'] ) {
1796 $customer_params['password'] = OsAuthHelper::hash_password( $customer_params['password'] );
1797 $customer_params['is_guest'] = false;
1798 } else {
1799 // Password is blank or does not match the confirmation
1800 $status = LATEPOINT_STATUS_ERROR;
1801 $response_html = __( 'Setting password is required and should match password confirmation', 'latepoint' );
1802 }
1803 }
1804 }
1805 // If no errors, proceed
1806 if ( $status == LATEPOINT_STATUS_SUCCESS ) {
1807 if ( OsAuthHelper::is_customer_logged_in() ) {
1808 $customer = OsAuthHelper::get_logged_in_customer();
1809 $is_new_customer = $customer->is_new_record();
1810 } else {
1811 $customer = new OsCustomerModel();
1812 $is_new_customer = true;
1813 }
1814 $old_customer_data = $is_new_customer ? [] : $customer->get_data_vars();
1815 $customer->set_data( $customer_params, LATEPOINT_PARAMS_SCOPE_PUBLIC );
1816 if ( $customer->save() ) {
1817 if ( $is_new_customer ) {
1818 do_action( 'latepoint_customer_created', $customer );
1819 } else {
1820 do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
1821 }
1822
1823 self::$booking_object->customer_id = $customer->id;
1824 if ( ! OsAuthHelper::is_customer_logged_in() ) {
1825 OsAuthHelper::authorize_customer( $customer->id );
1826 }
1827 $customer->set_timezone_name();
1828 } else {
1829 $status = LATEPOINT_STATUS_ERROR;
1830 $response_html = $customer->get_error_messages();
1831 if ( is_array( $response_html ) ) {
1832 $response_html = implode( ', ', $response_html );
1833 }
1834 }
1835 }
1836 if ( $status == LATEPOINT_STATUS_ERROR ) {
1837 return new WP_Error( LATEPOINT_STATUS_ERROR, $response_html );
1838 }
1839
1840 }
1841
1842
1843 // VERIFICATION STEP
1844
1845 public static function process_step_verify() {
1846
1847 }
1848
1849 public static function prepare_step_verify() {
1850 $cart = OsCartsHelper::get_or_create_cart();
1851
1852 $cart->set_singular_payment_attributes();
1853
1854 self::$vars_for_view['cart'] = $cart;
1855 self::$vars_for_view['customer'] = OsAuthHelper::get_logged_in_customer();
1856 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1857 }
1858
1859 // PAYMENT
1860
1861 public static function process_step_payment__portions() {
1862 }
1863
1864 public static function prepare_step_payment__portions() {
1865 }
1866
1867 public static function process_step_payment__times() {
1868 }
1869
1870 public static function prepare_step_payment__times() {
1871 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
1872
1873 self::$vars_for_view['enabled_payment_times'] = $enabled_payment_times;
1874 }
1875
1876 public static function process_step_payment__methods() {
1877 }
1878
1879 public static function prepare_step_payment__methods() {
1880 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( self::$cart_object->payment_time );
1881 self::$vars_for_view['enabled_payment_methods'] = $enabled_payment_methods;
1882 }
1883
1884 public static function process_step_payment__processors() {
1885 }
1886
1887 public static function prepare_step_payment__processors() {
1888 $enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors();
1889 self::$vars_for_view['enabled_payment_processors'] = $enabled_payment_processors;
1890 }
1891
1892 public static function process_step_payment__pay() {
1893 }
1894
1895 public static function prepare_step_payment__pay() {
1896 $booking_form_page_url = self::$params['booking_form_page_url'] ?? OsUtilHelper::get_referrer();
1897 $order_intent = OsOrderIntentHelper::create_or_update_order_intent( self::$cart_object, self::$restrictions, self::$presets, $booking_form_page_url );
1898 }
1899
1900
1901 // CONFIRMATION
1902
1903 public static function process_step_confirmation() {
1904 }
1905
1906 public static function prepare_step_confirmation() {
1907 self::$vars_for_view['customer'] = OsAuthHelper::get_logged_in_customer();
1908 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1909 if ( ! self::$order_object->is_new_record() ) {
1910 self::$vars_for_view['order'] = self::$order_object;
1911 self::$vars_for_view['order_bookings'] = self::$order_object->get_bookings_from_order_items();
1912 self::$vars_for_view['order_bundles'] = self::$order_object->get_bundles_from_order_items();
1913 self::$vars_for_view['price_breakdown_rows'] = self::$order_object->generate_price_breakdown_rows();
1914 self::$vars_for_view['is_bundle_scheduling'] = false;
1915 } else {
1916 // TRY SAVING BOOKING
1917 // check if it's a scheduling request for an existing order item, it means its a bundle
1918 $is_bundle_scheduling = self::is_bundle_scheduling();
1919 self::$vars_for_view['is_bundle_scheduling'] = $is_bundle_scheduling;
1920 if ( $is_bundle_scheduling ) {
1921 $order_item = new OsOrderItemModel( self::$booking_object->order_item_id );
1922 $order = new OsOrderModel( $order_item->order_id );
1923 self::$vars_for_view['order'] = $order;
1924 self::$vars_for_view['order_bookings'] = $order->get_bookings_from_order_items();
1925 self::$vars_for_view['order_bundles'] = $order->get_bundles_from_order_items();
1926 self::$vars_for_view['price_breakdown_rows'] = self::$cart_object->generate_price_breakdown_rows();
1927
1928 if(!empty(self::$booking_object->generate_recurrent_sequence)){
1929 $recurrence = new OsRecurrenceModel();
1930 $recurrence->rules = wp_json_encode( self::$booking_object->generate_recurrent_sequence['rules'] );
1931 $recurrence->overrides = wp_json_encode( self::$booking_object->generate_recurrent_sequence['overrides'] );
1932 if ( $recurrence->save() ) {
1933 self::$booking_object->recurrence_id = $recurrence->id;
1934 // we don't need these attributes anymore as we will get them from the recurrence model by ID
1935 self::$booking_object->generate_recurrent_sequence = [];
1936 $customer_timezone = self::$booking_object->get_customer_timezone();
1937 $recurring_bookings_data_and_errors = OsFeatureRecurringBookingsHelper::generate_recurring_bookings_data( self::$booking_object, $recurrence->get_rules(), $recurrence->get_overrides(), $customer_timezone );
1938 foreach ( $recurring_bookings_data_and_errors['bookings_data'] as $recurrence_bookings_datum ) {
1939 if ( $recurrence_bookings_datum['unchecked'] == 'yes' ) {
1940 continue;
1941 }
1942 self::$booking_object = $recurrence_bookings_datum['booking'];
1943 // set it again as booking object might have changed if agent or location were set to ANY, they are assigned now
1944 self::set_active_cart_item_object();
1945 if ( self::$booking_object->is_bookable() ) {
1946
1947 if ( self::$booking_object->save() ) {
1948 do_action( 'latepoint_booking_created', self::$booking_object );
1949 } else {
1950 // error saving booking
1951 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1952 }
1953 } else {
1954 // is not bookable
1955 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1956 }
1957 }
1958 }
1959 }else{
1960 if ( self::$booking_object->is_bookable() ) {
1961 self::$booking_object->calculate_end_time();
1962 self::$booking_object->calculate_end_date();
1963 self::$booking_object->set_utc_datetimes();
1964 $service = new OsServiceModel( self::$booking_object->service_id );
1965 self::$booking_object->buffer_before = $service->buffer_before;
1966 self::$booking_object->buffer_after = $service->buffer_after;
1967
1968 if ( self::$booking_object->save() ) {
1969 do_action( 'latepoint_booking_created', self::$booking_object );
1970 } else {
1971 // error saving booking
1972 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1973 }
1974 } else {
1975 // is not bookable
1976 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1977 }
1978 }
1979
1980
1981 } else {
1982 $order_intent = OsOrderIntentHelper::create_or_update_order_intent( self::$cart_object, self::$restrictions, self::$presets );
1983 if ( $order_intent->is_processing() ) {
1984 return new WP_Error( LATEPOINT_STATUS_ERROR, __( 'Processing...', 'latepoint' ), [ 'send_to_step' => 'resubmit' ] );
1985 }
1986 if ( $order_intent->convert_to_order() ) {
1987 $order = new OsOrderModel( $order_intent->order_id );
1988 self::$cart_object->clear();
1989 self::$vars_for_view['order'] = $order;
1990 self::$vars_for_view['order_bookings'] = $order->get_bookings_from_order_items();
1991 self::$vars_for_view['order_bundles'] = $order->get_bundles_from_order_items();
1992 self::$vars_for_view['price_breakdown_rows'] = $order->generate_price_breakdown_rows();
1993 } else {
1994 // ERROR CONVERTING TO ORDER
1995 OsDebugHelper::log( 'Error saving order', 'order_error', $order_intent->get_error_messages() );
1996 $response_html = $order_intent->get_error_messages();
1997 $error_data = ( $order_intent->get_error_data( 'send_to_step' ) ) ? [ 'send_to_step' => $order_intent->get_error_data( 'send_to_step' ) ] : '';
1998
1999 return new WP_Error( LATEPOINT_STATUS_ERROR, $response_html, $error_data );
2000 }
2001 }
2002 }
2003 }
2004
2005 public static function output_list_option( $option ) {
2006 $html = '';
2007 $html .= '<div tabindex="0" class="lp-option ' . esc_attr( $option['css_class'] ) . '" ' . $option['attrs'] . '>';
2008 $html .= '<div class="lp-option-image-w"><div class="lp-option-image" style="background-image: url(' . esc_url( $option['image_url'] ) . ')"></div></div>';
2009 $html .= '<div class="lp-option-label">' . esc_html( $option['label'] ) . '</div>';
2010 $html .= '</div>';
2011
2012 return $html;
2013 }
2014
2015 public static function get_steps_for_select(): array {
2016 $steps = self::get_step_codes_in_order();
2017 $steps_with_labels = [];
2018 foreach ( $steps as $step_code ) {
2019 $steps_with_labels[ $step_code ] = self::get_step_label_by_code( $step_code );
2020 }
2021
2022 return $steps_with_labels;
2023 }
2024
2025
2026 public static function save_step_codes_in_order( array $step_codes_in_order ): bool {
2027 return OsSettingsHelper::save_setting_by_name( 'step_codes_in_order', implode( ',', $step_codes_in_order ) );
2028 }
2029
2030
2031 public static function save_steps_settings( $steps_settings ): bool {
2032 self::$steps_settings = $steps_settings;
2033
2034 return OsSettingsHelper::save_setting_by_name( 'steps_settings', self::$steps_settings );
2035 }
2036
2037
2038 public static function get_step_settings( string $step_code ): array {
2039 $settings = self::get_steps_settings();
2040
2041 return $settings[ $step_code ] ?? [];
2042 }
2043
2044 public static function get_steps_settings(): array {
2045 if ( ! empty( self::$steps_settings ) ) {
2046 return self::$steps_settings;
2047 }
2048
2049 $steps_settings_from_db = OsSettingsHelper::get_settings_value( 'steps_settings', [] );
2050 $step_codes = self::get_step_codes_in_order();
2051
2052
2053 if ( empty( $steps_settings_from_db ) ) {
2054 $steps_settings = [
2055 'shared' => [
2056 'steps_support_text' => '<h5>Questions?</h5><p>Call (858) 939-3746 for help</p>'
2057 ]
2058 ];
2059 foreach ( $step_codes as $step_code ) {
2060 $steps_settings[ $step_code ] = self::get_default_value_for_step_settings( $step_code );
2061 }
2062 OsSettingsHelper::save_setting_by_name( 'steps_settings', $steps_settings );
2063 self::$steps_settings = $steps_settings;
2064 } else {
2065 // iterate step codes to see if each has a setting
2066 $changed = false;
2067 foreach ( $step_codes as $step_code ) {
2068 if ( ! isset( $steps_settings_from_db[ $step_code ] ) ) {
2069 $steps_settings_from_db[ $step_code ] = self::get_default_value_for_step_settings( $step_code );
2070 $changed = true;
2071 }
2072 }
2073 if ( $changed ) {
2074 OsSettingsHelper::save_setting_by_name( 'steps_settings', $steps_settings_from_db );
2075 }
2076 self::$steps_settings = $steps_settings_from_db;
2077 }
2078
2079 return self::$steps_settings;
2080 }
2081
2082 /**
2083 * @param string $step_code
2084 * @param string $placement before, after
2085 *
2086 * @return string
2087 */
2088 public static function get_formatted_extra_step_content( string $step_code, string $placement ): string {
2089 $content = self::get_step_setting_value( $step_code, 'main_panel_content_' . $placement );
2090
2091 return ! empty( $content ) ? '<div class="latepoint-step-content-text-left">' . $content . '</div>' : '';
2092 }
2093
2094
2095 public static function get_step_setting_value( string $step_code, string $setting_key, $default = '' ) {
2096 $steps_settings = self::get_step_settings( $step_code );
2097
2098 return $steps_settings[ $setting_key ] ?? $default;
2099 }
2100
2101 public static function get_step_settings_edit_form_html( string $selected_step_code ): string {
2102 $step_settings_html = '';
2103 switch ( $selected_step_code ) {
2104 case 'booking__services':
2105 $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' ) ] );
2106 break;
2107 case 'booking__agents':
2108 $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' ) ] );
2109 $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' ) ] );
2110 $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' ) ] );
2111 $step_settings_html .= '<div class="control-under-toggler" id="lp-any-agent-settings" ' . ( OsSettingsHelper::is_on( 'allow_any_agent' ) ? '' : 'style="display: none;"' ) . '>';
2112 $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() );
2113 $step_settings_html .= '</div>';
2114 break;
2115 case 'booking__datepicker':
2116 $step_settings_html .= OsFormHelper::select_field( 'steps_settings[booking__datepicker][time_pick_style]', __( 'Show Time Slots as', 'latepoint' ), [
2117 'timebox' => 'Time Boxes',
2118 'timeline' => 'Timeline'
2119 ], OsStepsHelper::get_time_pick_style() );
2120 $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' ) ] );
2121 $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' ) ] );
2122 $step_settings_html .= OsFormHelper::toggler_field( 'steps_settings[booking__datepicker][hide_unavailable_slots]', __( 'Hide slots that are not available', 'latepoint' ), OsStepsHelper::hide_unavailable_slots(), false, false, [ 'sub_label' => __( 'Hides time boxes that are not available, instead of showing them in gray.', 'latepoint' ) ] );
2123 $step_settings_html .= OsFormHelper::toggler_field( 'steps_settings[booking__datepicker][disable_searching_first_available_slot]', __( 'Disable auto searching for first available slot', 'latepoint' ), OsStepsHelper::disable_searching_first_available_slot(), false, false, [ 'sub_label' => __( 'If checked, this will stop calendar from automatically scrolling to a first available slot', 'latepoint' ) ] );
2124 break;
2125 case 'confirmation':
2126 $step_settings_html .= OsFormHelper::select_field( 'steps_settings[confirmation][order_confirmation_message_style]', __( 'Message Style', 'latepoint' ), [ 'green' => __( 'Green', 'latepoint' ),
2127 'yellow' => __( 'Yellow', 'latepoint' )
2128 ], self::get_step_setting_value( $selected_step_code, 'order_confirmation_message_style', 'green' ) );
2129 break;
2130 }
2131 /**
2132 * Generates HTML for step settings form in the preview
2133 *
2134 * @param {string} $step_settings_html html that is going to be output on the step settings form
2135 * @param {string} $selected_step_code step code that settings are requested for
2136 * @returns {string} $step_settings_html Filtered HTML of the settings form
2137 *
2138 * @since 5.0.0
2139 * @hook latepoint_get_step_settings_edit_form_html
2140 *
2141 */
2142 $step_settings_html = apply_filters( 'latepoint_get_step_settings_edit_form_html', $step_settings_html, $selected_step_code );
2143 if ( empty( $step_settings_html ) ) {
2144 $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>';
2145 }
2146
2147 return $step_settings_html;
2148 }
2149
2150 public static function get_default_value_for_step_settings( string $step_code ): array {
2151 $settings = [
2152 'booking__services' => [
2153 'side_panel_heading' => 'Service Selection',
2154 'side_panel_description' => 'Please select a service for which you want to schedule an appointment',
2155 'main_panel_heading' => 'Available Services'
2156 ],
2157 'booking__locations' => [
2158 'side_panel_heading' => 'Location Selection',
2159 'side_panel_description' => 'Please select a location where you want to schedule an appointment',
2160 'main_panel_heading' => 'Available Locations'
2161 ],
2162 'booking__agents' => [
2163 'side_panel_heading' => 'Agent Selection',
2164 'side_panel_description' => 'Please select an agent that will be providing you a service',
2165 'main_panel_heading' => 'Available Agents'
2166 ],
2167 'booking__datepicker' => [
2168 'side_panel_heading' => 'Select Date & Time',
2169 'side_panel_description' => 'Please select date and time for your appointment',
2170 'main_panel_heading' => 'Date & Time Selection'
2171 ],
2172 'customer' => [
2173 'side_panel_heading' => 'Enter Your Information',
2174 'side_panel_description' => 'Please enter your contact information',
2175 'main_panel_heading' => 'Customer Information'
2176 ],
2177 'verify' => [
2178 'side_panel_heading' => 'Verify Order Details',
2179 'side_panel_description' => 'Double check your reservation details and click submit button if everything is correct',
2180 'main_panel_heading' => 'Verify Order Details',
2181 ],
2182 'payment__times' => [
2183 'side_panel_heading' => 'Payment Time Selection',
2184 'side_panel_description' => 'Please choose when you would like to pay for your appointment',
2185 'main_panel_heading' => 'When would you like to pay?'
2186 ],
2187 'payment__portions' => [
2188 'side_panel_heading' => 'Payment Portion Selection',
2189 'side_panel_description' => 'Please select how much you would like to pay now',
2190 'main_panel_heading' => 'How much would you like to pay now?'
2191 ],
2192 'payment__methods' => [
2193 'side_panel_heading' => 'Payment Method Selection',
2194 'side_panel_description' => 'Please select a payment method you would like to make a payment with',
2195 'main_panel_heading' => 'Select payment method'
2196 ],
2197 'payment__processors' => [
2198 'side_panel_heading' => 'Payment Processor Selection',
2199 'side_panel_description' => 'Please select a payment processor you want to process the payment with',
2200 'main_panel_heading' => 'Select payment processor'
2201 ],
2202 'payment__pay' => [
2203 'side_panel_heading' => 'Make a Payment',
2204 'side_panel_description' => 'Please enter your payment information so we can process the payment',
2205 'main_panel_heading' => 'Enter your payment information'
2206 ],
2207 'confirmation' => [
2208 'side_panel_heading' => 'Confirmation',
2209 'side_panel_description' => 'Your order has been placed. Please retain this confirmation for your record.',
2210 'main_panel_heading' => 'Order Confirmation'
2211 ]
2212 ];
2213
2214
2215 $settings = apply_filters( 'latepoint_settings_for_step_codes', $settings );
2216
2217 return $settings[ $step_code ] ?? [];
2218 }
2219
2220
2221 public static function get_default_side_panel_image_html_for_step_code( string $step_code ): string {
2222 $svg = '';
2223 switch ( $step_code ) {
2224 case 'booking__locations':
2225 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2226 <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 "/>
2227 <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>';
2228 break;
2229 case 'booking__services':
2230 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2231 <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 "/>
2232 <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"/>
2233 <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"/>
2234 <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"/>
2235 <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" />
2236 <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"/>
2237 <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"/>
2238 </svg>';
2239 break;
2240 case 'booking__agents':
2241 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2242 <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>
2243 <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>
2244 <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>
2245 <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>
2246 <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>
2247 </svg>';
2248 break;
2249 case 'booking__datepicker':
2250 case 'customer':
2251 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2252 <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"/>
2253 <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"/>
2254 <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"/>
2255 <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"/>
2256 <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"/>
2257 <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"/>
2258 <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"/>
2259 </svg>';
2260 break;
2261 case 'payment__times':
2262 case 'payment__portions':
2263 case 'payment__methods':
2264 case 'payment__processors':
2265 case 'payment__pay':
2266 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2267 <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" />
2268 <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"/>
2269 <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"/>
2270 <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"/>
2271 <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"/>
2272 </svg>';
2273 break;
2274 case 'verify':
2275 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
2276 <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"/>
2277 <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"/>
2278 <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"/>
2279 <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"/>
2280 <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 "/>
2281 </svg>';
2282 break;
2283 case 'confirmation':
2284 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
2285 <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"/>
2286 <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"/>
2287 <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"/>
2288 <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"/>
2289 <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"/>
2290 </svg>';
2291 break;
2292 }
2293
2294 /**
2295 * Generates an SVG image for step code, if there was no custom image set
2296 *
2297 * @param {string} $svg image svg code
2298 * @param {string} $step_code step name code
2299 *
2300 * @since 5.0.0
2301 * @hook latepoint_svg_for_step_code
2302 *
2303 */
2304 return apply_filters( 'latepoint_svg_for_step_code', $svg, $step_code );
2305 }
2306
2307
2308 public static function get_time_pick_style() {
2309 return OsStepsHelper::get_step_setting_value( 'booking__datepicker', 'time_pick_style', 'timebox' );
2310 }
2311
2312 /**
2313 * Generates a preview for a selected step to show on booking form preview in settings
2314 *
2315 * @param string $selected_step_code
2316 *
2317 * @return void
2318 */
2319 public static function get_step_content_preview( string $selected_step_code ) {
2320 switch ( $selected_step_code ) {
2321 case 'booking__services':
2322 OsBookingHelper::generate_services_bundles_and_categories_list();
2323 break;
2324 case 'booking__agents':
2325 $agents_model = new OsAgentModel();
2326 $agents = $agents_model->should_be_active()->get_results_as_models();
2327 OsAgentHelper::generate_agents_list( $agents );
2328 break;
2329 case 'booking__datepicker':
2330 $booking = new OsBookingModel();
2331 $services = new OsServiceModel();
2332 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2333 if ( $service ) {
2334 $booking->service_id = $service->id;
2335 echo OsCalendarHelper::generate_dates_and_times_picker( $booking, new OsWpDateTime( 'now' ), ! OsStepsHelper::disable_searching_first_available_slot() );
2336 ?>
2337
2338
2339 <?php
2340 } else {
2341 echo 'You need to have an active service to generate the calendar';
2342 }
2343 break;
2344 case 'booking__locations':
2345 OsLocationHelper::generate_locations_and_categories_list();
2346 break;
2347 case 'customer':
2348 $booking = new OsBookingModel();
2349 $services = new OsServiceModel();
2350 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2351 $customer = new OsCustomerModel();
2352 $default_fields_for_customer = OsSettingsHelper::get_default_fields_for_customer();
2353
2354 $current_step_code = $selected_step_code;
2355
2356 include LATEPOINT_VIEWS_ABSPATH . 'booking_form_settings/previews/_customer.php';
2357 break;
2358 case 'payment__times':
2359 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>';
2360 break;
2361 case 'payment__portions':
2362 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>';
2363 break;
2364 case 'payment__methods':
2365 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>';
2366 break;
2367 case 'payment__pay':
2368 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "Payment form generated by selected payment processor will appear here", 'latepoint' ) . '</div>';
2369 break;
2370 case 'confirmation':
2371 echo '<div class="summary-status-wrapper summary-status-style-' . esc_attr( OsStepsHelper::get_step_setting_value( $selected_step_code, 'order_confirmation_message_style', 'green' ) ) . '">';
2372 echo '<div class="summary-status-inner">';
2373 echo '<div class="ss-icon"></div>';
2374 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>';
2375 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>';
2376 echo '<div class="ss-confirmation-number"><span>' . esc_html__( 'Order #', 'latepoint' ) . '</span><strong>KDFJ934K</strong></div>';
2377 echo '</div>';
2378 echo '</div>';
2379 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "Order information will appear here.", 'latepoint' ) . '</div>';
2380 break;
2381 }
2382 do_action( 'latepoint_get_step_content_preview', $selected_step_code );
2383 }
2384
2385 public static function hide_slot_availability_count(): bool {
2386 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_slot_availability_count' ) );
2387 }
2388
2389 public static function hide_timepicker_when_one_slot_available(): bool {
2390 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_timepicker_when_one_slot_available' ) );
2391 }
2392
2393 public static function build_booking_object_for_current_step_preview( string $current_step ): OsBookingModel {
2394 $booking = new OsBookingModel();
2395 $steps_in_order = self::get_step_codes_in_order();
2396
2397 $current_step_index = array_search( $current_step, $steps_in_order );
2398 if ( $current_step_index === false ) {
2399 return $booking;
2400 }
2401 $completed_steps = array_slice( $steps_in_order, 0, $current_step_index );
2402 foreach ( $completed_steps as $completed_step ) {
2403 self::set_booking_object_values_for_completed_step( $booking, $completed_step );
2404 }
2405
2406 return $booking;
2407 }
2408
2409 public static function set_booking_object_values_for_completed_step( OsBookingModel $booking, string $completed_step ): OsBookingModel {
2410 switch ( $completed_step ) {
2411 case 'booking__services':
2412 $services = new OsServiceModel();
2413 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2414 if ( $service ) {
2415 $booking->service_id = $service->id;
2416 }
2417 break;
2418 case 'booking__locations':
2419 $locations = new OsLocationModel();
2420 $location = $locations->should_be_active()->set_limit( 1 )->get_results_as_models();
2421 if ( $location ) {
2422 $booking->location_id = $location->id;
2423 }
2424 break;
2425 case 'booking__agents':
2426 $agents = new OsAgentModel();
2427 $agent = $agents->should_be_active()->set_limit( 1 )->get_results_as_models();
2428 if ( $agent ) {
2429 $booking->agent_id = $agent->id;
2430 }
2431 break;
2432 case 'customer':
2433 $customers = new OsCustomerModel();
2434 $customer = $customers->set_limit( 1 )->get_results_as_models();
2435 if ( $customer ) {
2436 $booking->customer_id = $customer->id;
2437 }
2438 break;
2439 case 'booking__datepicker':
2440 $tomorrow = new OsWpDateTime( 'tomorrow' );
2441 $booking->start_date = $tomorrow->format( 'Y-m-d' );
2442 $booking->start_time = 600;
2443
2444 break;
2445 }
2446
2447 /**
2448 * Sets values for booking object depending on a completed step code
2449 *
2450 * @param {OsBookingModel} $booking booking object
2451 * @param {string} $completed_step step code that was completed
2452 *
2453 * @since 5.0.0
2454 * @hook latepoint_set_booking_object_values_for_completed_step
2455 *
2456 */
2457 return apply_filters( 'latepoint_set_booking_object_values_for_completed_step', $booking, $completed_step );
2458 }
2459
2460 public static function generate_summary_key_value_pairs( OsBookingModel $booking ): string {
2461 $html = '';
2462
2463
2464 if ( $booking->location_id ) {
2465 $html .= '<div class="summary-box summary-box-location-info">
2466 <div class="summary-box-heading">
2467 <div class="sbh-item">' . __( 'Location', 'latepoint' ) . '</div>
2468 <div class="sbh-line"></div>
2469 </div>
2470 <div class="summary-box-content with-media">
2471 <div class="sbc-content-i">
2472 <div class="sbc-main-item">' . $booking->location->name . '</div>
2473 </div>
2474 </div>
2475 </div>';
2476 }
2477 if ( $booking->customer_id ) {
2478 $html .= '<div class="summary-box summary-box-customer-info">
2479 <div class="summary-box-heading">
2480 <div class="sbh-item">' . __( 'Customer', 'latepoint' ) . '</div>
2481 <div class="sbh-line"></div>
2482 </div>
2483 <div class="summary-box-content with-media">
2484 <div class="os-avatar-w">
2485 <div class="os-avatar"><span>' . esc_html( $booking->customer->get_initials() ) . '</span></div>
2486 </div>
2487 <div class="sbc-content-i">
2488 <div class="sbc-main-item">' . esc_html( $booking->customer->full_name ) . '</div>
2489 <div class="sbc-sub-item">' . esc_html( $booking->customer->email ) . '</div>
2490 </div>
2491 </div>';
2492 $customer_attributes = [];
2493 $customer_attributes = apply_filters( 'latepoint_booking_summary_customer_attributes', $customer_attributes, $booking->customer );
2494 if ( $customer_attributes ) {
2495 $html .= '<div class="summary-attributes sa-clean sa-hidden">';
2496 foreach ( $customer_attributes as $attribute ) {
2497 $html .= '<span>' . esc_html( $attribute['label'] ) . ': <strong>' . esc_html( $attribute['value'] ) . '</strong></span>';
2498 }
2499 $html .= '</div>';
2500 }
2501 $html .= '</div>';
2502 }
2503 if ( OsSettingsHelper::is_off( 'steps_hide_agent_info' ) && $booking->agent_id && $booking->agent_id != LATEPOINT_ANY_AGENT ) {
2504 $bio_html = '';
2505 if ( OsSettingsHelper::steps_show_agent_bio() ) {
2506 $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>';
2507 $bio_html .= OsAgentHelper::generate_bio( $booking->agent );
2508 }
2509 $html .= '<div class="summary-box summary-box-agent-info">
2510 <div class="summary-box-heading">
2511 <div class="sbh-item">' . __( 'Agent', 'latepoint' ) . '</div>
2512 <div class="sbh-line"></div>
2513 </div>
2514 <div class="summary-box-content with-media">
2515 <div class="os-avatar-w"
2516 style="background-image: url(' . ( ( $booking->agent->avatar_image_id ) ? $booking->agent->get_avatar_url() : '' ) . ')">
2517 ' . ( ( ! $booking->agent->avatar_image_id ) ? '<div class="os-avatar"><span>' . esc_html( $booking->agent->get_initials() ) . '</span></div>' : '' ) . '
2518 </div>
2519 <div class="sbc-content-i">
2520 <div class="sbc-main-item">' . esc_html( $booking->agent->full_name ) . '</div>
2521 ' . $bio_html . '
2522 </div>
2523 </div>
2524 </div>';
2525 }
2526
2527
2528 /**
2529 * Key value pairs of summary values for the booking summary panel
2530 *
2531 * @param {string} $html HTML of key value pairs
2532 * @param {OsBookingModel} $booking Booking object that is used to generate the summary
2533 * @returns {string} $html The filtered HTML of key value pairs
2534 *
2535 * @since 5.0.0
2536 * @hook latepoint_summary_key_value_pairs
2537 *
2538 */
2539 $html = apply_filters( 'latepoint_summary_key_value_pairs', $html, $booking );
2540
2541 if ( $html ) {
2542 $html = '<div class="summary-boxes-columns">' . $html . '</div>';
2543 }
2544
2545 return $html;
2546 }
2547
2548 public static function is_ready_for_summary() {
2549 if ( ! empty( self::$order_object ) && ! self::$order_object->is_new_record() ) {
2550 // order object is set - don't need to show summary anymore
2551 return false;
2552 }
2553 if ( ! self::$cart_object->is_empty() ) {
2554 // cart has items inside - show summary
2555 return true;
2556 }
2557 if ( self::$active_cart_item->is_bundle() ) {
2558 // bundle selected already - show summary
2559 return true;
2560 }
2561 if ( ! empty( self::$booking_object->service_id ) ) {
2562 // service is selected for a booking - show summary
2563 return true;
2564 }
2565
2566
2567 return false;
2568 }
2569
2570 public static function set_active_cart_item_object( array $cart_item_params = [] ): OsCartItemModel {
2571 self::$active_cart_item = new OsCartItemModel();
2572 if ( ! empty( $cart_item_params['id'] ) ) {
2573 self::$active_cart_item->id = $cart_item_params['id'];
2574 // try to find it in cart
2575 $cart_item = new OsCartItemModel( self::$active_cart_item->id );
2576 if ( $cart_item->is_new_record() ) {
2577 // not found, reset active cart item ID
2578 self::$active_cart_item = new OsCartItemModel();
2579 }
2580 }
2581 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 );
2582 if ( self::$active_cart_item->is_bundle() ) {
2583 if ( empty( $cart_item_params['item_data'] ) ) {
2584 self::$active_cart_item->item_data = empty( self::$presets['selected_bundle'] ) ? '' : wp_json_encode( [ 'bundle_id' => self::$presets['selected_bundle'] ] );
2585 } else {
2586 // bundle gets data from params
2587 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'];
2588 }
2589 } else {
2590 // booking gets data from booking object
2591 self::$active_cart_item->item_data = wp_json_encode( self::$booking_object->generate_params_for_booking_form(), true );
2592 }
2593
2594 return self::$active_cart_item;
2595 }
2596
2597 public static function get_cart_item_object() {
2598 return self::$active_cart_item;
2599 }
2600
2601
2602 /**
2603 *
2604 * Given a step code, returns the first sub step if found, or returns the parent step code if no children
2605 *
2606 * @param string $parent_code
2607 *
2608 * @return string
2609 */
2610 public static function get_first_step_for_parent_code( string $parent_code ): string {
2611 $first_step_code = '';
2612 $step_codes = self::$step_codes_in_order;
2613 foreach ( $step_codes as $step_code ) {
2614 $loop_parent_code = explode( '__', $step_code )[0];
2615 if ( $loop_parent_code == $parent_code ) {
2616 $first_step_code = $step_code;
2617 break;
2618 }
2619 }
2620
2621 return $first_step_code;
2622 }
2623
2624 public static function check_step_code_access( string $step_code_to_access ): string {
2625 if ( $step_code_to_access == 'confirmation' && ! self::$order_object->is_new_record() ) {
2626 return $step_code_to_access;
2627 }
2628 // loops through all steps and checks if they satisfy condition to be skipped
2629 for ( $i = 0; $i < count( self::$step_codes_in_order ); $i ++ ) {
2630 $code = self::$step_codes_in_order[ $i ];
2631 $parent_code = explode( '__', $code )[0];
2632
2633 $next_code = ( ( $i + 1 ) < count( self::$step_codes_in_order ) ) ? self::$step_codes_in_order[ $i + 1 ] : false;
2634 $next_parent_code = $next_code ? explode( '__', $next_code )[0] : false;
2635
2636 if ( $step_code_to_access == $code ) {
2637 break;
2638 }
2639 switch ( $parent_code ) {
2640 // 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
2641 case 'customer':
2642 if ( ! OsAuthHelper::is_customer_logged_in() ) {
2643 $step_code_to_access = $code;
2644 break 2;
2645 }
2646 break;
2647 case 'booking':
2648 if ( $next_parent_code && $next_parent_code != $parent_code && self::$cart_object->is_empty() ) {
2649 // $step_code_to_access = self::get_first_step_for_parent_code($parent_code);
2650 // break 2;
2651 }
2652 break;
2653 }
2654 }
2655
2656 /**
2657 * Checks if a step code can be accessed, returns the step code that can be accessed
2658 *
2659 * @param {string} $step_code_to_access step code that needs to be checked for access
2660 * @returns {string} $step_code_to_access The filtered step code that can be accessed
2661 *
2662 * @since 5.0.0
2663 * @hook latepoint_check_step_code_access
2664 *
2665 */
2666 return apply_filters( 'latepoint_check_step_code_access', $step_code_to_access );
2667 }
2668
2669 public static function get_first_step_code( string $step_code, $step_codes = false ): string {
2670 if ( ! $step_codes ) {
2671 $step_codes = self::get_step_codes_in_order();
2672 }
2673 if ( isset( $step_codes[ $step_code ] ) ) {
2674 return $step_code;
2675 }
2676 $unflat_step_codes = self::unflatten_steps( $step_codes );
2677
2678 // TODO add support for more than 2 dimentional parent/child arrays
2679 if ( isset( $unflat_step_codes[ $step_code ] ) ) {
2680 return implode( '__', [ $step_code, array_key_first( $unflat_step_codes[ $step_code ] ) ] );
2681 }
2682
2683 return '';
2684 }
2685
2686 public static function build_cart_object(): OsCartModel {
2687 if ( ! isset( self::$cart_object ) ) {
2688 self::set_cart_object();
2689 }
2690
2691 return self::$cart_object;
2692 }
2693
2694 public static function set_order_object( array $params = [] ): OsOrderModel {
2695 self::$order_object = new OsOrderModel();
2696
2697 return self::$order_object;
2698 }
2699
2700 public static function set_cart_object( array $params = [] ): OsCartModel {
2701 self::$cart_object = OsCartsHelper::get_or_create_cart();
2702 if( self::$cart_object->order_intent_id ){
2703 $order_intent = new OsOrderIntentModel(self::$cart_object->order_intent_id);
2704 if($order_intent->is_converted()){
2705 $order_intent->mark_cart_converted(self::$cart_object);
2706 }
2707 }
2708 if ( self::$cart_object->order_id ) {
2709 self::load_order_object( self::$cart_object->order_id );
2710 } else {
2711 self::load_order_object();
2712 self::$cart_object->set_data( $params );
2713
2714 // set source id
2715 if ( isset( self::$restrictions['source_id'] ) ) {
2716 self::$cart_object->source_id = self::$restrictions['source_id'];
2717 }
2718
2719 self::$cart_object->calculate_prices();
2720 }
2721
2722 return self::$cart_object;
2723 }
2724
2725 public static function set_cart_object_from_order_intent( OsOrderIntentModel $order_intent ): OsCartModel {
2726 OsCartsHelper::get_or_create_cart();
2727 self::$cart_object->clear();
2728
2729
2730 // add items from intent
2731 $intent_cart_items = json_decode( $order_intent->cart_items_data, true );
2732 foreach ( $intent_cart_items as $cart_item_data ) {
2733 OsCartsHelper::add_item_to_cart( OsCartsHelper::create_cart_item_from_item_data( $cart_item_data ) );
2734 }
2735
2736 // restore payment info
2737 $payment_data = json_decode( $order_intent->payment_data, true );
2738 self::$cart_object->payment_method = $payment_data['method'];
2739 self::$cart_object->payment_time = $payment_data['time'];
2740 self::$cart_object->payment_portion = $payment_data['portion'];
2741 self::$cart_object->payment_token = $payment_data['token'];
2742 self::$cart_object->payment_processor = $payment_data['processor'];
2743
2744 return self::$cart_object;
2745 }
2746
2747 public static function hide_unavailable_slots() {
2748 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_unavailable_slots' ) );
2749 }
2750
2751 public static function disable_searching_first_available_slot() {
2752 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'disable_searching_first_available_slot' ) );
2753 }
2754
2755 private static function set_recurring_booking_properties( array $params ) {
2756 if ( ! empty( $params['is_recurring'] ) && $params['is_recurring'] == LATEPOINT_VALUE_ON ) {
2757 self::$booking_object->generate_recurrent_sequence = [ 'rules' => $params['recurrence']['rules'] ?? [], 'overrides' => $params['recurrence']['overrides'] ?? [] ];
2758 }
2759 }
2760 }