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 / page-availability-general / availability_general__page.php

availability_general__page.php in Booking Calendar 11.9, at includes/page-availability-general/availability_general__page.php

1,513 lines 60.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * General availability admin page.
4 *
5 * @package Booking Calendar
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Check whether the General Availability page is active.
14 *
15 * @return bool
16 */
17 function wpbc_availability_general__is_page() {
18
19 if ( ! is_admin() ) {
20 return false;
21 }
22
23 if ( ! function_exists( 'wpbc_is_availability_page' ) || ! wpbc_is_availability_page() ) {
24 return false;
25 }
26
27 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
28 $active_tab = isset( $_REQUEST['tab'] ) ? sanitize_key( wp_unslash( $_REQUEST['tab'] ) ) : '';
29
30 return ( 'general_availability' === $active_tab );
31 }
32
33 /**
34 * Enqueue CSS for General Availability.
35 *
36 * @param string $where_to_load Where assets are loaded.
37 *
38 * @return void
39 */
40 function wpbc_availability_general_enqueue_css_files( $where_to_load ) {
41
42 if ( ( ! is_admin() ) || ( ! in_array( $where_to_load, array( 'admin', 'both' ), true ) ) ) {
43 return;
44 }
45
46 if ( ! wpbc_availability_general__is_page() ) {
47 return;
48 }
49
50 wp_enqueue_style(
51 'wpbc-availability-general-page',
52 trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/availability_general_page.css',
53 array( 'wpbc-calendar', 'wpbc-all-admin' ),
54 WP_BK_VERSION_NUM
55 );
56 }
57 add_action( 'wpbc_enqueue_css_files', 'wpbc_availability_general_enqueue_css_files', 66 );
58
59 /**
60 * Enqueue JS for General Availability.
61 *
62 * @param string $where_to_load Where assets are loaded.
63 *
64 * @return void
65 */
66 function wpbc_availability_general_enqueue_js_files( $where_to_load ) {
67
68 if ( ( ! is_admin() ) || ( ! in_array( $where_to_load, array( 'admin', 'both' ), true ) ) ) {
69 return;
70 }
71
72 if ( ! wpbc_availability_general__is_page() ) {
73 return;
74 }
75
76 wp_enqueue_script(
77 'wpbc-availability-general-page',
78 trailingslashit( plugins_url( '', __FILE__ ) ) . '_out/availability_general_page.js',
79 array( 'jquery', 'wpbc_all' ),
80 WP_BK_VERSION_NUM,
81 array( 'in_footer' => WPBC_JS_IN_FOOTER )
82 );
83
84 wp_localize_script(
85 'wpbc-availability-general-page',
86 'wpbc_availability_general_page',
87 array(
88 'ajax_url' => admin_url( 'admin-ajax.php' ),
89 'nonce' => wp_create_nonce( 'wpbc_availability_general_ajax_nonce' ),
90 'action' => 'WPBC_AJX_AVAILABILITY_GENERAL_SAVE',
91 'preview_action' => 'WPBC_AJX_AVAILABILITY_GENERAL_PREVIEW',
92 'settings' => wpbc_availability_general__get_settings_response(),
93 'default_settings' => wpbc_availability_general__get_default_settings(),
94 'open_section' => wpbc_availability_general__get_open_section(),
95 'is_buffer_available' => class_exists( 'wpdev_bk_biz_m' ),
96 'is_available_limit_available' => class_exists( 'wpdev_bk_biz_m' ),
97 'i18n' => array(
98 'saving' => __( 'Saving', 'booking' ),
99 'saved' => __( 'General availability settings updated.', 'booking' ),
100 'reset_applied' => __( 'Default availability settings are ready for preview. Click Save Changes to apply them.', 'booking' ),
101 'save_failed' => __( 'Unable to save general availability settings.', 'booking' ),
102 'reset_confirm' => __( 'Reset controls to default values? Saved settings will not change until you click Save Changes.', 'booking' ),
103 'preview_failed' => __( 'Unable to refresh calendar preview.', 'booking' ),
104 'security_error' => __( 'Security check failed.', 'booking' ),
105 'loading' => __( 'Loading', 'booking' ),
106 'buffer_preview' => __( 'Buffer preview', 'booking' ),
107 'before_booking' => __( 'Before booking', 'booking' ),
108 'after_booking' => __( 'After booking', 'booking' ),
109 'no_buffer' => __( 'No booking buffer is selected.', 'booking' ),
110 'add_interval' => __( 'Add interval', 'booking' ),
111 'remove_interval' => __( 'Remove working interval', 'booking' ),
112 'interval_limit' => __( 'No additional one-hour interval is available for this weekday.', 'booking' ),
113 ),
114 'max_working_time_intervals' => wpbc_availability_general__get_max_working_time_intervals_per_day(),
115 )
116 );
117 }
118 add_action( 'wpbc_enqueue_js_files', 'wpbc_availability_general_enqueue_js_files', 66 );
119
120 /**
121 * Print tooltip initializer for General Availability.
122 *
123 * @return void
124 */
125 function wpbc_availability_general_print_tooltip_js() {
126
127 if ( ! wpbc_availability_general__is_page() ) {
128 return;
129 }
130
131 if ( function_exists( 'wpbc_bs_javascript_tooltips' ) ) {
132 wpbc_bs_javascript_tooltips();
133 }
134 }
135 add_action( 'admin_footer', 'wpbc_availability_general_print_tooltip_js', 67 );
136
137 /**
138 * Get resource options.
139 *
140 * @return array
141 */
142 function wpbc_availability_general__get_resources() {
143
144 $resources = array();
145
146 if ( function_exists( 'wpbc_ajx_get_sorted_booking_resources_arr' ) && function_exists( 'wpbc_ajx_get_all_booking_resources_arr' ) ) {
147 $resource_rows = wpbc_ajx_get_sorted_booking_resources_arr( wpbc_ajx_get_all_booking_resources_arr() );
148 foreach ( $resource_rows as $resource ) {
149 $resources[] = array(
150 'id' => isset( $resource['booking_type_id'] ) ? (int) $resource['booking_type_id'] : 1,
151 'title' => isset( $resource['title'] ) ? $resource['title'] : __( 'Booking resource', 'booking' ),
152 );
153 }
154 }
155
156 if ( empty( $resources ) ) {
157 $resources[] = array(
158 'id' => function_exists( 'wpbc_get_default_resource' ) ? (int) wpbc_get_default_resource() : 1,
159 'title' => __( 'Booking resource', 'booking' ),
160 );
161 }
162
163 return $resources;
164 }
165
166 /**
167 * Get capability required to manage General Availability.
168 *
169 * @return string
170 */
171 function wpbc_availability_general__get_manage_cap() {
172
173 $min_user_role = get_bk_option( 'booking_user_role_settings' );
174 $capability = array(
175 'administrator' => 'activate_plugins',
176 'editor' => 'publish_pages',
177 'author' => 'publish_posts',
178 'contributor' => 'edit_posts',
179 'subscriber' => 'read',
180 );
181
182 return isset( $capability[ $min_user_role ] ) ? $capability[ $min_user_role ] : 'manage_options';
183 }
184
185 /**
186 * Get selected resource ID for preview.
187 *
188 * @return int
189 */
190 function wpbc_availability_general__get_preview_resource_id() {
191
192 $resources = wpbc_availability_general__get_resources();
193 $default_resource = isset( $resources[0]['id'] ) ? (int) $resources[0]['id'] : 1;
194
195 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
196 $resource_id = isset( $_REQUEST['resource_id'] ) ? absint( wp_unslash( $_REQUEST['resource_id'] ) ) : $default_resource;
197 $resource_ids = wp_list_pluck( $resources, 'id' );
198
199 return in_array( $resource_id, $resource_ids, true ) ? $resource_id : $default_resource;
200 }
201
202 /**
203 * Get selected month count for preview.
204 *
205 * @return int
206 */
207 function wpbc_availability_general__get_preview_months_count() {
208
209 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
210 $months = isset( $_REQUEST['months_count'] ) ? absint( wp_unslash( $_REQUEST['months_count'] ) ) : 6;
211
212 return in_array( $months, array( 1, 2, 3, 4, 6, 12 ), true ) ? $months : 6;
213 }
214
215 /**
216 * Get the General Availability section that should be opened on page load.
217 *
218 * @return string
219 */
220 function wpbc_availability_general__get_open_section() {
221
222 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
223 $open_section = isset( $_REQUEST['wpbc_ag_open'] ) ? sanitize_key( wp_unslash( $_REQUEST['wpbc_ag_open'] ) ) : '';
224
225 return in_array( $open_section, array( 'weekdays', 'from_today', 'buffer', 'working_time' ), true ) ? $open_section : '';
226 }
227
228 /**
229 * Validate preview resource ID.
230 *
231 * @param int $resource_id Resource ID.
232 *
233 * @return int
234 */
235 function wpbc_availability_general__sanitize_preview_resource_id( $resource_id ) {
236
237 $resources = wpbc_availability_general__get_resources();
238 $default_resource = isset( $resources[0]['id'] ) ? (int) $resources[0]['id'] : 1;
239 $resource_ids = wp_list_pluck( $resources, 'id' );
240
241 return in_array( (int) $resource_id, $resource_ids, true ) ? (int) $resource_id : $default_resource;
242 }
243
244 /**
245 * Validate preview months count.
246 *
247 * @param int $months_count Number of months.
248 *
249 * @return int
250 */
251 function wpbc_availability_general__sanitize_preview_months_count( $months_count ) {
252
253 $months_count = absint( $months_count );
254
255 return in_array( $months_count, array( 1, 2, 3, 4, 6, 12 ), true ) ? $months_count : 6;
256 }
257
258 /**
259 * Get unavailable-from-today select options.
260 *
261 * @return array
262 */
263 function wpbc_availability_general__get_unavailable_from_today_options() {
264
265 $options = array( '0' => ' - ' );
266
267 foreach ( range( 5, 55, 5 ) as $extra_num ) {
268 $options[ $extra_num . 'm' ] = $extra_num . ' ' . __( 'minutes', 'booking' );
269 }
270 $options['60m'] = '1 ' . __( 'hour', 'booking' );
271 foreach ( range( 65, 115, 5 ) as $extra_num ) {
272 $options[ $extra_num . 'm' ] = '1 ' . __( 'hour', 'booking' ) . ' ' . ( $extra_num - 60 ) . ' ' . __( 'minutes', 'booking' );
273 }
274 foreach ( range( 120, 1380, 60 ) as $extra_num ) {
275 $options[ $extra_num . 'm' ] = ( $extra_num / 60 ) . ' ' . __( 'hours', 'booking' );
276 }
277
278 $options['1'] = '1 ' . __( 'day', 'booking' );
279 for ( $ii = 2; $ii < 92; $ii++ ) {
280 $options[ (string) $ii ] = $ii . ' ' . __( 'days', 'booking' );
281 }
282
283 return $options;
284 }
285
286 /**
287 * Get available-from-today select options.
288 *
289 * @return array
290 */
291 function wpbc_availability_general__get_available_from_today_options() {
292
293 $options = array( '' => ' - ' );
294
295 foreach ( range( 1, 365 ) as $value ) {
296 $options[ (string) $value ] = (string) $value;
297 }
298
299 return $options;
300 }
301
302 /**
303 * Get minute buffer options.
304 *
305 * @return array
306 */
307 function wpbc_availability_general__get_buffer_minutes_options() {
308
309 $options = array( '' => ' - ' );
310
311 foreach ( range( 5, 55, 5 ) as $extra_num ) {
312 $options[ $extra_num . 'm' ] = $extra_num . ' ' . __( 'minutes', 'booking' );
313 }
314 $options['60m'] = '1 ' . __( 'hour', 'booking' );
315 foreach ( range( 65, 115, 5 ) as $extra_num ) {
316 $options[ $extra_num . 'm' ] = '1 ' . __( 'hour', 'booking' ) . ' ' . ( $extra_num - 60 ) . ' ' . __( 'minutes', 'booking' );
317 }
318 foreach ( range( 120, 1380, 60 ) as $extra_num ) {
319 $options[ $extra_num . 'm' ] = ( $extra_num / 60 ) . ' ' . __( 'hours', 'booking' );
320 }
321
322 return $options;
323 }
324
325 /**
326 * Get day buffer options.
327 *
328 * @return array
329 */
330 function wpbc_availability_general__get_buffer_days_options() {
331
332 $options = array( '' => ' - ' );
333
334 foreach ( range( 1, 30 ) as $extra_num ) {
335 $options[ $extra_num . 'd' ] = $extra_num . ' ' . __( 'day(s)', 'booking' );
336 }
337
338 return $options;
339 }
340
341 /**
342 * Get time options for future working-time controls.
343 *
344 * @param string|string[] $selected Selected values that must remain available.
345 *
346 * @return array
347 */
348 function wpbc_availability_general__get_working_time_options( $selected = '' ) {
349
350 $options = array();
351
352 for ( $minutes = 0; $minutes <= 1440; $minutes += 30 ) {
353 $hours = (int) floor( $minutes / 60 );
354 $mins = $minutes % 60;
355 $value = sprintf( '%02d:%02d', $hours, $mins );
356 $label = ( 1440 === $minutes ) ? '24:00' : $value;
357
358 $options[ $value ] = $label;
359 }
360
361 $selected_values = is_array( $selected ) ? $selected : array( $selected );
362 foreach ( $selected_values as $selected_value ) {
363 $selected_value = is_scalar( $selected_value ) ? (string) $selected_value : '';
364 if ( '' !== $selected_value && ! isset( $options[ $selected_value ] ) ) {
365 $options[ $selected_value ] = $selected_value;
366 }
367 }
368
369 uksort(
370 $options,
371 function ( $first_time, $second_time ) {
372 return wpbc_working_time__time_to_seconds( $first_time ) - wpbc_working_time__time_to_seconds( $second_time );
373 }
374 );
375
376 return $options;
377 }
378
379 /**
380 * Return the maximum number of editable intervals for one weekday.
381 *
382 * @return int Maximum interval count.
383 */
384 function wpbc_availability_general__get_max_working_time_intervals_per_day() {
385
386 return 8;
387 }
388
389 /**
390 * Collect every stored endpoint needed by the current inspector selects.
391 *
392 * Resource switching happens without reloading the inspector. Including all
393 * canonical endpoints prevents a minute-aligned custom time from disappearing
394 * when the selected resource changes in the browser.
395 *
396 * @param array $working_time_settings Canonical Working Time settings.
397 *
398 * @return string[] Unique H:i endpoints.
399 */
400 function wpbc_availability_general__get_working_time_option_values( $working_time_settings ) {
401
402 $selected_values = array();
403 $schedules = array();
404
405 if ( isset( $working_time_settings['default']['weekdays'] ) ) {
406 $schedules[] = $working_time_settings['default']['weekdays'];
407 }
408 if ( isset( $working_time_settings['resources'] ) && is_array( $working_time_settings['resources'] ) ) {
409 foreach ( $working_time_settings['resources'] as $resource_settings ) {
410 if ( isset( $resource_settings['weekdays'] ) ) {
411 $schedules[] = $resource_settings['weekdays'];
412 }
413 }
414 }
415
416 foreach ( $schedules as $weekdays ) {
417 foreach ( (array) $weekdays as $day_intervals ) {
418 foreach ( (array) $day_intervals as $interval ) {
419 if ( isset( $interval['start_second'] ) ) {
420 $selected_values[] = wpbc_working_time__seconds_to_time( $interval['start_second'] );
421 }
422 if ( isset( $interval['end_second'] ) ) {
423 $selected_values[] = wpbc_working_time__seconds_to_time( $interval['end_second'] );
424 }
425 }
426 }
427 }
428
429 return array_values( array_unique( $selected_values ) );
430 }
431
432 /**
433 * Render a select field.
434 *
435 * @param string $name Field name.
436 * @param array $options Options.
437 * @param string $selected Selected value.
438 * @param array $args Extra args.
439 *
440 * @return void
441 */
442 function wpbc_availability_general__render_select( $name, $options, $selected, $args = array() ) {
443
444 $args = wp_parse_args(
445 $args,
446 array(
447 'id' => $name,
448 'class' => 'wpbc_ag_field_control',
449 'disabled' => false,
450 )
451 );
452 ?>
453 <select id="<?php echo esc_attr( $args['id'] ); ?>" name="<?php echo esc_attr( $name ); ?>" class="<?php echo esc_attr( $args['class'] ); ?>" <?php disabled( $args['disabled'] ); ?>>
454 <?php foreach ( $options as $value => $label ) : ?>
455 <option value="<?php echo esc_attr( $value ); ?>" <?php selected( (string) $selected, (string) $value ); ?>><?php echo esc_html( $label ); ?></option>
456 <?php endforeach; ?>
457 </select>
458 <?php
459 }
460
461 /**
462 * Render Working Time weekday rows.
463 *
464 * @param string $prefix Field prefix.
465 * @param array $weekdays Weekday intervals.
466 * @param array $time_options Allowed time choices.
467 *
468 * @return void
469 */
470 function wpbc_availability_general__render_working_time_weekdays( $prefix, $weekdays, $time_options = array() ) {
471
472 $days = array(
473 0 => _x( 'Sun', 'Short weekday name', 'booking' ),
474 1 => _x( 'Mon', 'Short weekday name', 'booking' ),
475 2 => _x( 'Tue', 'Short weekday name', 'booking' ),
476 3 => _x( 'Wed', 'Short weekday name', 'booking' ),
477 4 => _x( 'Thu', 'Short weekday name', 'booking' ),
478 5 => _x( 'Fri', 'Short weekday name', 'booking' ),
479 6 => _x( 'Sat', 'Short weekday name', 'booking' ),
480 );
481
482 $start_week_day = absint( get_bk_option( 'booking_start_day_weeek' ) );
483 if ( $start_week_day > 6 ) {
484 $start_week_day = 0;
485 }
486 $days_ordered = array_slice( $days, $start_week_day, null, true ) + array_slice( $days, 0, $start_week_day, true );
487 if ( empty( $time_options ) ) {
488 $time_options = wpbc_availability_general__get_working_time_options();
489 }
490 ?>
491 <div
492 class="wpbc_ag_working_time_rows"
493 data-wpbc-working-time-weekdays="<?php echo esc_attr( $prefix ); ?>"
494 data-max-intervals="<?php echo esc_attr( (string) wpbc_availability_general__get_max_working_time_intervals_per_day() ); ?>"
495 >
496 <?php foreach ( $days_ordered as $day_num => $day_title ) : ?>
497 <?php
498 $day_intervals = ! empty( $weekdays[ $day_num ] ) && is_array( $weekdays[ $day_num ] )
499 ? array_values( $weekdays[ $day_num ] )
500 : array();
501 $is_open = ! empty( $day_intervals );
502 $display_intervals = $is_open
503 ? $day_intervals
504 : array( array( 'start_second' => 9 * HOUR_IN_SECONDS, 'end_second' => 18 * HOUR_IN_SECONDS ) );
505 /* translators: %s: Short weekday name. */
506 $intervals_label = sprintf( __( '%s Working Time intervals', 'booking' ), $day_title );
507 /* translators: %s: Short weekday name. */
508 $add_interval_label = sprintf( __( 'Add %s Working Time interval', 'booking' ), $day_title );
509 ?>
510 <div class="wpbc_ag_working_time_row<?php echo $is_open ? ' is-enabled' : ''; ?>" data-wpbc-working-time-day="<?php echo esc_attr( (string) $day_num ); ?>">
511 <label class="wpbc_ag_switch wpbc_ag_working_time_day">
512 <input type="checkbox" name="<?php echo esc_attr( $prefix ); ?>_days[]" value="<?php echo esc_attr( $day_num ); ?>" data-wpbc-working-time-day-toggle="1" <?php checked( $is_open ); ?> />
513 <span class="wpbc_ag_switch_control" aria-hidden="true"><span class="wpbc_ag_switch_knob"></span></span>
514 <span class="wpbc_ag_switch_label"><?php echo esc_html( $day_title ); ?></span>
515 </label>
516 <div class="wpbc_ag_working_time_intervals" data-wpbc-working-time-intervals="1" aria-label="<?php echo esc_attr( $intervals_label ); ?>">
517 <?php foreach ( $display_intervals as $interval_index => $interval ) : ?>
518 <?php wpbc_availability_general__render_working_time_interval( $prefix, $day_num, $day_title, $interval_index, $interval, $time_options ); ?>
519 <?php endforeach; ?>
520 </div>
521 <button type="button" class="button-link wpbc_ag_working_time_add" data-wpbc-add-working-time-interval="1" aria-label="<?php echo esc_attr( $add_interval_label ); ?>">
522 <i class="menu_icon icon-1x wpbc_icn_add" aria-hidden="true"></i>
523 <span><?php esc_html_e( 'Add interval', 'booking' ); ?></span>
524 </button>
525 </div>
526 <?php endforeach; ?>
527 </div>
528 <?php
529 }
530
531 /**
532 * Render one editable Working Time interval.
533 *
534 * @param string $prefix Field prefix.
535 * @param int $day_num Canonical weekday number from 0 through 6.
536 * @param string $day_title Localized weekday title.
537 * @param int $interval_index Zero-based interval index.
538 * @param array $interval Canonical interval values.
539 * @param array $time_options Allowed time choices.
540 *
541 * @return void
542 */
543 function wpbc_availability_general__render_working_time_interval( $prefix, $day_num, $day_title, $interval_index, $interval, $time_options ) {
544
545 $start_id = $prefix . '_start_' . $day_num . '_' . $interval_index;
546 $end_id = $prefix . '_end_' . $day_num . '_' . $interval_index;
547 /* translators: %s: Short weekday name. */
548 $remove_label = sprintf( __( 'Remove %s Working Time interval', 'booking' ), $day_title );
549 ?>
550 <div class="wpbc_ag_working_time_times" data-wpbc-working-time-interval="1" data-interval-index="<?php echo esc_attr( (string) $interval_index ); ?>">
551 <label class="screen-reader-text" for="<?php echo esc_attr( $start_id ); ?>"><?php esc_html_e( 'Start time', 'booking' ); ?></label>
552 <?php
553 wpbc_availability_general__render_select(
554 $prefix . '_start[' . $day_num . '][]',
555 $time_options,
556 wpbc_working_time__seconds_to_time( $interval['start_second'] ),
557 array(
558 'id' => $start_id,
559 'class' => 'wpbc_ag_field_control wpbc_ag_working_time_start',
560 )
561 );
562 ?>
563 <span class="wpbc_ag_working_time_separator" aria-hidden="true">&ndash;</span>
564 <label class="screen-reader-text" for="<?php echo esc_attr( $end_id ); ?>"><?php esc_html_e( 'End time', 'booking' ); ?></label>
565 <?php
566 wpbc_availability_general__render_select(
567 $prefix . '_end[' . $day_num . '][]',
568 $time_options,
569 wpbc_working_time__seconds_to_time( $interval['end_second'] ),
570 array(
571 'id' => $end_id,
572 'class' => 'wpbc_ag_field_control wpbc_ag_working_time_end',
573 )
574 );
575 ?>
576 <button type="button" class="wpbc_ag_working_time_remove" data-wpbc-remove-working-time-interval="1" aria-label="<?php echo esc_attr( $remove_label ); ?>">
577 <i class="menu_icon icon-1x wpbc_icn_delete_outline" aria-hidden="true"></i>
578 </button>
579 </div>
580 <?php
581 }
582
583 /**
584 * Get default General Availability settings.
585 *
586 * @return array
587 */
588 function wpbc_availability_general__get_default_settings() {
589
590 return array(
591 'weekdays' => array(),
592 'booking_unavailable_days_num_from_today' => '0',
593 'booking_available_days_num_from_today' => '',
594 'booking_unavailable_extra_in_out' => '',
595 'booking_unavailable_extra_minutes_in' => '',
596 'booking_unavailable_extra_minutes_out' => '',
597 'booking_unavailable_extra_days_in' => '',
598 'booking_unavailable_extra_days_out' => '',
599 'working_time' => wpbc_working_time__get_default_settings(),
600 );
601 }
602
603 /**
604 * Convert one posted Working Time value to canonical seconds.
605 *
606 * @param mixed $raw_time Untrusted H:i value.
607 *
608 * @return int|null Seconds from midnight, or null when invalid.
609 */
610 function wpbc_availability_general__validate_working_time_value( $raw_time ) {
611
612 if ( ! is_scalar( $raw_time ) ) {
613 return null;
614 }
615
616 $time_value = sanitize_text_field( (string) $raw_time );
617 if ( ! preg_match( '/^(?:(?:[01]\d|2[0-3]):[0-5]\d|24:00)$/', $time_value ) ) {
618 return null;
619 }
620
621 return wpbc_working_time__time_to_seconds( $time_value );
622 }
623
624 /**
625 * Validate posted multi-interval Working Time weekdays.
626 *
627 * @param array $post_data Raw post data.
628 * @param string $prefix Field prefix.
629 *
630 * @return array<int,array<int,array{start_second:int,end_second:int}>>|WP_Error Validated schedule or an error.
631 */
632 function wpbc_availability_general__validate_working_time_weekdays( $post_data, $prefix ) {
633
634 $weekdays = array();
635 $enabled_days = array();
636 $start_values = isset( $post_data[ $prefix . '_start' ] ) && is_array( $post_data[ $prefix . '_start' ] ) ? wp_unslash( $post_data[ $prefix . '_start' ] ) : array();
637 $end_values = isset( $post_data[ $prefix . '_end' ] ) && is_array( $post_data[ $prefix . '_end' ] ) ? wp_unslash( $post_data[ $prefix . '_end' ] ) : array();
638
639 if ( isset( $post_data[ $prefix . '_days' ] ) && is_array( $post_data[ $prefix . '_days' ] ) ) {
640 foreach ( wp_unslash( $post_data[ $prefix . '_days' ] ) as $day_num ) {
641 if ( is_scalar( $day_num ) && preg_match( '/^[0-6]$/', (string) $day_num ) ) {
642 $enabled_days[] = (int) $day_num;
643 }
644 }
645 }
646 $enabled_days = array_values( array_unique( $enabled_days ) );
647
648 for ( $day = 0; $day <= 6; $day++ ) {
649 $weekdays[ $day ] = array();
650 if ( ! in_array( $day, $enabled_days, true ) ) {
651 continue;
652 }
653
654 $day_start_values = isset( $start_values[ $day ] ) ? array_values( (array) $start_values[ $day ] ) : array();
655 $day_end_values = isset( $end_values[ $day ] ) ? array_values( (array) $end_values[ $day ] ) : array();
656 if ( empty( $day_start_values ) || count( $day_start_values ) !== count( $day_end_values ) ) {
657 return new WP_Error( 'wpbc_availability_general_working_time_interval_missing', __( 'Every enabled weekday must contain a complete Working Time interval.', 'booking' ) );
658 }
659
660 foreach ( $day_start_values as $interval_index => $raw_start_value ) {
661 $start_second = wpbc_availability_general__validate_working_time_value( $raw_start_value );
662 $end_second = wpbc_availability_general__validate_working_time_value( $day_end_values[ $interval_index ] );
663
664 if ( null === $start_second || null === $end_second ) {
665 return new WP_Error( 'wpbc_availability_general_working_time_value_invalid', __( 'Enter a valid start and end time for every Working Time interval.', 'booking' ) );
666 }
667
668 $weekdays[ $day ][] = array(
669 'start_second' => $start_second,
670 'end_second' => $end_second,
671 );
672 }
673 }
674
675 return wpbc_working_time__validate_weekday_intervals( $weekdays, wpbc_availability_general__get_max_working_time_intervals_per_day() );
676 }
677
678 /**
679 * Validate posted Working Time settings.
680 *
681 * @param array $post_data Raw post data.
682 *
683 * @return array|WP_Error Validated Working Time settings or an error.
684 */
685 function wpbc_availability_general__validate_working_time_data( $post_data ) {
686
687 $current_settings = wpbc_working_time__get_settings();
688 $resource_id = isset( $post_data['booking_working_time_resource_id'] ) ? absint( wp_unslash( $post_data['booking_working_time_resource_id'] ) ) : 0;
689 $resource_mode = isset( $post_data['booking_working_time_resource_mode'] ) ? sanitize_key( wp_unslash( $post_data['booking_working_time_resource_mode'] ) ) : 'inherit';
690
691 if ( ! in_array( $resource_mode, array( 'inherit', 'custom', 'disabled' ), true ) ) {
692 $resource_mode = 'inherit';
693 }
694
695 $default_weekdays = wpbc_availability_general__validate_working_time_weekdays( $post_data, 'booking_working_time_default' );
696 if ( is_wp_error( $default_weekdays ) ) {
697 return $default_weekdays;
698 }
699
700 $resource_weekdays = isset( $current_settings['resources'][ $resource_id ]['weekdays'] )
701 ? $current_settings['resources'][ $resource_id ]['weekdays']
702 : $current_settings['default']['weekdays'];
703 if ( 'custom' === $resource_mode ) {
704 $resource_weekdays = wpbc_availability_general__validate_working_time_weekdays( $post_data, 'booking_working_time_resource' );
705 if ( is_wp_error( $resource_weekdays ) ) {
706 return $resource_weekdays;
707 }
708 }
709
710 $current_settings['enabled'] = ( isset( $post_data['booking_working_time_enabled'] ) && 'On' === sanitize_text_field( wp_unslash( $post_data['booking_working_time_enabled'] ) ) ) ? 'On' : 'Off';
711 $current_settings['default'] = array(
712 'weekdays' => $default_weekdays,
713 );
714
715 if ( $resource_id > 0 ) {
716 $current_settings['resources'][ $resource_id ] = array(
717 'mode' => $resource_mode,
718 'weekdays' => $resource_weekdays,
719 );
720 }
721
722 return wpbc_working_time__normalize_settings( $current_settings );
723 }
724
725 /**
726 * Validate posted settings.
727 *
728 * @param array $post_data Raw post data.
729 *
730 * @return array|WP_Error Validated settings or an error.
731 */
732 function wpbc_availability_general__validate_data( $post_data ) {
733
734 $cleaned = wpbc_availability_general__get_default_settings();
735
736 if ( isset( $post_data['booking_unavailable_days'] ) && is_array( $post_data['booking_unavailable_days'] ) ) {
737 foreach ( $post_data['booking_unavailable_days'] as $day_num ) {
738 $day_num = absint( $day_num );
739 if ( $day_num >= 0 && $day_num <= 6 ) {
740 $cleaned['weekdays'][] = $day_num;
741 }
742 }
743 }
744
745 if ( isset( $post_data['booking_unavailable_days_num_from_today'] ) ) {
746 $value = sanitize_text_field( wp_unslash( $post_data['booking_unavailable_days_num_from_today'] ) );
747 if ( preg_match( '/^\d+m$/', $value ) || preg_match( '/^\d+$/', $value ) ) {
748 $cleaned['booking_unavailable_days_num_from_today'] = $value;
749 }
750 }
751
752 if ( isset( $post_data['booking_available_days_num_from_today'] ) ) {
753 $value = sanitize_text_field( wp_unslash( $post_data['booking_available_days_num_from_today'] ) );
754 $cleaned['booking_available_days_num_from_today'] = ( '' === $value ) ? '' : (string) absint( $value );
755 }
756
757 if ( isset( $post_data['booking_unavailable_extra_in_out'] ) ) {
758 $value = sanitize_text_field( wp_unslash( $post_data['booking_unavailable_extra_in_out'] ) );
759 $cleaned['booking_unavailable_extra_in_out'] = in_array( $value, array( '', 'm', 'd' ), true ) ? $value : '';
760 }
761
762 foreach ( array( 'booking_unavailable_extra_minutes_in', 'booking_unavailable_extra_minutes_out' ) as $key ) {
763 if ( isset( $post_data[ $key ] ) ) {
764 $value = sanitize_text_field( wp_unslash( $post_data[ $key ] ) );
765 $cleaned[ $key ] = preg_match( '/^\d+m$/', $value ) ? $value : '';
766 }
767 }
768
769 foreach ( array( 'booking_unavailable_extra_days_in', 'booking_unavailable_extra_days_out' ) as $key ) {
770 if ( isset( $post_data[ $key ] ) ) {
771 $value = sanitize_text_field( wp_unslash( $post_data[ $key ] ) );
772 $cleaned[ $key ] = preg_match( '/^\d+d$/', $value ) ? $value : '';
773 }
774 }
775
776 $cleaned['working_time'] = wpbc_availability_general__validate_working_time_data( $post_data );
777 if ( is_wp_error( $cleaned['working_time'] ) ) {
778 return $cleaned['working_time'];
779 }
780
781 return $cleaned;
782 }
783
784 /**
785 * Update settings.
786 *
787 * @param array $cleaned_data Cleaned settings.
788 *
789 * @return void
790 */
791 function wpbc_availability_general__update_settings( $cleaned_data ) {
792
793 for ( $i = 0; $i < 7; $i++ ) {
794 update_bk_option( 'booking_unavailable_day' . $i, in_array( $i, $cleaned_data['weekdays'], true ) ? 'On' : 'Off' );
795 }
796
797 update_bk_option( 'booking_unavailable_days_num_from_today', $cleaned_data['booking_unavailable_days_num_from_today'] );
798
799 if ( class_exists( 'wpdev_bk_biz_m' ) ) {
800 update_bk_option( 'booking_available_days_num_from_today', $cleaned_data['booking_available_days_num_from_today'] );
801 update_bk_option( 'booking_unavailable_extra_in_out', $cleaned_data['booking_unavailable_extra_in_out'] );
802 update_bk_option( 'booking_unavailable_extra_minutes_in', $cleaned_data['booking_unavailable_extra_minutes_in'] );
803 update_bk_option( 'booking_unavailable_extra_minutes_out', $cleaned_data['booking_unavailable_extra_minutes_out'] );
804 update_bk_option( 'booking_unavailable_extra_days_in', $cleaned_data['booking_unavailable_extra_days_in'] );
805 update_bk_option( 'booking_unavailable_extra_days_out', $cleaned_data['booking_unavailable_extra_days_out'] );
806 }
807
808 wpbc_working_time__update_settings( $cleaned_data['working_time'] );
809 }
810
811 /**
812 * Get current settings prepared for JavaScript and AJAX responses.
813 *
814 * @return array
815 */
816 function wpbc_availability_general__get_settings_response() {
817
818 $hints = function_exists( 'wpbc_get_unavailable_from_today_hints_arr' ) ? wpbc_get_unavailable_from_today_hints_arr() : array();
819 $weekdays = array();
820
821 for ( $i = 0; $i < 7; $i++ ) {
822 if ( 'On' === get_bk_option( 'booking_unavailable_day' . $i ) ) {
823 $weekdays[] = $i;
824 }
825 }
826
827 return array(
828 'weekdays' => $weekdays,
829 'booking_unavailable_days_num_from_today' => get_bk_option( 'booking_unavailable_days_num_from_today' ),
830 'booking_available_days_num_from_today' => get_bk_option( 'booking_available_days_num_from_today' ),
831 'booking_unavailable_extra_in_out' => get_bk_option( 'booking_unavailable_extra_in_out' ),
832 'booking_unavailable_extra_minutes_in' => get_bk_option( 'booking_unavailable_extra_minutes_in' ),
833 'booking_unavailable_extra_minutes_out' => get_bk_option( 'booking_unavailable_extra_minutes_out' ),
834 'booking_unavailable_extra_days_in' => get_bk_option( 'booking_unavailable_extra_days_in' ),
835 'booking_unavailable_extra_days_out' => get_bk_option( 'booking_unavailable_extra_days_out' ),
836 'working_time' => wpbc_working_time__get_settings(),
837 'hints' => array(
838 'booking_unavailable_days_num_from_today__hint' => isset( $hints['booking_unavailable_days_num_from_today__hint'] ) ? $hints['booking_unavailable_days_num_from_today__hint'] : '',
839 'booking_available_days_num_from_today__hint' => isset( $hints['booking_available_days_num_from_today__hint'] ) ? $hints['booking_available_days_num_from_today__hint'] : '',
840 ),
841 );
842 }
843
844 /**
845 * Render booking resource calendar preview.
846 *
847 * @param int $resource_id Resource ID.
848 * @param int $months_count Number of months.
849 *
850 * @return void
851 */
852 function wpbc_availability_general__render_calendar_preview( $resource_id, $months_count ) {
853
854 $resource_id = wpbc_availability_general__sanitize_preview_resource_id( $resource_id );
855 $months_count = wpbc_availability_general__sanitize_preview_months_count( $months_count );
856 $months_in_row = min( 3, max( 1, $months_count ) );
857 $calendar_css_class_outer = 'wpbc_calendar_wraper';
858
859 if ( 'Off' !== get_bk_option( 'booking_change_over_days_triangles' ) ) {
860 $calendar_css_class_outer .= ' wpbc_change_over_triangle';
861 }
862 ?>
863 <div class="wpbc_ag_calendar_panel" data-wpbc-ag-calendar-panel="1" data-resource-id="<?php echo esc_attr( $resource_id ); ?>" data-months-count="<?php echo esc_attr( $months_count ); ?>">
864 <div class="<?php echo esc_attr( $calendar_css_class_outer ); ?>">
865 <div class="bk_calendar_frame months_num_in_row_<?php echo esc_attr( $months_in_row ); ?> cal_month_num_<?php echo esc_attr( $months_count ); ?>">
866 <div id="calendar_booking<?php echo esc_attr( $resource_id ); ?>"><?php esc_html_e( 'Calendar is loading...', 'booking' ); ?></div>
867 </div>
868 </div>
869 <textarea rows="3" cols="50" id="date_booking<?php echo esc_attr( $resource_id ); ?>" name="date_booking<?php echo esc_attr( $resource_id ); ?>" autocomplete="off" hidden></textarea>
870 <?php
871 if ( function_exists( 'wpbc__calendar__load' ) ) {
872 $start_script_code = wpbc__calendar__load(
873 array(
874 'resource_id' => $resource_id,
875 'aggregate_resource_id_arr' => array(),
876 'selected_dates_without_calendar' => '',
877 'calendar_number_of_months' => $months_count,
878 'start_month_calendar' => false,
879 'shortcode_options' => '',
880 'custom_form' => 'standard',
881 'skip_general_availability' => 1,
882 )
883 );
884 echo $start_script_code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
885 }
886 ?>
887 </div>
888 <?php
889 }
890
891 /**
892 * Render select field with previous / next stepper buttons.
893 *
894 * @param string $name Field name.
895 * @param array $options Options.
896 * @param string $selected Selected value.
897 * @param array $args Extra args.
898 *
899 * @return void
900 */
901 function wpbc_availability_general__render_select_with_stepper( $name, $options, $selected, $args = array() ) {
902
903 $args = wp_parse_args(
904 $args,
905 array(
906 'id' => $name,
907 'disabled' => false,
908 )
909 );
910 ?>
911 <div class="wpbc_ag_stepper_control" data-wpbc-ag-stepper-wrap="<?php echo esc_attr( $name ); ?>">
912 <button type="button" class="button button-secondary wpbc_button_no_background wpbc_ag_stepper_button" data-wpbc-ag-stepper="<?php echo esc_attr( $name ); ?>" data-step="-1" aria-label="<?php esc_attr_e( 'Decrease value', 'booking' ); ?>" <?php disabled( $args['disabled'] ); ?>>
913 <i class="menu_icon icon-1x wpbc_icn_keyboard_arrow_left"></i>
914 </button>
915 <?php
916 wpbc_availability_general__render_select(
917 $name,
918 $options,
919 $selected,
920 array(
921 'id' => $args['id'],
922 'disabled' => $args['disabled'],
923 )
924 );
925 ?>
926 <button type="button" class="button button-secondary wpbc_button_no_background wpbc_ag_stepper_button" data-wpbc-ag-stepper="<?php echo esc_attr( $name ); ?>" data-step="1" aria-label="<?php esc_attr_e( 'Increase value', 'booking' ); ?>" <?php disabled( $args['disabled'] ); ?>>
927 <i class="menu_icon icon-1x wpbc_icn_keyboard_arrow_right"></i>
928 </button>
929 </div>
930 <?php
931 }
932
933 /**
934 * Render select field with an attached range slider.
935 *
936 * @param string $name Field name.
937 * @param array $options Options.
938 * @param string $selected Selected value.
939 * @param array $args Extra args.
940 *
941 * @return void
942 */
943 function wpbc_availability_general__render_select_with_slider( $name, $options, $selected, $args = array() ) {
944
945 $args = wp_parse_args(
946 $args,
947 array(
948 'disabled' => false,
949 'slider_label' => __( 'Adjust value', 'booking' ),
950 )
951 );
952
953 $option_values = array_keys( $options );
954 $selected_index = 0;
955
956 foreach ( $option_values as $option_index => $option_value ) {
957 if ( (string) $selected === (string) $option_value ) {
958 $selected_index = (int) $option_index;
959 break;
960 }
961 }
962
963 wpbc_availability_general__render_select( $name, $options, $selected, $args );
964 ?>
965 <div class="wpbc_ag_range_control" data-wpbc-ag-range-wrap="<?php echo esc_attr( $name ); ?>">
966 <input type="range" min="0" max="<?php echo esc_attr( max( 0, count( $options ) - 1 ) ); ?>" step="1" value="<?php echo esc_attr( $selected_index ); ?>" data-wpbc-ag-range-for="<?php echo esc_attr( $name ); ?>" aria-label="<?php echo esc_attr( $args['slider_label'] ); ?>" <?php disabled( $args['disabled'] ); ?> />
967 <span class="wpbc_ag_range_value" data-wpbc-ag-range-value-for="<?php echo esc_attr( $name ); ?>"><?php echo esc_html( isset( $options[ $option_values[ $selected_index ] ] ) ? $options[ $option_values[ $selected_index ] ] : '' ); ?></span>
968 </div>
969 <?php
970 }
971
972 /**
973 * Show General Availability save button in top toolbar.
974 *
975 * @param string $page_tag Current page tag.
976 * @param string $active_page_tab Current tab.
977 * @param string $active_page_subtab Current subtab.
978 *
979 * @return void
980 */
981 function wpbc_availability_general__top_toolbar__show_save_button( $page_tag, $active_page_tab, $active_page_subtab ) {
982
983 if ( ( 'wpbc-availability' !== $page_tag ) || ( 'general_availability' !== $active_page_tab ) ) {
984 return;
985 }
986
987 if ( ! wpbc_is_mu_user_can_be_here( 'only_super_admin' ) || ! current_user_can( wpbc_availability_general__get_manage_cap() ) ) {
988 return;
989 }
990 ?>
991 <div class="wpbc_ui_el__buttons_group wpbc_ag__top_toolbar_group">
992 <a href="javascript:void(0);"
993 class="button button-secondary wpbc_ag__top_btn_reset"
994 data-wpbc-ag-reset="1">
995 <i class="menu_icon icon-1x wpbc_icn_rotate_left"></i>
996 <span class="in-button-text">&nbsp;&nbsp;<?php esc_html_e( 'Reset', 'booking' ); ?></span>
997 </a>
998 <a href="javascript:void(0);"
999 class="button button-primary wpbc_ag__top_btn_save"
1000 data-wpbc-ag-save="1"
1001 data-wpbc-u-busy-text="<?php esc_attr_e( 'Saving', 'booking' ); ?>...">
1002 <i class="menu_icon icon-1x wpbc-bi-check2-circle"></i>
1003 <span class="in-button-text">&nbsp;&nbsp;<?php esc_html_e( 'Save Changes', 'booking' ); ?></span>
1004 </a>
1005 </div>
1006 <?php
1007 }
1008 add_action( 'wpbc_ui_el__top_nav__content_end', 'wpbc_availability_general__top_toolbar__show_save_button', 20, 3 );
1009
1010 /**
1011 * Block direct access for regular users in MultiUser context.
1012 *
1013 * @param bool $is_show_this_page Whether to show page.
1014 * @param string $page_tag Current page.
1015 * @param string $active_page_tab Active tab.
1016 * @param string $active_page_subtab Active subtab.
1017 *
1018 * @return bool
1019 */
1020 function wpbc_availability_general__check_showing_page( $is_show_this_page, $page_tag, $active_page_tab, $active_page_subtab ) {
1021
1022 if ( ( 'wpbc-availability' === $page_tag ) && ( 'general_availability' === $active_page_tab ) && ( ! wpbc_is_mu_user_can_be_here( 'only_super_admin' ) || ! current_user_can( wpbc_availability_general__get_manage_cap() ) ) ) {
1023 return false;
1024 }
1025
1026 return $is_show_this_page;
1027 }
1028 add_filter( 'wpbc_before_showing_settings_page_is_show_page', 'wpbc_availability_general__check_showing_page', 20, 4 );
1029
1030 /**
1031 * General Availability tab.
1032 */
1033 class WPBC_Page_Availability_General_Dedicated extends WPBC_Page_Structure {
1034
1035 /**
1036 * Whether settings were saved during this request.
1037 *
1038 * @var bool
1039 */
1040 private $is_updated = false;
1041
1042 /**
1043 * Validation failure retained for the non-AJAX form fallback.
1044 *
1045 * @var WP_Error|null
1046 */
1047 private $validation_error = null;
1048
1049 /**
1050 * Menu slug.
1051 *
1052 * @return string
1053 */
1054 public function in_page() {
1055
1056 if ( ! wpbc_is_mu_user_can_be_here( 'only_super_admin' ) || ! current_user_can( wpbc_availability_general__get_manage_cap() ) ) {
1057 return (string) wp_rand( 100000, 1000000 );
1058 }
1059
1060 return 'wpbc-availability';
1061 }
1062
1063 /**
1064 * Register tab.
1065 *
1066 * @return array
1067 */
1068 public function tabs() {
1069
1070 $tabs = array();
1071
1072 $tabs['general_availability'] = array(
1073 'is_show_top_path' => true,
1074 'right_vertical_sidebar__is_show' => true,
1075 'right_vertical_sidebar__default_view_mode' => '',
1076 'right_vertical_sidebar_compact__is_show' => true,
1077 'left_navigation__default_view_mode' => '', // FixIn:11.7.1.1: 'compact',
1078 'top_path_title' => __( 'Schedule & Rules', 'booking' ),
1079 'title' => __( 'Schedule & Rules', 'booking' ),
1080 'hint' => __( 'Define global front-end availability rules for all calendars, including unavailable weekdays, booking buffers, and availability limits.', 'booking' ),
1081 'page_title' => __( 'Schedule & Rules', 'booking' ),
1082 'link' => '',
1083 'position' => '',
1084 'css_classes' => 'wpbc_top_tab__general_availability',
1085 'icon' => '',
1086 'font_icon' => 'wpbc-bi-calendar2-day',
1087 'font_icon_right' => '',
1088 'default' => false,
1089 'disabled' => false,
1090 'hided' => false,
1091 'subtabs' => array(),
1092 'folder_style' => 'order:93;',
1093 'is_show_top_navigation' => true,
1094 );
1095
1096 return $tabs;
1097 }
1098
1099 /**
1100 * Save settings.
1101 *
1102 * @return void
1103 */
1104 public function maybe_update() {
1105
1106 $form_name = 'wpbc_availability_general_form';
1107
1108 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1109 if ( ! isset( $_POST[ 'is_form_submitted_' . $form_name ] ) ) {
1110 return;
1111 }
1112
1113 check_admin_referer( 'wpbc_settings_page_' . $form_name );
1114
1115 if ( ! wpbc_is_mu_user_can_be_here( 'only_super_admin' ) || ! current_user_can( wpbc_availability_general__get_manage_cap() ) ) {
1116 return;
1117 }
1118
1119 $cleaned_data = wpbc_availability_general__validate_data( $_POST ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1120 if ( is_wp_error( $cleaned_data ) ) {
1121 $this->validation_error = $cleaned_data;
1122 return;
1123 }
1124
1125 wpbc_availability_general__update_settings( $cleaned_data );
1126
1127 $this->is_updated = true;
1128 }
1129
1130 /**
1131 * Right compact sidebar tabs.
1132 *
1133 * @return void
1134 */
1135 public function right_sidebar_compact_content() {
1136
1137 WPBC_UI_Sidebar_Panels::render_rightbar_tabs(
1138 array(
1139 array(
1140 'id' => 'wpbc_tab_ag_settings',
1141 'panel_id' => 'wpbc_ag__inspector_settings',
1142 'title' => __( 'Settings', 'booking' ),
1143 'icon' => 'wpbc_icn_tune',
1144 'selected' => true,
1145 ),
1146 array(
1147 'id' => 'wpbc_tab_ag_notes',
1148 'panel_id' => 'wpbc_ag__inspector_notes',
1149 'title' => __( 'Notes', 'booking' ),
1150 'icon' => 'wpbc-bi-info-circle',
1151 ),
1152 ),
1153 array(
1154 'aria_label' => __( 'Schedule & Rules Panels', 'booking' ),
1155 'context' => 'general_availability',
1156 'class' => 'wpbc_ag_rightbar_tabs',
1157 )
1158 );
1159 }
1160
1161 /**
1162 * Right sidebar panels.
1163 *
1164 * @return void
1165 */
1166 public function right_sidebar_content() {
1167 ?>
1168 <div class="wpbc_bfb__panel--library wpbc_rightbar_palette wpbc_ag_rightbar_panels">
1169 <?php
1170 WPBC_UI_Sidebar_Panels::render_panel(
1171 array(
1172 'id' => 'wpbc_ag__inspector_settings',
1173 'labelledby' => 'wpbc_tab_ag_settings',
1174 'class' => 'wpbc_ag__inspector_settings',
1175 ),
1176 array( $this, 'right_panel_settings_content' )
1177 );
1178
1179 WPBC_UI_Sidebar_Panels::render_panel(
1180 array(
1181 'id' => 'wpbc_ag__inspector_notes',
1182 'labelledby' => 'wpbc_tab_ag_notes',
1183 'class' => 'wpbc_ag__inspector_notes',
1184 'hidden' => true,
1185 ),
1186 array( $this, 'right_panel_notes_content' )
1187 );
1188 ?>
1189 </div>
1190 <?php
1191 }
1192
1193 /**
1194 * Settings panel content.
1195 *
1196 * @return void
1197 */
1198 public function right_panel_settings_content() {
1199
1200 $form_name = 'wpbc_availability_general_form';
1201 $hints = function_exists( 'wpbc_get_unavailable_from_today_hints_arr' ) ? wpbc_get_unavailable_from_today_hints_arr() : array();
1202 $buffer_type = (string) get_bk_option( 'booking_unavailable_extra_in_out' );
1203 $is_buffer_available = class_exists( 'wpdev_bk_biz_m' );
1204 $is_available_limit_available = class_exists( 'wpdev_bk_biz_m' );
1205 $unavailable_weekdays = array();
1206 $working_time_settings = wpbc_working_time__get_settings();
1207 $working_time_default_settings = wpbc_working_time__get_default_settings();
1208 $working_time_options = wpbc_availability_general__get_working_time_options(
1209 wpbc_availability_general__get_working_time_option_values( $working_time_settings )
1210 );
1211 $working_time_resource_id = wpbc_availability_general__get_preview_resource_id();
1212 $open_section = wpbc_availability_general__get_open_section();
1213 $working_time_resource = isset( $working_time_settings['resources'][ $working_time_resource_id ] )
1214 ? $working_time_settings['resources'][ $working_time_resource_id ]
1215 : array(
1216 'mode' => 'inherit',
1217 'weekdays' => $working_time_default_settings['default']['weekdays'],
1218 );
1219
1220 for ( $i = 0; $i < 7; $i++ ) {
1221 if ( 'On' === get_bk_option( 'booking_unavailable_day' . $i ) ) {
1222 $unavailable_weekdays[] = $i;
1223 }
1224 }
1225
1226 WPBC_UI_Sidebar_Panels::render_inspector_header( __( 'Schedule & Rules', 'booking' ), __( 'Availability rules for all calendars.', 'booking' ) );
1227 ?>
1228 <form method="post" id="<?php echo esc_attr( $form_name ); ?>" class="wpbc_ag_settings_form" data-wpbc-ag-settings-form="1">
1229 <?php wp_nonce_field( 'wpbc_settings_page_' . $form_name ); ?>
1230 <input type="hidden" name="is_form_submitted_<?php echo esc_attr( $form_name ); ?>" value="1" />
1231 <div class="wpbc_bfb__inspector__body wpbc_ag_inspector_body">
1232 <?php if ( is_wp_error( $this->validation_error ) ) : ?>
1233 <div class="wpbc_ag_notice wpbc_ag_notice_error" role="alert">
1234 <?php echo esc_html( $this->validation_error->get_error_message() ); ?>
1235 </div>
1236 <?php endif; ?>
1237 <?php
1238 WPBC_UI_Sidebar_Panels::render_collapsible_group(
1239 array(
1240 'id' => 'wpbc_ag_weekdays_group',
1241 'group' => 'general-availability-weekdays',
1242 'title' => __( 'Unavailable Weekdays', 'booking' ),
1243 'open' => ( 'working_time' !== $open_section ),
1244 ),
1245 function () use ( $unavailable_weekdays ) {
1246 $days_ordered = array(
1247 1 => array(
1248 'short' => _x( 'Mo', 'Short weekday label', 'booking' ),
1249 'full' => __( 'Monday', 'booking' ),
1250 ),
1251 2 => array(
1252 'short' => _x( 'Tu', 'Short weekday label', 'booking' ),
1253 'full' => __( 'Tuesday', 'booking' ),
1254 ),
1255 3 => array(
1256 'short' => _x( 'We', 'Short weekday label', 'booking' ),
1257 'full' => __( 'Wednesday', 'booking' ),
1258 ),
1259 4 => array(
1260 'short' => _x( 'Th', 'Short weekday label', 'booking' ),
1261 'full' => __( 'Thursday', 'booking' ),
1262 ),
1263 5 => array(
1264 'short' => _x( 'Fr', 'Short weekday label', 'booking' ),
1265 'full' => __( 'Friday', 'booking' ),
1266 ),
1267 6 => array(
1268 'short' => _x( 'Sa', 'Short weekday label', 'booking' ),
1269 'full' => __( 'Saturday', 'booking' ),
1270 ),
1271 0 => array(
1272 'short' => _x( 'Su', 'Short weekday label', 'booking' ),
1273 'full' => __( 'Sunday', 'booking' ),
1274 ),
1275 );
1276 ?>
1277 <div class="wpbc_ag_weekday_grid">
1278 <?php foreach ( $days_ordered as $day_num => $day_labels ) : ?>
1279 <?php
1280 $unavailable_day_aria_label = sprintf(
1281 /* translators: %s: Full weekday name. */
1282 __( 'Set %s as unavailable', 'booking' ),
1283 $day_labels['full']
1284 );
1285 ?>
1286 <label class="wpbc_ag_switch wpbc_ag_weekday_switch">
1287 <input type="checkbox" name="booking_unavailable_days[]" value="<?php echo esc_attr( $day_num ); ?>" aria-label="<?php echo esc_attr( $unavailable_day_aria_label ); ?>" <?php checked( in_array( $day_num, $unavailable_weekdays, true ) ); ?> />
1288 <span class="wpbc_ag_switch_control" aria-hidden="true"><span class="wpbc_ag_switch_knob"></span></span>
1289 <span class="wpbc_ag_switch_label"><?php echo esc_html( $day_labels['short'] ); ?></span>
1290 </label>
1291 <?php endforeach; ?>
1292 </div>
1293 <p class="wpbc_ag_description"><?php esc_html_e( 'Selected weekdays will be unavailable in calendars and override other availability settings.', 'booking' ); ?></p>
1294 <?php
1295 }
1296 );
1297
1298 WPBC_UI_Sidebar_Panels::render_collapsible_group(
1299 array(
1300 'id' => 'wpbc_ag_working_time_group',
1301 'group' => 'general-availability-working-time',
1302 'title' => __( 'Working Time', 'booking' ),
1303 'open' => ( 'working_time' === $open_section ),
1304 ),
1305 function () use ( $working_time_settings, $working_time_resource_id, $working_time_resource, $working_time_options ) {
1306 ?>
1307 <div class="wpbc_ag_notice wpbc_ag_working_time_notice"><?php esc_html_e( 'Working Time restricts only time-based bookings, such as rangetime, start/end time, or start/duration time fields.', 'booking' ); ?></div>
1308 <input type="hidden" name="booking_working_time_resource_id" data-wpbc-working-time-resource-id="1" value="<?php echo esc_attr( $working_time_resource_id ); ?>" />
1309 <label class="wpbc_ag_switch wpbc_ag_switch_card">
1310 <input type="checkbox" name="booking_working_time_enabled" value="On" <?php checked( $working_time_settings['enabled'], 'On' ); ?> />
1311 <span class="wpbc_ag_switch_control" aria-hidden="true"><span class="wpbc_ag_switch_knob"></span></span>
1312 <span class="wpbc_ag_switch_label"><?php esc_html_e( 'Restrict time-based bookings to working time', 'booking' ); ?></span>
1313 </label>
1314
1315 <div class="wpbc_ag_working_time_block">
1316 <div class="wpbc_ag_scope_title"><?php esc_html_e( 'Default working time', 'booking' ); ?></div>
1317 <p class="wpbc_ag_description"><?php esc_html_e( 'Used by all booking resources unless a resource override is configured below.', 'booking' ); ?></p>
1318 <?php wpbc_availability_general__render_working_time_weekdays( 'booking_working_time_default', $working_time_settings['default']['weekdays'], $working_time_options ); ?>
1319 </div>
1320
1321 <div class="wpbc_ag_working_time_block wpbc_ag_working_time_resource_block" data-wpbc-working-time-resource-block="1">
1322 <div class="wpbc_ag_scope_title"><?php esc_html_e( 'Selected resource override', 'booking' ); ?></div>
1323 <p class="wpbc_ag_description"><?php esc_html_e( 'This override follows the booking resource selected in the page toolbar.', 'booking' ); ?></p>
1324 <div class="wpbc_ag_radio_stack wpbc_ag_working_time_mode">
1325 <label><input type="radio" name="booking_working_time_resource_mode" value="inherit" <?php checked( $working_time_resource['mode'], 'inherit' ); ?> /> <?php esc_html_e( 'Inherit default working time', 'booking' ); ?></label>
1326 <label><input type="radio" name="booking_working_time_resource_mode" value="custom" <?php checked( $working_time_resource['mode'], 'custom' ); ?> /> <?php esc_html_e( 'Use custom working time', 'booking' ); ?></label>
1327 <label><input type="radio" name="booking_working_time_resource_mode" value="disabled" <?php checked( $working_time_resource['mode'], 'disabled' ); ?> /> <?php esc_html_e( 'Do not restrict this resource by working time', 'booking' ); ?></label>
1328 </div>
1329 <div class="wpbc_ag_working_time_custom" data-wpbc-working-time-resource-custom="1">
1330 <?php wpbc_availability_general__render_working_time_weekdays( 'booking_working_time_resource', $working_time_resource['weekdays'], $working_time_options ); ?>
1331 </div>
1332 </div>
1333 <?php
1334 }
1335 );
1336
1337 WPBC_UI_Sidebar_Panels::render_collapsible_group(
1338 array(
1339 'id' => 'wpbc_ag_today_group',
1340 'group' => 'general-availability-from-today',
1341 'title' => __( 'Availability From Today', 'booking' ),
1342 'open' => false,
1343 ),
1344 function () use ( $hints, $is_available_limit_available ) {
1345 ?>
1346 <div class="wpbc_ag_field_row">
1347 <label class="wpbc_ag_field_label" for="booking_unavailable_days_num_from_today"><?php esc_html_e( 'Unavailable time from current time', 'booking' ); ?></label>
1348 <?php
1349 wpbc_availability_general__render_select_with_slider(
1350 'booking_unavailable_days_num_from_today',
1351 wpbc_availability_general__get_unavailable_from_today_options(),
1352 get_bk_option( 'booking_unavailable_days_num_from_today' ),
1353 array( 'slider_label' => __( 'Adjust unavailable time from current time', 'booking' ) )
1354 );
1355 ?>
1356 <code class="wpbc_ag_hint" data-wpbc-ag-hint="booking_unavailable_days_num_from_today"><span class="wpbc_ag_hint_unavailable"><?php esc_html_e( 'Unavailable', 'booking' ); ?></span><?php echo wp_kses_post( isset( $hints['booking_unavailable_days_num_from_today__hint'] ) ? $hints['booking_unavailable_days_num_from_today__hint'] : '' ); ?></code>
1357 </div>
1358 <div class="wpbc_ag_field_row">
1359 <label class="wpbc_ag_field_label" for="booking_available_days_num_from_today"><?php esc_html_e( 'Limit available days from today', 'booking' ); ?></label>
1360 <?php if ( ! $is_available_limit_available ) : ?>
1361 <div class="wpbc_ag_upgrade_required">
1362 <a class="wpbc_pro_label" href="<?php echo esc_url( 'https://wpbookingcalendar.com/features/' ); ?>" target="_blank">Pro | BM+</a>
1363 <span><?php esc_html_e( 'Limit available days from today is available only in Booking Calendar Business Medium or higher versions.', 'booking' ); ?></span>
1364 </div>
1365 <?php endif; ?>
1366 <div class="<?php echo $is_available_limit_available ? '' : 'wpbc_ag_is_locked'; ?>">
1367 <?php
1368 wpbc_availability_general__render_select_with_slider(
1369 'booking_available_days_num_from_today',
1370 wpbc_availability_general__get_available_from_today_options(),
1371 get_bk_option( 'booking_available_days_num_from_today' ),
1372 array(
1373 'slider_label' => __( 'Adjust available days limit', 'booking' ),
1374 'disabled' => ! $is_available_limit_available,
1375 )
1376 );
1377 ?>
1378 </div>
1379 <code class="wpbc_ag_hint" data-wpbc-ag-hint="booking_available_days_num_from_today"><span class="wpbc_ag_hint_available"><?php esc_html_e( 'Available', 'booking' ); ?></span><?php echo wp_kses_post( isset( $hints['booking_available_days_num_from_today__hint'] ) ? $hints['booking_available_days_num_from_today__hint'] : '' ); ?></code>
1380 </div>
1381 <?php
1382 }
1383 );
1384
1385 WPBC_UI_Sidebar_Panels::render_collapsible_group(
1386 array(
1387 'id' => 'wpbc_ag_buffer_group',
1388 'group' => 'general-availability-buffer',
1389 'title' => __( 'Booking Buffer', 'booking' ),
1390 'open' => false,
1391 ),
1392 function () use ( $buffer_type, $is_buffer_available ) {
1393 ?>
1394 <?php if ( ! $is_buffer_available ) : ?>
1395 <div class="wpbc_ag_upgrade_required">
1396 <a class="wpbc_pro_label" href="<?php echo esc_url( 'https://wpbookingcalendar.com/features/' ); ?>" target="_blank">Pro | BM+</a>
1397 <span><?php esc_html_e( 'Booking buffer is available only in Booking Calendar Business Medium or higher versions.', 'booking' ); ?></span>
1398 </div>
1399 <?php endif; ?>
1400 <div class="wpbc_ag_radio_stack <?php echo $is_buffer_available ? '' : 'wpbc_ag_is_locked'; ?>">
1401 <label><input type="radio" name="booking_unavailable_extra_in_out" value="" <?php checked( $buffer_type, '' ); ?> <?php disabled( ! $is_buffer_available ); ?> /> <?php echo esc_html( ucfirst( __( 'None', 'booking' ) ) ); ?></label>
1402 <label><input type="radio" name="booking_unavailable_extra_in_out" value="m" <?php checked( $buffer_type, 'm' ); ?> <?php disabled( ! $is_buffer_available ); ?> /> <?php echo esc_html( ucfirst( __( 'minutes', 'booking' ) ) . ' / ' . ucfirst( __( 'hours', 'booking' ) ) ); ?></label>
1403 <label><input type="radio" name="booking_unavailable_extra_in_out" value="d" <?php checked( $buffer_type, 'd' ); ?> <?php disabled( ! $is_buffer_available ); ?> /> <?php echo esc_html( ucfirst( __( 'day(s)', 'booking' ) ) ); ?></label>
1404 </div>
1405 <div class="wpbc_ag_buffer_fields" data-buffer-panel="m">
1406 <div class="wpbc_ag_field_row">
1407 <label class="wpbc_ag_field_label" for="booking_unavailable_extra_minutes_in"><?php esc_html_e( 'Before booking', 'booking' ); ?></label>
1408 <?php wpbc_availability_general__render_select_with_stepper( 'booking_unavailable_extra_minutes_in', wpbc_availability_general__get_buffer_minutes_options(), get_bk_option( 'booking_unavailable_extra_minutes_in' ), array( 'disabled' => ! $is_buffer_available ) ); ?>
1409 </div>
1410 <div class="wpbc_ag_field_row">
1411 <label class="wpbc_ag_field_label" for="booking_unavailable_extra_minutes_out"><?php esc_html_e( 'After booking', 'booking' ); ?></label>
1412 <?php wpbc_availability_general__render_select_with_stepper( 'booking_unavailable_extra_minutes_out', wpbc_availability_general__get_buffer_minutes_options(), get_bk_option( 'booking_unavailable_extra_minutes_out' ), array( 'disabled' => ! $is_buffer_available ) ); ?>
1413 </div>
1414 </div>
1415 <div class="wpbc_ag_buffer_fields" data-buffer-panel="d">
1416 <div class="wpbc_ag_field_row">
1417 <label class="wpbc_ag_field_label" for="booking_unavailable_extra_days_in"><?php esc_html_e( 'Before booking', 'booking' ); ?></label>
1418 <?php wpbc_availability_general__render_select_with_stepper( 'booking_unavailable_extra_days_in', wpbc_availability_general__get_buffer_days_options(), get_bk_option( 'booking_unavailable_extra_days_in' ), array( 'disabled' => ! $is_buffer_available ) ); ?>
1419 </div>
1420 <div class="wpbc_ag_field_row">
1421 <label class="wpbc_ag_field_label" for="booking_unavailable_extra_days_out"><?php esc_html_e( 'After booking', 'booking' ); ?></label>
1422 <?php wpbc_availability_general__render_select_with_stepper( 'booking_unavailable_extra_days_out', wpbc_availability_general__get_buffer_days_options(), get_bk_option( 'booking_unavailable_extra_days_out' ), array( 'disabled' => ! $is_buffer_available ) ); ?>
1423 </div>
1424 </div>
1425 <?php
1426 }
1427 );
1428 ?>
1429 </div>
1430 </form>
1431 <?php
1432 }
1433
1434 /**
1435 * Notes panel content.
1436 *
1437 * @return void
1438 */
1439 public function right_panel_notes_content() {
1440
1441 WPBC_UI_Sidebar_Panels::render_inspector_header( __( 'Notes', 'booking' ), __( 'How these rules are applied.', 'booking' ) );
1442 ?>
1443 <div class="wpbc_bfb__inspector__body wpbc_ag_inspector_body" style="padding:10px;">
1444 <div class="wpbc_ag_notice">
1445 <strong><?php esc_html_e( 'Note', 'booking' ); ?>!</strong>
1446 <?php esc_html_e( 'These options do not apply in the admin panel. These options apply only on the front-end side.', 'booking' ); ?>
1447 </div>
1448 <div class="wpbc_ag_scope_card">
1449 <div class="wpbc_ag_scope_title"><?php esc_html_e( 'Scope', 'booking' ); ?></div>
1450 <p><?php esc_html_e( 'The calendar on this page previews one selected booking resource. The settings in the palette are general availability settings and affect front-end calendars globally.', 'booking' ); ?></p>
1451 </div>
1452 </div>
1453 <?php
1454 }
1455
1456 /**
1457 * Show page content.
1458 *
1459 * @return void|false
1460 */
1461 public function content() {
1462
1463 do_action( 'wpbc_hook_settings_page_header', 'page_booking_availability_general' );
1464
1465 if ( ! wpbc_is_mu_user_can_be_here( 'only_super_admin' ) || ! current_user_can( wpbc_availability_general__get_manage_cap() ) ) {
1466 return false;
1467 }
1468
1469 if ( function_exists( 'wpbc_js_for_bookings_page' ) ) {
1470 wpbc_js_for_bookings_page();
1471 }
1472
1473 $resource_id = wpbc_availability_general__get_preview_resource_id();
1474 $months_count = wpbc_availability_general__get_preview_months_count();
1475 $resources = wpbc_availability_general__get_resources();
1476 ?>
1477 <div class="wpbc_ag_page wpdevelop" data-wpbc-ag-page="1" data-wpbc-ag-resource-id="<?php echo esc_attr( $resource_id ); ?>">
1478 <form method="get" class="wpbc_ag_toolbar wpbc_ts_toolbar wpbc_ag_preview_controls" data-wpbc-ag-preview-toolbar="1">
1479 <input type="hidden" name="page" value="wpbc-availability" />
1480 <input type="hidden" name="tab" value="general_availability" />
1481 <div class="wpbc_ts_control wpbc_ag_control_resource">
1482 <label for="wpbc_ag_resource_id"><?php esc_html_e( 'Booking resource', 'booking' ); ?></label>
1483 <select id="wpbc_ag_resource_id" name="resource_id">
1484 <?php foreach ( $resources as $resource ) : ?>
1485 <option value="<?php echo esc_attr( $resource['id'] ); ?>" <?php selected( $resource_id, (int) $resource['id'] ); ?>><?php echo esc_html( $resource['title'] ); ?></option>
1486 <?php endforeach; ?>
1487 </select>
1488 </div>
1489 <div class="wpbc_ts_control wpbc_ag_control_months">
1490 <label for="wpbc_ag_months_count"><?php esc_html_e( 'Months', 'booking' ); ?></label>
1491 <select id="wpbc_ag_months_count" name="months_count">
1492 <?php foreach ( array( 1, 2, 3, 4, 6, 12 ) as $month_option ) : ?>
1493 <option value="<?php echo esc_attr( $month_option ); ?>" <?php selected( $months_count, $month_option ); ?>><?php echo esc_html( $month_option ); ?></option>
1494 <?php endforeach; ?>
1495 </select>
1496 </div>
1497 </form>
1498 <?php wpbc_availability_general__render_calendar_preview( $resource_id, $months_count ); ?>
1499 <div class="wpbc_ag_calendar_notes" data-wpbc-ag-calendar-notes="1">
1500 <div class="wpbc_ts_hint wpbc_ag_hint_bar">
1501 <span class="wpbc_icn_info_outline"></span>
1502 <?php esc_html_e( 'Preview how the current general availability rules affect a front-end calendar.', 'booking' ); ?>
1503 </div>
1504 </div>
1505 </div>
1506 <?php
1507
1508 do_action( 'wpbc_hook_settings_page_footer', 'page_booking_availability_general' );
1509 }
1510
1511 }
1512 add_action( 'wpbc_menu_created', array( new WPBC_Page_Availability_General_Dedicated(), '__construct' ) );
1513