PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.2.11
LatePoint – Calendar Booking Plugin for Appointments and Events v5.2.11
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 / booking_helper.php
latepoint / lib / helpers Last commit date
activities_helper.php 3 months ago agent_helper.php 3 months ago analytics_helper.php 4 months ago auth_helper.php 3 months ago blocks_helper.php 3 months ago booking_helper.php 3 months ago bricks_helper.php 3 months ago bundles_helper.php 3 months ago calendar_helper.php 3 months ago carts_helper.php 3 months ago connector_helper.php 3 months ago csv_helper.php 3 months ago customer_helper.php 3 months ago customer_import_helper.php 3 months ago database_helper.php 3 months ago debug_helper.php 3 months ago defaults_helper.php 3 months ago elementor_helper.php 3 months ago email_helper.php 3 months ago encrypt_helper.php 3 months ago events_helper.php 3 months ago form_helper.php 3 months ago icalendar_helper.php 3 months ago image_helper.php 3 months ago invoices_helper.php 3 months ago license_helper.php 3 months ago location_helper.php 3 months ago marketing_systems_helper.php 3 months ago meeting_systems_helper.php 3 months ago menu_helper.php 3 months ago meta_helper.php 3 months ago migrations_helper.php 3 months ago money_helper.php 3 months ago notifications_helper.php 3 months ago nps_survey_helper.php 3 months ago order_intent_helper.php 3 months ago orders_helper.php 3 months ago otp_helper.php 3 months ago pages_helper.php 3 months ago params_helper.php 3 months ago payments_helper.php 3 months ago price_breakdown_helper.php 3 months ago process_jobs_helper.php 3 months ago processes_helper.php 3 months ago replacer_helper.php 3 months ago resource_helper.php 3 months ago roles_helper.php 3 months ago router_helper.php 3 months ago service_helper.php 3 months ago sessions_helper.php 3 months ago settings_helper.php 3 months ago short_links_systems_helper.php 3 months ago shortcodes_helper.php 3 months ago sms_helper.php 3 months ago steps_helper.php 3 months ago stripe_connect_helper.php 3 months ago styles_helper.php 3 months ago support_topics_helper.php 3 months ago time_helper.php 3 months ago timeline_helper.php 3 months ago transaction_helper.php 3 months ago transaction_intent_helper.php 3 months ago util_helper.php 3 months ago version_specific_updates_helper.php 3 months ago whatsapp_helper.php 3 months ago work_periods_helper.php 3 months ago wp_datetime.php 3 months ago wp_user_helper.php 3 months ago
booking_helper.php
1847 lines
1 <?php
2
3 class OsBookingHelper {
4
5
6 /**
7 * @param OsBookingModel $booking
8 *
9 * @return mixed|void
10 *
11 * Returns full amount to charge in database format 1999.0000
12 *
13 */
14 public static function calculate_full_amount_for_booking( OsBookingModel $booking ) {
15 if ( ! $booking->service_id ) {
16 return 0;
17 }
18 $amount = self::calculate_full_amount_for_service( $booking );
19 $amount = apply_filters( 'latepoint_calculate_full_amount_for_booking', $amount, $booking );
20 $amount = OsMoneyHelper::pad_to_db_format( $amount );
21
22 return $amount;
23 }
24
25
26 /**
27 * @param OsBookingModel $booking
28 *
29 * @return mixed|void
30 *
31 */
32 public static function calculate_full_amount_for_service( OsBookingModel $booking ) {
33 if ( ! $booking->service_id ) {
34 return 0;
35 }
36 $service = new OsServiceModel( $booking->service_id );
37 $amount_for_service = $service->get_full_amount_for_duration( $booking->duration );
38 $amount_for_service = apply_filters( 'latepoint_full_amount_for_service', $amount_for_service, $booking );
39
40 return $amount_for_service;
41 }
42
43
44 /**
45 * @param OsBookingModel $booking
46 *
47 * @return mixed|void
48 *
49 * Returns deposit amount to charge in database format 1999.0000
50 *
51 */
52 public static function calculate_deposit_amount_to_charge( OsBookingModel $booking ) {
53 if ( ! $booking->service_id ) {
54 return 0;
55 }
56 $service = new OsServiceModel( $booking->service_id );
57 $amount = $service->get_deposit_amount_for_duration( $booking->duration );
58 $amount = apply_filters( 'latepoint_deposit_amount_for_service', $amount, $booking );
59 $amount = OsMoneyHelper::pad_to_db_format( $amount );
60
61 return $amount;
62 }
63
64
65 /**
66 * @param array $item_data
67 *
68 * @return OsBookingModel
69 */
70 public static function build_booking_model_from_item_data( array $item_data ): OsBookingModel {
71 $booking = new OsBookingModel();
72 if ( $item_data['id'] ) {
73 $booking = $booking->load_by_id( $item_data['id'] );
74 if ( ! $booking ) {
75 $booking = new OsBookingModel();
76 }
77 }
78 $booking->set_data( $item_data );
79 // get buffers from service and set to booking object
80 if ( ! isset( $item_data['buffer_before'] ) && ! isset( $item_data['buffer_after'] ) ) {
81 $booking->set_buffers();
82 }
83 if ( empty( $booking->end_time ) ) {
84 $booking->calculate_end_date_and_time();
85 }
86 if ( empty( $booking->end_date ) ) {
87 $booking->calculate_end_date();
88 }
89 $booking->set_utc_datetimes();
90
91 return $booking;
92 }
93
94 public static function get_booking_id_and_manage_ability_by_key( string $key ) {
95 $booking_id = OsMetaHelper::get_booking_id_by_meta_value( 'key_to_manage_for_agent', $key );
96 if ( $booking_id ) {
97 return [
98 'booking_id' => $booking_id,
99 'for' => 'agent',
100 ];
101 }
102
103 $booking_id = OsMetaHelper::get_booking_id_by_meta_value( 'key_to_manage_for_customer', $key );
104 if ( $booking_id ) {
105 return [
106 'booking_id' => $booking_id,
107 'for' => 'customer',
108 ];
109 }
110
111 return false;
112 }
113
114 public static function is_action_allowed( string $action, OsBookingModel $booking, string $key = '' ) {
115 $is_allowed = false;
116 if ( empty( $booking->id ) ) {
117 return false;
118 }
119 if ( ! in_array( $action, [ 'cancel', 'reschedule' ] ) ) {
120 return false;
121 }
122 $action_result = false;
123 switch ( $action ) {
124 case 'cancel':
125 $action_result = OsCustomerHelper::can_cancel_booking( $booking );
126 break;
127 case 'reschedule':
128 $action_result = OsCustomerHelper::can_reschedule_booking( $booking );
129 break;
130 }
131 if ( ! empty( $key ) ) {
132 // key is passed, check if allowed through key
133 $agent_key_meta = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_agent', $booking->id );
134 $customer_key_meta = OsMetaHelper::get_booking_meta_by_key( 'key_to_manage_for_customer', $booking->id );
135 if ( $key == $agent_key_meta ) {
136 // agent can do everything, no need to check for action
137 $is_allowed = true;
138 } elseif ( $key == $customer_key_meta ) {
139 // customer
140 $is_allowed = $action_result;
141 }
142 } elseif ( OsAuthHelper::get_logged_in_customer_id() == $booking->customer_id ) {
143 $is_allowed = $action_result;
144 }
145
146 return $is_allowed;
147 }
148
149 public static function generate_add_to_calendar_links( OsBookingModel $booking, $key = false ): string {
150 $html = '<div class="add-to-calendar-types">
151 <div class="atc-heading-wrapper">
152 <div class="atc-heading">' . esc_html__( 'Calendar Type', 'latepoint' ) . '</div>
153 <div class="close-calendar-types"></div>
154 </div>
155 <a href="' . esc_url( $booking->get_ical_download_link( $key ) ) . '" target="_blank" class="atc-type atc-type-apple">
156 <div class="atc-type-image"></div>
157 <div class="atc-type-name">' . esc_html__( 'Apple Calendar', 'latepoint' ) . '</div>
158 </a>
159 <a href="' . esc_url( $booking->get_url_for_add_to_calendar_button( 'google' ) ) . '" target="_blank" class="atc-type atc-type-google">
160 <div class="atc-type-image"></div>
161 <div class="atc-type-name">' . esc_html__( 'Google Calendar', 'latepoint' ) . '</div>
162 </a>
163 <a href="' . esc_url( $booking->get_url_for_add_to_calendar_button( 'outlook' ) ) . '" target="_blank" class="atc-type atc-type-outlook">
164 <div class="atc-type-image"></div>
165 <div class="atc-type-name">' . esc_html__( 'Outlook.com', 'latepoint' ) . '</div>
166 </a>
167 <a href="' . esc_url( $booking->get_url_for_add_to_calendar_button( 'outlook' ) ) . '" target="_blank" class="atc-type atc-type-office-365">
168 <div class="atc-type-image"></div>
169 <div class="atc-type-name">' . esc_html__( 'Microsoft 365', 'latepoint' ) . '</div>
170 </a>
171 </div>';
172
173 return $html;
174 }
175
176
177 public static function get_bookings_for_select( $should_be_in_future = false ) {
178 $bookings = new OsBookingModel();
179 if ( $should_be_in_future ) {
180 $bookings = $bookings->should_be_in_future();
181 }
182 $bookings = $bookings->order_by( 'id desc' )->set_limit( 100 )->get_results_as_models();
183 $bookings_options = [];
184 foreach ( $bookings as $booking ) {
185 $name = $booking->service->name . ', ' . $booking->agent->full_name . ', ' . $booking->customer->full_name . ' [' . $booking->booking_code . ' : ' . $booking->id . ']';
186 $bookings_options[] = [
187 'value' => $booking->id,
188 'label' => esc_html( $name ),
189 ];
190 }
191
192 return $bookings_options;
193 }
194
195 /**
196 *
197 * Determine whether to show
198 *
199 * @param $rows
200 *
201 * @return bool
202 */
203 public static function is_breakdown_free( $rows ) {
204 return ( ( empty( $rows['subtotal']['raw_value'] ) || ( (float) $rows['subtotal']['raw_value'] <= 0 ) ) && ( empty( $rows['total']['raw_value'] ) || ( (float) $rows['total']['raw_value'] <= 0 ) ) );
205 }
206
207 public static function output_price_breakdown( $rows ) {
208 foreach ( $rows['before_subtotal'] as $row ) {
209 self::output_price_breakdown_row( $row );
210 }
211 // if there is nothing between subtotal and total - don't show subtotal as it will be identical to total
212 if ( ! empty( $rows['after_subtotal'] ) ) {
213 if ( ! empty( $rows['subtotal'] ) ) {
214 echo '<div class="subtotal-separator"></div>';
215 self::output_price_breakdown_row( $rows['subtotal'] );
216 }
217 foreach ( $rows['after_subtotal'] as $row ) {
218 self::output_price_breakdown_row( $row );
219 }
220 }
221 if ( ! empty( $rows['total'] ) ) {
222 self::output_price_breakdown_row( $rows['total'] );
223 }
224 if ( ! empty( $rows['payments'] ) ) {
225 foreach ( $rows['payments'] as $row ) {
226 self::output_price_breakdown_row( $row );
227 }
228 }
229 if ( ! empty( $rows['balance'] ) ) {
230 self::output_price_breakdown_row( $rows['balance'] );
231 }
232 }
233
234 public static function output_price_breakdown_row( $row ) {
235 if ( ! empty( $row['items'] ) ) {
236 if ( ! empty( $row['heading'] ) ) {
237 echo '<div class="summary-box-heading"><div class="sbh-item">' . esc_html( $row['heading'] ) . '</div><div class="sbh-line"></div></div>';
238 }
239 foreach ( $row['items'] as $row_item ) {
240 self::output_price_breakdown_row( $row_item );
241 }
242 } else {
243 $extra_class = '';
244 if ( isset( $row['style'] ) && $row['style'] == 'strong' ) {
245 $extra_class .= ' spi-strong';
246 }
247 if ( isset( $row['style'] ) && $row['style'] == 'total' ) {
248 $extra_class .= ' spi-total';
249 }
250 if ( isset( $row['type'] ) && $row['type'] == 'credit' ) {
251 $extra_class .= ' spi-positive';
252 }
253 ?>
254 <div class="summary-price-item-w <?php echo esc_attr( $extra_class ); ?>">
255 <div class="spi-name">
256 <?php echo $row['label']; ?>
257 <?php if ( ! empty( $row['note'] ) ) {
258 echo '<span class="pi-note">' . esc_html( $row['note'] ) . '</span>';
259 } ?>
260 <?php if ( ! empty( $row['badge'] ) ) {
261 echo '<span class="pi-badge">' . esc_html( $row['badge'] ) . '</span>';
262 } ?>
263 </div>
264 <div class="spi-price"><?php echo esc_html( $row['value'] ); ?></div>
265 </div>
266 <?php
267 }
268 }
269
270 public static function output_price_breakdown_row_as_input_field( $row, $base_name ) {
271 $field_name = $base_name . '[' . OsUtilHelper::random_text( 'alnum', 8 ) . ']';
272 if ( ! empty( $row['items'] ) ) {
273 echo OsFormHelper::hidden_field( $field_name . '[heading]', $row['heading'] ?? '' );
274 foreach ( $row['items'] as $row_item ) {
275 self::output_price_breakdown_row_as_input_field( $row_item, $field_name . '[items]' );
276 }
277 } else {
278 $wrapper_class = ( $row['raw_value'] < 0 ) ? [ 'class' => 'green-value-input' ] : [];
279 $label = $row['label'] ?? '';
280 if ( ! empty( $row['note'] ) ) {
281 $label .= ' ' . $row['note'];
282 }
283 echo OsFormHelper::money_field( $field_name . '[value]', $label, $row['raw_value'], [ 'theme' => 'right-aligned' ], [], $wrapper_class );
284 echo OsFormHelper::hidden_field( $field_name . '[label]', $row['label'] ?? '' );
285 echo OsFormHelper::hidden_field( $field_name . '[style]', $row['style'] ?? '' );
286 echo OsFormHelper::hidden_field( $field_name . '[type]', $row['type'] ?? '' );
287 echo OsFormHelper::hidden_field( $field_name . '[note]', $row['note'] ?? '' );
288 echo OsFormHelper::hidden_field( $field_name . '[badge]', $row['badge'] ?? '' );
289 }
290 if ( ! empty( $row['sub_items'] ) ) {
291 foreach ( $row['sub_items'] as $row_item ) {
292 self::output_price_breakdown_row_as_input_field( $row_item, $field_name . '[sub_items]' );
293 }
294 }
295 }
296
297 /**
298 * @param \LatePoint\Misc\Filter $filter
299 * @param bool $accessed_from_backend
300 *
301 * @return array
302 */
303 public static function get_blocked_periods_grouped_by_day( \LatePoint\Misc\Filter $filter, bool $accessed_from_backend = false ): array {
304 $grouped_blocked_periods = [];
305
306 if ( $filter->date_from ) {
307 $date_from = OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
308 $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
309
310 # Loop through days to fill in days that might have no bookings
311 for ( $day = clone $date_from; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
312 $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] = [];
313 }
314 }
315 if ( ! $accessed_from_backend ) {
316 $today = new OsWpDateTime( 'today' );
317 $earliest_possible_booking = OsSettingsHelper::get_earliest_possible_booking_restriction( $filter->service_id );
318
319 $block_end_datetime = OsTimeHelper::now_datetime_object();
320 if ( $earliest_possible_booking ) {
321 try {
322 $block_end_datetime->modify( $earliest_possible_booking );
323 } catch ( Exception $e ) {
324 $block_end_datetime = OsTimeHelper::now_datetime_object();
325 }
326 }
327 for ( $day = clone $today; $day->format( 'Y-m-d' ) <= $block_end_datetime->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
328 // loop days from now to the earliest possible booking and block timeslots if these days were actually requested
329 if ( isset( $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] ) ) {
330 $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ][] = new \LatePoint\Misc\BlockedPeriod(
331 [
332 'start_time' => 0,
333 'end_time' => ( $day->format( 'Y-m-d' ) < $block_end_datetime->format( 'Y-m-d' ) ) ? 24 * 60 : OsTimeHelper::convert_datetime_to_minutes( $block_end_datetime ),
334 'start_date' => $day->format( 'Y-m-d' ),
335 'end_date' => $day->format( 'Y-m-d' ),
336 ]
337 );
338 }
339 }
340 $latest_possible_booking = OsSettingsHelper::get_latest_possible_booking_restriction( $filter->service_id );
341 if ( $latest_possible_booking ) {
342 try {
343 $latest_booking_datetime = OsTimeHelper::now_datetime_object();
344 $latest_booking_datetime->modify( $latest_possible_booking );
345 } catch ( Exception $e ) {
346 $latest_booking_datetime = null;
347 }
348 if ( $latest_booking_datetime && $filter->date_from ) {
349 $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
350 // Start from the latest_booking_datetime day
351 for ( $day = clone $latest_booking_datetime; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
352 if ( isset( $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ] ) ) {
353 $grouped_blocked_periods[ $day->format( 'Y-m-d' ) ][] = new \LatePoint\Misc\BlockedPeriod(
354 [
355 'start_time' => ( $day->format( 'Y-m-d' ) == $latest_booking_datetime->format( 'Y-m-d' ) ) ? OsTimeHelper::convert_datetime_to_minutes( $latest_booking_datetime ) : 0,
356 'end_time' => 24 * 60,
357 'start_date' => $day->format( 'Y-m-d' ),
358 'end_date' => $day->format( 'Y-m-d' ),
359 ]
360 );
361 }
362 }
363 }
364 }
365 }
366
367 $grouped_blocked_periods = apply_filters( 'latepoint_blocked_periods_for_range', $grouped_blocked_periods, $filter );
368
369 return $grouped_blocked_periods;
370 }
371
372 /**
373 * @param \LatePoint\Misc\Filter $filter
374 *
375 * @return array
376 */
377 public static function get_booked_periods_grouped_by_day( \LatePoint\Misc\Filter $filter ): array {
378 $booked_periods = self::get_booked_periods( $filter );
379
380 $grouped_booked_periods = [];
381 if ( $filter->date_from ) {
382 $date_from = OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
383 $date_to = ( $filter->date_to ) ? OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_to ) : OsWpDateTime::os_createFromFormat( 'Y-m-d', $filter->date_from );
384
385 # Loop through days to fill in days that might have no bookings
386 for ( $day = clone $date_from; $day->format( 'Y-m-d' ) <= $date_to->format( 'Y-m-d' ); $day->modify( '+1 day' ) ) {
387 $grouped_booked_periods[ $day->format( 'Y-m-d' ) ] = [];
388 }
389 foreach ( $booked_periods as $booked_period ) {
390 $grouped_booked_periods[ $booked_period->start_date ][] = $booked_period;
391 // if event spans multiple days - add to other days as well
392 if ( $booked_period->end_date && ( $booked_period->start_date != $booked_period->end_date ) ) {
393 $grouped_booked_periods[ $booked_period->end_date ][] = $booked_period;
394 }
395 }
396 }
397
398 return $grouped_booked_periods;
399 }
400
401 /**
402 * @param \LatePoint\Misc\Filter $filter
403 *
404 * @return \LatePoint\Misc\BookedPeriod[]
405 */
406 public static function get_booked_periods( \LatePoint\Misc\Filter $filter ): array {
407
408
409 $bookings = self::get_bookings( $filter, true );
410 $booked_periods = [];
411
412 foreach ( $bookings as $booking ) {
413 $booked_periods[] = \LatePoint\Misc\BookedPeriod::create_from_booking_model( $booking );
414 }
415
416
417 if ( $filter->consider_cart_items ) {
418 $cart = OsCartsHelper::get_or_create_cart();
419 $bookings_in_cart = $cart->get_bookings_from_cart_items();
420
421 foreach ( $bookings_in_cart as $cart_booking ) {
422 $booked_periods[] = \LatePoint\Misc\BookedPeriod::create_from_booking_model( $cart_booking );
423 }
424 }
425
426 // TODO Update all filters to accept new "filter" variable (In Google Calendar addon)
427 $booked_periods = apply_filters( 'latepoint_get_booked_periods', $booked_periods, $filter );
428
429 return $booked_periods;
430 }
431
432
433 /**
434 * @param \LatePoint\Misc\Filter $filter
435 * @param bool $as_models
436 *
437 * @return array
438 */
439 public static function get_bookings( \LatePoint\Misc\Filter $filter, bool $as_models = false ): array {
440 $bookings = new OsBookingModel();
441 if ( $filter->date_from ) {
442 if ( $filter->date_from && $filter->date_to ) {
443 # both start and end date provided - means it's a range
444 $bookings->where(
445 [
446 'start_date >=' => $filter->date_from,
447 'start_date <=' => $filter->date_to,
448 ]
449 );
450 } else {
451 # only start_date provided - means it's a specific date requested
452 $bookings->where( [ 'start_date' => $filter->date_from ] );
453 }
454 }
455
456
457 if ( $filter->connections ) {
458 $connection_conditions = [];
459 foreach ( $filter->connections as $connection ) {
460 $connection_conditions[] = [
461 'AND' =>
462 [
463 'agent_id' => $connection->agent_id,
464 'service_id' => $connection->service_id,
465 'location_id' => $connection->location_id,
466 ],
467 ];
468 }
469 $bookings->where( [ 'OR' => $connection_conditions ] );
470 } else {
471 if ( $filter->agent_id ) {
472 $bookings->where( [ 'agent_id' => $filter->agent_id ] );
473 }
474 if ( $filter->location_id ) {
475 $bookings->where( [ 'location_id' => $filter->location_id ] );
476 }
477 if ( $filter->service_id ) {
478 $bookings->where( [ 'service_id' => $filter->service_id ] );
479 }
480 }
481 if ( $filter->statuses ) {
482 $bookings->where( [ 'status' => $filter->statuses ] );
483 }
484 if ( $filter->exclude_booking_ids ) {
485 $bookings->where( [ 'id NOT IN' => $filter->exclude_booking_ids ] );
486 }
487 $bookings->order_by( 'start_time asc, end_time asc, service_id asc' );
488 $bookings = ( $as_models ) ? $bookings->get_results_as_models() : $bookings->get_results();
489
490 // make sure to return empty array if nothing is found
491 if ( empty( $bookings ) ) {
492 $bookings = [];
493 }
494
495 return $bookings;
496 }
497
498 public static function generate_ical_event_string( $booking ) {
499 // translators: %1$s is agent name, %2$s is service name
500 $booking_description = sprintf( __( 'Appointment with %1$s for %2$s', 'latepoint' ), $booking->agent->full_name, $booking->service->name );
501
502 $ics = new ICS(
503 array(
504 'location' => $booking->location->full_address,
505 'description' => '',
506 'dtstart' => $booking->format_start_date_and_time_for_google(),
507 'dtend' => $booking->format_end_date_and_time_for_google(),
508 'summary' => $booking_description,
509 'url' => get_site_url(),
510 )
511 );
512
513 return $ics->to_string();
514 }
515
516 /**
517 * @param \LatePoint\Misc\BookingRequest $booking_request
518 *
519 * @return bool
520 *
521 * Checks if requested booking slot is available, loads work periods and booked periods from database and checks availability against them
522 */
523 public static function is_booking_request_available( \LatePoint\Misc\BookingRequest $booking_request, $settings = [] ): bool {
524 try {
525 $requested_date = new OsWpDateTime( $booking_request->start_date );
526 } catch ( Exception $e ) {
527 return false;
528 }
529 $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $requested_date, $requested_date, $settings );
530 if ( empty( $resources[ $requested_date->format( 'Y-m-d' ) ] ) ) {
531 return false;
532 }
533 $is_available = false;
534
535
536 // check if satisfies earliest and latest bookings - check per-service settings first, then global
537 $earliest_possible_booking = OsSettingsHelper::get_earliest_possible_booking_restriction( $booking_request->service_id );
538 $latest_possible_booking = OsSettingsHelper::get_latest_possible_booking_restriction( $booking_request->service_id );
539
540
541 if ( $earliest_possible_booking || $latest_possible_booking ) {
542 // check earliest
543 if ( ! empty( $earliest_possible_booking ) ) {
544 try {
545 $earliest_possible_booking_date = new OsWpDateTime( $earliest_possible_booking );
546 if ( $earliest_possible_booking_date > $booking_request->get_start_datetime() ) {
547 return false;
548 }
549 } catch ( Exception $e ) {
550
551 }
552 }
553 if ( ! empty( $latest_possible_booking ) ) {
554 // check latest
555 try {
556 $latest_possible_booking_date = new OsWpDateTime( $latest_possible_booking );
557 if ( $latest_possible_booking_date < $booking_request->get_start_datetime() ) {
558 return false;
559 }
560 } catch ( Exception $e ) {
561
562 }
563 }
564 }
565
566 foreach ( $resources[ $requested_date->format( 'Y-m-d' ) ] as $resource ) {
567 foreach ( $resource->slots as $slot ) {
568 if ( $slot->start_time == $booking_request->start_time && $slot->can_accomodate( $booking_request->total_attendees ) ) {
569 $is_available = true;
570 }
571 if ( $is_available ) {
572 break;
573 }
574 }
575 if ( $is_available ) {
576 break;
577 }
578 }
579
580 return $is_available;
581 }
582
583 /**
584 *
585 * Checks if two bookings are part of the same group appointment
586 *
587 * @param bool|OsBookingModel $booking
588 * @param bool|OsBookingModel $compare_booking
589 *
590 * @return bool
591 */
592 public static function check_if_group_bookings( $booking, $compare_booking ): bool {
593 if ( $booking && $compare_booking && ( $compare_booking->start_time == $booking->start_time ) && ( $compare_booking->end_time == $booking->end_time ) && ( $compare_booking->service_id == $booking->service_id ) && ( $compare_booking->location_id == $booking->location_id ) ) {
594 return true;
595 } else {
596 return false;
597 }
598 }
599
600
601 public static function process_actions_after_save( $booking_id ) {
602 }
603
604 /**
605 * @param DateTime $start_date
606 * @param DateTime $end_date
607 * @param \LatePoint\Misc\BookingRequest $booking_request
608 * @param \LatePoint\Misc\BookingResource[] $resources
609 * @param array $settings
610 *
611 * @return string
612 * @throws Exception
613 */
614 public static function get_quick_availability_days( DateTime $start_date, DateTime $end_date, \LatePoint\Misc\BookingRequest $booking_request, array $resources = [], array $settings = [] ) {
615 $default_settings = [
616 'work_boundaries' => false,
617 'exclude_booking_ids' => [],
618 ];
619 $settings = array_merge( $default_settings, $settings );
620
621 $html = '';
622
623 if ( ! $resources ) {
624 $resources = OsResourceHelper::get_resources_grouped_by_day( $booking_request, $start_date, $end_date, $settings );
625 }
626 if ( ! $settings['work_boundaries'] ) {
627 $settings['work_boundaries'] = OsResourceHelper::get_work_boundaries_for_groups_of_resources( $resources );
628 }
629
630 if ( $start_date->format( 'j' ) != '1' ) {
631 $html .= '<div class="ma-month-label">' . OsUtilHelper::get_month_name_by_number( $start_date->format( 'n' ) ) . '</div>';
632 }
633
634 for ( $day_date = clone $start_date; $day_date <= $end_date; $day_date->modify( '+1 day' ) ) {
635 // first day of month, output month name
636 if ( $day_date->format( 'j' ) == '1' ) {
637 $html .= '<div class="ma-month-label">' . OsUtilHelper::get_month_name_by_number( $day_date->format( 'n' ) ) . '</div>';
638 }
639 $html .= '<div class="ma-day ma-day-number-' . $day_date->format( 'N' ) . '">';
640 $html .= '<div class="ma-day-info">';
641 $html .= '<span class="ma-day-number">' . $day_date->format( 'j' ) . '</span>';
642 $html .= '<span class="ma-day-weekday">' . OsUtilHelper::get_weekday_name_by_number( $day_date->format( 'N' ), true ) . '</span>';
643 $html .= '</div>';
644 $html .= OsTimelineHelper::availability_timeline( $booking_request, $settings['work_boundaries'], $resources[ $day_date->format( 'Y-m-d' ) ], [ 'book_on_click' => false ] );
645 $html .= '</div>';
646 }
647
648 return $html;
649 }
650
651 public static function count_pending_bookings() {
652 $bookings = new OsBookingModel();
653
654 return $bookings->filter_allowed_records()->where( [ 'status IN' => OsBookingHelper::get_booking_statuses_for_pending_page() ] )->count();
655 }
656
657
658 public static function generate_bundles_folder(): void {
659 $bundles_model = new OsBundleModel();
660 $bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models();
661
662 if ( $bundles ) {
663 ?>
664 <div class="os-item-category-w os-items os-as-rows os-animated-child">
665 <div class="os-item-category-info-w os-item os-animated-self with-plus">
666 <div class="os-item-category-info os-item-i" tabindex="0">
667 <div class="os-item-img-w"><i class="latepoint-icon latepoint-icon-shopping-bag"></i></div>
668 <div class="os-item-name-w">
669 <div class="os-item-name"><?php echo esc_html__( 'Bundle & Save', 'latepoint' ); ?></div>
670 </div>
671 <?php if ( OsSettingsHelper::is_on( 'show_service_categories_count' ) && count( $bundles ) ) { ?>
672 <div class="os-item-child-count">
673 <span><?php echo count( $bundles ); ?></span> <?php esc_html_e( 'Bundles', 'latepoint' ); ?>
674 </div>
675 <?php } ?>
676 </div>
677 </div>
678 <div class="os-bundles os-animated-parent os-items os-as-rows os-selectable-items">
679 <?php
680 foreach ( $bundles as $bundle ) { ?>
681 <div class="os-animated-child os-item os-selectable-item <?php echo ( $bundle->charge_amount ) ? 'os-priced-item' : ''; ?> <?php if ( $bundle->short_description ) {
682 echo 'with-description'; } ?>"
683 tabindex="0"
684 data-item-price="<?php echo esc_attr( $bundle->charge_amount ); ?>"
685 data-priced-item-type="bundle"
686 data-summary-field-name="bundle"
687 data-summary-value="<?php echo esc_attr( $bundle->name ); ?>"
688 data-item-id="<?php echo esc_attr( $bundle->id ); ?>"
689 data-cart-item-item-data-key="bundle_id"
690 data-os-call-func="latepoint_bundle_selected">
691 <div class="os-service-selector os-item-i os-animated-self"
692 data-bundle-id="<?php echo esc_attr( $bundle->id ); ?>">
693 <span class="os-item-img-w"><i class="latepoint-icon latepoint-icon-shopping-bag"></i></span>
694 <span class="os-item-name-w">
695 <span class="os-item-name"><?php echo esc_html( $bundle->name ); ?></span>
696 <?php if ( $bundle->short_description ) { ?>
697 <span class="os-item-desc"><?php echo wp_kses_post( $bundle->short_description ); ?></span>
698 <?php } ?>
699 </span>
700
701 <?php if ( $bundle->charge_amount > 0 ) { ?>
702 <span class="os-item-price-w">
703 <span class="os-item-price">
704 <?php echo esc_html( OsMoneyHelper::format_price( $bundle->charge_amount ) ); ?>
705 </span>
706 </span>
707 <?php } ?>
708 </div>
709 </div>
710 <?php
711 }
712 ?>
713 </div>
714 </div>
715 <?php
716 }
717 }
718
719 public static function generate_services_list( $services = false, $preselected_service = false ) {
720 if ( $services && is_array( $services ) && ! empty( $services ) ) { ?>
721 <div class="os-services os-animated-parent os-items os-as-rows os-selectable-items">
722 <?php foreach ( $services as $service ) {
723 // if service is preselected - only output that service, skip the rest
724 if ( $preselected_service && $service->id != $preselected_service->id ) {
725 continue;
726 }
727 $service_durations = $service->get_all_durations_arr();
728 $is_priced = ( ! ( count( $service_durations ) > 1 ) && $service->charge_amount ) ? true : false;
729 ?>
730 <div class="os-animated-child os-item os-selectable-item <?php echo ( $preselected_service && $service->id == $preselected_service->id ) ? 'selected is-preselected' : ''; ?> <?php echo ( $is_priced ) ? 'os-priced-item' : ''; ?> <?php if ( $service->short_description ) {
731 echo 'with-description'; } ?>"
732 tabindex="0"
733 data-item-price="<?php echo esc_attr( $service->charge_amount ); ?>"
734 data-priced-item-type="service"
735 data-summary-field-name="service"
736 data-summary-value="<?php echo esc_attr( $service->name ); ?>"
737 data-item-id="<?php echo esc_attr( $service->id ); ?>"
738 data-cart-item-item-data-key="service_id"
739 data-os-call-func="latepoint_service_selected"
740 data-id-holder=".latepoint_service_id">
741 <div class="os-service-selector os-item-i os-animated-self" data-service-id="<?php echo esc_attr( $service->id ); ?>">
742 <?php if ( $service->selection_image_id ) { ?>
743 <span class="os-item-img-w" style="background-image: url(<?php echo esc_url( $service->selection_image_url ); ?>);"></span>
744 <?php } ?>
745 <span class="os-item-name-w">
746 <span class="os-item-name"><?php echo esc_html( $service->name ); ?></span>
747 <?php if ( $service->short_description ) { ?>
748 <span class="os-item-desc"><?php echo wp_kses_post( $service->short_description ); ?></span>
749 <?php } ?>
750 </span>
751 <?php if ( $service->price_min > 0 ) { ?>
752 <span class="os-item-price-w">
753 <span class="os-item-price">
754 <?php
755 /**
756 * Filters the display price value shown on the service tile on a booking form
757 *
758 * @since 5.1.94
759 * @hook latepoint_booking_form_display_service_price
760 *
761 * @param {string} $price displayed price that will be outputted
762 * @param {OsServiceModel} $service Service that the price is displayed for
763 *
764 * @returns {string} Filtered displayed price
765 */
766 $display_price = apply_filters( 'latepoint_booking_form_display_service_price', $service->price_min_formatted, $service );
767 echo esc_html( $display_price ) ?>
768 </span>
769 <?php if ( $service->price_min != $service->price_max ) { ?>
770 <span class="os-item-price-label"><?php esc_html_e( 'Starts From', 'latepoint' ); ?></span>
771 <?php } ?>
772 </span>
773 <?php } ?>
774 </div>
775 </div>
776 <?php } ?>
777 </div>
778 <?php }
779 }
780
781 public static function generate_services_bundles_and_categories_list( $parent_id = false, array $settings = [] ) {
782 $default_settings = [
783 'show_service_categories_arr' => false,
784 'show_services_arr' => false,
785 'preselected_service' => false,
786 'preselected_category' => false,
787 ];
788 $settings = array_merge( $default_settings, $settings );
789
790 if ( $settings['preselected_service'] ) {
791 OsBookingHelper::generate_services_list( [ $settings['preselected_service'] ], $settings['preselected_service'] );
792
793 return;
794 }
795
796 $service_categories = new OsServiceCategoryModel();
797 $args = array();
798 if ( $settings['show_service_categories_arr'] && is_array( $settings['show_service_categories_arr'] ) ) {
799 if ( $parent_id ) {
800 $service_categories->where( [ 'parent_id' => $parent_id ] );
801 } else {
802 if ( $settings['preselected_category'] ) {
803 $service_categories->where( [ 'id' => $settings['preselected_category'] ] );
804 } else {
805 $service_categories->where_in( 'id', $settings['show_service_categories_arr'] );
806 $service_categories->where(
807 [
808 'parent_id' => [
809 'OR' => [
810 'IS NULL',
811 ' NOT IN' => $settings['show_service_categories_arr'],
812 ],
813 ],
814 ]
815 );
816 }
817 }
818 } else {
819 if ( $settings['preselected_category'] ) {
820 $service_categories->where( [ 'id' => $settings['preselected_category'] ] );
821 } else {
822 $args['parent_id'] = $parent_id ? $parent_id : 'IS NULL';
823 }
824 }
825 $service_categories = $service_categories->where( $args )->order_by( 'order_number asc' )->get_results_as_models();
826
827 $main_parent_class = ( $parent_id ) ? 'os-animated-parent' : 'os-item-categories-main-parent os-animated-parent';
828 if ( ! $settings['preselected_category'] ) {
829 echo '<div class="os-item-categories-holder ' . esc_attr( $main_parent_class ) . '">';
830 }
831
832 // generate services that have no category
833 if ( $parent_id == false && $settings['preselected_category'] == false ) { ?>
834 <?php
835 $services_without_category = new OsServiceModel();
836 if ( $settings['show_services_arr'] ) {
837 $services_without_category->where_in( 'id', $settings['show_services_arr'] );
838 }
839 $services_without_category = $services_without_category->where( [ 'category_id' => 0 ] )->should_be_active()->get_results_as_models();
840 if ( $services_without_category ) {
841 OsBookingHelper::generate_services_list( $services_without_category, false );
842 }
843 }
844
845 if ( is_array( $service_categories ) ) {
846 foreach ( $service_categories as $service_category ) { ?>
847 <?php
848 $services = [];
849 $category_services = $service_category->get_active_services();
850 if ( is_array( $category_services ) ) {
851 // if show selected services restriction is set - filter
852 if ( $settings['show_services_arr'] ) {
853 foreach ( $category_services as $category_service ) {
854 if ( in_array( $category_service->id, $settings['show_services_arr'] ) ) {
855 $services[] = $category_service;
856 }
857 }
858 } else {
859 $services = $category_services;
860 }
861 }
862 $child_categories = new OsServiceCategoryModel();
863 $count_child_categories = $child_categories->where( [ 'parent_id' => $service_category->id ] )->count();
864 // show only if it has either at least one child category or service
865 if ( $count_child_categories || count( $services ) ) {
866 // preselected category, just show contents, not the wrapper
867 if ( $service_category->id == $settings['preselected_category'] ) {
868 OsBookingHelper::generate_services_list( $services, false );
869 OsBookingHelper::generate_services_bundles_and_categories_list( $service_category->id, array_merge( $settings, [ 'preselected_category' => false ] ) );
870 } else { ?>
871 <div class="os-item-category-w os-items os-as-rows os-animated-child"
872 data-id="<?php echo esc_attr( $service_category->id ); ?>">
873 <div class="os-item-category-info-w os-item os-animated-self with-plus">
874 <div class="os-item-category-info os-item-i">
875 <div class="os-item-img-w"
876 style="background-image: url(<?php echo esc_url( $service_category->selection_image_url ); ?>);"></div>
877 <div class="os-item-name-w">
878 <div class="os-item-name"><?php echo esc_html( $service_category->name ); ?></div>
879 <?php if ( ! empty( $service_category->short_description ) ) { ?>
880 <div class="os-item-desc"><?php echo $service_category->short_description; ?></div>
881 <?php } ?>
882 </div>
883 <?php if ( OsSettingsHelper::is_on( 'show_service_categories_count' ) && count( $services ) ) { ?>
884 <div class="os-item-child-count">
885 <span><?php echo count( $services ); ?></span> <?php esc_html_e( 'Services', 'latepoint' ); ?>
886 </div>
887 <?php } ?>
888 </div>
889 </div>
890 <?php OsBookingHelper::generate_services_list( $services, false ); ?>
891 <?php OsBookingHelper::generate_services_bundles_and_categories_list( $service_category->id, array_merge( $settings, [ 'preselected_category' => false ] ) ); ?>
892 </div><?php
893 }
894 }
895 }
896 }
897 if ( ! $settings['preselected_category'] && ! $parent_id ) {
898 OsBookingHelper::generate_bundles_folder();
899 }
900 if ( ! $settings['preselected_category'] ) {
901 echo '</div>';
902 }
903 }
904
905 public static function group_booking_btn_html( $booking_id = false ) {
906 $html = 'data-os-params="' . esc_attr( http_build_query( [ 'booking_id' => $booking_id ] ) ) . '"
907 data-os-action="' . esc_attr( OsRouterHelper::build_route_name( 'bookings', 'grouped_bookings_quick_view' ) ) . '"
908 data-os-output-target="lightbox"
909 data-os-lightbox-classes="width-500"
910 data-os-after-call="latepoint_init_grouped_bookings_form"';
911
912 return $html;
913 }
914
915 public static function quick_booking_btn_html( $booking_id = false, $params = array() ) {
916 $html = '';
917 if ( $booking_id ) {
918 $params['booking_id'] = $booking_id;
919 }
920 $route = OsRouterHelper::build_route_name( 'orders', 'quick_edit' );
921
922 $params_str = http_build_query( $params );
923 $html = 'data-os-params="' . esc_attr( $params_str ) . '"
924 data-os-action="' . esc_attr( $route ) . '"
925 data-os-output-target="side-panel"
926 data-os-after-call="latepoint_init_quick_order_form"';
927
928 return $html;
929 }
930
931
932 /**
933 * @param OsBookingModel $booking
934 *
935 * @return false|mixed
936 *
937 * Search for available location based on booking requirements. Will return false if no available location found.
938 */
939 public static function get_any_location_for_booking_by_rule( OsBookingModel $booking ) {
940 // ANY LOCATION SELECTED
941 // get available locations
942 $connected_ids = OsLocationHelper::get_location_ids_for_service_and_agent( $booking->service_id, $booking->agent_id );
943
944 // If date/time is selected - filter locations who are available at that time
945 if ( $booking->start_date && $booking->start_time ) {
946 $available_location_ids = [];
947 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking );
948 foreach ( $connected_ids as $location_id ) {
949 $booking_request->location_id = $location_id;
950 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
951 $available_location_ids[] = $location_id;
952 }
953 }
954 $connected_ids = array_intersect( $available_location_ids, $connected_ids );
955 }
956
957
958 $locations_model = new OsLocationModel();
959 if ( ! empty( $connected_ids ) ) {
960 $locations_model->where_in( 'id', $connected_ids );
961 $locations = $locations_model->should_be_active()->get_results_as_models();
962 } else {
963 $locations = [];
964 }
965
966 if ( empty( $locations ) ) {
967 return false;
968 }
969
970 $selected_location_id = $connected_ids[ wp_rand( 0, count( $connected_ids ) - 1 ) ];
971 $booking->location_id = $selected_location_id;
972
973 return $selected_location_id;
974 }
975
976 /**
977 * @param OsBookingModel $booking
978 *
979 * @return false|mixed
980 *
981 * Search for available agent based on booking requirements and agent picking preferences. Will return false if no available agent found.
982 */
983 public static function get_any_agent_for_booking_by_rule( OsBookingModel $booking ) {
984 // ANY AGENT SELECTED
985 // get available agents
986 $connected_ids = OsAgentHelper::get_agent_ids_for_service_and_location( $booking->service_id, $booking->location_id );
987
988 // If date/time is selected - filter agents who are available at that time
989 if ( $booking->start_date && $booking->start_time ) {
990 $available_agent_ids = [];
991 $booking_request = \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking );
992 foreach ( $connected_ids as $agent_id ) {
993 $booking_request->agent_id = $agent_id;
994 if ( OsBookingHelper::is_booking_request_available( $booking_request ) ) {
995 $available_agent_ids[] = $agent_id;
996 }
997 }
998 $connected_ids = array_intersect( $available_agent_ids, $connected_ids );
999 }
1000
1001
1002 /**
1003 * Get IDs of agents that are eligible to be assigned a booking that has "ANY" agent pre-selected
1004 *
1005 * @param {array} $connected_ids Array of eligible Agent IDs
1006 * @param {OsBookingModel} $booking Booking that needs agent ID
1007 *
1008 * @returns {array} Filtered array of IDs of eligible agents
1009 * @since 4.7.6
1010 * @hook latepoint_agent_ids_assignable_to_any_agent_booking
1011 *
1012 */
1013 $connected_ids = apply_filters( 'latepoint_agent_ids_assignable_to_any_agent_booking', $connected_ids, $booking );
1014
1015 if ( ! empty( $connected_ids ) ) {
1016 $agents_model = new OsAgentModel();
1017 $agents_model->where_in( 'id', $connected_ids );
1018 $agents = $agents_model->should_be_active()->get_results_as_models();
1019 } else {
1020 $agents = [];
1021 }
1022
1023 if ( empty( $agents ) ) {
1024 return false;
1025 }
1026
1027
1028 $selected_agent_id = false;
1029 $agent_order_rule = OsSettingsHelper::get_any_agent_order();
1030 switch ( $agent_order_rule ) {
1031 case LATEPOINT_ANY_AGENT_ORDER_RANDOM:
1032 $selected_agent_id = $connected_ids[ wp_rand( 0, count( $connected_ids ) - 1 ) ];
1033 break;
1034 case LATEPOINT_ANY_AGENT_ORDER_PRICE_HIGH:
1035 $highest_price = false;
1036 foreach ( $agents as $agent ) {
1037 $booking->agent_id = $agent->id;
1038 $price = OsBookingHelper::calculate_full_amount_for_booking( $booking );
1039 if ( $highest_price === false && $selected_agent_id === false ) {
1040 $highest_price = $price;
1041 $selected_agent_id = $agent->id;
1042 } else {
1043 if ( $highest_price < $price ) {
1044 $highest_price = $price;
1045 $selected_agent_id = $agent->id;
1046 }
1047 }
1048 }
1049 break;
1050 case LATEPOINT_ANY_AGENT_ORDER_PRICE_LOW:
1051 $lowest_price = false;
1052 foreach ( $agents as $agent ) {
1053 $booking->agent_id = $agent->id;
1054 $price = OsBookingHelper::calculate_full_amount_for_booking( $booking );
1055 if ( $lowest_price === false && $selected_agent_id === false ) {
1056 $lowest_price = $price;
1057 $selected_agent_id = $agent->id;
1058 } else {
1059 if ( $lowest_price > $price ) {
1060 $lowest_price = $price;
1061 $selected_agent_id = $agent->id;
1062 }
1063 }
1064 }
1065 break;
1066 case LATEPOINT_ANY_AGENT_ORDER_BUSY_HIGH:
1067 $max_bookings = false;
1068 foreach ( $agents as $agent ) {
1069 $agent_total_bookings = OsBookingHelper::get_total_bookings_for_date( $booking->start_date, [ 'agent_id' => $agent->id ] );
1070 if ( $max_bookings === false && $selected_agent_id === false ) {
1071 $max_bookings = $agent_total_bookings;
1072 $selected_agent_id = $agent->id;
1073 } else {
1074 if ( $max_bookings < $agent_total_bookings ) {
1075 $max_bookings = $agent_total_bookings;
1076 $selected_agent_id = $agent->id;
1077 }
1078 }
1079 }
1080 break;
1081 case LATEPOINT_ANY_AGENT_ORDER_BUSY_LOW:
1082 $min_bookings = false;
1083 foreach ( $agents as $agent ) {
1084 $agent_total_bookings = OsBookingHelper::get_total_bookings_for_date( $booking->start_date, [ 'agent_id' => $agent->id ] );
1085 if ( $min_bookings === false && $selected_agent_id === false ) {
1086 $min_bookings = $agent_total_bookings;
1087 $selected_agent_id = $agent->id;
1088 } else {
1089 if ( $min_bookings > $agent_total_bookings ) {
1090 $min_bookings = $agent_total_bookings;
1091 $selected_agent_id = $agent->id;
1092 }
1093 }
1094 }
1095 break;
1096 }
1097 /**
1098 * Get ID of agent that will be assigned to a booking, depending on order rules, where agent is set to ANY
1099 *
1100 * @param {integer} $selected_agent_id Currently selected agent ID
1101 * @param {OsAgentModel[]} $agents Array of eligible agent models to pick from
1102 * @param {OsBookingModel} $booking Booking that needs agent ID
1103 * @param {string} $agent_order_rule Rule of agent ordering
1104 *
1105 * @returns {integer} ID of the agent that will be assigned to this booking
1106 * @since 4.7.6
1107 * @hook latepoint_get_any_agent_id_for_booking_by_rule
1108 *
1109 */
1110 $selected_agent_id = apply_filters( 'latepoint_get_any_agent_id_for_booking_by_rule', $selected_agent_id, $agents, $booking, $agent_order_rule );
1111 $booking->agent_id = $selected_agent_id;
1112
1113 return $selected_agent_id;
1114 }
1115
1116
1117 public static function get_total_bookings_for_date( $date, $conditions = [], $grouped = false ) {
1118 $args = [ 'start_date' => $date ];
1119 if ( isset( $conditions['agent_id'] ) && $conditions['agent_id'] ) {
1120 $args['agent_id'] = $conditions['agent_id'];
1121 }
1122 if ( isset( $conditions['service_id'] ) && $conditions['service_id'] ) {
1123 $args['service_id'] = $conditions['service_id'];
1124 }
1125 if ( isset( $conditions['location_id'] ) && $conditions['location_id'] ) {
1126 $args['location_id'] = $conditions['location_id'];
1127 }
1128
1129
1130 $bookings = new OsBookingModel();
1131 if ( $grouped ) {
1132 $bookings->group_by( 'start_date, start_time, end_time, service_id, location_id' );
1133 }
1134 $bookings = $bookings->where( $args );
1135
1136 return $bookings->count();
1137 }
1138
1139
1140 /**
1141 *
1142 * Get list of statuses that block timeslot availability
1143 *
1144 * @return array
1145 */
1146 public static function get_timeslot_blocking_statuses(): array {
1147 $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'timeslot_blocking_statuses', '' ) );
1148
1149 /**
1150 * Get list of statuses that block timeslot availability
1151 *
1152 * @param {array} $statuses array of status codes that block timeslot availability
1153 * @returns {array} The filtered array of status codes
1154 *
1155 * @since 4.7.0
1156 * @hook latepoint_get_timeslot_blocking_statuses
1157 *
1158 */
1159 return apply_filters( 'latepoint_get_timeslot_blocking_statuses', $statuses );
1160 }
1161
1162
1163 /**
1164 *
1165 * Get list of statuses that appear on pending page
1166 *
1167 * @return array
1168 */
1169 public static function get_booking_statuses_for_pending_page(): array {
1170 $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'need_action_statuses', '' ) );
1171
1172 /**
1173 * Get list of statuses that appear on pending page
1174 *
1175 * @param {array} $statuses array of status codes that appear on pending page
1176 * @returns {array} The filtered array of status codes
1177 *
1178 * @since 4.7.0
1179 * @hook latepoint_get_booking_statuses_for_pending_page
1180 *
1181 */
1182 return apply_filters( 'latepoint_get_booking_statuses_for_pending_page', $statuses );
1183 }
1184
1185 /**
1186 *
1187 * Get list of statuses that are not cancelled
1188 *
1189 * @return array
1190 */
1191 public static function get_non_cancelled_booking_statuses(): array {
1192 $statuses = self::get_statuses_list();
1193 if ( isset( $statuses[ LATEPOINT_BOOKING_STATUS_CANCELLED ] ) ) {
1194 unset( $statuses[ LATEPOINT_BOOKING_STATUS_CANCELLED ] );
1195 }
1196 $statuses = array_keys( $statuses );
1197
1198 /**
1199 * Get list of statuses that are not cancelled
1200 *
1201 * @param {array} $statuses array of status codes that are not cancelled
1202 * @returns {array} The filtered array of status codes
1203 *
1204 * @since 5.0.5
1205 * @hook get_non_cancelled_booking_statuses
1206 *
1207 */
1208 return apply_filters( 'get_non_cancelled_booking_statuses', $statuses );
1209 }
1210
1211
1212 public static function get_default_booking_status( $service_id = false ) {
1213 if ( $service_id ) {
1214 $service = new OsServiceModel( $service_id );
1215 if ( $service && ! empty( $service->id ) ) {
1216 return $service->get_default_booking_status();
1217 }
1218 }
1219 $default_status = OsSettingsHelper::get_settings_value( 'default_booking_status' );
1220 if ( $default_status ) {
1221 return $default_status;
1222 } else {
1223 return LATEPOINT_BOOKING_STATUS_APPROVED;
1224 }
1225 }
1226
1227
1228 public static function change_booking_status( $booking_id, $new_status ) {
1229 $booking = new OsBookingModel( $booking_id );
1230 if ( ! $booking_id || ! $booking ) {
1231 return false;
1232 }
1233
1234 if ( $new_status == $booking->status ) {
1235 return true;
1236 } else {
1237 $old_booking = clone $booking;
1238 if ( $booking->update_status( $new_status ) ) {
1239 do_action( 'latepoint_booking_updated', $booking, $old_booking );
1240
1241 return true;
1242 } else {
1243 return false;
1244 }
1245 }
1246 }
1247
1248
1249 /**
1250 * @param \LatePoint\Misc\BookingRequest $booking_request
1251 * @param \LatePoint\Misc\BookedPeriod[]
1252 * @param int $capacity
1253 *
1254 * @return bool
1255 */
1256 public static function is_timeframe_in_booked_periods( \LatePoint\Misc\BookingRequest $booking_request, array $booked_periods, OsServiceModel $service ): bool {
1257 if ( empty( $booked_periods ) ) {
1258 return false;
1259 }
1260 $count_existing_attendees = 0;
1261 foreach ( $booked_periods as $period ) {
1262 if ( self::is_period_overlapping( $booking_request->get_start_time_with_buffer(), $booking_request->get_end_time_with_buffer(), $period->start_time_with_buffer(), $period->end_time_with_buffer() ) ) {
1263 // if it's the same service overlapping - count how many times
1264 // TODO maybe add an option to toggle on/off ability to share a timeslot capacity between two different services
1265 if ( $booking_request->service_id == $period->service_id ) {
1266 $count_existing_attendees += $period->total_attendees;
1267 } else {
1268 return true;
1269 }
1270 }
1271 }
1272 if ( $count_existing_attendees > 0 ) {
1273 // if there are attendees, check if they are below minimum need for timeslot to be blocked, if they are - then the slot is considered booked
1274 if ( ( $count_existing_attendees + $booking_request->total_attendees ) <= $service->get_capacity_needed_before_slot_is_blocked() ) {
1275 return false;
1276 } else {
1277 return true;
1278 }
1279 } else {
1280 // no attendees in the overlapping booked periods yet, just check if the requested number of attendees is within the service capacity
1281 if ( $booking_request->total_attendees <= $service->capacity_max ) {
1282 return false;
1283 } else {
1284 return true;
1285 }
1286 }
1287 }
1288
1289
1290 public static function is_period_overlapping( $period_one_start, $period_one_end, $period_two_start, $period_two_end ) {
1291 // https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap/
1292 return ( ( $period_one_start < $period_two_end ) && ( $period_two_start < $period_one_end ) );
1293 }
1294
1295 public static function is_period_inside_another( $period_one_start, $period_one_end, $period_two_start, $period_two_end ) {
1296 return ( ( $period_one_start >= $period_two_start ) && ( $period_one_end <= $period_two_end ) );
1297 }
1298
1299 // args = [agent_id, 'service_id', 'location_id']
1300 public static function get_bookings_for_date( $date, $args = [] ) {
1301 $bookings = new OsBookingModel();
1302 $args['start_date'] = $date;
1303 // if any of these are false or 0 - remove it from arguments list
1304 if ( isset( $args['location_id'] ) && empty( $args['location_id'] ) ) {
1305 unset( $args['location_id'] );
1306 }
1307 if ( isset( $args['agent_id'] ) && empty( $args['agent_id'] ) ) {
1308 unset( $args['agent_id'] );
1309 }
1310 if ( isset( $args['service_id'] ) && empty( $args['service_id'] ) ) {
1311 unset( $args['service_id'] );
1312 }
1313
1314 $bookings->where( $args )->order_by( 'start_time asc, end_time asc, service_id asc' );
1315
1316 return $bookings->get_results_as_models();
1317 }
1318
1319 /**
1320 * @param \LatePoint\Misc\Filter $filter
1321 *
1322 * @return int
1323 */
1324 public static function count_bookings( \LatePoint\Misc\Filter $filter ) {
1325 $bookings = new OsBookingModel();
1326 $query_args = [];
1327 if ( $filter->date_from ) {
1328 $query_args['start_date'] = $filter->date_from;
1329 }
1330 if ( $filter->location_id ) {
1331 $query_args['location_id'] = $filter->location_id;
1332 }
1333 if ( $filter->agent_id ) {
1334 $query_args['agent_id'] = $filter->agent_id;
1335 }
1336 if ( $filter->service_id ) {
1337 $query_args['service_id'] = $filter->service_id;
1338 }
1339
1340 return $bookings->should_not_be_cancelled()->where( $query_args )->count();
1341 }
1342
1343
1344 public static function get_nice_status_name( $status ) {
1345 $statuses_list = OsBookingHelper::get_statuses_list();
1346 if ( $status && isset( $statuses_list[ $status ] ) ) {
1347 return $statuses_list[ $status ];
1348 } else {
1349 return __( 'Undefined Status', 'latepoint' );
1350 }
1351 }
1352
1353
1354 public static function get_statuses_list() {
1355 $statuses = [
1356 LATEPOINT_BOOKING_STATUS_APPROVED => __( 'Approved', 'latepoint' ),
1357 LATEPOINT_BOOKING_STATUS_PENDING => __( 'Pending Approval', 'latepoint' ),
1358 LATEPOINT_BOOKING_STATUS_CANCELLED => __( 'Cancelled', 'latepoint' ),
1359 LATEPOINT_BOOKING_STATUS_NO_SHOW => __( 'No Show', 'latepoint' ),
1360 LATEPOINT_BOOKING_STATUS_COMPLETED => __( 'Completed', 'latepoint' ),
1361 ];
1362 $additional_statuses = array_map( 'trim', explode( ',', OsSettingsHelper::get_settings_value( 'additional_booking_statuses', '' ) ) );
1363 if ( ! empty( $additional_statuses ) ) {
1364 foreach ( $additional_statuses as $status ) {
1365 if ( ! empty( $status ) ) {
1366 $statuses[ str_replace( ' ', '_', strtolower( $status ) ) ] = $status;
1367 }
1368 }
1369 }
1370 $statuses = apply_filters( 'latepoint_booking_statuses', $statuses );
1371
1372 return $statuses;
1373 }
1374
1375
1376 public static function get_weekdays_arr( $full_name = false ) {
1377 if ( $full_name ) {
1378 $weekdays = array(
1379 __( 'Monday', 'latepoint' ),
1380 __( 'Tuesday', 'latepoint' ),
1381 __( 'Wednesday', 'latepoint' ),
1382 __( 'Thursday', 'latepoint' ),
1383 __( 'Friday', 'latepoint' ),
1384 __( 'Saturday', 'latepoint' ),
1385 __( 'Sunday', 'latepoint' ),
1386 );
1387 } else {
1388 $weekdays = array(
1389 __( 'Mon', 'latepoint' ),
1390 __( 'Tue', 'latepoint' ),
1391 __( 'Wed', 'latepoint' ),
1392 __( 'Thu', 'latepoint' ),
1393 __( 'Fri', 'latepoint' ),
1394 __( 'Sat', 'latepoint' ),
1395 __( 'Sun', 'latepoint' ),
1396 );
1397 }
1398
1399 return $weekdays;
1400 }
1401
1402 public static function get_weekday_name_by_number( $weekday_number, $full_name = false ) {
1403 $weekdays = OsBookingHelper::get_weekdays_arr( $full_name );
1404 if ( ! isset( $weekday_number ) || $weekday_number < 1 || $weekday_number > 7 ) {
1405 return '';
1406 } else {
1407 return $weekdays[ $weekday_number - 1 ];
1408 }
1409 }
1410
1411 public static function get_stat( $stat, $args = [] ) {
1412 if ( ! in_array( $stat, [ 'duration', 'price', 'bookings' ] ) ) {
1413 return false;
1414 }
1415 $defaults = [
1416 'customer_id' => false,
1417 'agent_id' => false,
1418 'service_id' => false,
1419 'location_id' => false,
1420 'date_from' => false,
1421 'date_to' => false,
1422 'group_by' => false,
1423 'exclude_status' => false,
1424 ];
1425 $args = array_merge( $defaults, $args );
1426 $bookings = new OsBookingModel();
1427 $query_args = array( $args['date_from'], $args['date_to'] );
1428 switch ( $stat ) {
1429 case 'duration':
1430 $stat_query = 'SUM(end_time - start_time)';
1431 break;
1432 case 'price':
1433 $stat_query = 'sum(total)';
1434 break;
1435 case 'bookings':
1436 $stat_query = 'count(id)';
1437 break;
1438 }
1439 $select_query = $stat_query . ' as stat';
1440 if ( $args['group_by'] ) {
1441 $select_query .= ',' . $args['group_by'];
1442 }
1443 $bookings->select( $select_query );
1444
1445
1446 if ( $args['date_from'] ) {
1447 $bookings->where( [ 'start_date >=' => $args['date_from'] ] );
1448 }
1449 if ( $args['date_to'] ) {
1450 $bookings->where( [ 'start_date <=' => $args['date_to'] ] );
1451 }
1452 if ( $args['service_id'] ) {
1453 $bookings->where( [ 'service_id' => $args['service_id'] ] );
1454 }
1455 if ( $args['agent_id'] ) {
1456 $bookings->where( [ 'agent_id' => $args['agent_id'] ] );
1457 }
1458 if ( $args['location_id'] ) {
1459 $bookings->where( [ 'location_id' => $args['location_id'] ] );
1460 }
1461 if ( $args['customer_id'] ) {
1462 $bookings->where( [ 'customer_id' => $args['customer_id'] ] );
1463 }
1464 if ( $args['group_by'] ) {
1465 $bookings->group_by( $args['group_by'] );
1466 }
1467 // TODO, need to support custom status exclusions
1468 if ( $args['exclude_status'] == LATEPOINT_BOOKING_STATUS_CANCELLED ) {
1469 $bookings->should_not_be_cancelled();
1470 }
1471
1472 $stat_total = $bookings->get_results( ARRAY_A );
1473 if ( $args['group_by'] ) {
1474 return $stat_total;
1475 } else {
1476 return isset( $stat_total[0]['stat'] ) ? $stat_total[0]['stat'] : 0;
1477 }
1478 }
1479
1480 public static function get_new_customer_stat_for_period( DateTime $date_from, DateTime $date_to, \LatePoint\Misc\Filter $filter ) {
1481 // TODO make sure filter is respected
1482 $customers = new OsCustomerModel();
1483
1484 return $customers->filter_allowed_records()->where(
1485 [
1486 'created_at >=' => $date_from->format( 'Y-m-d' ),
1487 'created_at <=' => $date_to->format( 'Y-m-d' ),
1488 ]
1489 )->count();
1490 }
1491
1492 public static function get_stat_for_period( $stat, $date_from, $date_to, \LatePoint\Misc\Filter $filter, $group_by = false ) {
1493 if ( ! in_array( $stat, [ 'duration', 'price', 'bookings' ] ) ) {
1494 return false;
1495 }
1496 if ( ! in_array( $group_by, [ false, 'agent_id', 'service_id', 'location_id' ] ) ) {
1497 return false;
1498 }
1499 $bookings = new OsBookingModel();
1500 switch ( $stat ) {
1501 case 'duration':
1502 $stat_query = 'SUM(end_time - start_time)';
1503 break;
1504 case 'price':
1505 $stat_query = 'sum(' . LATEPOINT_TABLE_ORDER_ITEMS . '.subtotal)';
1506 $bookings->join( LATEPOINT_TABLE_ORDER_ITEMS, [ 'id' => $bookings->table_name . '.order_item_id' ] );
1507 $bookings->join( LATEPOINT_TABLE_ORDERS, [ 'id' => LATEPOINT_TABLE_ORDER_ITEMS . '.order_id' ] );
1508 break;
1509 case 'bookings':
1510 $stat_query = 'count(id)';
1511 break;
1512 }
1513 $select_query = $stat_query . ' as stat';
1514 if ( $group_by ) {
1515 $select_query .= ',' . $group_by;
1516 }
1517 $bookings->select( $select_query )->where(
1518 [
1519 'start_date >=' => $date_from,
1520 'start_date <= ' => $date_to,
1521 ]
1522 );
1523
1524 if ( $filter->service_id ) {
1525 $bookings->where( [ 'service_id' => $filter->service_id ] );
1526 }
1527 if ( $filter->agent_id ) {
1528 $bookings->where( [ 'agent_id' => $filter->agent_id ] );
1529 }
1530 if ( $filter->location_id ) {
1531 $bookings->where( [ 'location_id' => $filter->location_id ] );
1532 }
1533
1534 $bookings->should_not_be_cancelled();
1535
1536 if ( $group_by ) {
1537 $bookings->group_by( $group_by );
1538 }
1539
1540 $stat_total = $bookings->get_results( ARRAY_A );
1541 if ( $group_by ) {
1542 return $stat_total;
1543 } else {
1544 return isset( $stat_total[0]['stat'] ) ? $stat_total[0]['stat'] : 0;
1545 }
1546 }
1547
1548 public static function get_total_bookings_per_day_for_period( $date_from, $date_to, \LatePoint\Misc\Filter $filter ) {
1549 $bookings = new OsBookingModel();
1550 $bookings->select( 'count(id) as bookings_per_day, start_date' )
1551 ->where(
1552 [
1553 'start_date >=' => $date_from,
1554 'start_date <=' => $date_to,
1555 ]
1556 )
1557 ->where( [ 'status NOT IN' => OsCalendarHelper::get_booking_statuses_hidden_from_calendar() ] );
1558 if ( $filter->service_id ) {
1559 $bookings->where( [ 'service_id' => $filter->service_id ] );
1560 }
1561 if ( $filter->agent_id ) {
1562 $bookings->where( [ 'agent_id' => $filter->agent_id ] );
1563 }
1564 if ( $filter->location_id ) {
1565 $bookings->where( [ 'location_id' => $filter->location_id ] );
1566 }
1567 $bookings->group_by( 'start_date' );
1568
1569 return $bookings->get_results();
1570 }
1571
1572
1573 public static function get_min_max_work_periods( $specific_weekdays = false, $service_id = false, $agent_id = false ) {
1574 $select_string = 'MIN(start_time) as start_time, MAX(end_time) as end_time';
1575 $work_periods = new OsWorkPeriodModel();
1576 $work_periods = $work_periods->select( $select_string );
1577 $query_args = array(
1578 'service_id' => 0,
1579 'agent_id' => 0,
1580 );
1581 if ( $service_id ) {
1582 $query_args['service_id'] = $service_id;
1583 }
1584 if ( $agent_id ) {
1585 $query_args['agent_id'] = $agent_id;
1586 }
1587 if ( $specific_weekdays && ! empty( $specific_weekdays ) ) {
1588 $query_args['week_day'] = $specific_weekdays;
1589 }
1590 $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A );
1591 if ( ( $service_id || $agent_id ) && empty( $results['min_start_time'] ) ) {
1592 if ( $service_id && empty( $results['min_start_time'] ) ) {
1593 $query_args['service_id'] = 0;
1594 $work_periods = new OsWorkPeriodModel();
1595 $work_periods = $work_periods->select( $select_string );
1596 $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A );
1597 }
1598 if ( $agent_id && empty( $results['min_start_time'] ) ) {
1599 $query_args['agent_id'] = 0;
1600 $work_periods = new OsWorkPeriodModel();
1601 $work_periods = $work_periods->select( $select_string );
1602 $results = $work_periods->set_limit( 1 )->where( $query_args )->get_results( ARRAY_A );
1603 }
1604 }
1605 if ( $results ) {
1606 return array( $results['start_time'], $results['end_time'] );
1607 } else {
1608 return false;
1609 }
1610 }
1611
1612
1613 public static function get_work_start_end_time_for_multiple_dates( $dates = false, $service_id = false, $agent_id = false ) {
1614 $specific_weekdays = array();
1615 if ( $dates ) {
1616 foreach ( $dates as $date ) {
1617 $target_date = new OsWpDateTime( $date );
1618 $weekday = $target_date->format( 'N' );
1619 if ( ! in_array( $weekday, $specific_weekdays ) ) {
1620 $specific_weekdays[] = $weekday;
1621 }
1622 }
1623 }
1624 $work_minmax_start_end = self::get_min_max_work_periods( $specific_weekdays, $service_id, $agent_id );
1625
1626 return $work_minmax_start_end;
1627 }
1628
1629 /**
1630 * @param int $minute
1631 * @param \LatePoint\Misc\WorkPeriod[] $work_periods_arr
1632 *
1633 * @return bool
1634 */
1635 public static function is_minute_in_work_periods( int $minute, array $work_periods_arr ): bool {
1636 // print_r($work_periods_arr);
1637 if ( empty( $work_periods_arr ) ) {
1638 return false;
1639 }
1640 foreach ( $work_periods_arr as $work_period ) {
1641 // end of period does not count because we cant make appointment with 0 duration
1642 if ( $work_period->start_time <= $minute && $work_period->end_time > $minute ) {
1643 return true;
1644 }
1645 }
1646
1647 return false;
1648 }
1649
1650 public static function get_calendar_start_end_time( $bookings, $work_start_minutes, $work_end_minutes ) {
1651 $calendar_start_minutes = $work_start_minutes;
1652 $calendar_end_minutes = $work_end_minutes;
1653 if ( $bookings ) {
1654 foreach ( $bookings as $bookings_for_agent ) {
1655 if ( $bookings_for_agent ) {
1656 foreach ( $bookings_for_agent as $booking ) {
1657 if ( $booking->start_time < $calendar_start_minutes ) {
1658 $calendar_start_minutes = $booking->start_time;
1659 }
1660 if ( $booking->end_time > $calendar_end_minutes ) {
1661 $calendar_end_minutes = $booking->end_time;
1662 }
1663 }
1664 }
1665 }
1666 }
1667
1668 return [ $calendar_start_minutes, $calendar_end_minutes ];
1669 }
1670
1671 public static function generate_direct_manage_booking_url( OsBookingModel $booking, string $for ): string {
1672 if ( ! in_array( $for, [ 'agent', 'customer' ] ) ) {
1673 return '';
1674 }
1675 $key = $booking->get_key_to_manage_for( $for );
1676 $url = OsRouterHelper::build_admin_post_link( [ 'manage_booking_by_key', 'show' ], [ 'key' => $key ] );
1677
1678 return $url;
1679 }
1680
1681 public static function generate_summary_actions_for_booking( OsBookingModel $booking, ?string $key = null ) {
1682 ?>
1683 <div class="booking-full-summary-actions">
1684 <div class="add-to-calendar-wrapper">
1685 <a href="#" class="open-calendar-types booking-summary-action-btn"><i class="latepoint-icon latepoint-icon-calendar"></i><span><?php esc_html_e( 'Add to Calendar', 'latepoint' ); ?></span></a>
1686 <?php echo OsBookingHelper::generate_add_to_calendar_links( $booking, $key ?? $booking->get_key_to_manage_for( 'customer' ) ); ?>
1687 </div>
1688 <a href="<?php echo esc_url( $booking->get_print_link( $key ?? $booking->get_key_to_manage_for( 'customer' ) ) ); ?>" class="print-booking-btn booking-summary-action-btn" target="_blank"><i class="latepoint-icon latepoint-icon-printer"></i><span><?php esc_html_e( 'Print', 'latepoint' ); ?></span></a>
1689 <?php
1690 if ( $booking->is_upcoming() ) {
1691 if ( OsCustomerHelper::can_reschedule_booking( $booking ) ) { ?>
1692 <a href="#" class="latepoint-request-booking-reschedule booking-summary-action-btn" data-os-after-call="latepoint_init_reschedule" data-os-lightbox-classes="width-450 reschedule-calendar-wrapper" data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'manage_booking_by_key', 'request_reschedule_calendar' ) ); ?>" data-os-params="<?php echo esc_attr( OsUtilHelper::build_os_params( [ 'key' => $key ?? $booking->get_key_to_manage_for( 'customer' ) ] ) ); ?>" data-os-output-target="lightbox">
1693 <i class="latepoint-icon latepoint-icon-calendar"></i>
1694 <span><?php esc_html_e( 'Reschedule', 'latepoint' ); ?></span>
1695 </a>
1696 <?php
1697 }
1698 if ( OsCustomerHelper::can_cancel_booking( $booking ) ) { ?>
1699 <a href="#" class="booking-summary-action-btn cancel-appointment-btn"
1700 data-os-prompt="<?php esc_attr_e( 'Are you sure you want to cancel this appointment?', 'latepoint' ); ?>"
1701 data-os-success-action="reload"
1702 data-os-action="<?php echo esc_attr( OsRouterHelper::build_route_name( 'manage_booking_by_key', 'request_cancellation' ) ); ?>"
1703 data-os-params="<?php echo esc_attr( OsUtilHelper::build_os_params( [ 'key' => $key ?? $booking->get_key_to_manage_for( 'customer' ) ], 'cancel_booking_' . $booking->id ) ); ?>">
1704 <i class="latepoint-icon latepoint-icon-ui-24"></i>
1705 <span><?php esc_html_e( 'Cancel', 'latepoint' ); ?></span>
1706 </a>
1707 <?php
1708 }
1709 }
1710 do_action( 'latepoint_booking_summary_after_booking_actions', $booking );
1711 ?>
1712 </div>
1713 <?php
1714 }
1715
1716 public static function generate_summary_for_booking( OsBookingModel $booking, $cart_item_id = false, ?string $viewer = 'customer' ): string {
1717 $summary_html = '';
1718 $summary_html .= apply_filters( 'latepoint_booking_summary_before_summary_box', '', $booking );
1719 $summary_html .= '<div class="summary-box main-box" ' . ( ( $cart_item_id ) ? 'data-cart-item-id="' . $cart_item_id . '"' : '' ) . '>';
1720 $output_timezone_name = $viewer == 'customer' ? $booking->get_customer_timezone_name() : OsTimeHelper::get_wp_timezone_name();
1721 if ( ! empty( $booking->start_datetime_utc ) ) {
1722 $summary_html .= '<div class="summary-box-booking-date-box">';
1723 $summary_html .= '<div class="summary-box-booking-date-day">' . $booking->start_datetime_in_format( 'j', $output_timezone_name ) . '</div>';
1724 $summary_html .= '<div class="summary-box-booking-date-month">' . OsUtilHelper::get_month_name_by_number( $booking->start_datetime_in_format( 'n', $output_timezone_name ), true ) . '</div>';
1725 $summary_html .= '</div>';
1726 }
1727 $summary_html .= '<div class="summary-box-inner">';
1728 $service_headings = [];
1729 $service_headings = apply_filters( 'latepoint_booking_summary_service_headings', $service_headings, $booking );
1730 if ( $service_headings ) {
1731 $summary_html .= '<div class="summary-box-heading">';
1732 foreach ( $service_headings as $heading ) {
1733 $summary_html .= '<div class="sbh-item">' . $heading . '</div>';
1734 }
1735 $summary_html .= '<div class="sbh-line"></div>';
1736 $summary_html .= '</div>';
1737 }
1738 $summary_html .= '<div class="summary-box-content os-cart-item">';
1739 if ( $cart_item_id && OsCartsHelper::can_checkout_multiple_items() ) {
1740 $summary_html .= '<div class="os-remove-item-from-cart" role="button" tabindex="0" data-confirm-text="' . __( 'Are you sure you want to remove this item from your cart?', 'latepoint' ) . '" data-cart-item-id="' . $cart_item_id . '" data-route="' . OsRouterHelper::build_route_name( 'carts', 'remove_item_from_cart' ) . '">
1741 <div class="os-remove-from-cart-icon"></div>
1742 </div>';
1743 }
1744 $summary_html .= '<div class="sbc-big-item">' . $booking->get_service_name_for_summary() . '</div>';
1745 if ( $booking->start_date ) {
1746 $summary_html .= '<div class="sbc-highlighted-item">' . $booking->get_nice_datetime_for_summary( $viewer ) . '</div>';
1747 }
1748 /**
1749 * Output summary of the booking data after a start date and time
1750 *
1751 * @since 5.2.0
1752 * @hook latepoint_summary_booking_info_after_start_date
1753 *
1754 * @param {string} $summary_html HTML of the summary
1755 * @param {OsBookingModel} $booking Booking object that is being outputted
1756 * @param {string} $cart_item_id ID of a cart item this booking belongs to
1757 * @param {string} $viewer determines who is viewing this summary, can be customer or agent
1758 *
1759 * @returns {string} Filtered HTML
1760 */
1761 $summary_html = apply_filters( 'latepoint_summary_booking_info_after_start_date', $summary_html, $booking, $cart_item_id, $viewer );
1762 $summary_html .= '</div>';
1763
1764 $service_attributes = [];
1765 $service_attributes = apply_filters( 'latepoint_booking_summary_service_attributes', $service_attributes, $booking );
1766 if ( $service_attributes ) {
1767 $summary_html .= '<div class="summary-attributes sa-clean">';
1768 foreach ( $service_attributes as $attribute ) {
1769 $summary_html .= '<span>' . $attribute['label'] . ': <strong>' . $attribute['value'] . '</strong></span>';
1770 }
1771 $summary_html .= '</div>';
1772 }
1773 $summary_html .= '</div>';
1774 $summary_html .= apply_filters( 'latepoint_booking_summary_after_summary_box_inner', '', $booking );
1775 $summary_html .= '</div>';
1776 $summary_html .= apply_filters( 'latepoint_booking_summary_after_summary_box', '', $booking );
1777
1778 return $summary_html;
1779 }
1780
1781
1782 /**
1783 * @param OsBookingModel[] $bookings
1784 *
1785 * @return bool
1786 */
1787 public static function bookings_have_same_agent( array $bookings ): bool {
1788 return ( count( array_unique( array_column( $bookings, 'agent_id' ) ) ) == 1 );
1789 }
1790
1791 /**
1792 * @param OsBookingModel[] $bookings
1793 *
1794 * @return bool
1795 */
1796 public static function bookings_have_same_location( array $bookings ): bool {
1797 return ( count( array_unique( array_column( $bookings, 'location_id' ) ) ) == 1 );
1798 }
1799
1800 /**
1801 * @param OsBookingModel[] $bookings
1802 *
1803 * @return bool
1804 */
1805 public static function bookings_have_same_service( array $bookings ): bool {
1806 return ( count( array_unique( array_column( $bookings, 'service_id' ) ) ) == 1 );
1807 }
1808
1809 public static function prepare_new_from_params( array $params ): OsBookingModel {
1810 $booking = new OsBookingModel();
1811
1812 $services = OsServiceHelper::get_allowed_active_services();
1813 $agents = OsAgentHelper::get_allowed_active_agents();
1814
1815 // LOAD FROM PASSED PARAMS
1816 $booking->order_item_id = $params['order_item_id'] ?? '';
1817 $booking->service_id = ! empty( $params['service_id'] ) ? OsUtilHelper::first_value_if_array( $params['service_id'] ) : '';
1818 if ( empty( $booking->service_id ) && ! empty( $services ) ) {
1819 $booking->service_id = $services[0]->id;
1820 }
1821
1822 $booking->agent_id = ! empty( $params['agent_id'] ) ? OsUtilHelper::first_value_if_array( $params['agent_id'] ) : '';
1823 if ( empty( $booking->agent_id ) && ! empty( $agents ) ) {
1824 $booking->agent_id = $agents[0]->id;
1825 }
1826
1827 if ( ! empty( $params['order_id'] ) ) {
1828 $order = new OsOrderModel( $params['order_id'] );
1829 $booking->customer_id = $order->customer_id;
1830 } else {
1831 $booking->customer_id = ! empty( $params['customer_id'] ) ? OsUtilHelper::first_value_if_array( $params['customer_id'] ) : '';
1832 }
1833
1834 $booking->location_id = ! empty( $params['location_id'] ) ? OsUtilHelper::first_value_if_array( $params['location_id'] ) : OsLocationHelper::get_default_location_id( true );
1835 $booking->start_date = $params['start_date'] ?? OsTimeHelper::today_date( 'Y-m-d' );
1836 $booking->start_time = $params['start_time'] ?? 600;
1837
1838 $booking->end_time = ( $booking->service_id ) ? $booking->calculate_end_time() : $booking->start_time + 60;
1839 $booking->end_date = $booking->calculate_end_date();
1840 $booking->buffer_before = ( $booking->service_id ) ? $booking->service->buffer_before : 0;
1841 $booking->buffer_after = ( $booking->service_id ) ? $booking->service->buffer_after : 0;
1842 $booking->status = LATEPOINT_BOOKING_STATUS_APPROVED;
1843
1844 return $booking;
1845 }
1846 }
1847