PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 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 All 205 releases
booking / includes / _working_time / working_time.php

working_time.php in Booking Calendar 11.9, at includes/_working_time/working_time.php

486 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Working Time settings and availability helpers.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Get default Working Time settings.
14 *
15 * @return array
16 */
17 function wpbc_working_time__get_default_settings() {
18
19 $weekdays = array();
20
21 for ( $day = 0; $day <= 6; $day++ ) {
22 $weekdays[ $day ] = array();
23 if ( $day >= 1 && $day <= 5 ) {
24 $weekdays[ $day ][] = array(
25 'start_second' => 9 * HOUR_IN_SECONDS,
26 'end_second' => 18 * HOUR_IN_SECONDS,
27 );
28 }
29 }
30
31 return array(
32 'enabled' => 'Off',
33 'default' => array(
34 'weekdays' => $weekdays,
35 ),
36 'resources' => array(),
37 );
38 }
39
40 /**
41 * Normalize Working Time settings.
42 *
43 * @param mixed $settings Settings.
44 *
45 * @return array
46 */
47 function wpbc_working_time__normalize_settings( $settings ) {
48
49 $defaults = wpbc_working_time__get_default_settings();
50
51 if ( ! is_array( $settings ) ) {
52 return $defaults;
53 }
54
55 $settings = wp_parse_args( $settings, $defaults );
56
57 $settings['enabled'] = ( 'On' === (string) $settings['enabled'] ) ? 'On' : 'Off';
58 $settings['default'] = array(
59 'weekdays' => wpbc_working_time__normalize_weekday_intervals(
60 isset( $settings['default']['weekdays'] ) ? $settings['default']['weekdays'] : array()
61 ),
62 );
63
64 if ( ! is_array( $settings['resources'] ) ) {
65 $settings['resources'] = array();
66 }
67
68 foreach ( $settings['resources'] as $resource_id => $resource_settings ) {
69 $resource_id = absint( $resource_id );
70 if ( empty( $resource_id ) || ! is_array( $resource_settings ) ) {
71 unset( $settings['resources'][ $resource_id ] );
72 continue;
73 }
74
75 $mode = isset( $resource_settings['mode'] ) ? (string) $resource_settings['mode'] : 'inherit';
76 if ( ! in_array( $mode, array( 'inherit', 'custom', 'disabled' ), true ) ) {
77 $mode = 'inherit';
78 }
79
80 $settings['resources'][ $resource_id ] = array(
81 'mode' => $mode,
82 'weekdays' => wpbc_working_time__normalize_weekday_intervals(
83 isset( $resource_settings['weekdays'] ) ? $resource_settings['weekdays'] : array()
84 ),
85 );
86 }
87
88 return $settings;
89 }
90
91 /**
92 * Normalize weekly intervals.
93 *
94 * @param mixed $weekdays Weekday intervals.
95 *
96 * @return array
97 */
98 function wpbc_working_time__normalize_weekday_intervals( $weekdays ) {
99
100 $normalized = array();
101
102 for ( $day = 0; $day <= 6; $day++ ) {
103 $normalized[ $day ] = array();
104
105 if ( empty( $weekdays[ $day ] ) || ! is_array( $weekdays[ $day ] ) ) {
106 continue;
107 }
108
109 foreach ( $weekdays[ $day ] as $interval ) {
110 if ( ! is_array( $interval ) ) {
111 continue;
112 }
113
114 $start_second = isset( $interval['start_second'] ) ? absint( $interval['start_second'] ) : 0;
115 $end_second = isset( $interval['end_second'] ) ? absint( $interval['end_second'] ) : 0;
116 $start_second = max( 0, min( DAY_IN_SECONDS, $start_second ) );
117 $end_second = max( 0, min( DAY_IN_SECONDS, $end_second ) );
118
119 if ( $start_second < $end_second ) {
120 $normalized[ $day ][] = array(
121 'start_second' => $start_second,
122 'end_second' => $end_second,
123 );
124 }
125 }
126
127 usort(
128 $normalized[ $day ],
129 function ( $a, $b ) {
130 return $a['start_second'] - $b['start_second'];
131 }
132 );
133 }
134
135 return $normalized;
136 }
137
138 /**
139 * Validate a complete canonical weekly Working Time schedule.
140 *
141 * This strict validator complements the compatibility normalizer above. It is
142 * intended for administration mutation boundaries, where silently dropping an
143 * invalid or overlapping interval would make the saved schedule differ from
144 * what the user submitted. Intervals that touch at one boundary are valid.
145 *
146 * @param mixed $weekdays Untrusted weekday interval records.
147 * @param int $max_intervals_per_day Maximum intervals accepted for one weekday.
148 *
149 * @return array<int,array<int,array{start_second:int,end_second:int}>>|WP_Error Validated weekdays or an error.
150 */
151 function wpbc_working_time__validate_weekday_intervals( $weekdays, $max_intervals_per_day = 8 ) {
152
153 if ( ! is_array( $weekdays ) ) {
154 return new WP_Error( 'wpbc_working_time_weekdays_invalid', __( 'Enter a valid weekly Working Time schedule.', 'booking' ) );
155 }
156
157 foreach ( array_keys( $weekdays ) as $day_key ) {
158 if ( ! preg_match( '/^[0-6]$/', (string) $day_key ) ) {
159 return new WP_Error( 'wpbc_working_time_weekday_invalid', __( 'The Working Time schedule contains an invalid weekday.', 'booking' ) );
160 }
161 }
162
163 $max_intervals_per_day = max( 1, min( 48, absint( $max_intervals_per_day ) ) );
164 $normalized_weekdays = array();
165
166 for ( $day_number = 0; $day_number <= 6; $day_number++ ) {
167 $raw_intervals = isset( $weekdays[ $day_number ] ) && is_array( $weekdays[ $day_number ] )
168 ? array_values( $weekdays[ $day_number ] )
169 : array();
170
171 if ( count( $raw_intervals ) > $max_intervals_per_day ) {
172 return new WP_Error(
173 'wpbc_working_time_interval_limit',
174 /* translators: %d: Maximum intervals per weekday. */
175 sprintf( __( 'Add no more than %d Working Time intervals to one weekday.', 'booking' ), $max_intervals_per_day ),
176 array( 'day_number' => $day_number )
177 );
178 }
179
180 $normalized_intervals = array();
181 foreach ( $raw_intervals as $raw_interval ) {
182 if ( ! is_array( $raw_interval ) || array_diff( array_keys( $raw_interval ), array( 'start_second', 'end_second' ) ) ) {
183 return new WP_Error(
184 'wpbc_working_time_interval_invalid',
185 __( 'Enter a valid start and end time for every Working Time interval.', 'booking' ),
186 array( 'day_number' => $day_number )
187 );
188 }
189
190 $raw_start_second = isset( $raw_interval['start_second'] ) ? $raw_interval['start_second'] : null;
191 $raw_end_second = isset( $raw_interval['end_second'] ) ? $raw_interval['end_second'] : null;
192 if (
193 ! is_scalar( $raw_start_second )
194 || ! is_scalar( $raw_end_second )
195 || ! preg_match( '/^\d+$/', (string) $raw_start_second )
196 || ! preg_match( '/^\d+$/', (string) $raw_end_second )
197 ) {
198 return new WP_Error(
199 'wpbc_working_time_interval_invalid',
200 __( 'Enter a valid start and end time for every Working Time interval.', 'booking' ),
201 array( 'day_number' => $day_number )
202 );
203 }
204
205 $start_second = absint( $raw_start_second );
206 $end_second = absint( $raw_end_second );
207 if (
208 $start_second >= $end_second
209 || DAY_IN_SECONDS < $end_second
210 || 0 !== $start_second % MINUTE_IN_SECONDS
211 || 0 !== $end_second % MINUTE_IN_SECONDS
212 ) {
213 return new WP_Error(
214 'wpbc_working_time_interval_invalid',
215 __( 'Every Working Time interval must end after it starts and stay within one day.', 'booking' ),
216 array( 'day_number' => $day_number )
217 );
218 }
219
220 $normalized_intervals[] = array(
221 'start_second' => $start_second,
222 'end_second' => $end_second,
223 );
224 }
225
226 usort(
227 $normalized_intervals,
228 function ( $first_interval, $second_interval ) {
229 if ( $first_interval['start_second'] === $second_interval['start_second'] ) {
230 return $first_interval['end_second'] - $second_interval['end_second'];
231 }
232
233 return $first_interval['start_second'] - $second_interval['start_second'];
234 }
235 );
236
237 $previous_end = -1;
238 foreach ( $normalized_intervals as $normalized_interval ) {
239 if ( $normalized_interval['start_second'] < $previous_end ) {
240 return new WP_Error(
241 'wpbc_working_time_interval_overlap',
242 __( 'Working Time intervals on the same weekday cannot overlap or repeat.', 'booking' ),
243 array( 'day_number' => $day_number )
244 );
245 }
246 $previous_end = $normalized_interval['end_second'];
247 }
248
249 $normalized_weekdays[ $day_number ] = $normalized_intervals;
250 }
251
252 return $normalized_weekdays;
253 }
254
255 /**
256 * Get stored Working Time settings.
257 *
258 * @return array
259 */
260 function wpbc_working_time__get_settings() {
261
262 $settings = get_bk_option( 'booking_working_time_rules', array() );
263
264 return wpbc_working_time__normalize_settings( maybe_unserialize( $settings ) );
265 }
266
267 /**
268 * Save Working Time settings.
269 *
270 * @param array $settings Settings.
271 *
272 * @return void
273 */
274 function wpbc_working_time__update_settings( $settings ) {
275
276 $settings = wpbc_working_time__normalize_settings( $settings );
277
278 update_bk_option( 'booking_working_time_rules', $settings );
279 update_bk_option( 'booking_working_time_enabled', $settings['enabled'] );
280 }
281
282 /**
283 * Add default Working Time options on activation/update.
284 *
285 * @return void
286 */
287 function wpbc_activation__working_time() {
288
289 $settings = wpbc_working_time__get_default_settings();
290
291 add_bk_option( 'booking_working_time_enabled', 'Off' );
292 add_bk_option( 'booking_working_time_rules', $settings );
293 }
294 add_bk_action( 'wpbc_free_version_activation', 'wpbc_activation__working_time' );
295 add_bk_action( 'wpbc_other_versions_activation', 'wpbc_activation__working_time' );
296
297 /**
298 * Keep Working Time options present after plugin updates for existing installs.
299 *
300 * @return void
301 */
302 function wpbc_working_time__maybe_install_options() {
303
304 if ( ! is_admin() ) {
305 return;
306 }
307
308 if (
309 ( false === get_bk_option( 'booking_working_time_enabled', false ) )
310 || ( false === get_bk_option( 'booking_working_time_rules', false ) )
311 ) {
312 wpbc_activation__working_time();
313 }
314 }
315 add_action( 'admin_init', 'wpbc_working_time__maybe_install_options' );
316
317 /**
318 * Remove Working Time options when Booking Calendar data is deleted on deactivation.
319 *
320 * @return void
321 */
322 function wpbc_deactivation__working_time() {
323
324 delete_bk_option( 'booking_working_time_enabled' );
325 delete_bk_option( 'booking_working_time_rules' );
326 }
327 add_bk_action( 'wpbc_free_version_deactivation', 'wpbc_deactivation__working_time' );
328 add_bk_action( 'wpbc_other_versions_deactivation', 'wpbc_deactivation__working_time' );
329
330 /**
331 * Convert time string into seconds.
332 *
333 * @param string $time_value Time in H:i format.
334 *
335 * @return int
336 */
337 function wpbc_working_time__time_to_seconds( $time_value ) {
338
339 $time_value = (string) $time_value;
340 if ( ! preg_match( '/^([01]?\d|2[0-4]):([0-5]\d)$/', $time_value, $matches ) ) {
341 return 0;
342 }
343
344 $hours = min( 24, absint( $matches[1] ) );
345 $mins = ( 24 === $hours ) ? 0 : absint( $matches[2] );
346
347 return min( DAY_IN_SECONDS, ( $hours * HOUR_IN_SECONDS ) + ( $mins * MINUTE_IN_SECONDS ) );
348 }
349
350 /**
351 * Convert seconds into H:i time.
352 *
353 * @param int $seconds Seconds.
354 *
355 * @return string
356 */
357 function wpbc_working_time__seconds_to_time( $seconds ) {
358
359 $seconds = max( 0, min( DAY_IN_SECONDS, absint( $seconds ) ) );
360 $hours = (int) floor( $seconds / HOUR_IN_SECONDS );
361 $mins = (int) floor( ( $seconds % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS );
362
363 return sprintf( '%02d:%02d', $hours, $mins );
364 }
365
366 /**
367 * Get effective Working Time rule for a resource.
368 *
369 * @param int $resource_id Resource ID.
370 *
371 * @return array|false
372 */
373 function wpbc_working_time__get_effective_rule( $resource_id ) {
374
375 $settings = wpbc_working_time__get_settings();
376 $resource_id = absint( $resource_id );
377
378 if ( 'On' !== $settings['enabled'] ) {
379 return false;
380 }
381
382 if ( ! empty( $settings['resources'][ $resource_id ] ) ) {
383 $resource_settings = $settings['resources'][ $resource_id ];
384
385 if ( 'disabled' === $resource_settings['mode'] ) {
386 return false;
387 }
388
389 if ( 'custom' === $resource_settings['mode'] ) {
390 return array(
391 'source' => 'resource',
392 'weekdays' => $resource_settings['weekdays'],
393 );
394 }
395 }
396
397 return array(
398 'source' => 'default',
399 'weekdays' => $settings['default']['weekdays'],
400 );
401 }
402
403 /**
404 * Get working intervals for date/resource.
405 *
406 * @param int $resource_id Resource ID.
407 * @param string $sql_date SQL date.
408 *
409 * @return array
410 */
411 function wpbc_working_time__get_working_intervals_for_date( $resource_id, $sql_date ) {
412
413 $rule = wpbc_working_time__get_effective_rule( $resource_id );
414 if ( false === $rule || empty( $sql_date ) ) {
415 return array();
416 }
417
418 $weekday = (int) gmdate( 'w', strtotime( $sql_date . ' 00:00:00' ) );
419
420 return isset( $rule['weekdays'][ $weekday ] ) ? $rule['weekdays'][ $weekday ] : array();
421 }
422
423 /**
424 * Get outside-working-time intervals for date/resource.
425 *
426 * @param int $resource_id Resource ID.
427 * @param string $sql_date SQL date.
428 *
429 * @return array
430 */
431 function wpbc_working_time__get_non_working_intervals_for_date( $resource_id, $sql_date ) {
432
433 $working_intervals = wpbc_working_time__get_working_intervals_for_date( $resource_id, $sql_date );
434
435 if ( false === wpbc_working_time__get_effective_rule( $resource_id ) ) {
436 return array();
437 }
438
439 if ( empty( $working_intervals ) ) {
440 return array(
441 array(
442 'date' => $sql_date,
443 'resource_id' => absint( $resource_id ),
444 'start_second' => 0,
445 'end_second' => DAY_IN_SECONDS,
446 'source_type' => 'working_time',
447 'readable_time' => __( 'Full day', 'booking' ),
448 ),
449 );
450 }
451
452 $busy = array();
453 $cursor = 0;
454
455 foreach ( $working_intervals as $interval ) {
456 $start_second = absint( $interval['start_second'] );
457 $end_second = absint( $interval['end_second'] );
458
459 if ( $cursor < $start_second ) {
460 $busy[] = array(
461 'date' => $sql_date,
462 'resource_id' => absint( $resource_id ),
463 'start_second' => $cursor,
464 'end_second' => $start_second,
465 'source_type' => 'working_time',
466 'readable_time' => wpbc_transform_timerange__seconds_arr__in__formated_hours( array( $cursor, $start_second ) ),
467 );
468 }
469
470 $cursor = max( $cursor, $end_second );
471 }
472
473 if ( $cursor < DAY_IN_SECONDS ) {
474 $busy[] = array(
475 'date' => $sql_date,
476 'resource_id' => absint( $resource_id ),
477 'start_second' => $cursor,
478 'end_second' => DAY_IN_SECONDS,
479 'source_type' => 'working_time',
480 'readable_time' => wpbc_transform_timerange__seconds_arr__in__formated_hours( array( $cursor, DAY_IN_SECONDS ) ),
481 );
482 }
483
484 return $busy;
485 }
486