PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.1.91
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.1.91
5.6.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
2761 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 $original_booking = clone self::$booking_object; // we need to clone it, because is_bookable will set location and agent to set values from ANY, and we don't want that for our recurring bookings
1494 if ( self::$booking_object->is_bookable( [ 'skip_customer_check' => true ] ) ) {
1495 // create recurring record and assign it to this booking
1496 if ( ! empty( $original_booking->generate_recurrent_sequence ) ) {
1497 // Recurring booking
1498 $recurrence = new OsRecurrenceModel();
1499 $recurrence->rules = wp_json_encode( $original_booking->generate_recurrent_sequence['rules'] );
1500 $recurrence->overrides = wp_json_encode( $original_booking->generate_recurrent_sequence['overrides'] );
1501 if ( $recurrence->save() ) {
1502 $original_booking->recurrence_id = $recurrence->id;
1503 // we don't need these attributes anymore as we will get them from the recurrence model by ID
1504 $original_booking->generate_recurrent_sequence = [];
1505 $customer_timezone = $original_booking->get_customer_timezone();
1506 $recurring_bookings_data_and_errors = OsFeatureRecurringBookingsHelper::generate_recurring_bookings_data( $original_booking, $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 if(empty($main_cart_item_id)) $main_cart_item_id = self::$active_cart_item->id;
1520 }
1521 if($main_cart_item_id) self::$fields_to_update['active_cart_item[id]'] = $main_cart_item_id;
1522 }
1523 } else {
1524 // Single time booking
1525 // only do this for new cart item, if modifying existing one - then the set_active_cart_item method will take care of updating it
1526 // set it again as booking object might have changed if agent or location were set to ANY, they are assigned now
1527 self::set_active_cart_item_object();
1528 if ( self::is_bundle_scheduling() ) {
1529 // we don't need to use a cart for bundle scheduling
1530 } else {
1531 self::$cart_object->add_item( self::$active_cart_item );
1532 self::$fields_to_update['active_cart_item[id]'] = self::$active_cart_item->id;
1533 }
1534 }
1535 self::reset_booking_object();
1536
1537 return true;
1538 } else {
1539 throw new Exception( implode( ',', self::$booking_object->get_error_messages() ) );
1540 }
1541 }
1542 }
1543 }
1544
1545 public static function process_step_booking() {
1546
1547 if ( ! self::is_bundle_scheduling() ) {
1548 // check if we are processing the last step of a booking sequence
1549 $booking_steps = [];
1550 foreach ( self::$step_codes_in_order as $step_code ) {
1551 if ( strpos( $step_code, 'booking__' ) !== false ) {
1552 $booking_steps[] = $step_code;
1553 }
1554 }
1555 if ( end( $booking_steps ) == self::$step_to_process ) {
1556 try {
1557 self::add_current_item_to_cart();
1558 } catch ( Exception $e ) {
1559 return new WP_Error( 'booking_slot_not_available', $e->getMessage() );
1560 }
1561 }
1562 }
1563
1564
1565 }
1566
1567 public static function reset_booking_object() {
1568 self::set_booking_object( [] );
1569 }
1570
1571 public static function prepare_step_booking() {
1572
1573 }
1574
1575
1576 // SERVICES
1577
1578 public static function process_step_booking__services() {
1579 }
1580
1581 public static function prepare_step_booking__services() {
1582 $bundles_model = new OsBundleModel();
1583 $bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models();
1584
1585 $services_model = new OsServiceModel();
1586 $show_selected_services_arr = self::$restrictions['show_services'] ? explode( ',', self::$restrictions['show_services'] ) : false;
1587 $show_service_categories_arr = self::$restrictions['show_service_categories'] ? explode( ',', self::$restrictions['show_service_categories'] ) : false;
1588 $preselected_category = self::$presets['selected_service_category'];
1589 $preselected_duration = self::$presets['selected_duration'];
1590 $preselected_total_attendees = self::$presets['selected_total_attendees'];
1591
1592 $connected_ids = OsConnectorHelper::get_connected_object_ids( 'service_id', [
1593 'agent_id' => self::$booking_object->agent_id,
1594 'location_id' => self::$booking_object->location_id
1595 ] );
1596 // if "show only specific services" is selected (restrictions) - remove ids that are not found in connection
1597 $show_services_arr = ( ! empty( $show_selected_services_arr ) && ! empty( $connected_ids ) ) ? array_intersect( $connected_ids, $show_selected_services_arr ) : $connected_ids;
1598 if ( ! empty( $show_services_arr ) ) {
1599 $services_model->where_in( 'id', $show_services_arr );
1600 }
1601
1602 $services = $services_model->should_be_active()->should_not_be_hidden()->order_by( 'order_number asc' )->get_results_as_models();
1603
1604 self::$vars_for_view['show_services_arr'] = $show_services_arr;
1605 self::$vars_for_view['show_service_categories_arr'] = $show_service_categories_arr;
1606 self::$vars_for_view['preselected_category'] = $preselected_category;
1607 self::$vars_for_view['preselected_duration'] = $preselected_duration;
1608 self::$vars_for_view['preselected_total_attendees'] = $preselected_total_attendees;
1609 self::$vars_for_view['services'] = $services;
1610 self::$vars_for_view['bundles'] = $bundles;
1611 }
1612
1613 // AGENTS
1614
1615 public static function process_step_booking__agents() {
1616 }
1617
1618 public static function prepare_step_booking__agents() {
1619 $agents_model = new OsAgentModel();
1620
1621 $show_selected_agents_arr = ( self::$restrictions['show_agents'] ) ? explode( ',', self::$restrictions['show_agents'] ) : false;
1622 // Find agents that actually offer selected service (if selected) at selected location (if selected)
1623 $connected_ids = OsConnectorHelper::get_connected_object_ids( 'agent_id', [
1624 'service_id' => self::$booking_object->service_id,
1625 'location_id' => self::$booking_object->location_id
1626 ] );
1627
1628 // If date/time is selected - filter agents who are available at that time
1629 if ( self::$booking_object->start_date && self::$booking_object->start_time ) {
1630 $available_agent_ids = [];
1631 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( self::$booking_object );
1632 foreach ( $connected_ids as $agent_id ) {
1633 $booking_request->agent_id = $agent_id;
1634 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
1635 $available_agent_ids[] = $agent_id;
1636 }
1637 }
1638 $connected_ids = array_intersect( $available_agent_ids, $connected_ids );
1639 }
1640
1641
1642 // if show only specific agents are selected (restrictions) - remove ids that are not found in connection
1643 $show_agents_arr = ( $show_selected_agents_arr ) ? array_intersect( $connected_ids, $show_selected_agents_arr ) : $connected_ids;
1644 if ( ! empty( $show_agents_arr ) ) {
1645 $agents_model->where_in( 'id', $show_agents_arr );
1646 $agents = $agents_model->should_be_active()->get_results_as_models();
1647 self::$vars_for_view['agents'] = $agents;
1648 } else {
1649 // no available or connected agents
1650 self::$vars_for_view['agents'] = [];
1651 }
1652 }
1653
1654
1655 // DATEPICKER
1656
1657 public static function prepare_step_booking__datepicker() {
1658 if ( empty( self::$booking_object->agent_id ) ) {
1659 self::$booking_object->agent_id = LATEPOINT_ANY_AGENT;
1660 }
1661 self::$vars_for_view['calendar_start_date'] = self::$restrictions['calendar_start_date'] ? self::$restrictions['calendar_start_date'] : 'today';
1662 }
1663
1664 public static function process_step_booking__datepicker() {
1665 }
1666
1667
1668 // CONTACT
1669
1670
1671 public static function prepare_step_customer() {
1672
1673 if ( OsAuthHelper::is_customer_logged_in() ) {
1674 self::$booking_object->customer = OsAuthHelper::get_logged_in_customer();
1675 self::$booking_object->customer_id = self::$booking_object->customer->id;
1676 } else {
1677 self::$booking_object->customer = new OsCustomerModel();
1678 }
1679
1680 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1681 self::$vars_for_view['customer'] = self::$booking_object->customer;
1682 }
1683
1684 private static function customer_params(): array {
1685 $params = OsParamsHelper::get_param( 'customer' );
1686 if ( empty( $params ) ) {
1687 return [];
1688 }
1689
1690 $customer_params = OsParamsHelper::permit_params( $params, [
1691 'first_name',
1692 'last_name',
1693 'email',
1694 'phone',
1695 'notes',
1696 'password',
1697 'password_confirmation'
1698 ] );
1699
1700 if ( ! empty( $customer_params['first_name'] ) ) {
1701 $customer_params['first_name'] = sanitize_text_field( $customer_params['first_name'] );
1702 }
1703 if ( ! empty( $customer_params['last_name'] ) ) {
1704 $customer_params['last_name'] = sanitize_text_field( $customer_params['last_name'] );
1705 }
1706 if ( ! empty( $customer_params['email'] ) ) {
1707 $customer_params['email'] = sanitize_email( $customer_params['email'] );
1708 }
1709 if ( ! empty( $customer_params['phone'] ) ) {
1710 $customer_params['phone'] = sanitize_text_field( $customer_params['phone'] );
1711 }
1712 if ( ! empty( $customer_params['notes'] ) ) {
1713 $customer_params['notes'] = sanitize_textarea_field( $customer_params['notes'] );
1714 }
1715
1716 /**
1717 * Filtered customer params for steps
1718 *
1719 * @param {array} $customer_params a filtered array of customer params
1720 * @param {array} $params unfiltered 'customer' params
1721 * @returns {array} $customer_params a filtered array of customer params
1722 *
1723 * @since 5.0.14
1724 * @hook latepoint_customer_params_on_steps
1725 *
1726 */
1727 return apply_filters( 'latepoint_customer_params_on_steps', $customer_params, $params );
1728 }
1729
1730 public static function process_step_customer() {
1731 $status = LATEPOINT_STATUS_SUCCESS;
1732
1733 $customer_params = self::customer_params();
1734
1735 $logged_in_customer = OsAuthHelper::get_logged_in_customer();
1736
1737
1738 if ( $logged_in_customer ) {
1739 // LOGGED IN ALREADY
1740 // Check if they are changing the email on file
1741 if ( $logged_in_customer->email != $customer_params['email'] ) {
1742 // Check if other customer already has this email
1743 $customer = new OsCustomerModel();
1744 $customer_with_email_exist = $customer->where( array(
1745 'email' => $customer_params['email'],
1746 'id !=' => $logged_in_customer->id
1747 ) )->set_limit( 1 )->get_results_as_models();
1748 // check if another customer (or if wp user login enabled - another wp user) exists with the email that this user tries to update to
1749 if ( $customer_with_email_exist || ( OsAuthHelper::wp_users_as_customers() && email_exists( $customer_params['email'] ) ) ) {
1750 $status = LATEPOINT_STATUS_ERROR;
1751 $response_html = __( 'Another customer is registered with this email.', 'latepoint' );
1752 }
1753 }
1754 } else {
1755 // NEW REGISTRATION (NOT LOGGED IN)
1756 if ( OsAuthHelper::wp_users_as_customers() ) {
1757 // WP USERS AS CUSTOMERS
1758 if ( email_exists( $customer_params['email'] ) ) {
1759 // wordpress user with this email already exists, ask to login
1760 $status = LATEPOINT_STATUS_ERROR;
1761 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1762 } else {
1763 // wp user does not exist - search for latepoint customer
1764 $customer = new OsCustomerModel();
1765 $customer = $customer->where( array( 'email' => $customer_params['email'] ) )->set_limit( 1 )->get_results_as_models();
1766 if ( $customer ) {
1767 // latepoint customer with this email exits, create wp user for them
1768 $wp_user = OsCustomerHelper::create_wp_user_for_customer( $customer );
1769 $status = LATEPOINT_STATUS_ERROR;
1770 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1771 } else {
1772 // no latepoint customer or wp user with this email found, can proceed
1773 }
1774 }
1775 } else {
1776 // LATEPOINT CUSTOMERS
1777 $customer = new OsCustomerModel();
1778 $customer_exist = $customer->where( array( 'email' => $customer_params['email'] ) )->set_limit( 1 )->get_results_as_models();
1779 if ( $customer_exist ) {
1780 // customer with this email exists - check if current customer was registered as a guest
1781 if ( OsSettingsHelper::is_on( 'steps_hide_login_register_tabs' ) || ( $customer_exist->can_login_without_password() && ! OsSettingsHelper::is_on( 'steps_require_setting_password' ) ) ) {
1782 // guest account, login automatically
1783 $status == LATEPOINT_STATUS_SUCCESS;
1784 OsAuthHelper::authorize_customer( $customer_exist->id );
1785 } else {
1786 // Not a guest account, ask to login
1787 $status = LATEPOINT_STATUS_ERROR;
1788 $response_html = __( 'An account with that email address already exists. Please try signing in.', 'latepoint' );
1789 }
1790 } else {
1791 // no latepoint customer with this email found, can proceed
1792 }
1793 }
1794 // if not logged in - check if password has to be set
1795 if ( ! OsAuthHelper::is_customer_logged_in() && OsSettingsHelper::is_on( 'steps_require_setting_password' ) ) {
1796 if ( ! empty( $customer_params['password'] ) && $customer_params['password'] == $customer_params['password_confirmation'] ) {
1797 $customer_params['password'] = OsAuthHelper::hash_password( $customer_params['password'] );
1798 $customer_params['is_guest'] = false;
1799 } else {
1800 // Password is blank or does not match the confirmation
1801 $status = LATEPOINT_STATUS_ERROR;
1802 $response_html = __( 'Setting password is required and should match password confirmation', 'latepoint' );
1803 }
1804 }
1805 }
1806 // If no errors, proceed
1807 if ( $status == LATEPOINT_STATUS_SUCCESS ) {
1808 if ( OsAuthHelper::is_customer_logged_in() ) {
1809 $customer = OsAuthHelper::get_logged_in_customer();
1810 $is_new_customer = $customer->is_new_record();
1811 } else {
1812 $customer = new OsCustomerModel();
1813 $is_new_customer = true;
1814 }
1815 $old_customer_data = $is_new_customer ? [] : $customer->get_data_vars();
1816 $customer->set_data( $customer_params, LATEPOINT_PARAMS_SCOPE_PUBLIC );
1817 if ( $customer->save() ) {
1818 if ( $is_new_customer ) {
1819 do_action( 'latepoint_customer_created', $customer );
1820 } else {
1821 do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
1822 }
1823
1824 self::$booking_object->customer_id = $customer->id;
1825 if ( ! OsAuthHelper::is_customer_logged_in() ) {
1826 OsAuthHelper::authorize_customer( $customer->id );
1827 }
1828 $customer->set_timezone_name();
1829 } else {
1830 $status = LATEPOINT_STATUS_ERROR;
1831 $response_html = $customer->get_error_messages();
1832 if ( is_array( $response_html ) ) {
1833 $response_html = implode( ', ', $response_html );
1834 }
1835 }
1836 }
1837 if ( $status == LATEPOINT_STATUS_ERROR ) {
1838 return new WP_Error( LATEPOINT_STATUS_ERROR, $response_html );
1839 }
1840
1841 }
1842
1843
1844 // VERIFICATION STEP
1845
1846 public static function process_step_verify() {
1847
1848 }
1849
1850 public static function prepare_step_verify() {
1851 $cart = OsCartsHelper::get_or_create_cart();
1852
1853 $cart->set_singular_payment_attributes();
1854
1855 self::$vars_for_view['cart'] = $cart;
1856 self::$vars_for_view['customer'] = OsAuthHelper::get_logged_in_customer();
1857 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1858 }
1859
1860 // PAYMENT
1861
1862 public static function process_step_payment__portions() {
1863 }
1864
1865 public static function prepare_step_payment__portions() {
1866 }
1867
1868 public static function process_step_payment__times() {
1869 }
1870
1871 public static function prepare_step_payment__times() {
1872 $enabled_payment_times = OsPaymentsHelper::get_enabled_payment_times();
1873
1874 self::$vars_for_view['enabled_payment_times'] = $enabled_payment_times;
1875 }
1876
1877 public static function process_step_payment__methods() {
1878 }
1879
1880 public static function prepare_step_payment__methods() {
1881 $enabled_payment_methods = OsPaymentsHelper::get_enabled_payment_methods_for_payment_time( self::$cart_object->payment_time );
1882 self::$vars_for_view['enabled_payment_methods'] = $enabled_payment_methods;
1883 }
1884
1885 public static function process_step_payment__processors() {
1886 }
1887
1888 public static function prepare_step_payment__processors() {
1889 $enabled_payment_processors = OsPaymentsHelper::get_enabled_payment_processors();
1890 self::$vars_for_view['enabled_payment_processors'] = $enabled_payment_processors;
1891 }
1892
1893 public static function process_step_payment__pay() {
1894 }
1895
1896 public static function prepare_step_payment__pay() {
1897 $booking_form_page_url = self::$params['booking_form_page_url'] ?? OsUtilHelper::get_referrer();
1898 $order_intent = OsOrderIntentHelper::create_or_update_order_intent( self::$cart_object, self::$restrictions, self::$presets, $booking_form_page_url );
1899 }
1900
1901
1902 // CONFIRMATION
1903
1904 public static function process_step_confirmation() {
1905 }
1906
1907 public static function prepare_step_confirmation() {
1908 self::$vars_for_view['customer'] = OsAuthHelper::get_logged_in_customer();
1909 self::$vars_for_view['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
1910 if ( ! self::$order_object->is_new_record() ) {
1911 self::$vars_for_view['order'] = self::$order_object;
1912 self::$vars_for_view['order_bookings'] = self::$order_object->get_bookings_from_order_items();
1913 self::$vars_for_view['order_bundles'] = self::$order_object->get_bundles_from_order_items();
1914 self::$vars_for_view['price_breakdown_rows'] = self::$order_object->generate_price_breakdown_rows();
1915 self::$vars_for_view['is_bundle_scheduling'] = false;
1916 } else {
1917 // TRY SAVING BOOKING
1918 // check if it's a scheduling request for an existing order item, it means its a bundle
1919 $is_bundle_scheduling = self::is_bundle_scheduling();
1920 self::$vars_for_view['is_bundle_scheduling'] = $is_bundle_scheduling;
1921 if ( $is_bundle_scheduling ) {
1922 $order_item = new OsOrderItemModel( self::$booking_object->order_item_id );
1923 $order = new OsOrderModel( $order_item->order_id );
1924 self::$vars_for_view['order'] = $order;
1925 self::$vars_for_view['order_bookings'] = $order->get_bookings_from_order_items();
1926 self::$vars_for_view['order_bundles'] = $order->get_bundles_from_order_items();
1927 self::$vars_for_view['price_breakdown_rows'] = self::$cart_object->generate_price_breakdown_rows();
1928
1929 if(!empty(self::$booking_object->generate_recurrent_sequence)){
1930 $recurrence = new OsRecurrenceModel();
1931 $recurrence->rules = wp_json_encode( self::$booking_object->generate_recurrent_sequence['rules'] );
1932 $recurrence->overrides = wp_json_encode( self::$booking_object->generate_recurrent_sequence['overrides'] );
1933 if ( $recurrence->save() ) {
1934 self::$booking_object->recurrence_id = $recurrence->id;
1935 // we don't need these attributes anymore as we will get them from the recurrence model by ID
1936 self::$booking_object->generate_recurrent_sequence = [];
1937 $customer_timezone = self::$booking_object->get_customer_timezone();
1938 $recurring_bookings_data_and_errors = OsFeatureRecurringBookingsHelper::generate_recurring_bookings_data( self::$booking_object, $recurrence->get_rules(), $recurrence->get_overrides(), $customer_timezone );
1939 foreach ( $recurring_bookings_data_and_errors['bookings_data'] as $recurrence_bookings_datum ) {
1940 if ( $recurrence_bookings_datum['unchecked'] == 'yes' ) {
1941 continue;
1942 }
1943 self::$booking_object = $recurrence_bookings_datum['booking'];
1944 // set it again as booking object might have changed if agent or location were set to ANY, they are assigned now
1945 self::set_active_cart_item_object();
1946 if ( self::$booking_object->is_bookable() ) {
1947
1948 if ( self::$booking_object->save() ) {
1949 do_action( 'latepoint_booking_created', self::$booking_object );
1950 } else {
1951 // error saving booking
1952 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1953 }
1954 } else {
1955 // is not bookable
1956 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1957 }
1958 }
1959 }
1960 }else{
1961 if ( self::$booking_object->is_bookable() ) {
1962 self::$booking_object->calculate_end_time();
1963 self::$booking_object->calculate_end_date();
1964 self::$booking_object->set_utc_datetimes();
1965 $service = new OsServiceModel( self::$booking_object->service_id );
1966 self::$booking_object->buffer_before = $service->buffer_before;
1967 self::$booking_object->buffer_after = $service->buffer_after;
1968
1969 if ( self::$booking_object->save() ) {
1970 do_action( 'latepoint_booking_created', self::$booking_object );
1971 } else {
1972 // error saving booking
1973 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1974 }
1975 } else {
1976 // is not bookable
1977 self::$booking_object->add_error( 'booking_error', self::$booking_object->get_error_messages() );
1978 }
1979 }
1980
1981
1982 } else {
1983 $order_intent = OsOrderIntentHelper::create_or_update_order_intent( self::$cart_object, self::$restrictions, self::$presets );
1984 if ( $order_intent->is_processing() ) {
1985 return new WP_Error( LATEPOINT_STATUS_ERROR, __( 'Processing...', 'latepoint' ), [ 'send_to_step' => 'resubmit' ] );
1986 }
1987 if ( $order_intent->convert_to_order() ) {
1988 $order = new OsOrderModel( $order_intent->order_id );
1989 self::$cart_object->clear();
1990 self::$vars_for_view['order'] = $order;
1991 self::$vars_for_view['order_bookings'] = $order->get_bookings_from_order_items();
1992 self::$vars_for_view['order_bundles'] = $order->get_bundles_from_order_items();
1993 self::$vars_for_view['price_breakdown_rows'] = $order->generate_price_breakdown_rows();
1994 } else {
1995 // ERROR CONVERTING TO ORDER
1996 OsDebugHelper::log( 'Error saving order', 'order_error', $order_intent->get_error_messages() );
1997 $response_html = $order_intent->get_error_messages();
1998 $error_data = ( $order_intent->get_error_data( 'send_to_step' ) ) ? [ 'send_to_step' => $order_intent->get_error_data( 'send_to_step' ) ] : '';
1999
2000 return new WP_Error( LATEPOINT_STATUS_ERROR, $response_html, $error_data );
2001 }
2002 }
2003 }
2004 }
2005
2006 public static function output_list_option( $option ) {
2007 $html = '';
2008 $html .= '<div tabindex="0" class="lp-option ' . esc_attr( $option['css_class'] ) . '" ' . $option['attrs'] . '>';
2009 $html .= '<div class="lp-option-image-w"><div class="lp-option-image" style="background-image: url(' . esc_url( $option['image_url'] ) . ')"></div></div>';
2010 $html .= '<div class="lp-option-label">' . esc_html( $option['label'] ) . '</div>';
2011 $html .= '</div>';
2012
2013 return $html;
2014 }
2015
2016 public static function get_steps_for_select(): array {
2017 $steps = self::get_step_codes_in_order();
2018 $steps_with_labels = [];
2019 foreach ( $steps as $step_code ) {
2020 $steps_with_labels[ $step_code ] = self::get_step_label_by_code( $step_code );
2021 }
2022
2023 return $steps_with_labels;
2024 }
2025
2026
2027 public static function save_step_codes_in_order( array $step_codes_in_order ): bool {
2028 return OsSettingsHelper::save_setting_by_name( 'step_codes_in_order', implode( ',', $step_codes_in_order ) );
2029 }
2030
2031
2032 public static function save_steps_settings( $steps_settings ): bool {
2033 self::$steps_settings = $steps_settings;
2034
2035 return OsSettingsHelper::save_setting_by_name( 'steps_settings', self::$steps_settings );
2036 }
2037
2038
2039 public static function get_step_settings( string $step_code ): array {
2040 $settings = self::get_steps_settings();
2041
2042 return $settings[ $step_code ] ?? [];
2043 }
2044
2045 public static function get_steps_settings(): array {
2046 if ( ! empty( self::$steps_settings ) ) {
2047 return self::$steps_settings;
2048 }
2049
2050 $steps_settings_from_db = OsSettingsHelper::get_settings_value( 'steps_settings', [] );
2051 $step_codes = self::get_step_codes_in_order();
2052
2053
2054 if ( empty( $steps_settings_from_db ) ) {
2055 $steps_settings = [
2056 'shared' => [
2057 'steps_support_text' => '<h5>Questions?</h5><p>Call (858) 939-3746 for help</p>'
2058 ]
2059 ];
2060 foreach ( $step_codes as $step_code ) {
2061 $steps_settings[ $step_code ] = self::get_default_value_for_step_settings( $step_code );
2062 }
2063 OsSettingsHelper::save_setting_by_name( 'steps_settings', $steps_settings );
2064 self::$steps_settings = $steps_settings;
2065 } else {
2066 // iterate step codes to see if each has a setting
2067 $changed = false;
2068 foreach ( $step_codes as $step_code ) {
2069 if ( ! isset( $steps_settings_from_db[ $step_code ] ) ) {
2070 $steps_settings_from_db[ $step_code ] = self::get_default_value_for_step_settings( $step_code );
2071 $changed = true;
2072 }
2073 }
2074 if ( $changed ) {
2075 OsSettingsHelper::save_setting_by_name( 'steps_settings', $steps_settings_from_db );
2076 }
2077 self::$steps_settings = $steps_settings_from_db;
2078 }
2079
2080 return self::$steps_settings;
2081 }
2082
2083 /**
2084 * @param string $step_code
2085 * @param string $placement before, after
2086 *
2087 * @return string
2088 */
2089 public static function get_formatted_extra_step_content( string $step_code, string $placement ): string {
2090 $content = self::get_step_setting_value( $step_code, 'main_panel_content_' . $placement );
2091
2092 return ! empty( $content ) ? '<div class="latepoint-step-content-text-left">' . $content . '</div>' : '';
2093 }
2094
2095
2096 public static function get_step_setting_value( string $step_code, string $setting_key, $default = '' ) {
2097 $steps_settings = self::get_step_settings( $step_code );
2098
2099 return $steps_settings[ $setting_key ] ?? $default;
2100 }
2101
2102 public static function get_step_settings_edit_form_html( string $selected_step_code ): string {
2103 $step_settings_html = '';
2104 switch ( $selected_step_code ) {
2105 case 'booking__services':
2106 $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' ) ] );
2107 break;
2108 case 'booking__agents':
2109 $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' ) ] );
2110 $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' ) ] );
2111 $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' ) ] );
2112 $step_settings_html .= '<div class="control-under-toggler" id="lp-any-agent-settings" ' . ( OsSettingsHelper::is_on( 'allow_any_agent' ) ? '' : 'style="display: none;"' ) . '>';
2113 $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() );
2114 $step_settings_html .= '</div>';
2115 break;
2116 case 'booking__datepicker':
2117 $step_settings_html .= OsFormHelper::select_field( 'steps_settings[booking__datepicker][time_pick_style]', __( 'Show Time Slots as', 'latepoint' ), [
2118 'timebox' => 'Time Boxes',
2119 'timeline' => 'Timeline'
2120 ], OsStepsHelper::get_time_pick_style() );
2121 $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' ) ] );
2122 $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' ) ] );
2123 $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' ) ] );
2124 $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' ) ] );
2125 break;
2126 case 'confirmation':
2127 $step_settings_html .= OsFormHelper::select_field( 'steps_settings[confirmation][order_confirmation_message_style]', __( 'Message Style', 'latepoint' ), [ 'green' => __( 'Green', 'latepoint' ),
2128 'yellow' => __( 'Yellow', 'latepoint' )
2129 ], self::get_step_setting_value( $selected_step_code, 'order_confirmation_message_style', 'green' ) );
2130 break;
2131 }
2132 /**
2133 * Generates HTML for step settings form in the preview
2134 *
2135 * @param {string} $step_settings_html html that is going to be output on the step settings form
2136 * @param {string} $selected_step_code step code that settings are requested for
2137 * @returns {string} $step_settings_html Filtered HTML of the settings form
2138 *
2139 * @since 5.0.0
2140 * @hook latepoint_get_step_settings_edit_form_html
2141 *
2142 */
2143 $step_settings_html = apply_filters( 'latepoint_get_step_settings_edit_form_html', $step_settings_html, $selected_step_code );
2144 if ( empty( $step_settings_html ) ) {
2145 $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>';
2146 }
2147
2148 return $step_settings_html;
2149 }
2150
2151 public static function get_default_value_for_step_settings( string $step_code ): array {
2152 $settings = [
2153 'booking__services' => [
2154 'side_panel_heading' => 'Service Selection',
2155 'side_panel_description' => 'Please select a service for which you want to schedule an appointment',
2156 'main_panel_heading' => 'Available Services'
2157 ],
2158 'booking__locations' => [
2159 'side_panel_heading' => 'Location Selection',
2160 'side_panel_description' => 'Please select a location where you want to schedule an appointment',
2161 'main_panel_heading' => 'Available Locations'
2162 ],
2163 'booking__agents' => [
2164 'side_panel_heading' => 'Agent Selection',
2165 'side_panel_description' => 'Please select an agent that will be providing you a service',
2166 'main_panel_heading' => 'Available Agents'
2167 ],
2168 'booking__datepicker' => [
2169 'side_panel_heading' => 'Select Date & Time',
2170 'side_panel_description' => 'Please select date and time for your appointment',
2171 'main_panel_heading' => 'Date & Time Selection'
2172 ],
2173 'customer' => [
2174 'side_panel_heading' => 'Enter Your Information',
2175 'side_panel_description' => 'Please enter your contact information',
2176 'main_panel_heading' => 'Customer Information'
2177 ],
2178 'verify' => [
2179 'side_panel_heading' => 'Verify Order Details',
2180 'side_panel_description' => 'Double check your reservation details and click submit button if everything is correct',
2181 'main_panel_heading' => 'Verify Order Details',
2182 ],
2183 'payment__times' => [
2184 'side_panel_heading' => 'Payment Time Selection',
2185 'side_panel_description' => 'Please choose when you would like to pay for your appointment',
2186 'main_panel_heading' => 'When would you like to pay?'
2187 ],
2188 'payment__portions' => [
2189 'side_panel_heading' => 'Payment Portion Selection',
2190 'side_panel_description' => 'Please select how much you would like to pay now',
2191 'main_panel_heading' => 'How much would you like to pay now?'
2192 ],
2193 'payment__methods' => [
2194 'side_panel_heading' => 'Payment Method Selection',
2195 'side_panel_description' => 'Please select a payment method you would like to make a payment with',
2196 'main_panel_heading' => 'Select payment method'
2197 ],
2198 'payment__processors' => [
2199 'side_panel_heading' => 'Payment Processor Selection',
2200 'side_panel_description' => 'Please select a payment processor you want to process the payment with',
2201 'main_panel_heading' => 'Select payment processor'
2202 ],
2203 'payment__pay' => [
2204 'side_panel_heading' => 'Make a Payment',
2205 'side_panel_description' => 'Please enter your payment information so we can process the payment',
2206 'main_panel_heading' => 'Enter your payment information'
2207 ],
2208 'confirmation' => [
2209 'side_panel_heading' => 'Confirmation',
2210 'side_panel_description' => 'Your order has been placed. Please retain this confirmation for your record.',
2211 'main_panel_heading' => 'Order Confirmation'
2212 ]
2213 ];
2214
2215
2216 $settings = apply_filters( 'latepoint_settings_for_step_codes', $settings );
2217
2218 return $settings[ $step_code ] ?? [];
2219 }
2220
2221
2222 public static function get_default_side_panel_image_html_for_step_code( string $step_code ): string {
2223 $svg = '';
2224 switch ( $step_code ) {
2225 case 'booking__locations':
2226 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2227 <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 "/>
2228 <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>';
2229 break;
2230 case 'booking__services':
2231 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2232 <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 "/>
2233 <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"/>
2234 <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"/>
2235 <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"/>
2236 <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" />
2237 <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"/>
2238 <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"/>
2239 </svg>';
2240 break;
2241 case 'booking__agents':
2242 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2243 <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>
2244 <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>
2245 <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>
2246 <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>
2247 <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>
2248 </svg>';
2249 break;
2250 case 'booking__datepicker':
2251 case 'customer':
2252 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2253 <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"/>
2254 <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"/>
2255 <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"/>
2256 <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"/>
2257 <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"/>
2258 <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"/>
2259 <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"/>
2260 </svg>';
2261 break;
2262 case 'payment__times':
2263 case 'payment__portions':
2264 case 'payment__methods':
2265 case 'payment__processors':
2266 case 'payment__pay':
2267 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73 73">
2268 <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" />
2269 <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"/>
2270 <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"/>
2271 <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"/>
2272 <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"/>
2273 </svg>';
2274 break;
2275 case 'verify':
2276 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
2277 <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"/>
2278 <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"/>
2279 <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"/>
2280 <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"/>
2281 <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 "/>
2282 </svg>';
2283 break;
2284 case 'confirmation':
2285 $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
2286 <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"/>
2287 <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"/>
2288 <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"/>
2289 <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"/>
2290 <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"/>
2291 </svg>';
2292 break;
2293 }
2294
2295 /**
2296 * Generates an SVG image for step code, if there was no custom image set
2297 *
2298 * @param {string} $svg image svg code
2299 * @param {string} $step_code step name code
2300 *
2301 * @since 5.0.0
2302 * @hook latepoint_svg_for_step_code
2303 *
2304 */
2305 return apply_filters( 'latepoint_svg_for_step_code', $svg, $step_code );
2306 }
2307
2308
2309 public static function get_time_pick_style() {
2310 return OsStepsHelper::get_step_setting_value( 'booking__datepicker', 'time_pick_style', 'timebox' );
2311 }
2312
2313 /**
2314 * Generates a preview for a selected step to show on booking form preview in settings
2315 *
2316 * @param string $selected_step_code
2317 *
2318 * @return void
2319 */
2320 public static function get_step_content_preview( string $selected_step_code ) {
2321 switch ( $selected_step_code ) {
2322 case 'booking__services':
2323 OsBookingHelper::generate_services_bundles_and_categories_list();
2324 break;
2325 case 'booking__agents':
2326 $agents_model = new OsAgentModel();
2327 $agents = $agents_model->should_be_active()->get_results_as_models();
2328 OsAgentHelper::generate_agents_list( $agents );
2329 break;
2330 case 'booking__datepicker':
2331 $booking = new OsBookingModel();
2332 $services = new OsServiceModel();
2333 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2334 if ( $service ) {
2335 $booking->service_id = $service->id;
2336 echo OsCalendarHelper::generate_dates_and_times_picker( $booking, new OsWpDateTime( 'now' ), ! OsStepsHelper::disable_searching_first_available_slot() );
2337 ?>
2338
2339
2340 <?php
2341 } else {
2342 echo 'You need to have an active service to generate the calendar';
2343 }
2344 break;
2345 case 'booking__locations':
2346 OsLocationHelper::generate_locations_and_categories_list();
2347 break;
2348 case 'customer':
2349 $booking = new OsBookingModel();
2350 $services = new OsServiceModel();
2351 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2352 $customer = new OsCustomerModel();
2353 $default_fields_for_customer = OsSettingsHelper::get_default_fields_for_customer();
2354
2355 $current_step_code = $selected_step_code;
2356
2357 include LATEPOINT_VIEWS_ABSPATH . 'booking_form_settings/previews/_customer.php';
2358 break;
2359 case 'payment__times':
2360 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>';
2361 break;
2362 case 'payment__portions':
2363 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>';
2364 break;
2365 case 'payment__methods':
2366 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>';
2367 break;
2368 case 'payment__pay':
2369 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "Payment form generated by selected payment processor will appear here", 'latepoint' ) . '</div>';
2370 break;
2371 case 'confirmation':
2372 echo '<div class="summary-status-wrapper summary-status-style-' . esc_attr( OsStepsHelper::get_step_setting_value( $selected_step_code, 'order_confirmation_message_style', 'green' ) ) . '">';
2373 echo '<div class="summary-status-inner">';
2374 echo '<div class="ss-icon"></div>';
2375 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>';
2376 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>';
2377 echo '<div class="ss-confirmation-number"><span>' . esc_html__( 'Order #', 'latepoint' ) . '</span><strong>KDFJ934K</strong></div>';
2378 echo '</div>';
2379 echo '</div>';
2380 echo '<div class="booking-preview-step-skipped-message">' . esc_html__( "Order information will appear here.", 'latepoint' ) . '</div>';
2381 break;
2382 }
2383 do_action( 'latepoint_get_step_content_preview', $selected_step_code );
2384 }
2385
2386 public static function hide_slot_availability_count(): bool {
2387 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_slot_availability_count' ) );
2388 }
2389
2390 public static function hide_timepicker_when_one_slot_available(): bool {
2391 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_timepicker_when_one_slot_available' ) );
2392 }
2393
2394 public static function build_booking_object_for_current_step_preview( string $current_step ): OsBookingModel {
2395 $booking = new OsBookingModel();
2396 $steps_in_order = self::get_step_codes_in_order();
2397
2398 $current_step_index = array_search( $current_step, $steps_in_order );
2399 if ( $current_step_index === false ) {
2400 return $booking;
2401 }
2402 $completed_steps = array_slice( $steps_in_order, 0, $current_step_index );
2403 foreach ( $completed_steps as $completed_step ) {
2404 self::set_booking_object_values_for_completed_step( $booking, $completed_step );
2405 }
2406
2407 return $booking;
2408 }
2409
2410 public static function set_booking_object_values_for_completed_step( OsBookingModel $booking, string $completed_step ): OsBookingModel {
2411 switch ( $completed_step ) {
2412 case 'booking__services':
2413 $services = new OsServiceModel();
2414 $service = $services->should_be_active()->set_limit( 1 )->get_results_as_models();
2415 if ( $service ) {
2416 $booking->service_id = $service->id;
2417 }
2418 break;
2419 case 'booking__locations':
2420 $locations = new OsLocationModel();
2421 $location = $locations->should_be_active()->set_limit( 1 )->get_results_as_models();
2422 if ( $location ) {
2423 $booking->location_id = $location->id;
2424 }
2425 break;
2426 case 'booking__agents':
2427 $agents = new OsAgentModel();
2428 $agent = $agents->should_be_active()->set_limit( 1 )->get_results_as_models();
2429 if ( $agent ) {
2430 $booking->agent_id = $agent->id;
2431 }
2432 break;
2433 case 'customer':
2434 $customers = new OsCustomerModel();
2435 $customer = $customers->set_limit( 1 )->get_results_as_models();
2436 if ( $customer ) {
2437 $booking->customer_id = $customer->id;
2438 }
2439 break;
2440 case 'booking__datepicker':
2441 $tomorrow = new OsWpDateTime( 'tomorrow' );
2442 $booking->start_date = $tomorrow->format( 'Y-m-d' );
2443 $booking->start_time = 600;
2444
2445 break;
2446 }
2447
2448 /**
2449 * Sets values for booking object depending on a completed step code
2450 *
2451 * @param {OsBookingModel} $booking booking object
2452 * @param {string} $completed_step step code that was completed
2453 *
2454 * @since 5.0.0
2455 * @hook latepoint_set_booking_object_values_for_completed_step
2456 *
2457 */
2458 return apply_filters( 'latepoint_set_booking_object_values_for_completed_step', $booking, $completed_step );
2459 }
2460
2461 public static function generate_summary_key_value_pairs( OsBookingModel $booking ): string {
2462 $html = '';
2463
2464
2465 if ( $booking->location_id ) {
2466 $html .= '<div class="summary-box summary-box-location-info">
2467 <div class="summary-box-heading">
2468 <div class="sbh-item">' . __( 'Location', 'latepoint' ) . '</div>
2469 <div class="sbh-line"></div>
2470 </div>
2471 <div class="summary-box-content with-media">
2472 <div class="sbc-content-i">
2473 <div class="sbc-main-item">' . $booking->location->name . '</div>
2474 </div>
2475 </div>
2476 </div>';
2477 }
2478 if ( $booking->customer_id ) {
2479 $html .= '<div class="summary-box summary-box-customer-info">
2480 <div class="summary-box-heading">
2481 <div class="sbh-item">' . __( 'Customer', 'latepoint' ) . '</div>
2482 <div class="sbh-line"></div>
2483 </div>
2484 <div class="summary-box-content with-media">
2485 <div class="os-avatar-w">
2486 <div class="os-avatar"><span>' . esc_html( $booking->customer->get_initials() ) . '</span></div>
2487 </div>
2488 <div class="sbc-content-i">
2489 <div class="sbc-main-item">' . esc_html( $booking->customer->full_name ) . '</div>
2490 <div class="sbc-sub-item">' . esc_html( $booking->customer->email ) . '</div>
2491 </div>
2492 </div>';
2493 $customer_attributes = [];
2494 $customer_attributes = apply_filters( 'latepoint_booking_summary_customer_attributes', $customer_attributes, $booking->customer );
2495 if ( $customer_attributes ) {
2496 $html .= '<div class="summary-attributes sa-clean sa-hidden">';
2497 foreach ( $customer_attributes as $attribute ) {
2498 $html .= '<span>' . esc_html( $attribute['label'] ) . ': <strong>' . esc_html( $attribute['value'] ) . '</strong></span>';
2499 }
2500 $html .= '</div>';
2501 }
2502 $html .= '</div>';
2503 }
2504 if ( OsSettingsHelper::is_off( 'steps_hide_agent_info' ) && $booking->agent_id && $booking->agent_id != LATEPOINT_ANY_AGENT ) {
2505 $bio_html = '';
2506 if ( OsSettingsHelper::steps_show_agent_bio() ) {
2507 $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>';
2508 $bio_html .= OsAgentHelper::generate_bio( $booking->agent );
2509 }
2510 $html .= '<div class="summary-box summary-box-agent-info">
2511 <div class="summary-box-heading">
2512 <div class="sbh-item">' . __( 'Agent', 'latepoint' ) . '</div>
2513 <div class="sbh-line"></div>
2514 </div>
2515 <div class="summary-box-content with-media">
2516 <div class="os-avatar-w"
2517 style="background-image: url(' . ( ( $booking->agent->avatar_image_id ) ? $booking->agent->get_avatar_url() : '' ) . ')">
2518 ' . ( ( ! $booking->agent->avatar_image_id ) ? '<div class="os-avatar"><span>' . esc_html( $booking->agent->get_initials() ) . '</span></div>' : '' ) . '
2519 </div>
2520 <div class="sbc-content-i">
2521 <div class="sbc-main-item">' . esc_html( $booking->agent->full_name ) . '</div>
2522 ' . $bio_html . '
2523 </div>
2524 </div>
2525 </div>';
2526 }
2527
2528
2529 /**
2530 * Key value pairs of summary values for the booking summary panel
2531 *
2532 * @param {string} $html HTML of key value pairs
2533 * @param {OsBookingModel} $booking Booking object that is used to generate the summary
2534 * @returns {string} $html The filtered HTML of key value pairs
2535 *
2536 * @since 5.0.0
2537 * @hook latepoint_summary_key_value_pairs
2538 *
2539 */
2540 $html = apply_filters( 'latepoint_summary_key_value_pairs', $html, $booking );
2541
2542 if ( $html ) {
2543 $html = '<div class="summary-boxes-columns">' . $html . '</div>';
2544 }
2545
2546 return $html;
2547 }
2548
2549 public static function is_ready_for_summary() {
2550 if ( ! empty( self::$order_object ) && ! self::$order_object->is_new_record() ) {
2551 // order object is set - don't need to show summary anymore
2552 return false;
2553 }
2554 if ( ! self::$cart_object->is_empty() ) {
2555 // cart has items inside - show summary
2556 return true;
2557 }
2558 if ( self::$active_cart_item->is_bundle() ) {
2559 // bundle selected already - show summary
2560 return true;
2561 }
2562 if ( ! empty( self::$booking_object->service_id ) ) {
2563 // service is selected for a booking - show summary
2564 return true;
2565 }
2566
2567
2568 return false;
2569 }
2570
2571 public static function set_active_cart_item_object( array $cart_item_params = [] ): OsCartItemModel {
2572 self::$active_cart_item = new OsCartItemModel();
2573 if ( ! empty( $cart_item_params['id'] ) ) {
2574 self::$active_cart_item->id = $cart_item_params['id'];
2575 // try to find it in cart
2576 $cart_item = new OsCartItemModel( self::$active_cart_item->id );
2577 if ( $cart_item->is_new_record() ) {
2578 // not found, reset active cart item ID
2579 self::$active_cart_item = new OsCartItemModel();
2580 }
2581 }
2582 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 );
2583 if ( self::$active_cart_item->is_bundle() ) {
2584 if ( empty( $cart_item_params['item_data'] ) ) {
2585 self::$active_cart_item->item_data = empty( self::$presets['selected_bundle'] ) ? '' : wp_json_encode( [ 'bundle_id' => self::$presets['selected_bundle'] ] );
2586 } else {
2587 // bundle gets data from params
2588 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'];
2589 }
2590 } else {
2591 // booking gets data from booking object
2592 self::$active_cart_item->item_data = wp_json_encode( self::$booking_object->generate_params_for_booking_form(), true );
2593 }
2594
2595 return self::$active_cart_item;
2596 }
2597
2598 public static function get_cart_item_object() {
2599 return self::$active_cart_item;
2600 }
2601
2602
2603 /**
2604 *
2605 * Given a step code, returns the first sub step if found, or returns the parent step code if no children
2606 *
2607 * @param string $parent_code
2608 *
2609 * @return string
2610 */
2611 public static function get_first_step_for_parent_code( string $parent_code ): string {
2612 $first_step_code = '';
2613 $step_codes = self::$step_codes_in_order;
2614 foreach ( $step_codes as $step_code ) {
2615 $loop_parent_code = explode( '__', $step_code )[0];
2616 if ( $loop_parent_code == $parent_code ) {
2617 $first_step_code = $step_code;
2618 break;
2619 }
2620 }
2621
2622 return $first_step_code;
2623 }
2624
2625 public static function check_step_code_access( string $step_code_to_access ): string {
2626 if ( $step_code_to_access == 'confirmation' && ! self::$order_object->is_new_record() ) {
2627 return $step_code_to_access;
2628 }
2629 // loops through all steps and checks if they satisfy condition to be skipped
2630 for ( $i = 0; $i < count( self::$step_codes_in_order ); $i ++ ) {
2631 $code = self::$step_codes_in_order[ $i ];
2632 $parent_code = explode( '__', $code )[0];
2633
2634 $next_code = ( ( $i + 1 ) < count( self::$step_codes_in_order ) ) ? self::$step_codes_in_order[ $i + 1 ] : false;
2635 $next_parent_code = $next_code ? explode( '__', $next_code )[0] : false;
2636
2637 if ( $step_code_to_access == $code ) {
2638 break;
2639 }
2640 switch ( $parent_code ) {
2641 // 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
2642 case 'customer':
2643 if ( ! OsAuthHelper::is_customer_logged_in() ) {
2644 $step_code_to_access = $code;
2645 break 2;
2646 }
2647 break;
2648 case 'booking':
2649 if ( $next_parent_code && $next_parent_code != $parent_code && self::$cart_object->is_empty() ) {
2650 // $step_code_to_access = self::get_first_step_for_parent_code($parent_code);
2651 // break 2;
2652 }
2653 break;
2654 }
2655 }
2656
2657 /**
2658 * Checks if a step code can be accessed, returns the step code that can be accessed
2659 *
2660 * @param {string} $step_code_to_access step code that needs to be checked for access
2661 * @returns {string} $step_code_to_access The filtered step code that can be accessed
2662 *
2663 * @since 5.0.0
2664 * @hook latepoint_check_step_code_access
2665 *
2666 */
2667 return apply_filters( 'latepoint_check_step_code_access', $step_code_to_access );
2668 }
2669
2670 public static function get_first_step_code( string $step_code, $step_codes = false ): string {
2671 if ( ! $step_codes ) {
2672 $step_codes = self::get_step_codes_in_order();
2673 }
2674 if ( isset( $step_codes[ $step_code ] ) ) {
2675 return $step_code;
2676 }
2677 $unflat_step_codes = self::unflatten_steps( $step_codes );
2678
2679 // TODO add support for more than 2 dimentional parent/child arrays
2680 if ( isset( $unflat_step_codes[ $step_code ] ) ) {
2681 return implode( '__', [ $step_code, array_key_first( $unflat_step_codes[ $step_code ] ) ] );
2682 }
2683
2684 return '';
2685 }
2686
2687 public static function build_cart_object(): OsCartModel {
2688 if ( ! isset( self::$cart_object ) ) {
2689 self::set_cart_object();
2690 }
2691
2692 return self::$cart_object;
2693 }
2694
2695 public static function set_order_object( array $params = [] ): OsOrderModel {
2696 self::$order_object = new OsOrderModel();
2697
2698 return self::$order_object;
2699 }
2700
2701 public static function set_cart_object( array $params = [] ): OsCartModel {
2702 self::$cart_object = OsCartsHelper::get_or_create_cart();
2703 if( self::$cart_object->order_intent_id ){
2704 $order_intent = new OsOrderIntentModel(self::$cart_object->order_intent_id);
2705 if($order_intent->is_converted()){
2706 $order_intent->mark_cart_converted(self::$cart_object);
2707 }
2708 }
2709 if ( self::$cart_object->order_id ) {
2710 self::load_order_object( self::$cart_object->order_id );
2711 } else {
2712 self::load_order_object();
2713 self::$cart_object->set_data( $params );
2714
2715 // set source id
2716 if ( isset( self::$restrictions['source_id'] ) ) {
2717 self::$cart_object->source_id = self::$restrictions['source_id'];
2718 }
2719
2720 self::$cart_object->calculate_prices();
2721 }
2722
2723 return self::$cart_object;
2724 }
2725
2726 public static function set_cart_object_from_order_intent( OsOrderIntentModel $order_intent ): OsCartModel {
2727 OsCartsHelper::get_or_create_cart();
2728 self::$cart_object->clear();
2729
2730
2731 // add items from intent
2732 $intent_cart_items = json_decode( $order_intent->cart_items_data, true );
2733 foreach ( $intent_cart_items as $cart_item_data ) {
2734 OsCartsHelper::add_item_to_cart( OsCartsHelper::create_cart_item_from_item_data( $cart_item_data ) );
2735 }
2736
2737 // restore payment info
2738 $payment_data = json_decode( $order_intent->payment_data, true );
2739 self::$cart_object->payment_method = $payment_data['method'];
2740 self::$cart_object->payment_time = $payment_data['time'];
2741 self::$cart_object->payment_portion = $payment_data['portion'];
2742 self::$cart_object->payment_token = $payment_data['token'];
2743 self::$cart_object->payment_processor = $payment_data['processor'];
2744
2745 return self::$cart_object;
2746 }
2747
2748 public static function hide_unavailable_slots() {
2749 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'hide_unavailable_slots' ) );
2750 }
2751
2752 public static function disable_searching_first_available_slot() {
2753 return OsUtilHelper::is_on( self::get_step_setting_value( 'booking__datepicker', 'disable_searching_first_available_slot' ) );
2754 }
2755
2756 private static function set_recurring_booking_properties( array $params ) {
2757 if ( ! empty( $params['is_recurring'] ) && $params['is_recurring'] == LATEPOINT_VALUE_ON ) {
2758 self::$booking_object->generate_recurrent_sequence = [ 'rules' => $params['recurrence']['rules'] ?? [], 'overrides' => $params['recurrence']['overrides'] ?? [] ];
2759 }
2760 }
2761 }