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