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