PluginProbe
Booking Calendar / 10.15.6
Booking Calendar v10.15.6
11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 All 203 releases
booking / includes / _front_end / class-fe-shortcode-params.php

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

697 lines 20.2 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 if ( ! WPBC_GET_Request::has_non_empty_get( 'booking_hash' ) ) {
430 return array(
431 'ok' => true,
432 'params_arr' => $params_arr,
433 'error_html' => '',
434 );
435 }
436
437 // If booking_hash exists and this is calendar-only, show warning.
438 if ( 'calendar' === $context ) {
439 $is_error = '<div class="wpbc_after_booking_thank_you_section"><div class="wpbc_ty__container"><div class="wpbc_ty__header"><strong>'
440 . esc_html__( 'Oops!', 'booking' )
441 . '</strong> '
442 . esc_html__( 'We could not manage booking on availability calendar without booking form. The link you used may be incorrect.', 'booking' )
443 . '</div></div></div>';
444
445 return array(
446 'ok' => false,
447 'params_arr' => $params_arr,
448 'error_html' => $is_error,
449 );
450 }
451
452 $get_booking_hash = WPBC_GET_Request::get_sanitized( 'booking_hash' );
453
454 $my_booking_id_type = wpbc_hash__get_booking_id__resource_id( $get_booking_hash );
455
456 $html_error_message = '<div class="wpbc_after_booking_thank_you_section"><div class="wpbc_ty__container"><div class="wpbc_ty__header"><strong>'
457 . esc_html__( 'Oops!', 'booking' )
458 . '</strong> '
459 . 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' )
460 . '</div></div></div>';
461
462 $is_error = false;
463
464 if ( false !== $my_booking_id_type ) {
465 list( $my_edited_bk_id, $my_boook_type ) = $my_booking_id_type;
466
467 if ( empty( $my_boook_type ) ) {
468 $is_error = $html_error_message;
469 }
470 } else {
471 $is_error = $html_error_message;
472 }
473
474 if ( false !== $is_error ) {
475 return array(
476 'ok' => false,
477 'params_arr' => $params_arr,
478 'error_html' => $is_error,
479 );
480 }
481
482 if ( 'form' === $context ) {
483
484 // Parent resource rewrite for child booking resources (legacy behavior).
485 if ( ( ! WPBC_GET_Request::has_get( 'booking_pay' ) ) && function_exists( 'wpbc_is_this_child_resource' ) && wpbc_is_this_child_resource( $my_boook_type ) ) {
486 $bk_parent_br_id = absint( wpbc_get_parent_resource( $my_boook_type ) );
487 $params_arr['resource_id'] = $bk_parent_br_id;
488 }
489 }
490
491 return array(
492 'ok' => true,
493 'params_arr' => $params_arr,
494 'error_html' => '',
495 );
496 }
497
498 /**
499 * Normalize calendar_dates_start/end:
500 * If only one boundary is set, compute the other based on max visible days.
501 *
502 * @param string $start - start date.
503 * @param string $end - end date.
504 *
505 * @return array('start' => string, 'end' => string)
506 */
507 public static function normalize_calendar_dates_range( $start, $end ) {
508
509 $start = (string) $start;
510 $end = (string) $end;
511
512 $days = (int) wpbc_get_max_visible_days_in_calendar();
513
514 $tz = wp_timezone(); // WordPress site timezone (DateTimeZone).
515
516 if ( ( '' !== $start ) && ( '' === $end ) ) {
517 $dt = DateTimeImmutable::createFromFormat( 'Y-m-d', $start, $tz );
518 if ( $dt ) {
519 $end = $dt->modify( '+' . $days . ' days' )->format( 'Y-m-d' );
520 }
521 }
522
523 if ( ( '' === $start ) && ( '' !== $end ) ) {
524 $dt = DateTimeImmutable::createFromFormat( 'Y-m-d', $end, $tz );
525 if ( $dt ) {
526 $start = $dt->modify( '-' . $days . ' days' )->format( 'Y-m-d' );
527 }
528 }
529
530 return array(
531 'start' => $start,
532 'end' => $end,
533 );
534 }
535
536 }
537
538
539 /**
540 * Shortcode "options" parameter parser for legacy "{parameter ...}" syntax.
541 *
542 * Converts options string like:
543 * options='{parameter name="my_param" value="value"},{parameter name="other_param" value="other value"}'
544 *
545 * Into a sanitized associative array:
546 * array(
547 * 'my_param' => 'value',
548 * 'other_param' => 'other value',
549 * )
550 *
551 * Used by booking form shortcodes like:
552 * [text some_field_name "my_param"]
553 *
554 * @since 11.0.x
555 */
556 class WPBC_FE_Options_Parser {
557
558 /**
559 * Parse shortcode options parameter. e.g. - {parameter name="..." value="..."}
560 *
561 * Paramaters defined in the Booking Calendar shortcode, like this:
562 * options='{parameter name="my_param" value="value"},{parameter name="other_param" value="other value"}'
563 * Usage in booking form:
564 * [text some_field_name "my_param"] and [text other_field_name "other_param"]
565 *
566 * Returns sanitized associative array:
567 * array( 'param_name' => 'param_val', 'bfb_form_id' => '12', ... )
568 *
569 * @param mixed $option_param_value - shortcode options parameter value, e.g.: '{parameter name="my_param" value="value"},{parameter name="other_param" value="other value"}'.
570 *
571 * @return array - founded options: [ 'my_param' => 'value', 'other_param' => 'other value'' ]
572 */
573 public static function parse_for_parameter__in_shortcode_options( $option_param_value ) {
574
575 $custom_params = array();
576
577 $raw = is_scalar( $option_param_value ) ? (string) $option_param_value : '';
578 $raw = trim( $raw );
579
580 if ( '' === $raw ) {
581 return $custom_params;
582 }
583
584 // Find {parameter ...} blocks.
585 if ( ! preg_match_all( '/\{\s*parameter\b([^}]*)\}/i', $raw, $blocks ) ) {
586 return $custom_params;
587 }
588
589 foreach ( $blocks[1] as $attrs ) {
590
591 $name = '';
592 $value = '';
593
594 if ( preg_match( '/\bname\s*=\s*([\'"])(.*?)\1/i', $attrs, $m1 ) ) {
595 $name = (string) $m1[2];
596 }
597 if ( preg_match( '/\bvalue\s*=\s*([\'"])(.*?)\1/i', $attrs, $m2 ) ) {
598 $value = (string) $m2[2];
599 }
600
601 if ( ( '' !== $name ) && ( '' !== $value ) ) {
602 $custom_params[ sanitize_text_field( (string) $name ) ] = sanitize_text_field( (string) $value );
603 }
604 }
605
606 return $custom_params;
607 }
608 }
609
610
611 /**
612 * Front-End Request Helper - get sanitized $_GET params.
613 *
614 * Purpose:
615 * - Centralize access to request (mostly $_GET) values used by front-end rendering.
616 * - Perform basic sanitization/unslash in one place.
617 * - Keep other classes "pure" (no direct $_GET usage).
618 *
619 * @package Booking Calendar
620 * @since 11.0.x
621 */
622 class WPBC_GET_Request {
623
624 /**
625 * Does request contain given GET key?
626 *
627 * @param string $key - Get key slug.
628 *
629 * @return bool
630 */
631 public static function has_get( $key ) {
632
633 $key = is_scalar( $key ) ? (string) $key : '';
634 $key = trim( $key );
635
636 if ( '' === $key ) {
637 return false;
638 }
639
640 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
641 return isset( $_GET[ $key ] );
642 }
643
644 /**
645 * Does request contain given non EMPTY - GET key?
646 *
647 * @param string $key - Get key slug.
648 *
649 * @return bool
650 */
651 public static function has_non_empty_get( $key ) {
652 return ( '' !== trim( self::get_sanitized( $key ) ) );
653 }
654
655 /**
656 * Get sanitized scalar value from $_GET (unslash + sanitize).
657 *
658 * @param string $key
659 * @param string $default_val
660 * @param string $sanitize_type 'text' | 'key'
661 *
662 * @return string
663 */
664 public static function get_sanitized( $key, $default_val = '', $sanitize_type = 'text' ) {
665
666 $key = is_scalar( $key ) ? (string) $key : '';
667 $key = trim( $key );
668
669 if ( '' === $key ) {
670 return (string) $default_val;
671 }
672
673 if ( ! self::has_get( $key ) ) {
674 return (string) $default_val;
675 }
676
677 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
678 $raw = wp_unslash( $_GET[ $key ] );
679
680 // If attacker sends ?x[]=1 we must NOT accept "Array" as a valid value.
681 if ( is_array( $raw ) || is_null( $raw ) ) {
682 return (string) $default_val;
683 }
684
685 $raw = (string) $raw;
686
687 if ( 'key' === $sanitize_type ) {
688 $val = sanitize_key( $raw );
689
690 return ( '' !== $val ) ? $val : (string) $default_val;
691 }
692
693 return sanitize_text_field( $raw );
694 }
695 }
696
697