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