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