PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.6.6
LatePoint – Calendar Booking Plugin for Appointments and Events v5.6.6
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 3 months ago agent_helper.php 3 months ago analytics_helper.php 1 week ago auth_helper.php 6 days ago blocks_helper.php 3 weeks ago booking_helper.php 4 days ago bricks_helper.php 3 months ago bundles_helper.php 4 days ago calendar_helper.php 2 days ago carts_helper.php 3 months ago connector_helper.php 3 months ago csv_helper.php 3 months ago customer_helper.php 1 month ago customer_import_helper.php 1 month ago database_helper.php 1 week ago debug_helper.php 3 months ago defaults_helper.php 3 months ago elementor_helper.php 3 months ago email_helper.php 3 months ago encrypt_helper.php 3 months ago events_helper.php 3 months ago form_helper.php 3 months ago icalendar_helper.php 3 months ago image_helper.php 3 months ago invoices_helper.php 3 weeks ago license_helper.php 3 months ago location_helper.php 3 months ago marketing_systems_helper.php 3 months ago meeting_systems_helper.php 3 months ago menu_helper.php 3 weeks ago meta_helper.php 3 months ago migrations_helper.php 3 months ago money_helper.php 3 months ago notifications_helper.php 3 months ago nps_survey_helper.php 3 months ago order_intent_helper.php 2 months ago orders_helper.php 3 months ago otp_helper.php 3 months ago pages_helper.php 3 months ago params_helper.php 3 months ago payments_helper.php 2 months ago plugin_version_update_helper.php 2 months ago price_breakdown_helper.php 3 months ago process_jobs_helper.php 3 months ago processes_helper.php 3 months ago razorpay_connect_helper.php 1 week ago replacer_helper.php 3 months ago resource_helper.php 4 days ago roles_helper.php 3 weeks ago router_helper.php 3 months ago service_helper.php 3 months ago sessions_helper.php 3 months ago settings_helper.php 1 week ago short_links_systems_helper.php 3 months ago shortcodes_helper.php 3 weeks ago sms_helper.php 3 months ago steps_helper.php 1 week ago stripe_connect_helper.php 1 week ago styles_helper.php 3 months ago support_topics_helper.php 3 months ago time_helper.php 2 months ago timeline_helper.php 2 months ago transaction_helper.php 1 month ago transaction_intent_helper.php 3 months ago util_helper.php 1 month ago version_specific_updates_helper.php 3 months ago whatsapp_helper.php 1 month ago work_periods_helper.php 3 months ago wp_datetime.php 3 months ago wp_user_helper.php 3 months ago
calendar_helper.php
602 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 // figure out when the earliest and latest bookings can be placed
297 try {
298 $earliest_possible_booking = ( $settings['earliest_possible_booking'] ) ? new OsWpDateTime( $settings['earliest_possible_booking'] ) : clone $now_datetime;
299 $latest_possible_booking = ( $settings['latest_possible_booking'] ) ? new OsWpDateTime( $settings['latest_possible_booking'] ) : clone $calendar_end;
300 } catch ( Exception $e ) {
301
302 }
303 // make sure they are set correctly
304 if ( empty( $earliest_possible_booking ) ) {
305 $earliest_possible_booking = clone $now_datetime;
306 }
307 if ( empty( $latest_possible_booking ) ) {
308 $latest_possible_booking = clone $calendar_end;
309 }
310
311 $date_range_start = ( $calendar_start->format( 'Y-m-d' ) > $earliest_possible_booking->format( 'Y-m-d' ) ) ? clone $calendar_start : clone $earliest_possible_booking;
312 $date_range_end = ( $calendar_end->format( 'Y-m-d' ) < $latest_possible_booking->format( 'Y-m-d' ) ) ? clone $calendar_end : clone $latest_possible_booking;
313
314 // make sure date range is within the requested calendar range
315 if ( ( $date_range_start->format( 'Y-m-d' ) >= $calendar_start->format( 'Y-m-d' ) )
316 && ( $date_range_end->format( 'Y-m-d' ) <= $calendar_end->format( 'Y-m-d' ) )
317 && ( $date_range_start->format( 'Y-m-d' ) <= $date_range_end->format( 'Y-m-d' ) ) ) {
318 $daily_resources = OsResourceHelper::get_resources_grouped_by_day(
319 $booking_request,
320 $date_range_start,
321 $date_range_end,
322 [
323 'accessed_from_backend' => $settings['accessed_from_backend'],
324 'exclude_booking_ids' => $settings['exclude_booking_ids'],
325 'consider_cart_items' => $settings['consider_cart_items'],
326 'timezone_name' => $settings['timezone_name'],
327 ]
328 );
329 } else {
330 $daily_resources = [];
331 }
332
333 $active_class = $settings['active'] ? 'active' : '';
334 $hide_single_slot_class = OsStepsHelper::hide_timepicker_when_one_slot_available() ? 'hide-if-single-slot' : '';
335 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' ) ) ) . '">';
336 echo '<div class="os-monthly-calendar-days">';
337 // DAYS LOOP START
338 for ( $day_date = clone $calendar_start; $day_date <= $calendar_end; $day_date->modify( '+1 day' ) ) {
339 if ( ! isset( $daily_resources[ $day_date->format( 'Y-m-d' ) ] ) ) {
340 $daily_resources[ $day_date->format( 'Y-m-d' ) ] = [];
341 }
342
343 $is_today = ( $day_date->format( 'Y-m-d' ) == $now_datetime->format( 'Y-m-d' ) );
344 $is_day_in_past = ( $day_date->format( 'Y-m-d' ) < $now_datetime->format( 'Y-m-d' ) );
345 $is_target_month = ( $day_date->format( 'Ym' ) == $target_date->format( 'Ym' ) );
346 $is_next_month = ( $day_date->format( 'Ym' ) > $target_date->format( 'Ym' ) );
347 $is_prev_month = ( $day_date->format( 'Ym' ) < $target_date->format( 'Ym' ) );
348 $not_in_allowed_period = false;
349
350 if ( $day_date->format( 'Y-m-d' ) < $earliest_possible_booking->format( 'Y-m-d' ) ) {
351 $not_in_allowed_period = true;
352 }
353 if ( $day_date->format( 'Y-m-d' ) > $latest_possible_booking->format( 'Y-m-d' ) ) {
354 $not_in_allowed_period = true;
355 }
356
357 $work_minutes = [];
358
359 if ( $settings['accessed_from_backend'] || ! $not_in_allowed_period ) {
360 // only do this if is in allowed period or accessed from backend
361 foreach ( $daily_resources[ $day_date->format( 'Y-m-d' ) ] as $resource ) {
362 if ( $is_day_in_past && $not_in_allowed_period ) {
363 continue;
364 }
365 $work_minutes = array_merge( $work_minutes, $resource->work_minutes );
366 }
367 $work_minutes = array_unique( $work_minutes, SORT_NUMERIC );
368 sort( $work_minutes, SORT_NUMERIC );
369 }
370
371
372
373 $work_boundaries = OsResourceHelper::get_work_boundaries_for_resources( $daily_resources[ $day_date->format( 'Y-m-d' ) ] );
374 $total_work_minutes = $work_boundaries->end_time - $work_boundaries->start_time;
375
376 $booking_slots = OsResourceHelper::get_ordered_booking_slots_from_resources( $daily_resources[ $day_date->format( 'Y-m-d' ) ] );
377
378 $bookable_minutes = [];
379 if ( $settings['accessed_from_backend'] || ! $not_in_allowed_period ) {
380 // only do this if is in allowed period or accessed from backend
381 foreach ( $booking_slots as $booking_slot ) {
382 if ( $booking_slot->can_accomodate( $booking_request->total_attendees ) ) {
383 $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();
384 }
385 }
386 ksort( $bookable_minutes );
387 }
388 $bookable_minutes_with_capacity_data = '';
389 // this is a group service
390 if ( $service->is_group_service() && ! $settings['hide_slot_availability_count'] ) {
391 foreach ( $bookable_minutes as $minute => $available_capacity ) {
392 $bookable_minutes_with_capacity_data .= $minute . ':' . $available_capacity . ',';
393 }
394 } else {
395 foreach ( $bookable_minutes as $minute => $available_capacity ) {
396 $bookable_minutes_with_capacity_data .= $minute . ',';
397 }
398 }
399 $bookable_minutes_with_capacity_data = rtrim( $bookable_minutes_with_capacity_data, ',' );
400
401
402 $bookable_slots_count = count( $bookable_minutes );
403 // TODO use work minutes instead to calculate minimum gap
404 $minimum_slot_gap = \LatePoint\Misc\BookingSlot::find_minimum_gap_between_slots( $booking_slots );
405 $day_class = 'os-day os-day-current week-day-' . strtolower( $day_date->format( 'N' ) );
406 $tabbable = true;
407 if ( empty( $bookable_minutes ) ) {
408 $day_class .= ' os-not-available';
409 $tabbable = false;
410 }
411 if ( $is_today ) {
412 $day_class .= ' os-today';
413 }
414 if ( $is_day_in_past ) {
415 $day_class .= ' os-day-passed';
416 $tabbable = false;
417 }
418 if ( $is_target_month ) {
419 $day_class .= ' os-month-current';
420 }
421 if ( $is_next_month ) {
422 $day_class .= ' os-month-next';
423 }
424 if ( $is_prev_month ) {
425 $day_class .= ' os-month-prev';
426 }
427 if ( $not_in_allowed_period ) {
428 $day_class .= ' os-not-in-allowed-period';
429 $tabbable = false;
430 }
431 if ( count( $bookable_minutes ) == 1 && OsStepsHelper::hide_timepicker_when_one_slot_available() ) {
432 $day_class .= ' os-one-slot-only';
433 }
434 if ( ( $day_date->format( 'Y-m-d' ) == $target_date->format( 'Y-m-d' ) ) && $settings['highlight_target_date'] ) {
435 $day_class .= ' selected';
436 }
437 ?>
438
439 <div <?php if ( $tabbable ) {
440 echo 'tabindex="0"';} ?> role="button" class="<?php echo esc_attr( $day_class ); ?>"
441 data-date="<?php echo esc_attr( $day_date->format( 'Y-m-d' ) ); ?>"
442 data-nice-date="<?php echo esc_attr( OsTimeHelper::get_nice_date_with_optional_year( $day_date->format( 'Y-m-d' ), false ) ); ?>"
443 data-service-duration="<?php echo esc_attr( $booking_request->duration ); ?>"
444 data-total-work-minutes="<?php echo esc_attr( $total_work_minutes ); ?>"
445 data-work-start-time="<?php echo esc_attr( $work_boundaries->start_time ); ?>"
446 data-work-end-time="<?php echo esc_attr( $work_boundaries->end_time ); ?>"
447 data-bookable-minutes="<?php echo esc_attr( $bookable_minutes_with_capacity_data ); ?>"
448 data-work-minutes="<?php echo esc_attr( implode( ',', $work_minutes ) ); ?>"
449 data-interval="<?php echo esc_attr( $selectable_time_interval ); ?>">
450 <?php if ( $settings['layout'] == 'horizontal' ) {
451 $weekday = esc_html( OsBookingHelper::get_weekday_name_by_number( $day_date->format( 'N' ) ) );
452 ?>
453 <div class="os-day-weekday"><?php echo OsUtilHelper::get_first_chars( $weekday, 1 ); ?></div>
454 <?php } ?>
455 <div class="os-day-box">
456 <?php
457 if ( $bookable_slots_count && ! $settings['hide_slot_availability_count'] ) {
458 // translators: %d is the number of slots available
459 echo '<div class="os-available-slots-tooltip">' . esc_html( sprintf( __( '%d Available', 'latepoint' ), $bookable_slots_count ) ) . '</div>';
460 } ?>
461 <div class="os-day-number"><?php echo esc_html( $day_date->format( 'j' ) ); ?></div>
462 <?php if ( ! $is_day_in_past && ! $not_in_allowed_period ) { ?>
463 <div class="os-day-status">
464 <?php
465 if ( $total_work_minutes > 0 && $bookable_slots_count ) {
466 $available_blocks_count = 0;
467 $not_available_started_count = 0;
468 $duration = $booking_request->duration;
469 $end_time = $work_boundaries->end_time - $duration;
470 $processed_count = 0;
471 $last_available_slot_time = false;
472 $bookable_ranges = [];
473 $loop_availability_status = false;
474 for ( $i = 0; $i < count( $booking_slots ); $i++ ) {
475 if ( $booking_slots[ $i ]->can_accomodate( $booking_request->total_attendees ) ) {
476 // AVAILABLE SLOT
477 if ( $loop_availability_status && $i > 0 && ( ( $booking_slots[ $i ]->start_time - $booking_slots[ $i - 1 ]->start_time ) > $minimum_slot_gap ) ) {
478 // big gap between previous slot and this slot
479 $bookable_ranges[] = $booking_slots[ $i - 1 ]->start_time + $minimum_slot_gap;
480 $bookable_ranges[] = $booking_slots[ $i ]->start_time;
481 }
482 if ( ! $loop_availability_status ) {
483 $bookable_ranges[] = $booking_slots[ $i ]->start_time;
484 }
485 $last_available_slot_time = $booking_slots[ $i ]->start_time;
486 $loop_availability_status = true;
487 } else {
488 // NOT AVAILABLE
489 // 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
490 if ( isset( $booking_slots[ $i + 1 ] ) && $booking_slots[ $i + 1 ]->start_time == $booking_slots[ $i ]->start_time ) {
491 continue;
492 }
493 // 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
494 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 ) {
495 continue;
496 }
497 // if last available slot exists and previous slot was also available
498 if ( $last_available_slot_time && $loop_availability_status ) {
499 $bookable_ranges[] = $last_available_slot_time + $minimum_slot_gap;
500 }
501 $loop_availability_status = false;
502 }
503 }
504 if ( $bookable_ranges ) {
505 for ( $i = 0; $i < count( $bookable_ranges ); $i += 2 ) {
506 $left = ( $bookable_ranges[ $i ] - $work_boundaries->start_time ) / $total_work_minutes * 100;
507 $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 );
508 echo '<div class="day-available" style="left:' . esc_attr( $left ) . '%;width:' . esc_attr( $width ) . '%;"></div>';
509 }
510 }
511 }
512 ?>
513 </div>
514 <?php } ?>
515 </div>
516 </div>
517
518 <?php
519
520 // DAYS LOOP END
521 }
522 echo '</div></div>';
523 }
524
525 // Used on holiday/custom schedule generator lightbox
526 public static function generate_monthly_calendar_days_only( $target_date_string = 'today', $highlight_target_date = false, bool $is_active = false ) {
527 $target_date = new OsWpDateTime( $target_date_string );
528 $calendar_start = clone $target_date;
529 $calendar_start->modify( 'first day of this month' );
530 $calendar_end = clone $target_date;
531 $calendar_end->modify( 'last day of this month' );
532
533 $weekday_for_first_day_of_month = $calendar_start->format( 'N' ) - 1;
534 $weekday_for_last_day_of_month = $calendar_end->format( 'N' ) - 1;
535
536
537 if ( $weekday_for_first_day_of_month > 0 ) {
538 $calendar_start->modify( '-' . $weekday_for_first_day_of_month . ' days' );
539 }
540
541 if ( $weekday_for_last_day_of_month < 6 ) {
542 $days_to_add = 6 - $weekday_for_last_day_of_month;
543 if ( $days_to_add > 0 ) {
544 $calendar_end->modify( '+' . $days_to_add . ' days' );
545 }
546 }
547
548 $active_class = $is_active ? 'active' : '';
549
550 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' ) ) ) . '">';
551 echo '<div class="os-monthly-calendar-days">';
552 for ( $day_date = clone $calendar_start; $day_date <= $calendar_end; $day_date->modify( '+1 day' ) ) {
553 $is_today = ( $day_date->format( 'Y-m-d' ) == OsTimeHelper::today_date() ) ? true : false;
554 $is_day_in_past = ( $day_date->format( 'Y-m-d' ) < OsTimeHelper::today_date() ) ? true : false;
555 $day_class = 'os-day os-day-current week-day-' . strtolower( $day_date->format( 'N' ) );
556
557 if ( $day_date->format( 'm' ) > $target_date->format( 'm' ) ) {
558 $day_class .= ' os-month-next';
559 }
560 if ( $day_date->format( 'm' ) < $target_date->format( 'm' ) ) {
561 $day_class .= ' os-month-prev';
562 }
563
564 if ( $is_today ) {
565 $day_class .= ' os-today';
566 }
567 if ( $highlight_target_date && ( $day_date->format( 'Y-m-d' ) == $target_date->format( 'Y-m-d' ) ) ) {
568 $day_class .= ' selected';
569 }
570 if ( $is_day_in_past ) {
571 $day_class .= ' os-day-passed';
572 } ?>
573 <div class="<?php echo esc_attr( $day_class ); ?>" data-date="<?php echo esc_attr( $day_date->format( 'Y-m-d' ) ); ?>">
574 <div class="os-day-box">
575 <div class="os-day-number"><?php echo esc_html( $day_date->format( 'j' ) ); ?></div>
576 </div>
577 </div><?php
578 }
579 echo '</div></div>';
580 }
581
582 public static function generate_calendar_quick_actions_link( OsWpDateTime $day_date, array $settings = [] ): string {
583 $defaults = [
584 'agent_id' => 0,
585 'location_id' => 0,
586 'service_id' => 0,
587 'start_time' => 600,
588 ];
589
590 $settings = OsUtilHelper::merge_default_atts( $defaults, $settings );
591
592 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(
593 [
594 'target_date' => $day_date->format( 'Y-m-d' ),
595 'start_time' => $settings['start_time'],
596 'agent_id' => $settings['agent_id'],
597 'location_id' => $settings['location_id'],
598 'service_id' => $settings['service_id'],
599 ]
600 ) . '" data-os-action="' . OsRouterHelper::build_route_name( 'calendars', 'quick_actions' ) . '"><i class="latepoint-icon latepoint-icon-more-horizontal"></i></a>';
601 }
602 }