PluginProbe
Booking Calendar / 11.8
Booking Calendar v11.8
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 10.11.2 10.11.3 All 202 releases
booking / includes / _front_end / class-fe-shortcode-params.php

class-fe-shortcode-params.php in Booking Calendar 11.8, at includes/_front_end/class-fe-shortcode-params.php

701 lines 20.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Front-End Shortcode Params Normalizer
4 *
5 * Goal:
6 * - Normalize and sanitize shortcode attributes into a stable params array
7 * for WPBC_FE_Render::render_booking_form() and ::render_calendar_only().
8 *
9 * @file: includes/_front_end/class-fe-shortcode-params.php
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Front-end shortcode params: Parse -> Sanitize (single entry point).
18 *
19 * @since 11.0.x
20 */
21 class WPBC_FE_Shortcode_Params {
22
23 /**
24 * Get primary resource id from attrs:
25 *
26 * IMPORTANT: does NOT apply aggregate/agregate.
27 *
28 * @param array $attr - array with all parameters if shortcode.
29 * @param int $default_id - default booking resource ID.
30 *
31 * @return int
32 */
33 public static function get_from_attr__primary_resource_id( $attr, $default_id = 1 ) {
34
35 $resource_id = intval( $default_id );
36 $type_value = null;
37
38 if ( isset( $attr['type'] ) ) {
39 $type_value = intval( $attr['type'] );
40 }
41
42 if ( isset( $attr['resource_id'] ) ) {
43 $type_value = intval( $attr['resource_id'] );
44 }
45
46 if ( null !== $type_value ) {
47 $resource_id = intval( $type_value );
48 }
49
50 return $resource_id;
51 }
52
53 /**
54 * Get resource id with aggregate/agregate support (legacy for [booking] / [bookingcalendar] / [bookingedit]).
55 *
56 * Sanitizes resource_id with support of:
57 * - aliases: resource_id -> type
58 * - aggregate/agregate
59 * - commas as separators
60 *
61 * Returns string (may include aggregate IDs "5;3;9") for renderer to split later.
62 *
63 * @param array $attr - array with all parameters if shortcode.
64 * @param int $default_id - default booking resource ID.
65 *
66 * @return int|string -> 5 | '5;9;10'
67 */
68 public static function get_from_attr__resource_id_with_aggregate( $attr, $default_id = 1 ) {
69
70 $resource_id = self::get_from_attr__primary_resource_id( $attr, $default_id );
71
72 // Aggregate (two spellings).
73 if ( isset( $attr['agregate'] ) && ( '' !== (string) $attr['agregate'] ) ) {
74 $resource_id .= ';' . (string) $attr['agregate'];
75 }
76 if ( isset( $attr['aggregate'] ) && ( '' !== (string) $attr['aggregate'] ) ) {
77 $resource_id .= ';' . (string) $attr['aggregate'];
78 }
79
80 // Normalize separators.
81 $resource_id = str_replace( ',', ';', $resource_id );
82
83 // Strict sanitize: digits and separators.
84 $resource_id__or__csd = wpbc_clean_digit_or_csd( $resource_id );
85 $resource_id = str_replace( ',', ';', $resource_id__or__csd );
86
87 return ( '' !== $resource_id ) ? $resource_id : (string) (int) $default_id;
88 }
89
90 /**
91 * Get calendar months count from shortcode attrs (nummonths) with fallback.
92 *
93 * @param array $attr - array with all parameters if shortcode.
94 * @param int $default_count - default count of months.
95 *
96 * @return int
97 */
98 public static function get_from_attr__months_count( $attr, $default_count = 1 ) {
99
100 $cal_count = intval( $default_count );
101
102 if ( isset( $attr['nummonths'] ) ) {
103 $cal_count = intval( $attr['nummonths'] );
104 }
105 if ( isset( $attr['monthsnum'] ) ) {
106 $cal_count = intval( $attr['monthsnum'] );
107 }
108 if ( isset( $attr['monthscount'] ) ) {
109 $cal_count = intval( $attr['monthscount'] );
110 }
111 if ( isset( $attr['months_count'] ) ) {
112 $cal_count = intval( $attr['months_count'] );
113 }
114
115 $cal_count = max( 1, min( 36, (int) $cal_count ) ); // choose your max.
116
117 return $cal_count;
118 }
119
120 /**
121 * Parse startmonth into legacy format array( year, month ) or false.
122 * Accepts "YYYY-M" or "YYYY-MM".
123 *
124 * @param array $attr - array with all parameters if shortcode.
125 * @param false|mixed $default_val - false, if not defined.
126 *
127 * @return false|mixed|string[]
128 */
129 public static function get_from_attr__start_month( $attr, $default_val = false ) {
130
131 $val = '';
132
133 if ( isset( $attr['startmonth'] ) ) {
134 $val = trim( (string) $attr['startmonth'] );
135 }
136
137 if ( '' === $val ) {
138 return $default_val;
139 }
140
141 if ( ! preg_match( '/^(\d{4})-(\d{1,2})$/', $val, $m ) ) {
142 return $default_val;
143 }
144
145 $year = (int) $m[1];
146 $month = (int) $m[2];
147
148 if ( ( $year < 1900 ) || ( $year > 2100 ) ) {
149 return $default_val;
150 }
151 if ( ( $month < 1 ) || ( $month > 12 ) ) {
152 return $default_val;
153 }
154
155 return array( (string) $year, (string) $month );
156 }
157
158 /**
159 * Get calendar_dates_start usualy in format '2027-01-01'
160 *
161 * @param array $attr - array with all parameters if shortcode.
162 * @param string $default_val - ''.
163 *
164 * @return string
165 */
166 public static function get_from_attr__calendar_dates_start( $attr, $default_val = '' ) {
167
168 $val = '';
169
170 if ( isset( $attr['calendar_dates_start'] ) ) {
171 $val = (string) wpbc_sanitize_date( $attr['calendar_dates_start'] );
172 }
173
174 if ( '' !== $val ) {
175 return $val;
176 }
177
178 return (string) $default_val;
179 }
180
181 /**
182 * Get calendar_dates_end usualy in format '2027-12-31'
183 *
184 * @param array $attr - array with all parameters if shortcode.
185 * @param string $default_val - ''.
186 *
187 * @return string
188 */
189 public static function get_from_attr__calendar_dates_end( $attr, $default_val = '' ) {
190
191 $val = '';
192
193 if ( isset( $attr['calendar_dates_end'] ) ) {
194 $val = (string) wpbc_sanitize_date( $attr['calendar_dates_end'] );
195 }
196
197 if ( '' !== $val ) {
198 return $val;
199 }
200
201 return (string) $default_val;
202 }
203
204 /**
205 * Get selected_dates for Only 'booking form' without calendar - usualy in format '24.12.2027, 25.12.2027';
206 *
207 * @param array $attr - array with all parameters if shortcode.
208 * @param string $default_val - ''.
209 *
210 * @return string
211 */
212 public static function get_from_attr__selected_dates_without_calendar( $attr, $default_val = '' ) {
213
214 $val = '';
215 // '24.12.2027, 25.12.2027'
216 if ( isset( $attr['selected_dates'] ) ) {
217 $val = (string) wpbc_sanitize_csv_dates( $attr['selected_dates'] );
218 }
219
220 if ( '' !== $val ) {
221 return $val;
222 }
223
224 return (string) $default_val;
225 }
226
227 /**
228 * Get options parameter from shortcode [booking ... options="..."] value (legacy key: 'options').
229 *
230 * @param array $attr - array with all parameters if shortcode.
231 * @param string $default_val - ''.
232 *
233 * @return string
234 */
235 public static function get_from_attr__options( $attr, $default_val = '' ) {
236
237 $options = '';
238
239 if ( isset( $attr['options'] ) ) {
240 $options = (string) $attr['options'];
241 $options = sanitize_text_field( wp_unslash( $options ) );
242 }
243
244 if ( ! empty( $options ) ) {
245 return $options;
246 }
247
248 return $default_val;
249 }
250
251 /**
252 * Get custom booking form name (form type) from shortcode attrs (key: form_type).
253 *
254 * @param array $attr - array with all parameters if shortcode.
255 * @param string $default_val - 'standard'.
256 *
257 * @return string
258 */
259 public static function get_from_attr__custom_form( $attr, $default_val = 'standard' ) {
260
261 $custom_form = '';
262
263 if ( isset( $attr['form_type'] ) ) {
264 $custom_form = sanitize_text_field( wp_unslash( $attr['form_type'] ) );
265 }
266 if ( isset( $attr['custom_form'] ) ) {
267 $custom_form = sanitize_text_field( wp_unslash( $attr['custom_form'] ) );
268 }
269 if ( isset( $attr['booking_form'] ) ) {
270 $custom_form = sanitize_text_field( wp_unslash( $attr['booking_form'] ) );
271 }
272 if ( isset( $attr['custom_booking_form'] ) ) {
273 $custom_form = sanitize_text_field( wp_unslash( $attr['custom_booking_form'] ) );
274 }
275
276 if ( ! empty( $custom_form ) ) {
277 return (string) $custom_form;
278 }
279
280 // Get Primary booking resource ID from shortcode attr.
281 $resource_id = self::get_from_attr__primary_resource_id( $attr, WPBC_FE_Attr_Postprocessor::get_default_booking_resource_id() );
282 // -------------------------------------------------------------------------------------------------------------
283 // Maybe get "Default Custom Form" for specific booking resource, in >= BM , if in shortcode 'custom_form' is EMPTY!
284 // -------------------------------------------------------------------------------------------------------------
285 $default_custom_form_name = apply_bk_filter( 'wpbc_get_default_custom_form', '', $resource_id );
286 if ( ! empty( $default_custom_form_name ) ) {
287 return (string) $default_custom_form_name;
288 }
289
290 return (string) $default_val;
291 }
292
293 /**
294 * Get status of form, e.g.: 'published' | 'preview' -> from shortcode attrs (key: form_status).
295 *
296 * @param array $attr - array with all parameters if shortcode.
297 * @param string $default_val - 'published'.
298 *
299 * @return string
300 */
301 public static function get_from_attr__form_status( $attr, $default_val = 'published' ) {
302
303 $val = '';
304
305 if ( isset( $attr['form_status'] ) ) {
306 $val = sanitize_key( wp_unslash( $attr['form_status'] ) );
307 }
308
309 // Normalize synonyms (optional).
310 if ( in_array( $val, array( 'publish', 'published' ), true ) ) {
311 $val = 'published';
312 }
313 if ( in_array( $val, array( 'preview' ), true ) ) {
314 $val = 'preview';
315 }
316
317 // Whitelist (keep tight to avoid future “draft” leakage on frontend).
318 if ( ! in_array( $val, array( 'published', 'preview' ), true ) ) {
319 $val = (string) $default_val;
320 }
321
322 return ( '' !== $val ) ? $val : (string) $default_val;
323 }
324 }
325
326
327 /**
328 * Post-processing helpers for normalized attributes.
329 *
330 * This class contains logic that operates on already-sanitized values:
331 * - aggregate resource splitting
332 * - resource validation
333 * - booking_hash resolution and parent-resource rewrite (legacy behavior)
334 * - date-range completion (calendar_dates_start/end)
335 *
336 * @since 11.0.x
337 */
338 class WPBC_FE_Attr_Postprocessor {
339
340
341 /**
342 * Get default Booking resource.
343 *
344 * @return int|mixed
345 */
346 public static function get_default_booking_resource_id() {
347
348 $resource_id = 1;
349
350 $legacy = wpbc_get_legacy_booking_instance();
351
352 if ( ( ! empty( $legacy ) ) && ( false !== $legacy->wpdev_bk_personal ) ) {
353 $resource_id = (int) $legacy->wpdev_bk_personal->get_default_booking_resource_id();
354 }
355
356 return (int) $resource_id;
357 }
358
359 /**
360 * If resource_id contains aggregate IDs "5;3;9", return first + full list, e.g: [ 'resource_id' => 5, 'aggregate_resource_id_arr' => [5,3,9] ]
361 *
362 * @param mixed $resource_id - e.g. "5;3;9" or "5" or 5.
363 *
364 * @return array(
365 * 'resource_id' => string,
366 * 'aggregate_resource_id_arr' => array
367 * )
368 */
369 public static function split_aggregate_resource_id( $resource_id ) {
370
371 $aggregate_resource_id_arr = array();
372
373 $resource_id = (string) $resource_id;
374
375 $resource_id = str_replace( ',', ';', wpbc_clean_digit_or_csd( $resource_id ) );
376
377 if ( false !== strpos( $resource_id, ';' ) ) {
378 $aggregate_resource_id_arr = explode( ';', $resource_id );
379 $resource_id = $aggregate_resource_id_arr[0];
380 }
381
382 $aggregate_resource_id_arr = array_values( array_filter( array_map( 'absint', $aggregate_resource_id_arr ) ) );
383 $aggregate_resource_id_arr = array_values( array_unique( $aggregate_resource_id_arr ) );
384
385 return array(
386 'resource_id' => $resource_id,
387 'aggregate_resource_id_arr' => $aggregate_resource_id_arr,
388 );
389 }
390
391 /**
392 * Validate resource_id parameter and check if booking resource exists.
393 *
394 * @param int|string $resource_id ID of booking resource.
395 *
396 * @return true|string True if valid, otherwise error message.
397 */
398 public static function validate_resource_id( $resource_id ) {
399
400 $resource_id = absint( $resource_id );
401
402 if ( 0 === $resource_id ) {
403 return __( 'Booking resource type is not defined. This can be, when at the URL is wrong booking hash.', 'booking' );
404 }
405
406 // Validation should not echo. Rendering layer decides about output.
407 $is_echo = false;
408
409 $is_booking_resource_exist = apply_bk_filter( 'wpdev_is_booking_resource_exist', true, $resource_id, $is_echo );
410
411 if ( ! $is_booking_resource_exist ) {
412 return 'Booking resource does not exist. [ID=' . esc_attr( $resource_id ) . ']';
413 }
414
415 return true;
416 }
417
418 /**
419 * Hash resolution, e.g. $_GET['booking_hash'] for Booking Form rendering (calendar+form). Keeps legacy messages and parent resource rewrite behavior.
420 *
421 * @param array $params_arr array of parameters from shortcode.
422 * @param string $context can be 'form'|'calendar'.
423 *
424 * @return array('ok' => bool, 'params_arr' => array, 'error_html' => string)
425 */
426 public static function resolve_booking_hash_and_maybe_get_parent_resource_id( $params_arr, $context = 'form' ) {
427
428 // Legacy behavior: ONLY checks key existence, not “non-empty”.
429 $get_booking_hash = isset( $params_arr['booking_hash'] ) ? sanitize_text_field( wp_unslash( (string) $params_arr['booking_hash'] ) ) : '';
430
431 if ( '' === $get_booking_hash ) {
432 $get_booking_hash = WPBC_GET_Request::has_non_empty_get( 'booking_hash' ) ? WPBC_GET_Request::get_sanitized( 'booking_hash' ) : '';
433 }
434
435 if ( '' === $get_booking_hash ) {
436 return array(
437 'ok' => true,
438 'params_arr' => $params_arr,
439 'error_html' => '',
440 );
441 }
442
443 // If booking_hash exists and this is calendar-only, show warning.
444 if ( 'calendar' === $context ) {
445 $is_error = '<div class="wpbc_after_booking_thank_you_section"><div class="wpbc_ty__container"><div class="wpbc_ty__header"><strong>'
446 . esc_html__( 'Oops!', 'booking' )
447 . '</strong> '
448 . esc_html__( 'We could not manage booking on availability calendar without booking form. The link you used may be incorrect.', 'booking' )
449 . '</div></div></div>';
450
451 return array(
452 'ok' => false,
453 'params_arr' => $params_arr,
454 'error_html' => $is_error,
455 );
456 }
457
458 $my_booking_id_type = wpbc_hash__get_booking_id__resource_id( $get_booking_hash );
459
460 $html_error_message = '<div class="wpbc_after_booking_thank_you_section"><div class="wpbc_ty__container"><div class="wpbc_ty__header"><strong>'
461 . esc_html__( 'Oops!', 'booking' )
462 . '</strong> '
463 . esc_html__( 'We could not find your booking. The link you used may be incorrect or has expired. If you need assistance, please contact our support team.', 'booking' )
464 . '</div></div></div>';
465
466 $is_error = false;
467
468 if ( false !== $my_booking_id_type ) {
469 list( $my_edited_bk_id, $my_boook_type ) = $my_booking_id_type;
470
471 if ( empty( $my_boook_type ) ) {
472 $is_error = $html_error_message;
473 }
474 } else {
475 $is_error = $html_error_message;
476 }
477
478 if ( false !== $is_error ) {
479 return array(
480 'ok' => false,
481 'params_arr' => $params_arr,
482 'error_html' => $is_error,
483 );
484 }
485
486 if ( 'form' === $context ) {
487
488 // Parent resource rewrite for child booking resources (legacy behavior).
489 if ( ( ! WPBC_GET_Request::has_get( 'booking_pay' ) ) && function_exists( 'wpbc_is_this_child_resource' ) && wpbc_is_this_child_resource( $my_boook_type ) ) {
490 $bk_parent_br_id = absint( wpbc_get_parent_resource( $my_boook_type ) );
491 $params_arr['resource_id'] = $bk_parent_br_id;
492 }
493 }
494
495 return array(
496 'ok' => true,
497 'params_arr' => $params_arr,
498 'error_html' => '',
499 );
500 }
501
502 /**
503 * Normalize calendar_dates_start/end:
504 * If only one boundary is set, compute the other based on max visible days.
505 *
506 * @param string $start - start date.
507 * @param string $end - end date.
508 *
509 * @return array('start' => string, 'end' => string)
510 */
511 public static function normalize_calendar_dates_range( $start, $end ) {
512
513 $start = (string) $start;
514 $end = (string) $end;
515
516 $days = (int) wpbc_get_max_visible_days_in_calendar();
517
518 $tz = wp_timezone(); // WordPress site timezone (DateTimeZone).
519
520 if ( ( '' !== $start ) && ( '' === $end ) ) {
521 $dt = DateTimeImmutable::createFromFormat( 'Y-m-d', $start, $tz );
522 if ( $dt ) {
523 $end = $dt->modify( '+' . $days . ' days' )->format( 'Y-m-d' );
524 }
525 }
526
527 if ( ( '' === $start ) && ( '' !== $end ) ) {
528 $dt = DateTimeImmutable::createFromFormat( 'Y-m-d', $end, $tz );
529 if ( $dt ) {
530 $start = $dt->modify( '-' . $days . ' days' )->format( 'Y-m-d' );
531 }
532 }
533
534 return array(
535 'start' => $start,
536 'end' => $end,
537 );
538 }
539
540 }
541
542
543 /**
544 * Shortcode "options" parameter parser for legacy "{parameter ...}" syntax.
545 *
546 * Converts options string like:
547 * options='{parameter name="my_param" value="value"},{parameter name="other_param" value="other value"}'
548 *
549 * Into a sanitized associative array:
550 * array(
551 * 'my_param' => 'value',
552 * 'other_param' => 'other value',
553 * )
554 *
555 * Used by booking form shortcodes like:
556 * [text some_field_name "my_param"]
557 *
558 * @since 11.0.x
559 */
560 class WPBC_FE_Options_Parser {
561
562 /**
563 * Parse shortcode options parameter. e.g. - {parameter name="..." value="..."}
564 *
565 * Paramaters defined in the Booking Calendar shortcode, like this:
566 * options='{parameter name="my_param" value="value"},{parameter name="other_param" value="other value"}'
567 * Usage in booking form:
568 * [text some_field_name "my_param"] and [text other_field_name "other_param"]
569 *
570 * Returns sanitized associative array:
571 * array( 'param_name' => 'param_val', 'bfb_form_id' => '12', ... )
572 *
573 * @param mixed $option_param_value - shortcode options parameter value, e.g.: '{parameter name="my_param" value="value"},{parameter name="other_param" value="other value"}'.
574 *
575 * @return array - founded options: [ 'my_param' => 'value', 'other_param' => 'other value'' ]
576 */
577 public static function parse_for_parameter__in_shortcode_options( $option_param_value ) {
578
579 $custom_params = array();
580
581 $raw = is_scalar( $option_param_value ) ? (string) $option_param_value : '';
582 $raw = trim( $raw );
583
584 if ( '' === $raw ) {
585 return $custom_params;
586 }
587
588 // Find {parameter ...} blocks.
589 if ( ! preg_match_all( '/\{\s*parameter\b([^}]*)\}/i', $raw, $blocks ) ) {
590 return $custom_params;
591 }
592
593 foreach ( $blocks[1] as $attrs ) {
594
595 $name = '';
596 $value = '';
597
598 if ( preg_match( '/\bname\s*=\s*([\'"])(.*?)\1/i', $attrs, $m1 ) ) {
599 $name = (string) $m1[2];
600 }
601 if ( preg_match( '/\bvalue\s*=\s*([\'"])(.*?)\1/i', $attrs, $m2 ) ) {
602 $value = (string) $m2[2];
603 }
604
605 if ( ( '' !== $name ) && ( '' !== $value ) ) {
606 $custom_params[ sanitize_text_field( (string) $name ) ] = sanitize_text_field( (string) $value );
607 }
608 }
609
610 return $custom_params;
611 }
612 }
613
614
615 /**
616 * Front-End Request Helper - get sanitized $_GET params.
617 *
618 * Purpose:
619 * - Centralize access to request (mostly $_GET) values used by front-end rendering.
620 * - Perform basic sanitization/unslash in one place.
621 * - Keep other classes "pure" (no direct $_GET usage).
622 *
623 * @package Booking Calendar
624 * @since 11.0.x
625 */
626 class WPBC_GET_Request {
627
628 /**
629 * Does request contain given GET key?
630 *
631 * @param string $key - Get key slug.
632 *
633 * @return bool
634 */
635 public static function has_get( $key ) {
636
637 $key = is_scalar( $key ) ? (string) $key : '';
638 $key = trim( $key );
639
640 if ( '' === $key ) {
641 return false;
642 }
643
644 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
645 return isset( $_GET[ $key ] );
646 }
647
648 /**
649 * Does request contain given non EMPTY - GET key?
650 *
651 * @param string $key - Get key slug.
652 *
653 * @return bool
654 */
655 public static function has_non_empty_get( $key ) {
656 return ( '' !== trim( self::get_sanitized( $key ) ) );
657 }
658
659 /**
660 * Get sanitized scalar value from $_GET (unslash + sanitize).
661 *
662 * @param string $key
663 * @param string $default_val
664 * @param string $sanitize_type 'text' | 'key'
665 *
666 * @return string
667 */
668 public static function get_sanitized( $key, $default_val = '', $sanitize_type = 'text' ) {
669
670 $key = is_scalar( $key ) ? (string) $key : '';
671 $key = trim( $key );
672
673 if ( '' === $key ) {
674 return (string) $default_val;
675 }
676
677 if ( ! self::has_get( $key ) ) {
678 return (string) $default_val;
679 }
680
681 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
682 $raw = wp_unslash( $_GET[ $key ] );
683
684 // If attacker sends ?x[]=1 we must NOT accept "Array" as a valid value.
685 if ( is_array( $raw ) || is_null( $raw ) ) {
686 return (string) $default_val;
687 }
688
689 $raw = (string) $raw;
690
691 if ( 'key' === $sanitize_type ) {
692 $val = sanitize_key( $raw );
693
694 return ( '' !== $val ) ? $val : (string) $default_val;
695 }
696
697 return sanitize_text_field( $raw );
698 }
699 }
700
701