PluginProbe
Booking Calendar / 11.6.1
Booking Calendar v11.6.1
11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 All 204 releases
booking / includes / page-availability-timeslots / ajax / availability_timeslots__front.php

availability_timeslots__front.php in Booking Calendar 11.6.1, at includes/page-availability-timeslots/ajax/availability_timeslots__front.php

936 lines 32.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Time slots availability helpers and public read endpoint.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Get DB table name for blocked time-slot intervals.
14 *
15 * @return string
16 */
17 function wpbc_availability_timeslots__table_name() {
18 global $wpdb;
19
20 return $wpdb->prefix . 'booking_availability_timeslots';
21 }
22
23 /**
24 * Get capability required to manage time-slot availability.
25 *
26 * @return string
27 */
28 function wpbc_availability_timeslots__get_manage_cap() {
29
30 $min_user_role = get_bk_option( 'booking_user_role_settings' );
31
32 $capability = array(
33 'administrator' => 'activate_plugins',
34 'editor' => 'publish_pages',
35 'author' => 'publish_posts',
36 'contributor' => 'edit_posts',
37 'subscriber' => 'read',
38 );
39
40 if ( isset( $capability[ $min_user_role ] ) ) {
41 return $capability[ $min_user_role ];
42 }
43
44 return 'manage_options';
45 }
46
47 /**
48 * Sanitize SQL date.
49 *
50 * @param mixed $date Date value.
51 *
52 * @return string
53 */
54 function wpbc_availability_timeslots__sanitize_date( $date ) {
55
56 if ( is_array( $date ) || is_object( $date ) ) {
57 return '';
58 }
59
60 $date = sanitize_text_field( wp_unslash( $date ) );
61 if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) && false !== strtotime( $date . ' 00:00:00' ) ) {
62 return $date;
63 }
64
65 return '';
66 }
67
68 /**
69 * Sanitize second in day.
70 *
71 * @param mixed $second Second value.
72 *
73 * @return int
74 */
75 function wpbc_availability_timeslots__sanitize_second( $second ) {
76
77 $second = intval( $second );
78
79 return max( 0, min( 86400, $second ) );
80 }
81
82 /**
83 * Normalize interval array.
84 *
85 * @param mixed $date Date.
86 * @param mixed $start_second Start second.
87 * @param mixed $end_second End second.
88 *
89 * @return array|null
90 */
91 function wpbc_availability_timeslots__normalize_interval( $date, $start_second, $end_second ) {
92
93 $date = wpbc_availability_timeslots__sanitize_date( $date );
94 $start_second = wpbc_availability_timeslots__sanitize_second( $start_second );
95 $end_second = wpbc_availability_timeslots__sanitize_second( $end_second );
96
97 if ( empty( $date ) || $start_second >= $end_second ) {
98 return null;
99 }
100
101 return array(
102 'date' => $date,
103 'start_second' => $start_second,
104 'end_second' => $end_second,
105 );
106 }
107
108 /**
109 * Convert seconds in day into readable 24h value.
110 *
111 * @param int $second Second.
112 *
113 * @return string
114 */
115 function wpbc_availability_timeslots__seconds_to_time( $second ) {
116
117 $second = wpbc_availability_timeslots__sanitize_second( $second );
118 $hours = floor( $second / HOUR_IN_SECONDS );
119 $mins = floor( ( $second % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS );
120
121 return sprintf( '%02d:%02d', $hours, $mins );
122 }
123
124 /**
125 * Convert interval to existing Booking Calendar readable time range.
126 *
127 * @param array $interval Interval.
128 *
129 * @return string
130 */
131 function wpbc_availability_timeslots__interval_to_readable( $interval ) {
132
133 return wpbc_availability_timeslots__seconds_to_time( $interval[0] ) . ' - ' . wpbc_availability_timeslots__seconds_to_time( $interval[1] );
134 }
135
136 /**
137 * Get SQL dates between two dates.
138 *
139 * @param string $date_start Start date.
140 * @param string $date_end End date.
141 *
142 * @return array
143 */
144 function wpbc_availability_timeslots__get_dates_in_range( $date_start, $date_end ) {
145
146 $date_start = wpbc_availability_timeslots__sanitize_date( $date_start );
147 $date_end = wpbc_availability_timeslots__sanitize_date( $date_end );
148 $dates = array();
149
150 if ( empty( $date_start ) || empty( $date_end ) ) {
151 return $dates;
152 }
153
154 if ( $date_start > $date_end ) {
155 $tmp = $date_start;
156 $date_start = $date_end;
157 $date_end = $tmp;
158 }
159
160 $timestamp = strtotime( $date_start . ' 00:00:00' );
161 $end_time = strtotime( $date_end . ' 00:00:00' );
162 $max_days = function_exists( 'wpbc_get_max_visible_days_in_calendar' ) ? intval( wpbc_get_max_visible_days_in_calendar() ) : 365;
163 $max_days = max( 1, min( 1095, $max_days ) );
164
165 while ( $timestamp <= $end_time && count( $dates ) < $max_days ) {
166 $dates[] = gmdate( 'Y-m-d', $timestamp );
167 $timestamp += DAY_IN_SECONDS;
168 }
169
170 return $dates;
171 }
172
173 /**
174 * Convert a Booking Calendar time range key into seconds.
175 *
176 * @param string|int $time_range_key Time key.
177 *
178 * @return array
179 */
180 function wpbc_availability_timeslots__time_key_to_seconds( $time_range_key ) {
181
182 if ( '0' === (string) $time_range_key || false === strpos( (string) $time_range_key, '-' ) ) {
183 return array( 0, 86400 );
184 }
185
186 return wpbc_transform_timerange__s_range__in__seconds_arr( $time_range_key );
187 }
188
189 /**
190 * Build date bounds for interval queries.
191 *
192 * @param string|array $dates_to_check Dates to check.
193 * @param int $max_days_count Maximum days count.
194 *
195 * @return array
196 */
197 function wpbc_availability_timeslots__get_date_bounds( $dates_to_check = 'CURDATE', $max_days_count = 365 ) {
198
199 $max_days_count = max( 1, intval( $max_days_count ) );
200
201 if ( is_array( $dates_to_check ) && ! empty( $dates_to_check ) ) {
202 $dates = array_values(
203 array_filter(
204 array_map( 'wpbc_availability_timeslots__sanitize_date', $dates_to_check )
205 )
206 );
207 sort( $dates );
208 if ( ! empty( $dates ) ) {
209 return array(
210 'start' => $dates[0],
211 'end' => $dates[ count( $dates ) - 1 ],
212 );
213 }
214 }
215
216 $today_ts = strtotime( wpbc_datetime_localized__use_wp_timezone( gmdate( 'Y-m-d H:i:s' ), 'Y-m-d 00:00:00' ) );
217
218 if ( 'ALL' === $dates_to_check ) {
219 return array(
220 'start' => gmdate( 'Y-m-d', strtotime( '-' . $max_days_count . ' days', $today_ts ) ),
221 'end' => gmdate( 'Y-m-d', strtotime( '+' . $max_days_count . ' days', $today_ts ) ),
222 );
223 }
224
225 return array(
226 'start' => gmdate( 'Y-m-d', $today_ts ),
227 'end' => gmdate( 'Y-m-d', strtotime( '+' . ( $max_days_count - 1 ) . ' days', $today_ts ) ),
228 );
229 }
230
231 /**
232 * Get blocked intervals for resources and dates.
233 *
234 * @param array $params Query params.
235 *
236 * @return array [date][resource_id][].
237 */
238 function wpbc_availability_timeslots__get_intervals_for_resources( $params = array() ) {
239
240 global $wpdb;
241
242 if ( ! wpbc_is_table_exists( 'booking_availability_timeslots' ) ) {
243 if ( function_exists( 'wpbc_activation__availability_timeslots' ) ) {
244 wpbc_activation__availability_timeslots();
245 }
246 if ( ! wpbc_is_table_exists( 'booking_availability_timeslots' ) ) {
247 return array();
248 }
249 }
250
251 $defaults = array(
252 'resource_id' => array(),
253 'dates_to_check' => 'CURDATE',
254 'date_start' => '',
255 'date_end' => '',
256 'max_days_count' => 365,
257 'use_cache' => true,
258 );
259 $params = wp_parse_args( $params, $defaults );
260
261 $resource_ids = wpbc_get_unique_array_of_resources_id( $params['resource_id'] );
262 $resource_ids = array_values( array_filter( array_map( 'intval', $resource_ids ) ) );
263
264 if ( empty( $resource_ids ) ) {
265 return array();
266 }
267
268 $date_start = wpbc_availability_timeslots__sanitize_date( $params['date_start'] );
269 $date_end = wpbc_availability_timeslots__sanitize_date( $params['date_end'] );
270
271 if ( empty( $date_start ) || empty( $date_end ) ) {
272 $bounds = wpbc_availability_timeslots__get_date_bounds( $params['dates_to_check'], $params['max_days_count'] );
273 $date_start = $bounds['start'];
274 $date_end = $bounds['end'];
275 }
276
277 if ( $date_start > $date_end ) {
278 $tmp = $date_start;
279 $date_start = $date_end;
280 $date_end = $tmp;
281 }
282
283 $cache_params = array(
284 'resource_id' => $resource_ids,
285 'date_start' => $date_start,
286 'date_end' => $date_end,
287 );
288
289 if ( $params['use_cache'] ) {
290 $cached_value = wpbc_cache__get( 'wpbc_availability_timeslots__get_intervals_for_resources', $cache_params );
291 if ( null !== $cached_value ) {
292 return $cached_value;
293 }
294 }
295
296 $table = wpbc_availability_timeslots__table_name();
297 $resource_placeholders = implode( ',', array_fill( 0, count( $resource_ids ), '%d' ) );
298 $query_values = array_merge( $resource_ids, array( $date_start, $date_end ) );
299
300 $sql = $wpdb->prepare(
301 "SELECT timeslot_id, resource_id, block_date, start_second, end_second, group_uid, source, note
302 FROM {$table}
303 WHERE status = 'active'
304 AND resource_id IN ({$resource_placeholders})
305 AND block_date >= %s
306 AND block_date <= %s
307 ORDER BY block_date ASC, resource_id ASC, start_second ASC, end_second ASC",
308 $query_values
309 );
310
311 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
312 $rows = $wpdb->get_results( $sql );
313
314 $intervals = array();
315
316 foreach ( $rows as $row ) {
317 $date = $row->block_date;
318 $resource_id = (int) $row->resource_id;
319
320 if ( ! isset( $intervals[ $date ] ) ) {
321 $intervals[ $date ] = array();
322 }
323 if ( ! isset( $intervals[ $date ][ $resource_id ] ) ) {
324 $intervals[ $date ][ $resource_id ] = array();
325 }
326
327 $intervals[ $date ][ $resource_id ][] = array(
328 'timeslot_id' => (int) $row->timeslot_id,
329 'resource_id' => $resource_id,
330 'date' => $date,
331 'start_second' => (int) $row->start_second,
332 'end_second' => (int) $row->end_second,
333 'group_uid' => (string) $row->group_uid,
334 'source' => (string) $row->source,
335 'note' => (string) $row->note,
336 'readable_time' => wpbc_availability_timeslots__seconds_to_time( (int) $row->start_second ) . ' - ' . wpbc_availability_timeslots__seconds_to_time( (int) $row->end_second ),
337 );
338 }
339
340 if ( $params['use_cache'] ) {
341 return wpbc_cache__save( 'wpbc_availability_timeslots__get_intervals_for_resources', $cache_params, $intervals );
342 }
343
344 return $intervals;
345 }
346
347 /**
348 * Merge aggregate resource blocks into primary resources.
349 *
350 * @param array $blocks Blocks [date][resource_id][].
351 * @param array $primary_resource_ids Primary resources.
352 * @param array $aggregate_resource_ids Aggregate resources.
353 *
354 * @return array
355 */
356 function wpbc_availability_timeslots__aggregate_merge_intervals( $blocks, $primary_resource_ids, $aggregate_resource_ids ) {
357
358 if ( empty( $aggregate_resource_ids ) || empty( $blocks ) ) {
359 return $blocks;
360 }
361
362 $primary_resource_ids = array_values( array_filter( array_map( 'intval', $primary_resource_ids ) ) );
363 $aggregate_resource_ids = array_values( array_filter( array_map( 'intval', $aggregate_resource_ids ) ) );
364
365 foreach ( $blocks as $date => $resource_blocks ) {
366 foreach ( $primary_resource_ids as $primary_resource_id ) {
367 if ( ! isset( $blocks[ $date ][ $primary_resource_id ] ) ) {
368 $blocks[ $date ][ $primary_resource_id ] = array();
369 }
370 foreach ( $aggregate_resource_ids as $aggregate_resource_id ) {
371 if ( empty( $resource_blocks[ $aggregate_resource_id ] ) ) {
372 continue;
373 }
374 $blocks[ $date ][ $primary_resource_id ] = array_merge( $blocks[ $date ][ $primary_resource_id ], $resource_blocks[ $aggregate_resource_id ] );
375 }
376 }
377 }
378
379 return $blocks;
380 }
381
382 /**
383 * Normalize a time interval into seconds array.
384 *
385 * @param array|string $interval Interval as array( start, end ) or 'start - end'.
386 *
387 * @return array
388 */
389 function wpbc_availability_timeslots__normalize_seconds_interval( $interval ) {
390
391 if ( is_string( $interval ) ) {
392 $interval = explode( '-', $interval );
393 }
394
395 if ( ! is_array( $interval ) || count( $interval ) < 2 ) {
396 return array();
397 }
398
399 return array(
400 wpbc_availability_timeslots__sanitize_second( $interval[0] ),
401 wpbc_availability_timeslots__sanitize_second( $interval[1] ),
402 );
403 }
404
405 /**
406 * Check if some form slot is still free.
407 *
408 * @param array $timeslots_to_check Timeslots as seconds arrays or 'start - end' strings.
409 * @param array $busy_intervals Busy intervals as seconds arrays or 'start - end' strings.
410 *
411 * @return bool
412 */
413 function wpbc_availability_timeslots__is_some_timeslot_available( $timeslots_to_check, $busy_intervals ) {
414
415 if ( empty( $timeslots_to_check ) ) {
416 return true;
417 }
418
419 $has_valid_slot = false;
420
421 foreach ( $timeslots_to_check as $slot ) {
422 $slot = wpbc_availability_timeslots__normalize_seconds_interval( $slot );
423
424 if ( empty( $slot ) || $slot[0] >= $slot[1] ) {
425 continue;
426 }
427
428 $has_valid_slot = true;
429 $is_intersected = false;
430 foreach ( $busy_intervals as $busy_interval ) {
431 $busy_interval = wpbc_availability_timeslots__normalize_seconds_interval( $busy_interval );
432
433 if ( empty( $busy_interval ) || $busy_interval[0] >= $busy_interval[1] ) {
434 continue;
435 }
436
437 if ( wpbc_is__intervals__intersected( $slot, $busy_interval ) ) {
438 $is_intersected = true;
439 break;
440 }
441 }
442
443 if ( ! $is_intersected ) {
444 return true;
445 }
446 }
447
448 if ( ! $has_valid_slot ) {
449 return true;
450 }
451
452 return false;
453 }
454
455 /**
456 * Apply saved blocked intervals to one availability resource object.
457 *
458 * @param object $availability_obj Availability object.
459 * @param array $blocked_intervals Blocked DB rows.
460 * @param array $timeslots_to_check Timeslots from booking form.
461 *
462 * @return object
463 */
464 function wpbc_availability_timeslots__apply_blocks_to_availability_obj( $availability_obj, $blocked_intervals, $timeslots_to_check = array() ) {
465
466 if ( empty( $blocked_intervals ) ) {
467 return $availability_obj;
468 }
469
470 $was_day_unavailable = ! empty( $availability_obj->is_day_unavailable );
471 $previous_day_status = $availability_obj->_day_status;
472
473 foreach ( $blocked_intervals as $blocked_interval ) {
474 $source_type = isset( $blocked_interval['source_type'] ) ? sanitize_key( $blocked_interval['source_type'] ) : 'availability_timeslot';
475 $is_working_time = ( 'working_time' === $source_type );
476 $timeslot_id = isset( $blocked_interval['timeslot_id'] ) ? (int) $blocked_interval['timeslot_id'] : 0;
477 $time_range = array(
478 wpbc_availability_timeslots__sanitize_second( $blocked_interval['start_second'] ),
479 wpbc_availability_timeslots__sanitize_second( $blocked_interval['end_second'] ),
480 );
481 $details_id = $is_working_time
482 ? 'working_time_' . ( isset( $blocked_interval['resource_id'] ) ? intval( $blocked_interval['resource_id'] ) : 0 ) . '_' . ( isset( $blocked_interval['date'] ) ? preg_replace( '/[^0-9\-]/', '', $blocked_interval['date'] ) : '' ) . '_' . $time_range[0] . '_' . $time_range[1]
483 : 'availability_timeslot_' . $timeslot_id;
484 $status_title = $is_working_time ? __( 'Outside working time', 'booking' ) : __( 'Unavailable time-slot', 'booking' );
485
486 if ( $time_range[0] >= $time_range[1] ) {
487 continue;
488 }
489
490 if ( ! $was_day_unavailable && ! $is_working_time ) {
491 $availability_obj->_day_status = 'time_slots_booking';
492 }
493 $availability_obj->booked_time_slots['in_seconds'][] = $time_range;
494 $availability_obj->booked_time_slots['readable24h'][] = wpbc_availability_timeslots__interval_to_readable( $time_range );
495 if ( ! $was_day_unavailable && ! $is_working_time ) {
496 $availability_obj->pending_approved[ $time_range[0] ] = 'approved';
497 }
498
499 $time_range_key = $time_range[0] . ' - ' . $time_range[1];
500 if ( empty( $availability_obj->__booked__times__details ) ) {
501 $availability_obj->__booked__times__details = array();
502 }
503
504 $availability_obj->__booked__times__details[ $time_range_key ] = (object) array(
505 'approved' => '1',
506 'booking_id' => $details_id,
507 'unavailable_timeslot_id' => $is_working_time ? 0 : $timeslot_id,
508 'availability_timeslot_id' => $is_working_time ? 0 : $timeslot_id,
509 'source_type' => $source_type,
510 'time_slot_status' => 'unavailable',
511 'time_slot_status_title' => $status_title,
512 'parent' => '0',
513 'prioritet' => '0',
514 'type' => isset( $blocked_interval['resource_id'] ) ? (string) intval( $blocked_interval['resource_id'] ) : '0',
515 'date_res_type' => null,
516 'block_date' => isset( $blocked_interval['date'] ) ? $blocked_interval['date'] : '',
517 'readable_time' => wpbc_transform_timerange__seconds_arr__in__formated_hours( $time_range ),
518 'group_uid' => isset( $blocked_interval['group_uid'] ) ? (string) $blocked_interval['group_uid'] : '',
519 '__summary__booking' => (object) array(
520 'source_type' => $source_type,
521 'unavailable_timeslot_id' => $is_working_time ? 0 : $timeslot_id,
522 'time_slot_status' => 'unavailable',
523 ),
524 );
525
526 if ( 'On' === get_bk_option( 'booking_is_show_booked_data_in_tooltips' ) ) {
527 $tooltip_text = '';
528 $tooltip_text .= '<div class="wpbc_tooltip_item tooltip_booked_time">' . wpbc_transform_timerange__seconds_arr__in__formated_hours( $time_range ) . '</div>';
529 $tooltip_text .= '<div class="wpbc_tooltip_item">' . esc_html( $status_title ) . '</div>';
530 $availability_obj->tooltips['details'][ $details_id ] = $tooltip_text;
531 }
532 }
533
534 $merged_seconds_arr = wpbc_merge_intersected_intervals( $availability_obj->booked_time_slots['in_seconds'] );
535
536 $availability_obj->booked_time_slots['merged_seconds'] = $merged_seconds_arr;
537 $availability_obj->booked_time_slots['merged_readable'] = array();
538
539 foreach ( $merged_seconds_arr as $busy_interval ) {
540 $availability_obj->booked_time_slots['merged_readable'][] = wpbc_transform_timerange__seconds_arr__in__formated_hours( $busy_interval );
541 }
542
543 if ( $was_day_unavailable ) {
544 $availability_obj->_day_status = $previous_day_status;
545 return $availability_obj;
546 }
547
548 ksort( $availability_obj->pending_approved );
549
550 if ( ! empty( $availability_obj->booked_time_slots['merged_readable'] ) ) {
551 $tooltip_content_arr = array_map(
552 function ( $item ) {
553 return '<div class="wpbc_tooltip_item">' . $item . '</div>';
554 },
555 $availability_obj->booked_time_slots['merged_readable']
556 );
557
558 $availability_obj->tooltips['times'] = '<div class="wpbc_tooltip_resource_container">' . implode( '', $tooltip_content_arr ) . '</div>';
559 }
560
561 if (
562 ( ! empty( $merged_seconds_arr ) )
563 && (
564 ( 0 >= (int) $merged_seconds_arr[0][0] && 86400 <= (int) $merged_seconds_arr[0][1] )
565 || ( ! wpbc_availability_timeslots__is_some_timeslot_available( $timeslots_to_check, $merged_seconds_arr ) )
566 )
567 ) {
568 $availability_obj->is_day_unavailable = true;
569 $availability_obj->_day_status = 'full_day_booking';
570 }
571
572 return $availability_obj;
573 }
574
575 /**
576 * Get admin URL for editing the rule that made a timeline day unavailable.
577 *
578 * @param string $source_type Unavailable source type.
579 * @param int $resource_id Booking resource ID.
580 *
581 * @return string
582 */
583 function wpbc_availability_timeslots__get_unavailable_rule_url( $source_type, $resource_id = 0 ) {
584
585 switch ( $source_type ) {
586 case 'days_availability':
587 $url = function_exists( 'wpbc_get_availability_url' )
588 ? esc_url_raw( wpbc_get_availability_url( true, false ) . '&tab=availability' )
589 : esc_url_raw( admin_url( 'admin.php?page=wpbc-availability&tab=availability' ) );
590 break;
591
592 case 'season_filter':
593 $url = function_exists( 'wpbc_get_availability_url' )
594 ? esc_url_raw( wpbc_get_availability_url( true, false ) . '&tab=season_availability' )
595 : esc_url_raw( admin_url( 'admin.php?page=wpbc-availability&tab=season_availability' ) );
596 break;
597
598 case 'weekday_unavailable':
599 case 'from_today_unavailable':
600 case 'limit_available_from_today':
601 case 'working_time':
602 $url = function_exists( 'wpbc_get_general_availability_url' )
603 ? esc_url_raw( wpbc_get_general_availability_url( true, false ) )
604 : esc_url_raw( admin_url( 'admin.php?page=wpbc-availability&tab=general_availability' ) );
605 $url = add_query_arg( 'wpbc_ag_open', 'working_time', $url );
606 break;
607
608 default:
609 return '';
610 }
611
612 if ( ! empty( $resource_id ) ) {
613 $url = add_query_arg( 'resource_id', absint( $resource_id ), $url );
614 }
615
616 return esc_url_raw( $url );
617 }
618
619 /**
620 * Get full-day unavailable statuses that come from availability rules.
621 *
622 * @return array
623 */
624 function wpbc_availability_timeslots__get_unavailable_day_statuses() {
625
626 return array(
627 'resource_availability' => array(
628 'source_type' => 'days_availability',
629 'title' => __( 'Unavailable day', 'booking' ),
630 'source' => __( 'Days Availability', 'booking' ),
631 ),
632 'weekday_unavailable' => array(
633 'source_type' => 'weekday_unavailable',
634 'title' => __( 'Unavailable weekday', 'booking' ),
635 'source' => __( 'General availability', 'booking' ),
636 ),
637 'season_filter' => array(
638 'source_type' => 'season_filter',
639 'title' => __( 'Season unavailable', 'booking' ),
640 'source' => __( 'Season Availability', 'booking' ),
641 ),
642 'from_today_unavailable' => array(
643 'source_type' => 'from_today_unavailable',
644 'title' => __( 'Unavailable from today', 'booking' ),
645 'source' => __( 'General availability', 'booking' ),
646 ),
647 'limit_available_from_today' => array(
648 'source_type' => 'limit_available_from_today',
649 'title' => __( 'Outside available date range', 'booking' ),
650 'source' => __( 'General availability', 'booking' ),
651 ),
652 );
653 }
654
655 /**
656 * Maybe add a read-only full-day bar for unavailable day rules.
657 *
658 * @param array $bars Bars array.
659 * @param string $date SQL date.
660 * @param int $resource_id Resource ID.
661 * @param object $availability_obj Availability object.
662 *
663 * @return array
664 */
665 function wpbc_availability_timeslots__maybe_add_unavailable_day_bar( $bars, $date, $resource_id, $availability_obj ) {
666
667 if ( empty( $availability_obj->_day_status ) ) {
668 return $bars;
669 }
670
671 $unavailable_statuses = wpbc_availability_timeslots__get_unavailable_day_statuses();
672 $day_status = (string) $availability_obj->_day_status;
673
674 if ( ! isset( $unavailable_statuses[ $day_status ] ) ) {
675 return $bars;
676 }
677
678 if ( ! isset( $bars[ $date ] ) ) {
679 $bars[ $date ] = array();
680 }
681 if ( ! isset( $bars[ $date ][ $resource_id ] ) ) {
682 $bars[ $date ][ $resource_id ] = array();
683 }
684
685 $status = $unavailable_statuses[ $day_status ];
686 $rule_url = wpbc_availability_timeslots__get_unavailable_rule_url( $status['source_type'], $resource_id );
687
688 $bars[ $date ][ $resource_id ][] = array(
689 'type' => 'unavailable_day',
690 'source_type' => $status['source_type'],
691 'editable' => false,
692 'start_second' => 0,
693 'end_second' => 86400,
694 'start_minute' => 0,
695 'end_minute' => 1440,
696 'booking_id' => '',
697 'booking_url' => '',
698 'rule_url' => $rule_url,
699 'rule_source' => $status['source'],
700 'unavailable_timeslot_id' => 0,
701 'approved' => '',
702 'status_title' => $status['title'],
703 'time_text' => __( 'Full day', 'booking' ),
704 'tooltip' => sprintf(
705 /* translators: 1: unavailable day status, 2: rule source. */
706 __( '%1$s | Source: %2$s', 'booking' ),
707 $status['title'],
708 $status['source']
709 ),
710 );
711
712 return $bars;
713 }
714
715 /**
716 * Convert availability details into timeline bars.
717 *
718 * @param array $availability_dates Availability dates.
719 * @param array $resource_ids Resource IDs.
720 *
721 * @return array
722 */
723 function wpbc_availability_timeslots__availability_to_bars( $availability_dates, $resource_ids ) {
724
725 $bars = array();
726 $resource_ids = array_values( array_filter( array_map( 'intval', $resource_ids ) ) );
727
728 foreach ( $availability_dates as $date => $date_obj ) {
729 foreach ( $resource_ids as $resource_id ) {
730 if ( empty( $date_obj[ $resource_id ] ) ) {
731 continue;
732 }
733
734 $bars = wpbc_availability_timeslots__maybe_add_unavailable_day_bar( $bars, $date, $resource_id, $date_obj[ $resource_id ] );
735
736 if ( empty( $date_obj[ $resource_id ]->__booked__times__details ) ) {
737 continue;
738 }
739
740 if ( ! isset( $bars[ $date ] ) ) {
741 $bars[ $date ] = array();
742 }
743 if ( ! isset( $bars[ $date ][ $resource_id ] ) ) {
744 $bars[ $date ][ $resource_id ] = array();
745 }
746
747 foreach ( $date_obj[ $resource_id ]->__booked__times__details as $time_key => $details ) {
748 $seconds = wpbc_availability_timeslots__time_key_to_seconds( $time_key );
749 $source_type = isset( $details->source_type ) ? sanitize_key( $details->source_type ) : 'booking';
750 $source_type = in_array( $source_type, array( 'availability_timeslot', 'working_time' ), true ) ? $source_type : 'booking';
751 $time_text = wpbc_transform_timerange__seconds_arr__in__formated_hours( $seconds );
752 $booking_id = isset( $details->booking_id ) ? (string) $details->booking_id : '';
753 $booking_url = '';
754 $status_text = '';
755 $tooltip = '';
756
757 if ( 'booking' === $source_type ) {
758 if ( '' !== $booking_id ) {
759 $booking_url = esc_url_raw( wpbc_get_bookings_url( true, false ) . '&wh_booking_id=' . intval( $booking_id ) . '&tab=vm_booking_listing' );
760 }
761 $status_text = ( isset( $details->approved ) && '1' === (string) $details->approved ) ? __( 'Approved', 'booking' ) : __( 'Pending', 'booking' );
762 $tooltip = sprintf(
763 /* translators: 1: booking id, 2: booking status, 3: booked time. */
764 __( 'Booking ID: %1$s | %2$s | %3$s', 'booking' ),
765 $booking_id,
766 $status_text,
767 $time_text
768 );
769 } elseif ( 'working_time' === $source_type ) {
770 $status_text = __( 'Outside working time', 'booking' );
771 $tooltip = sprintf(
772 /* translators: 1: unavailable time, 2: rule source. */
773 __( '%1$s | Source: %2$s', 'booking' ),
774 $time_text,
775 __( 'Working Time', 'booking' )
776 );
777 } else {
778 $status_text = __( 'Unavailable time-slot', 'booking' );
779 $tooltip = sprintf(
780 /* translators: 1: unavailable time-slot id, 2: unavailable time. */
781 __( 'Unavailable time-slot ID: %1$s | %2$s', 'booking' ),
782 isset( $details->unavailable_timeslot_id ) ? (int) $details->unavailable_timeslot_id : 0,
783 $time_text
784 );
785 }
786
787 $bars[ $date ][ $resource_id ][] = array(
788 'type' => ( 'working_time' === $source_type ) ? 'working_time' : ( ( 'availability_timeslot' === $source_type ) ? 'blocked' : 'booked' ),
789 'source_type' => $source_type,
790 'editable' => ( 'availability_timeslot' === $source_type ),
791 'start_second' => (int) $seconds[0],
792 'end_second' => (int) $seconds[1],
793 'start_minute' => (int) floor( $seconds[0] / MINUTE_IN_SECONDS ),
794 'end_minute' => (int) ceil( $seconds[1] / MINUTE_IN_SECONDS ),
795 'booking_id' => $booking_id,
796 'booking_url' => $booking_url,
797 'rule_url' => ( 'working_time' === $source_type ) ? wpbc_availability_timeslots__get_unavailable_rule_url( 'working_time', $resource_id ) : '',
798 'rule_source' => ( 'working_time' === $source_type ) ? __( 'Working Time', 'booking' ) : '',
799 'unavailable_timeslot_id' => isset( $details->unavailable_timeslot_id ) ? (int) $details->unavailable_timeslot_id : 0,
800 'approved' => isset( $details->approved ) ? (string) $details->approved : '',
801 'status_title' => $status_text,
802 'time_text' => $time_text,
803 'tooltip' => $tooltip,
804 );
805 }
806 }
807 }
808
809 return $bars;
810 }
811
812 /**
813 * Add read-only bars for time outside configured Working Time.
814 *
815 * @param array $bars Timeline bars.
816 * @param array $dates Dates to check.
817 * @param array $resource_ids Resource IDs.
818 *
819 * @return array
820 */
821 function wpbc_availability_timeslots__add_working_time_bars( $bars, $dates, $resource_ids ) {
822
823 if ( ! function_exists( 'wpbc_working_time__get_non_working_intervals_for_date' ) ) {
824 return $bars;
825 }
826
827 $dates = array_values( array_filter( array_map( 'wpbc_availability_timeslots__sanitize_date', $dates ) ) );
828 $resource_ids = array_values( array_filter( array_map( 'intval', $resource_ids ) ) );
829
830 foreach ( $dates as $date ) {
831 foreach ( $resource_ids as $resource_id ) {
832 if ( ! empty( $bars[ $date ][ $resource_id ] ) ) {
833 foreach ( $bars[ $date ][ $resource_id ] as $existing_bar ) {
834 if ( 'unavailable_day' === ( isset( $existing_bar['type'] ) ? $existing_bar['type'] : '' ) ) {
835 continue 2;
836 }
837 }
838 }
839
840 $intervals = wpbc_working_time__get_non_working_intervals_for_date( $resource_id, $date );
841
842 if ( empty( $intervals ) ) {
843 continue;
844 }
845
846 if ( ! isset( $bars[ $date ] ) ) {
847 $bars[ $date ] = array();
848 }
849 if ( ! isset( $bars[ $date ][ $resource_id ] ) ) {
850 $bars[ $date ][ $resource_id ] = array();
851 }
852
853 foreach ( $intervals as $interval ) {
854 $start_second = wpbc_availability_timeslots__sanitize_second( $interval['start_second'] );
855 $end_second = wpbc_availability_timeslots__sanitize_second( $interval['end_second'] );
856
857 if ( $start_second >= $end_second ) {
858 continue;
859 }
860
861 $time_text = wpbc_transform_timerange__seconds_arr__in__formated_hours( array( $start_second, $end_second ) );
862
863 $bars[ $date ][ $resource_id ][] = array(
864 'type' => 'working_time',
865 'source_type' => 'working_time',
866 'editable' => false,
867 'start_second' => $start_second,
868 'end_second' => $end_second,
869 'start_minute' => (int) floor( $start_second / MINUTE_IN_SECONDS ),
870 'end_minute' => (int) ceil( $end_second / MINUTE_IN_SECONDS ),
871 'booking_id' => '',
872 'booking_url' => '',
873 'rule_url' => wpbc_availability_timeslots__get_unavailable_rule_url( 'working_time', $resource_id ),
874 'rule_source' => __( 'Working Time', 'booking' ),
875 'unavailable_timeslot_id' => 0,
876 'approved' => '',
877 'status_title' => __( 'Outside working time', 'booking' ),
878 'time_text' => $time_text,
879 'tooltip' => sprintf(
880 /* translators: 1: unavailable time, 2: rule source. */
881 __( '%1$s | Source: %2$s', 'booking' ),
882 $time_text,
883 __( 'Working Time', 'booking' )
884 ),
885 );
886 }
887 }
888 }
889
890 return $bars;
891 }
892
893 /**
894 * AJAX: read blocked time slots for a calendar/frontend request.
895 *
896 * @return void
897 */
898 function wpbc_availability_timeslots_ajax_read() {
899
900 $resource_id = isset( $_REQUEST['resource_id'] ) ? intval( $_REQUEST['resource_id'] ) : wpbc_get_default_resource(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
901 $date_start = isset( $_REQUEST['date_start'] ) ? wpbc_availability_timeslots__sanitize_date( $_REQUEST['date_start'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
902 $date_end = isset( $_REQUEST['date_end'] ) ? wpbc_availability_timeslots__sanitize_date( $_REQUEST['date_end'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
903
904 $intervals = wpbc_availability_timeslots__get_intervals_for_resources(
905 array(
906 'resource_id' => $resource_id,
907 'date_start' => $date_start,
908 'date_end' => $date_end,
909 )
910 );
911
912 $dates_to_check = wpbc_availability_timeslots__get_dates_in_range( $date_start, $date_end );
913 $availability = wpbc_get_availability_per_days_arr(
914 array(
915 'resource_id' => $resource_id,
916 'dates_to_check' => $dates_to_check,
917 'force_check_from_today_unavailable' => true,
918 )
919 );
920 $resource_ids = isset( $availability['resources_id_arr__in_dates'] ) ? $availability['resources_id_arr__in_dates'] : array( $resource_id );
921 $timeline_bars = wpbc_availability_timeslots__availability_to_bars( $availability['dates'], $resource_ids );
922 $timeline_bars = wpbc_availability_timeslots__add_working_time_bars( $timeline_bars, $dates_to_check, $resource_ids );
923
924 wp_send_json_success(
925 array(
926 'resource_id' => $resource_id,
927 'date_start' => $date_start,
928 'date_end' => $date_end,
929 'intervals' => $intervals,
930 'bars' => $timeline_bars,
931 )
932 );
933 }
934 add_action( 'wp_ajax_WPBC_AJX_AVAILABILITY_TIMESLOTS_READ', 'wpbc_availability_timeslots_ajax_read' );
935 add_action( 'wp_ajax_nopriv_WPBC_AJX_AVAILABILITY_TIMESLOTS_READ', 'wpbc_availability_timeslots_ajax_read' );
936