PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.6.10
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.6.10
5.6.10 5.6.9 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 / calendar_helper.php
latepoint / lib / helpers Last commit date
activities_helper.php 5 months ago admin_nudges_helper.php 3 weeks ago agent_helper.php 5 months ago analytics_helper.php 3 weeks ago auth_helper.php 1 month ago blocks_helper.php 1 month ago booking_helper.php 1 month ago bricks_helper.php 5 months ago bundles_helper.php 1 month ago calendar_helper.php 3 weeks ago carts_helper.php 5 months ago connector_helper.php 5 months ago csv_helper.php 5 months ago customer_helper.php 2 months ago customer_import_helper.php 2 months ago database_helper.php 1 month ago debug_helper.php 5 months ago defaults_helper.php 5 months ago elementor_helper.php 5 months ago email_helper.php 5 months ago encrypt_helper.php 5 months ago events_helper.php 4 months ago form_helper.php 5 months ago icalendar_helper.php 5 months ago image_helper.php 5 months ago invoices_helper.php 1 month ago license_helper.php 5 months ago location_helper.php 5 months ago marketing_systems_helper.php 5 months ago meeting_systems_helper.php 5 months ago menu_helper.php 1 month ago meta_helper.php 5 months ago migrations_helper.php 5 months ago money_helper.php 4 months ago notifications_helper.php 5 months ago nps_survey_helper.php 5 months ago order_intent_helper.php 3 months ago orders_helper.php 1 month ago otp_helper.php 4 months ago pages_helper.php 5 months ago params_helper.php 5 months ago payments_helper.php 1 month ago paypal_connect_helper.php 3 weeks ago plugin_version_update_helper.php 3 months ago price_breakdown_helper.php 5 months ago process_jobs_helper.php 3 weeks ago processes_helper.php 5 months ago razorpay_connect_helper.php 3 weeks ago replacer_helper.php 5 months ago resource_helper.php 1 month ago roles_helper.php 3 weeks ago router_helper.php 5 months ago service_helper.php 5 months ago sessions_helper.php 5 months ago settings_helper.php 1 month ago short_links_systems_helper.php 5 months ago shortcodes_helper.php 1 month ago sms_helper.php 5 months ago steps_helper.php 1 week ago stripe_connect_helper.php 1 month ago styles_helper.php 5 months ago support_topics_helper.php 5 months ago time_helper.php 4 months ago timeline_helper.php 3 months ago transaction_helper.php 2 months ago transaction_intent_helper.php 5 months ago util_helper.php 3 weeks ago version_specific_updates_helper.php 1 month ago whatsapp_helper.php 2 months ago work_periods_helper.php 4 months ago wp_datetime.php 5 months ago wp_user_helper.php 5 months ago
calendar_helper.php
618 lines
1 <?php
2 /*
3 * Copyright (c) 2022 LatePoint LLC. All rights reserved.
4 */
5
6
7 class OsCalendarHelper {
8
9 public static function generate_dates_and_times_picker( OsBookingModel $booking, OsWpDateTime $target_date, $auto_search = false, array $calendar_settings = [] ): string {
10 $is_recurring_supported = apply_filters( 'latepoint_is_feature_recurring_bookings_on', false );
11 $can_service_be_recurring = $is_recurring_supported && ( $booking->service->get_meta_by_key( 'allow_recurring_bookings' ) == 'on' );
12 ob_start(); ?>
13 <div class="os-dates-and-times-w <?php echo $auto_search ? 'auto-search is-searching' : ''; ?> calendar-style-<?php echo OsStepsHelper::get_calendar_style(); ?>" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'steps', 'load_datepicker_month' ) ); ?>" data-allow-recurring="<?php echo $can_service_be_recurring ? 'yes' : 'no'; ?>">
14 <div class="os-dates-w" data-time-pick-style="<?php echo esc_attr( OsStepsHelper::get_time_pick_style() ); ?>">
15 <?php if ( $auto_search ) { ?>
16 <div class="os-calendar-searching-info"><?php echo sprintf( esc_html__( 'Searching %s for available dates', 'latepoint' ), '<span></span>' ); ?></div>
17 <?php } ?>
18 <?php $no_availability_message = apply_filters( 'latepoint_no_availability_message', __( 'No appointments are currently available. Please check back later or contact us for assistance.', 'latepoint' ) );
19 ?>
20 <div class="os-no-availability-message latepoint-message latepoint-message-info" style="display: none;"><?php echo esc_html( $no_availability_message ); ?></div>
21 <div class="os-calendar-while-searching-wrapper">
22 <?php OsCalendarHelper::generate_calendar_for_datepicker_step( \LatePoint\Misc\BookingRequest::create_from_booking_model( $booking ), $target_date, $calendar_settings ); ?>
23 </div>
24 </div>
25
26 <div class="time-selector-w <?php echo OsStepsHelper::hide_unavailable_slots() ? 'hide-not-available-slots' : ''; ?> <?php echo 'time-system-' . esc_attr( OsTimeHelper::get_time_system() ); ?> <?php echo ( OsSettingsHelper::is_on( 'show_booking_end_time' ) ) ? 'with-end-time' : 'without-end-time'; ?> style-<?php echo esc_attr( OsStepsHelper::get_time_pick_style() ); ?>">
27 <div class="times-header">
28 <div class="th-line"></div>
29 <div class="times-header-label">
30 <?php esc_html_e( 'Pick a slot for', 'latepoint' ); ?> <span></span>
31 <?php do_action( 'latepoint_step_datepicker_appointment_time_header_label', $booking, $calendar_settings ); ?>
32 </div>
33 <div class="th-line"></div>
34 </div>
35 <div class="os-times-w">
36 <div class="timeslots"></div>
37 </div>
38 </div>
39 <?php do_action( 'latepoint_dates_and_times_picker_after', $booking, $target_date, $calendar_settings ); ?>
40 </div>
41 <?php
42 $html = ob_get_clean();
43 return $html;
44 }
45
46 /**
47 * Get list of statuses which should not appear on calendar
48 *
49 * @return array
50 */
51 public static function get_booking_statuses_hidden_from_calendar(): array {
52 $statuses = explode( ',', OsSettingsHelper::get_settings_value( 'calendar_hidden_statuses', '' ) );
53
54 /**
55 * Get list of statuses which bookings should not appear on calendar
56 *
57 * @param {array} $statuses array of status codes that will be hidden from calendar
58 * @returns {array} The filtered array of status codes
59 *
60 * @since 4.7.0
61 * @hook latepoint_get_booking_statuses_hidden_from_calendar
62 *
63 */
64 return apply_filters( 'latepoint_get_booking_statuses_hidden_from_calendar', $statuses );
65 }
66
67
68 /**
69 * Returns an array of booking status codes to be displayed on calendar
70 *
71 * @return {array} The array of statuses
72 */
73 public static function get_booking_statuses_to_display_on_calendar(): array {
74 $hidden_statuses = self::get_booking_statuses_hidden_from_calendar();
75 $all_statuses = OsBookingHelper::get_statuses_list();
76 $eligible_statuses = [];
77 foreach ( $all_statuses as $status_code => $status_label ) {
78 if ( ! in_array( $status_code, $hidden_statuses ) ) {
79 $eligible_statuses[] = $status_code;
80 }
81 }
82
83 /**
84 * Returns an array of booking status codes to be displayed on calendar
85 *
86 * @param {array} array of statuses
87 *
88 * @returns {array} The array of statuses
89 *
90 * @since 4.7.0
91 * @hook latepoint_get_booking_statuses_to_display_on_calendar
92 *
93 */
94 return apply_filters( 'latepoint_get_booking_statuses_to_display_on_calendar', $eligible_statuses );
95 }
96
97 public static function is_external_calendar_enabled( string $external_calendar_code ): bool {
98 return OsSettingsHelper::is_on( 'enable_' . $external_calendar_code );
99 }
100
101 public static function get_list_of_external_calendars( $enabled_only = false ) {
102 $external_calendars = [];
103
104 /**
105 * Returns an array of external calendars
106 *
107 * @param {array} array of calendars
108 * @param {bool} filter to return only calendars that are enabled
109 *
110 * @returns {array} The array of external calendars
111 *
112 * @since 4.7.0
113 * @hook latepoint_list_of_external_calendars
114 *
115 */
116 return apply_filters( 'latepoint_list_of_external_calendars', $external_calendars, $enabled_only );
117 }
118
119
120 /**
121 * @param \LatePoint\Misc\BookingRequest $booking_request
122 * @param DateTime $target_date
123 * @param array $settings
124 *
125 * @return void
126 */
127 public static function generate_calendar_for_datepicker_step( \LatePoint\Misc\BookingRequest $booking_request, DateTime $target_date, array $settings = [] ) {
128 $defaults = [
129 'exclude_booking_ids' => [],
130 'number_of_months_to_preload' => 1,
131 'timezone_name' => false,
132 'layout' => 'classic',
133 'highlight_target_date' => false,
134 'consider_cart_items' => false,
135 'output_target_date_in_header' => false,
136 ];
137
138 $settings = OsUtilHelper::merge_default_atts( $defaults, $settings );
139
140 $weekdays = OsBookingHelper::get_weekdays_arr();
141 $today_date = new OsWpDateTime( 'today' );
142
143
144 ?>
145 <div class="os-current-month-label-w calendar-mobile-controls">
146 <div class="os-current-month-label">
147 <div class="current-month">
148 <?php if ( $settings['highlight_target_date'] && $settings['output_target_date_in_header'] ) {
149 echo esc_html( OsTimeHelper::get_nice_date_with_optional_year( $target_date->format( 'Y-m-d' ), false ) );
150 } else {
151 echo esc_html( OsUtilHelper::get_month_name_by_number( $target_date->format( 'n' ) ) );
152 } ?>
153 </div>
154 <div class="current-year"><?php echo esc_html( $target_date->format( 'Y' ) ); ?></div>
155 </div>
156 <div class="os-month-control-buttons-w">
157 <button type="button" class="os-month-prev-btn" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'steps', 'load_datepicker_month' ) ); ?>">
158 <i class="latepoint-icon latepoint-icon-arrow-left"></i></button>
159 <?php if ( $settings['layout'] == 'horizontal' ) {
160 echo '<button class="latepoint-btn latepoint-btn-outline os-month-today-btn" data-year="' . esc_attr( $today_date->format( 'Y' ) ) . '" data-month="' . esc_attr( $today_date->format( 'n' ) ) . '" data-date="' . esc_attr( $today_date->format( 'Y-m-d' ) ) . '">' . esc_html__( 'Today', 'latepoint' ) . '</button>';
161 } ?>
162 <button type="button" class="os-month-next-btn" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'steps', 'load_datepicker_month' ) ); ?>">
163 <i class="latepoint-icon latepoint-icon-arrow-right"></i></button>
164 </div>
165 </div>
166 <?php if ( $settings['layout'] == 'classic' ) { ?>
167 <div class="os-weekdays">
168 <?php
169 $start_of_week = OsSettingsHelper::get_start_of_week();
170
171 // Output the divs for each weekday
172 for ( $i = $start_of_week - 1; $i < $start_of_week - 1 + 7; $i++ ) {
173 // Calculate the index within the range of 0-6
174 $index = $i % 7;
175
176 // Output the div for the current weekday
177 echo '<div class="weekday weekday-' . esc_attr( $index + 1 ) . '">' . esc_html( OsUtilHelper::get_first_chars( $weekdays[ $index ], 1 ) ) . '</div>';
178 }
179 ?>
180 </div>
181 <?php } ?>
182 <div class="os-months">
183 <?php
184 $month_settings = [
185 'active' => true,
186 'timezone_name' => $settings['timezone_name'],
187 'highlight_target_date' => $settings['highlight_target_date'],
188 'exclude_booking_ids' => $settings['exclude_booking_ids'],
189 'consider_cart_items' => $settings['consider_cart_items'],
190 ];
191
192 // if it's not from admin - blackout dates that are not available to select due to date restrictions in settings
193 $month_settings['earliest_possible_booking'] = OsSettingsHelper::get_earliest_possible_booking_restriction( $booking_request->service_id );
194 $month_settings['latest_possible_booking'] = OsSettingsHelper::get_latest_possible_booking_restriction( $booking_request->service_id );
195
196 OsCalendarHelper::generate_single_month( $booking_request, $target_date, $month_settings );
197 for ( $i = 1; $i <= $settings['number_of_months_to_preload']; $i++ ) {
198 $next_month_target_date = clone $target_date;
199 $next_month_target_date->modify( 'first day of next month' );
200 $month_settings['active'] = false;
201 $month_settings['highlight_target_date'] = false;
202 OsCalendarHelper::generate_single_month( $booking_request, $next_month_target_date, $month_settings );
203 }
204 ?>
205 </div><?php
206 /**
207 * Fired after a datepicker calendar months are generated
208 *
209 * @param {BookingRequest} $booking_request instance of a booking request
210 * @param {DateTime} $target_date target date that is being loaded
211 * @param {array} $settings array of settings for the calendar
212 *
213 * @since 5.1.7
214 * @hook latepoint_after_datepicker_months
215 *
216 */
217 do_action( 'latepoint_after_datepicker_months', $booking_request, $target_date, $settings );
218 }
219
220 public static function generate_single_month( \LatePoint\Misc\BookingRequest $booking_request, DateTime $target_date, array $settings = [] ) {
221 $defaults = [
222 'accessed_from_backend' => false,
223 'active' => false,
224 'layout' => 'classic',
225 'highlight_target_date' => false,
226 'timezone_name' => false,
227 'earliest_possible_booking' => false,
228 'latest_possible_booking' => false,
229 'exclude_booking_ids' => [],
230 'consider_cart_items' => false,
231 'hide_slot_availability_count' => OsStepsHelper::hide_slot_availability_count(),
232 ];
233 $settings = OsUtilHelper::merge_default_atts( $defaults, $settings );
234
235
236 // set service to the first available if not set
237 // IMPORTANT, we have to have service in the booking request, otherwise we can't know duration and intervals
238 // service_id can be an array of shown service ids (e.g. admin calendar day view).
239 $service_id = null;
240
241 if ( is_array( $booking_request->service_id ) ) {
242 $service_id = [];
243 foreach ( $booking_request->service_id as $br_service_id ) {
244 $service_id[] = absint( $br_service_id );
245 }
246 } else {
247 $service_id = absint( $booking_request->service_id );
248 }
249
250 $service = new OsServiceModel();
251 $service = $service->where( [ 'id' => $service_id ] )->set_limit( 1 )->get_results_as_models();
252 if ( $service ) {
253 if ( ! $booking_request->duration ) {
254 $booking_request->duration = $service->duration;
255 }
256 $selectable_time_interval = $service->get_timeblock_interval();
257 } else {
258 echo '<div class="latepoint-message latepoint-message-error">' . esc_html__( 'In order to generate the calendar, a service must be selected.', 'latepoint' ) . '</div>';
259
260 return;
261 }
262
263
264 # Get bounds for a month of a targetted day
265 $calendar_start = clone $target_date;
266 $calendar_start->modify( 'first day of this month' );
267 $calendar_end = clone $target_date;
268 $calendar_end->modify( 'last day of this month' );
269
270
271 // if it's a classic layout - it means we need to load some days from previous and next month, to fill in blank spaces on the grid
272 if ( $settings['layout'] == 'classic' ) {
273 $weekday_for_first_day_of_month = intval( $calendar_start->format( 'N' ) );
274 $weekday_for_last_day_of_month = intval( $calendar_end->format( 'N' ) );
275
276 $week_starts_on = OsSettingsHelper::get_start_of_week();
277 $week_ends_on = $week_starts_on > 1 ? $week_starts_on - 1 : 7;
278
279 if ( $weekday_for_first_day_of_month != $week_starts_on ) {
280 $days_to_subtract = ( $weekday_for_first_day_of_month - $week_starts_on + 7 ) % 7;
281 if ( $days_to_subtract > 0 ) {
282 $calendar_start->modify( '-' . $days_to_subtract . ' days' );
283 }
284 }
285
286 if ( $weekday_for_last_day_of_month != $week_ends_on ) {
287 $days_to_add = ( $weekday_for_last_day_of_month > $week_ends_on ) ? abs( 7 - $weekday_for_last_day_of_month + $week_ends_on ) : ( $week_ends_on - $weekday_for_last_day_of_month );
288 if ( $days_to_add > 0 ) {
289 $calendar_end->modify( '+' . $days_to_add . ' days' );
290 }
291 }
292 }
293
294 $now_datetime = OsTimeHelper::now_datetime_object();
295
296 /**
297 * Filters the datetime treated as "now" when a calendar month is generated.
298 *
299 * Governs which day is "today" and which days are "in the past" (os-today / os-day-passed),
300 * the default earliest-possible-booking, and the start of the fetched booking range.
301 *
302 * @param {OsWpDateTime} $now_datetime current datetime, defaults to the server WordPress timezone
303 * @param {array} $settings calendar settings; includes 'timezone_name' (the customer's selected timezone)
304 * @returns {OsWpDateTime} datetime object used as "now" for day classification and range calculation
305 *
306 * @since 5.6.9
307 * @hook latepoint_calendar_now_datetime
308 *
309 */
310 $now_datetime = apply_filters( 'latepoint_calendar_now_datetime', $now_datetime, $settings );
311
312 // figure out when the earliest and latest bookings can be placed
313 try {
314 $earliest_possible_booking = ( $settings['earliest_possible_booking'] ) ? new OsWpDateTime( $settings['earliest_possible_booking'] ) : clone $now_datetime;
315 $latest_possible_booking = ( $settings['latest_possible_booking'] ) ? new OsWpDateTime( $settings['latest_possible_booking'] ) : clone $calendar_end;
316 } catch ( Exception $e ) {
317
318 }
319 // make sure they are set correctly
320 if ( empty( $earliest_possible_booking ) ) {
321 $earliest_possible_booking = clone $now_datetime;
322 }
323 if ( empty( $latest_possible_booking ) ) {
324 $latest_possible_booking = clone $calendar_end;
325 }
326
327 $date_range_start = ( $calendar_start->format( 'Y-m-d' ) > $earliest_possible_booking->format( 'Y-m-d' ) ) ? clone $calendar_start : clone $earliest_possible_booking;
328 $date_range_end = ( $calendar_end->format( 'Y-m-d' ) < $latest_possible_booking->format( 'Y-m-d' ) ) ? clone $calendar_end : clone $latest_possible_booking;
329
330 // make sure date range is within the requested calendar range
331 if ( ( $date_range_start->format( 'Y-m-d' ) >= $calendar_start->format( 'Y-m-d' ) )
332 && ( $date_range_end->format( 'Y-m-d' ) <= $calendar_end->format( 'Y-m-d' ) )
333 && ( $date_range_start->format( 'Y-m-d' ) <= $date_range_end->format( 'Y-m-d' ) ) ) {
334 $daily_resources = OsResourceHelper::get_resources_grouped_by_day(
335 $booking_request,
336 $date_range_start,
337 $date_range_end,
338 [
339 'accessed_from_backend' => $settings['accessed_from_backend'],
340 'exclude_booking_ids' => $settings['exclude_booking_ids'],
341 'consider_cart_items' => $settings['consider_cart_items'],
342 'timezone_name' => $settings['timezone_name'],
343 ]
344 );
345 } else {
346 $daily_resources = [];
347 }
348
349 $active_class = $settings['active'] ? 'active' : '';
350 $hide_single_slot_class = OsStepsHelper::hide_timepicker_when_one_slot_available() ? 'hide-if-single-slot' : '';
351 echo '<div class="os-monthly-calendar-days-w ' . esc_attr( $hide_single_slot_class . ' ' . $active_class ) . '" data-calendar-layout="' . esc_attr( $settings['layout'] ) . '" data-calendar-year="' . esc_attr( $target_date->format( 'Y' ) ) . '" data-calendar-month="' . esc_attr( $target_date->format( 'n' ) ) . '" data-calendar-month-label="' . esc_attr( OsUtilHelper::get_month_name_by_number( $target_date->format( 'n' ) ) ) . '">';
352 echo '<div class="os-monthly-calendar-days">';
353 // DAYS LOOP START
354 for ( $day_date = clone $calendar_start; $day_date <= $calendar_end; $day_date->modify( '+1 day' ) ) {
355 if ( ! isset( $daily_resources[ $day_date->format( 'Y-m-d' ) ] ) ) {
356 $daily_resources[ $day_date->format( 'Y-m-d' ) ] = [];
357 }
358
359 $is_today = ( $day_date->format( 'Y-m-d' ) == $now_datetime->format( 'Y-m-d' ) );
360 $is_day_in_past = ( $day_date->format( 'Y-m-d' ) < $now_datetime->format( 'Y-m-d' ) );
361 $is_target_month = ( $day_date->format( 'Ym' ) == $target_date->format( 'Ym' ) );
362 $is_next_month = ( $day_date->format( 'Ym' ) > $target_date->format( 'Ym' ) );
363 $is_prev_month = ( $day_date->format( 'Ym' ) < $target_date->format( 'Ym' ) );
364 $not_in_allowed_period = false;
365
366 if ( $day_date->format( 'Y-m-d' ) < $earliest_possible_booking->format( 'Y-m-d' ) ) {
367 $not_in_allowed_period = true;
368 }
369 if ( $day_date->format( 'Y-m-d' ) > $latest_possible_booking->format( 'Y-m-d' ) ) {
370 $not_in_allowed_period = true;
371 }
372
373 $work_minutes = [];
374
375 if ( $settings['accessed_from_backend'] || ! $not_in_allowed_period ) {
376 // only do this if is in allowed period or accessed from backend
377 foreach ( $daily_resources[ $day_date->format( 'Y-m-d' ) ] as $resource ) {
378 if ( $is_day_in_past && $not_in_allowed_period ) {
379 continue;
380 }
381 $work_minutes = array_merge( $work_minutes, $resource->work_minutes );
382 }
383 $work_minutes = array_unique( $work_minutes, SORT_NUMERIC );
384 sort( $work_minutes, SORT_NUMERIC );
385 }
386
387
388
389 $work_boundaries = OsResourceHelper::get_work_boundaries_for_resources( $daily_resources[ $day_date->format( 'Y-m-d' ) ] );
390 $total_work_minutes = $work_boundaries->end_time - $work_boundaries->start_time;
391
392 $booking_slots = OsResourceHelper::get_ordered_booking_slots_from_resources( $daily_resources[ $day_date->format( 'Y-m-d' ) ] );
393
394 $bookable_minutes = [];
395 if ( $settings['accessed_from_backend'] || ! $not_in_allowed_period ) {
396 // only do this if is in allowed period or accessed from backend
397 foreach ( $booking_slots as $booking_slot ) {
398 if ( $booking_slot->can_accomodate( $booking_request->total_attendees ) ) {
399 $bookable_minutes[ $booking_slot->start_time ] = isset( $bookable_minutes[ $booking_slot->start_time ] ) ? max( $booking_slot->available_capacity(), $bookable_minutes[ $booking_slot->start_time ] ) : $booking_slot->available_capacity();
400 }
401 }
402 ksort( $bookable_minutes );
403 }
404 $bookable_minutes_with_capacity_data = '';
405 // this is a group service
406 if ( $service->is_group_service() && ! $settings['hide_slot_availability_count'] ) {
407 foreach ( $bookable_minutes as $minute => $available_capacity ) {
408 $bookable_minutes_with_capacity_data .= $minute . ':' . $available_capacity . ',';
409 }
410 } else {
411 foreach ( $bookable_minutes as $minute => $available_capacity ) {
412 $bookable_minutes_with_capacity_data .= $minute . ',';
413 }
414 }
415 $bookable_minutes_with_capacity_data = rtrim( $bookable_minutes_with_capacity_data, ',' );
416
417
418 $bookable_slots_count = count( $bookable_minutes );
419 // TODO use work minutes instead to calculate minimum gap
420 $minimum_slot_gap = \LatePoint\Misc\BookingSlot::find_minimum_gap_between_slots( $booking_slots );
421 $day_class = 'os-day os-day-current week-day-' . strtolower( $day_date->format( 'N' ) );
422 $tabbable = true;
423 if ( empty( $bookable_minutes ) ) {
424 $day_class .= ' os-not-available';
425 $tabbable = false;
426 }
427 if ( $is_today ) {
428 $day_class .= ' os-today';
429 }
430 if ( $is_day_in_past ) {
431 $day_class .= ' os-day-passed';
432 $tabbable = false;
433 }
434 if ( $is_target_month ) {
435 $day_class .= ' os-month-current';
436 }
437 if ( $is_next_month ) {
438 $day_class .= ' os-month-next';
439 }
440 if ( $is_prev_month ) {
441 $day_class .= ' os-month-prev';
442 }
443 if ( $not_in_allowed_period ) {
444 $day_class .= ' os-not-in-allowed-period';
445 $tabbable = false;
446 }
447 if ( count( $bookable_minutes ) == 1 && OsStepsHelper::hide_timepicker_when_one_slot_available() ) {
448 $day_class .= ' os-one-slot-only';
449 }
450 if ( ( $day_date->format( 'Y-m-d' ) == $target_date->format( 'Y-m-d' ) ) && $settings['highlight_target_date'] ) {
451 $day_class .= ' selected';
452 }
453 ?>
454
455 <div <?php if ( $tabbable ) {
456 echo 'tabindex="0"';} ?> role="button" class="<?php echo esc_attr( $day_class ); ?>"
457 data-date="<?php echo esc_attr( $day_date->format( 'Y-m-d' ) ); ?>"
458 data-nice-date="<?php echo esc_attr( OsTimeHelper::get_nice_date_with_optional_year( $day_date->format( 'Y-m-d' ), false ) ); ?>"
459 data-service-duration="<?php echo esc_attr( $booking_request->duration ); ?>"
460 data-total-work-minutes="<?php echo esc_attr( $total_work_minutes ); ?>"
461 data-work-start-time="<?php echo esc_attr( $work_boundaries->start_time ); ?>"
462 data-work-end-time="<?php echo esc_attr( $work_boundaries->end_time ); ?>"
463 data-bookable-minutes="<?php echo esc_attr( $bookable_minutes_with_capacity_data ); ?>"
464 data-work-minutes="<?php echo esc_attr( implode( ',', $work_minutes ) ); ?>"
465 data-interval="<?php echo esc_attr( $selectable_time_interval ); ?>">
466 <?php if ( $settings['layout'] == 'horizontal' ) {
467 $weekday = esc_html( OsBookingHelper::get_weekday_name_by_number( $day_date->format( 'N' ) ) );
468 ?>
469 <div class="os-day-weekday"><?php echo OsUtilHelper::get_first_chars( $weekday, 1 ); ?></div>
470 <?php } ?>
471 <div class="os-day-box">
472 <?php
473 if ( $bookable_slots_count && ! $settings['hide_slot_availability_count'] ) {
474 // translators: %d is the number of slots available
475 echo '<div class="os-available-slots-tooltip">' . esc_html( sprintf( __( '%d Available', 'latepoint' ), $bookable_slots_count ) ) . '</div>';
476 } ?>
477 <div class="os-day-number"><?php echo esc_html( $day_date->format( 'j' ) ); ?></div>
478 <?php if ( ! $is_day_in_past && ! $not_in_allowed_period ) { ?>
479 <div class="os-day-status">
480 <?php
481 if ( $total_work_minutes > 0 && $bookable_slots_count ) {
482 $available_blocks_count = 0;
483 $not_available_started_count = 0;
484 $duration = $booking_request->duration;
485 $end_time = $work_boundaries->end_time - $duration;
486 $processed_count = 0;
487 $last_available_slot_time = false;
488 $bookable_ranges = [];
489 $loop_availability_status = false;
490 for ( $i = 0; $i < count( $booking_slots ); $i++ ) {
491 if ( $booking_slots[ $i ]->can_accomodate( $booking_request->total_attendees ) ) {
492 // AVAILABLE SLOT
493 if ( $loop_availability_status && $i > 0 && ( ( $booking_slots[ $i ]->start_time - $booking_slots[ $i - 1 ]->start_time ) > $minimum_slot_gap ) ) {
494 // big gap between previous slot and this slot
495 $bookable_ranges[] = $booking_slots[ $i - 1 ]->start_time + $minimum_slot_gap;
496 $bookable_ranges[] = $booking_slots[ $i ]->start_time;
497 }
498 if ( ! $loop_availability_status ) {
499 $bookable_ranges[] = $booking_slots[ $i ]->start_time;
500 }
501 $last_available_slot_time = $booking_slots[ $i ]->start_time;
502 $loop_availability_status = true;
503 } else {
504 // NOT AVAILABLE
505 // a different resource but with the same start time, so that if its available (checked in next loop iteration) - we don't block this slot
506 if ( isset( $booking_slots[ $i + 1 ] ) && $booking_slots[ $i + 1 ]->start_time == $booking_slots[ $i ]->start_time ) {
507 continue;
508 }
509 // check if last available slot had the same start time as current one, if so - we don't block this slot and move to the next one
510 if ( $last_available_slot_time == $booking_slots[ $i ]->start_time && isset( $booking_slots[ $i - 1 ] ) && $booking_slots[ $i - 1 ]->start_time == $booking_slots[ $i ]->start_time ) {
511 continue;
512 }
513 // if last available slot exists and previous slot was also available
514 if ( $last_available_slot_time && $loop_availability_status ) {
515 $bookable_ranges[] = $last_available_slot_time + $minimum_slot_gap;
516 }
517 $loop_availability_status = false;
518 }
519 }
520 if ( $bookable_ranges ) {
521 for ( $i = 0; $i < count( $bookable_ranges ); $i += 2 ) {
522 $left = ( $bookable_ranges[ $i ] - $work_boundaries->start_time ) / $total_work_minutes * 100;
523 $width = isset( $bookable_ranges[ $i + 1 ] ) ? ( ( $bookable_ranges[ $i + 1 ] - $bookable_ranges[ $i ] ) / $total_work_minutes * 100 ) : ( ( $work_boundaries->end_time - $bookable_ranges[ $i ] ) / $total_work_minutes * 100 );
524 echo '<div class="day-available" style="left:' . esc_attr( $left ) . '%;width:' . esc_attr( $width ) . '%;"></div>';
525 }
526 }
527 }
528 ?>
529 </div>
530 <?php } ?>
531 </div>
532 </div>
533
534 <?php
535
536 // DAYS LOOP END
537 }
538 echo '</div></div>';
539 }
540
541 // Used on holiday/custom schedule generator lightbox
542 public static function generate_monthly_calendar_days_only( $target_date_string = 'today', $highlight_target_date = false, bool $is_active = false ) {
543 $target_date = new OsWpDateTime( $target_date_string );
544 $calendar_start = clone $target_date;
545 $calendar_start->modify( 'first day of this month' );
546 $calendar_end = clone $target_date;
547 $calendar_end->modify( 'last day of this month' );
548
549 $weekday_for_first_day_of_month = $calendar_start->format( 'N' ) - 1;
550 $weekday_for_last_day_of_month = $calendar_end->format( 'N' ) - 1;
551
552
553 if ( $weekday_for_first_day_of_month > 0 ) {
554 $calendar_start->modify( '-' . $weekday_for_first_day_of_month . ' days' );
555 }
556
557 if ( $weekday_for_last_day_of_month < 6 ) {
558 $days_to_add = 6 - $weekday_for_last_day_of_month;
559 if ( $days_to_add > 0 ) {
560 $calendar_end->modify( '+' . $days_to_add . ' days' );
561 }
562 }
563
564 $active_class = $is_active ? 'active' : '';
565
566 echo '<div class="os-monthly-calendar-days-w ' . $active_class . '" data-calendar-year="' . esc_attr( $target_date->format( 'Y' ) ) . '" data-calendar-month="' . esc_attr( $target_date->format( 'n' ) ) . '" data-calendar-month-label="' . esc_attr( OsUtilHelper::get_month_name_by_number( $target_date->format( 'n' ) ) ) . '">';
567 echo '<div class="os-monthly-calendar-days">';
568 for ( $day_date = clone $calendar_start; $day_date <= $calendar_end; $day_date->modify( '+1 day' ) ) {
569 $is_today = ( $day_date->format( 'Y-m-d' ) == OsTimeHelper::today_date() ) ? true : false;
570 $is_day_in_past = ( $day_date->format( 'Y-m-d' ) < OsTimeHelper::today_date() ) ? true : false;
571 $day_class = 'os-day os-day-current week-day-' . strtolower( $day_date->format( 'N' ) );
572
573 if ( $day_date->format( 'm' ) > $target_date->format( 'm' ) ) {
574 $day_class .= ' os-month-next';
575 }
576 if ( $day_date->format( 'm' ) < $target_date->format( 'm' ) ) {
577 $day_class .= ' os-month-prev';
578 }
579
580 if ( $is_today ) {
581 $day_class .= ' os-today';
582 }
583 if ( $highlight_target_date && ( $day_date->format( 'Y-m-d' ) == $target_date->format( 'Y-m-d' ) ) ) {
584 $day_class .= ' selected';
585 }
586 if ( $is_day_in_past ) {
587 $day_class .= ' os-day-passed';
588 } ?>
589 <div class="<?php echo esc_attr( $day_class ); ?>" data-date="<?php echo esc_attr( $day_date->format( 'Y-m-d' ) ); ?>">
590 <div class="os-day-box">
591 <div class="os-day-number"><?php echo esc_html( $day_date->format( 'j' ) ); ?></div>
592 </div>
593 </div><?php
594 }
595 echo '</div></div>';
596 }
597
598 public static function generate_calendar_quick_actions_link( OsWpDateTime $day_date, array $settings = [] ): string {
599 $defaults = [
600 'agent_id' => 0,
601 'location_id' => 0,
602 'service_id' => 0,
603 'start_time' => 600,
604 ];
605
606 $settings = OsUtilHelper::merge_default_atts( $defaults, $settings );
607
608 return '<a href="#" data-os-after-call="latepoint_init_calendar_quick_actions" data-os-lightbox-classes="width-400" class="day-action-trigger" data-os-output-target="lightbox" data-os-params="' . OsUtilHelper::build_os_params(
609 [
610 'target_date' => $day_date->format( 'Y-m-d' ),
611 'start_time' => $settings['start_time'],
612 'agent_id' => $settings['agent_id'],
613 'location_id' => $settings['location_id'],
614 'service_id' => $settings['service_id'],
615 ]
616 ) . '" data-os-action="' . OsRouterHelper::build_route_name( 'calendars', 'quick_actions' ) . '"><i class="latepoint-icon latepoint-icon-more-horizontal"></i></a>';
617 }
618 }