PluginProbe
Booking Manager – Sync WP Booking Calendar – Import Events, Export Bookings to ICS Calendar / 2.1.21
Booking Manager – Sync WP Booking Calendar – Import Events, Export Bookings to ICS Calendar v2.1.21
2.1.22 2.1.21 2.1.20 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 trunk 1.1 2.0 2.0.1 2.0.10.2 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.17 2.0.18 2.0.2 2.0.20 2.0.21 All 56 releases
← All changes | core/wpbc/wpbm-bc-export.php +703 -157 2.0.1 → 2.1.21 View file →
@@ -3,9 +3,9 @@
3 3 * @version 1.0
4 4 * @description Booking Calendar integration - Export
5 5 *
6 6 * @author wpdevelop
7 - * @link http://oplugins.com/
7 + * @link https://oplugins.com/
8 8 * @email [email protected]
9 9 *
10 10 * @modified 2017-06-28
11 11 */
@@ -43,12 +43,35 @@
43 43
44 44 if ( false === $my_parsed_url ) { // seriously malformed URLs, parse_url() may return FALSE.
45 45 return;
46 46 }
47 -
47 +
48 48 $my_parsed_url_path = trim( $my_parsed_url[ 'path' ] );
49 49 $my_parsed_url_path = trim( $my_parsed_url_path, '/' );
50 50
51 + //FixIn: 2.0.5.4
52 + // check internal subfolder of WP, like http://server.com/my-website/ ... link ...
53 + $server_url_sufix = '';
54 + if ( function_exists( 'wp_parse_url' ) )
55 + $wp_home_server_url = wp_parse_url( home_url() );
56 + else
57 + $wp_home_server_url = @parse_url( home_url() );
58 +
59 + if ( ( false !== $wp_home_server_url ) && (! empty( $wp_home_server_url[ 'path' ] ) ) ){ // seriously malformed URLs, parse_url() may return FALSE.
60 + $server_url_sufix = trim( $wp_home_server_url[ 'path' ] ); // [path] => /my-website
61 + $server_url_sufix = trim( $server_url_sufix, '/' ); // my-website
62 + if ( ! empty( $server_url_sufix ) ) {
63 + $check_sufix = substr( $my_parsed_url_path, 0, strlen( $server_url_sufix ) );
64 + if ( $check_sufix === $server_url_sufix ) {
65 + $my_parsed_url_path = substr( $my_parsed_url_path, strlen( $server_url_sufix ) );
66 + $my_parsed_url_path = trim( $my_parsed_url_path, '/' );
67 + }
68 + }
69 + }
70 + //End FixIn: 2.0.5.4
71 +
72 +
73 +
51 74 // Get booking resources and Export Feed URLS
52 75 $is_continue = false;
53 76 if ( function_exists( 'wpbc_br_cache' ) ) {
54 77
@@ -108,13 +131,13 @@
108 131 $is_func_disabled = in_array( 'set_time_limit', $disabled );
109 132 if ( ! $is_func_disabled && ! ini_get( 'safe_mode' ) ) {
110 133 @set_time_limit( 0 );
111 134 }
135 + //FixIn: 2.0.18.2
136 +// if ( function_exists( 'get_magic_quotes_runtime' ) && get_magic_quotes_runtime() && version_compare( phpversion(), '5.4', '<' ) ) {
137 +// set_magic_quotes_runtime( 0 );
138 +// }
112 139
113 - if ( function_exists( 'get_magic_quotes_runtime' ) && get_magic_quotes_runtime() && version_compare( phpversion(), '5.4', '<' ) ) {
114 - set_magic_quotes_runtime( 0 );
115 - }
116 -
117 140 @session_write_close();
118 141 if ( function_exists( 'apache_setenv' ) ) {
119 142 @apache_setenv( 'no-gzip', 1 );
120 143 }
@@ -126,13 +149,16 @@
126 149 ////////////////////////////////////////////////////////////////////////////////////////////////////////////
127 150 // Set Headers before file download
128 151 ////////////////////////////////////////////////////////////////////////////////////////////////////////////
129 152 $file = array();
130 - $file['content_type'] = 'text/calendar';
153 + $file['content_type'] = 'text/calendar; charset=UTF-8';
131 154 $file['name'] = 'wpbm.ics';
155 +
156 + if ( function_exists( 'mb_detect_encoding' ) ) //FixIn: 2.0.5.3
157 + $text_encoding = mb_detect_encoding( $ics_export );
158 + else
159 + $text_encoding = ''; //Unknown
132 160
133 - $text_encoding = mb_detect_encoding( $ics_export );
134 -
135 161 //debuge( mb_detect_encoding( $ics_export ) );die;
136 162
137 163 if ( ( is_array( $text_encoding ) )
138 164 && ( ! in_array( 'UTF-8', $text_encoding ) )
@@ -146,9 +172,9 @@
146 172 header( "Robots: none" . "\n" );
147 173 @header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 200 OK' . "\n" );
148 174 header( "Content-Type: " . $file['content_type'] . "\n" );
149 175 header( "Content-Description: File Transfer" . "\n" );
150 - header( "Content-Disposition: attachment; filename=\"" . $file['name'] . "\"" . "\n" );
176 + header( "Content-Disposition: inline; filename=\"" . $file['name'] . "\"" . "\n" );
151 177 header( "Content-Transfer-Encoding: binary" . "\n" );
152 178
153 179 if ( (int) $file['size'] > 0 )
154 180 header( "Content-Length: " . $file['size'] . "\n" );
@@ -164,180 +190,699 @@
164 190 }
165 191 add_action( 'template_redirect', 'wpbm_make_export_ics_feeds' );
166 192
167 193
194 +// ---------------------------------------------------------------------------------------------------------------------
195 +// Helpers for ICS export:
196 +// ---------------------------------------------------------------------------------------------------------------------
168 197
169 -/** Define export ICS here
170 - * For testing: http://beta/?feed=wpbm-ics
198 +/**
199 + * Escape ICS text per RFC 5545: backslash, comma, semicolon; newlines -> \n
200 + *
201 + * @param string $text
202 + *
203 + * @return string
171 204 */
172 -function wpbm_export_ics_feed__wpbm_ics( $param = array( 'wh_booking_type' => '1' ) ) {
205 +function wpbc_ics_escape_text( $text ) {
206 + $text = (string) $text;
207 + $text = str_replace( array( "\\", ";", "," ), array( "\\\\", "\;", "\," ), $text );
208 + $text = str_replace( array( "\r\n", "\r" ), "\n", $text );
209 + $text = str_replace( "\n", "\\n", $text ); // single backslash-n.
173 210
174 - if ( ! function_exists( 'wpbc_api_get_bookings_arr' ) )
211 + return $text;
212 +}
213 +
214 +/**
215 + * Format SQL local datetime ("Y-m-d H:i:s") as UTC Z iCal (YYYYMMDDTHHMMSSZ).
216 + *
217 + * @param string|null $sql_dt Local SQL datetime or null for "now"
218 + * @param DateTimeZone $source_tz
219 + *
220 + * @return string
221 + */
222 +function wpbc_to_ics_utc( $sql_dt, DateTimeZone $source_tz ) {
223 + $dt = $sql_dt ? DateTime::createFromFormat( 'Y-m-d H:i:s', $sql_dt, $source_tz )
224 + : new DateTime( 'now', $source_tz );
225 + if ( ! $dt ) {
226 + $dt = new DateTime( 'now', $source_tz );
227 + }
228 + $dt->setTimezone( new DateTimeZone( 'UTC' ) );
229 +
230 + return $dt->format( 'Ymd\THis\Z' );
231 +}
232 +
233 +/**
234 + * Format SQL local datetime as local (no Z) in iCal basic (YYYYMMDDTHHMMSS).
235 + *
236 + * @param string $sql_dt
237 + * @param DateTimeZone $tz
238 + *
239 + * @return string
240 + */
241 +function wpbc_to_ics_local( $sql_dt, DateTimeZone $tz ) {
242 + $dt = DateTime::createFromFormat( 'Y-m-d H:i:s', $sql_dt, $tz );
243 + if ( ! $dt ) {
244 + $dt = new DateTime( $sql_dt ?: 'now', $tz );
245 + }
246 +
247 + return $dt->format( 'Ymd\THis' );
248 +}
249 +
250 +/**
251 + * Normalize seconds to :00 for clean slots (avoid :01/:02 artifacts).
252 + *
253 + * @param string $sql_dt
254 + * @param DateTimeZone $tz
255 + *
256 + * @return string "Y-m-d H:i:00" on success, original input if parsing fails
257 + */
258 +function wpbc_normalize_sec_sql( $sql_dt, DateTimeZone $tz ) {
259 + $dt = DateTime::createFromFormat( 'Y-m-d H:i:s', $sql_dt, $tz );
260 + if ( ! $dt ) {
261 + return $sql_dt;
262 + }
263 +
264 + return $dt->format( 'Y-m-d H:i:00' );
265 +}
266 +
267 +/**
268 + * Add/subtract whole days to a Y-m-d in a specific tz (DST-safe), return Y-m-d.
269 + *
270 + * @param string $ymd "Y-m-d"
271 + * @param int $days can be negative
272 + * @param DateTimeZone $tz
273 + *
274 + * @return string "Y-m-d"
275 + */
276 +function wpbc_add_days_ymd( $ymd, $days, DateTimeZone $tz ) {
277 + $dt = DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $ymd . ' 00:00:00', $tz );
278 + if ( ! $dt ) {
279 + $dt = new DateTimeImmutable( 'now', $tz );
280 + }
281 + $modifier = ( $days >= 0 ? '+' : '' ) . $days . ' days';
282 +
283 + return $dt->modify( $modifier )->format( 'Y-m-d' );
284 +}
285 +
286 +/**
287 + * Ensure DTEND is strictly after DTSTART (RFC 5545).
288 + *
289 + * - All-day mode: treat inputs as Y-m-d; if end <= start → end = start + 1 day (exclusive end).
290 + * - Timed mode : normalize to 'Y-m-d H:i:s'; if end <= start → end = start + 1 hour.
291 + *
292 + * @param string $start_in 'Y-m-d' or 'Y-m-d H:i:s'
293 + * @param string $end_in 'Y-m-d' or 'Y-m-d H:i:s' (can be empty)
294 + * @param DateTimeZone $tz
295 + * @param bool $all_day
296 + *
297 + * @return array [ $start_out, $end_out ] (all-day → Y-m-d; timed → 'Y-m-d H:i:00')
298 + */
299 +function wpbc_ics_coerce_order( $start_in, $end_in, DateTimeZone $tz, $all_day ) {
300 +
301 + // Helper for all-day date math.
302 + $_add_days = function( $ymd, $days ) use ( $tz ) {
303 + return wpbc_add_days_ymd( $ymd, $days, $tz );
304 + };
305 +
306 + if ( $all_day ) {
307 + $sd = substr( (string) $start_in, 0, 10 );
308 + $ed = substr( (string) ($end_in !== '' ? $end_in : $sd), 0, 10 );
309 + if ( $ed <= $sd ) {
310 + $ed = $_add_days( $sd, 1 );
311 + }
312 + return array( $sd, $ed );
313 + }
314 +
315 + // Timed mode: normalize to full SQL datetimes.
316 + $ss = ( strlen( (string) $start_in ) > 10 ) ? $start_in : ( substr( (string) $start_in, 0, 10 ) . ' 00:00:00' );
317 + $ee = ( $end_in !== '' )
318 + ? ( ( strlen( (string) $end_in ) > 10 ) ? $end_in : ( substr( (string) $end_in, 0, 10 ) . ' 00:00:00' ) )
319 + : '';
320 +
321 + $ds = DateTime::createFromFormat( 'Y-m-d H:i:s', $ss, $tz );
322 + if ( ! $ds ) { $ds = new DateTime( $ss ?: 'now', $tz ); }
323 +
324 + if ( $ee === '' ) {
325 + $de = clone $ds;
326 + $de->modify( '+1 hour' );
327 + } else {
328 + $de = DateTime::createFromFormat( 'Y-m-d H:i:s', $ee, $tz );
329 + if ( ! $de ) { $de = new DateTime( $ee, $tz ); }
330 + }
331 + if ( $de <= $ds ) {
332 + $de = clone $ds;
333 + $de->modify( '+1 hour' );
334 + }
335 + return array( $ds->format( 'Y-m-d H:i:00' ), $de->format( 'Y-m-d H:i:00' ) );
336 +}
337 +
338 +
339 +/**
340 + * Add a VTIMEZONE node exactly once per TZID.
341 + *
342 + * @param ZCiCal $icalobj
343 + * @param string $tzid
344 + * @param string|int $year_start "YYYY"
345 + * @param string|int $year_end "YYYY"
346 + * @param array $added_ref reference array to track added TZIDs
347 + *
348 + * @return void
349 + */
350 +/**
351 + * Add a VTIMEZONE node exactly once per TZID.
352 + * - For Europe/* zones: emit a compact RRULE-based CET/CEST block + X-LIC-LOCATION.
353 + * - For other zones: fall back to library helper (detailed transitions).
354 + */
355 +function wpbc_add_vtimezone_once( $icalobj, $tzid, $year_start, $year_end, array &$added_ref ) {
356 + if ( empty( $tzid ) ) {
357 + return;
358 + }
359 + if ( isset( $added_ref[ $tzid ] ) ) {
360 + return;
361 + }
362 +
363 + // Lightweight CET/CEST definition for Europe/* (covers Europe/Prague and peers).
364 + if ( strpos( $tzid, 'Europe/' ) === 0 ) {
365 + $vtz = new ZCiCalNode( 'VTIMEZONE', $icalobj->curnode );
366 + $vtz->addNode( new ZCiCalDataNode( 'TZID:' . $tzid ) );
367 + $vtz->addNode( new ZCiCalDataNode( 'X-LIC-LOCATION:' . $tzid ) );
368 +
369 + // STANDARD: last Sunday in October 03:00 -> CET (UTC+1).
370 + $std = new ZCiCalNode( 'STANDARD', $vtz );
371 + $std->addNode( new ZCiCalDataNode( 'DTSTART:19961027T030000' ) );
372 + $std->addNode( new ZCiCalDataNode( 'TZOFFSETFROM:+0200' ) );
373 + $std->addNode( new ZCiCalDataNode( 'TZOFFSETTO:+0100' ) );
374 + $std->addNode( new ZCiCalDataNode( 'TZNAME:CET' ) );
375 + $std->addNode( new ZCiCalDataNode( 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' ) );
376 +
377 + // DAYLIGHT: last Sunday in March 02:00 -> CEST (UTC+2).
378 + $dst = new ZCiCalNode( 'DAYLIGHT', $vtz );
379 + $dst->addNode( new ZCiCalDataNode( 'DTSTART:19960331T020000' ) );
380 + $dst->addNode( new ZCiCalDataNode( 'TZOFFSETFROM:+0100' ) );
381 + $dst->addNode( new ZCiCalDataNode( 'TZOFFSETTO:+0200' ) );
382 + $dst->addNode( new ZCiCalDataNode( 'TZNAME:CEST' ) );
383 + $dst->addNode( new ZCiCalDataNode( 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' ) );
384 +
385 + } else {
386 + // Fallback: detailed transitions for non‑Europe zones (safer across the globe).
387 + ZCTimeZoneHelper::getTZNode( (string) $year_start, (string) $year_end, $tzid, $icalobj->curnode );
388 + }
389 +
390 + $added_ref[ $tzid ] = true;
391 +}
392 +
393 +// ---------------------------------------------------------------------------------------------------------------------
394 +// Export ICS here.
395 +// ---------------------------------------------------------------------------------------------------------------------
396 +// FixIn: 2.1.20.1
397 +/**
398 + * Build the shortened booking-date representation used by the iCalendar exporter.
399 + *
400 + * This rebuild is required after child-resource dates have been removed from a
401 + * parent-resource feed. It follows the same range-compression rules as the
402 + * Booking Calendar booking-list API.
403 + *
404 + * @param array $booking_dates Booking-date objects for one booking.
405 + * @param int $main_resource_id Resource ID stored on the main booking row.
406 + *
407 + * @return array {
408 + * Shortened dates and their effective booking-resource IDs.
409 + *
410 + * @type array $dates_short Date values and range separators.
411 + * @type array $dates_short_id Effective resource IDs matching the date values.
412 + * }
413 + */
414 +function wpbc_ics_export_build_short_dates( $booking_dates, $main_resource_id ) {
415 +
416 + $dates_short = array();
417 + $dates_short_id = array();
418 + $last_date = '';
419 + $last_date_id = '';
420 + $last_token = '';
421 +
422 + foreach ( $booking_dates as $booking_date ) {
423 + $current_date = $booking_date->booking_date;
424 + $current_date_resource_id = ! empty( $booking_date->type_id )
425 + ? intval( $booking_date->type_id )
426 + : intval( $main_resource_id );
427 +
428 + if ( '' === $last_date ) {
429 + $dates_short[] = $current_date;
430 + $dates_short_id[] = $current_date_resource_id;
431 + $last_token = $current_date;
432 + } elseif ( wpbc_is_less_than_next_day( $current_date, $last_date, true ) ) {
433 + if ( '-' !== $last_token ) {
434 + $dates_short[] = '-';
435 + $dates_short_id[] = '';
436 + }
437 + $last_token = '-';
438 + } else {
439 + if ( $last_token !== $last_date ) {
440 + $dates_short[] = $last_date;
441 + $dates_short_id[] = $last_date_id;
442 + }
443 +
444 + $dates_short[] = ',';
445 + $dates_short_id[] = '';
446 + $dates_short[] = $current_date;
447 + $dates_short_id[] = $current_date_resource_id;
448 + $last_token = $current_date;
449 + }
450 +
451 + $last_date = $current_date;
452 + $last_date_id = $current_date_resource_id;
453 + }
454 +
455 + if ( ( '' !== $last_date ) && ( $last_token !== $last_date ) ) {
456 + $dates_short[] = $last_date;
457 + $dates_short_id[] = $last_date_id;
458 + }
459 +
460 + return array(
461 + 'dates_short' => $dates_short,
462 + 'dates_short_id' => $dates_short_id,
463 + );
464 +}
465 +
466 +
467 +/**
468 + * Limit a parent-resource iCalendar feed to dates occupying the parent itself.
469 + *
470 + * Business Large normally expands parent-resource booking-list queries to all
471 + * child resources. The iCalendar export uses that shared query, so this helper
472 + * removes child-resource dates after retrieval without changing Booking
473 + * Calendar listings, calendar views, search, or capacity allocation.
474 + *
475 + * A NULL booking-date type ID represents the resource stored in the main
476 + * booking row. An explicit type ID represents another capacity resource used
477 + * by that booking.
478 + *
479 + * @param array $bookings_arr Booking-list API result.
480 + * @param int $resource_id Resource requested by the iCalendar feed URL.
481 + *
482 + * @return array Filtered booking-list API result.
483 + */
484 +function wpbc_ics_export_filter_parent_resource_bookings( $bookings_arr, $resource_id ) {
485 +
486 + $resource_id = intval( $resource_id );
487 +
488 + if (
489 + ( $resource_id <= 0 )
490 + || ! class_exists( 'wpdev_bk_biz_l' )
491 + || ! function_exists( 'wpbc_br_cache' )
492 + || empty( $bookings_arr['bookings'] )
493 + ) {
494 + return $bookings_arr;
495 + }
496 +
497 + $resource_list = wpbc_br_cache()->get_resources();
498 + $is_parent_resource = false;
499 +
500 + foreach ( $resource_list as $resource_params ) {
501 + $parent_resource_id = isset( $resource_params['parent'] ) ? intval( $resource_params['parent'] ) : 0;
502 +
503 + if ( $resource_id === $parent_resource_id ) {
504 + $is_parent_resource = true;
505 + break;
506 + }
507 + }
508 +
509 + if ( ! $is_parent_resource ) {
510 + return $bookings_arr;
511 + }
512 +
513 + foreach ( $bookings_arr['bookings'] as $booking_id => $booking_obj ) {
514 + $main_resource_id = isset( $booking_obj->booking_type ) ? intval( $booking_obj->booking_type ) : 0;
515 + $parent_dates = array();
516 +
517 + if ( empty( $booking_obj->dates ) || ! is_array( $booking_obj->dates ) ) {
518 + unset( $bookings_arr['bookings'][ $booking_id ] );
519 + continue;
520 + }
521 +
522 + foreach ( $booking_obj->dates as $booking_date ) {
523 + $date_resource_id = ! empty( $booking_date->type_id )
524 + ? intval( $booking_date->type_id )
525 + : $main_resource_id;
526 +
527 + if ( $resource_id === $date_resource_id ) {
528 + $parent_dates[] = $booking_date;
529 + }
530 + }
531 +
532 + if ( empty( $parent_dates ) ) {
533 + unset( $bookings_arr['bookings'][ $booking_id ] );
534 + continue;
535 + }
536 +
537 + usort(
538 + $parent_dates,
539 + static function ( $first_date, $second_date ) {
540 + return strcmp( $first_date->booking_date, $second_date->booking_date );
541 + }
542 + );
543 +
544 + $short_dates = wpbc_ics_export_build_short_dates( $parent_dates, $main_resource_id );
545 +
546 + $booking_obj->dates = $parent_dates;
547 + $booking_obj->dates_short = $short_dates['dates_short'];
548 + $booking_obj->dates_short_id = $short_dates['dates_short_id'];
549 + }
550 +
551 + $bookings_arr['bookings_count'] = count( $bookings_arr['bookings'] );
552 +
553 + return $bookings_arr;
554 +}
555 +
556 +
557 +/**
558 + * Define export ICS here.
559 + * For testing: http://beta/?feed=wpbm-ics
560 + *
561 + * @param array $param - [ 'wh_booking_type' => '1', 'wh_trash' => '' ].
562 + *
563 + * @return string
564 + */
565 +function wpbm_export_ics_feed__wpbm_ics( $param = array( 'wh_booking_type' => '1', 'wh_trash' => '' ) ) {
566 +
567 + // FixIn: 2.1.13.1 – timezone-safe, UTC stamps, OTA all‑day, single VTIMEZONE, no closures.
568 +
569 + if ( ! function_exists( 'wpbc_api_get_bookings_arr' ) ) {
175 570 return '';
176 -
571 + }
572 +
573 + // Resolve export timezone: option 'booking_gcal_timezone' if set, else WP site tz.
574 + $site_tz_obj = wp_timezone();
575 + $tzid_opt_raw = (string) get_bk_option( 'booking_gcal_timezone' );
576 + $tzid_opt = trim( $tzid_opt_raw ); // "" when “Default” is selected
577 +
578 + // Validate TZID (must be a known IANA zone).
579 + $tzid = '';
580 + if ( $tzid_opt !== '' ) {
581 + try {
582 + // Will throw on invalid IDs (e.g., typos).
583 + $check = new DateTimeZone( $tzid_opt );
584 + $tzid = $tzid_opt; // valid — we’ll emit TZID and VTIMEZONE.
585 + }
586 + catch ( Exception $e ) {
587 + $tzid = ''; // invalid — treat as Default (no TZID emitted).
588 + }
589 + }
590 +
591 + // Export timezone object (priority: booking_gcal_timezone > site tz).
592 + $export_tz = ( $tzid !== '' ) ? new DateTimeZone( $tzid ) : $site_tz_obj;
593 +
594 + // End date of getting bookings (cutoff +2 years) – done in export tz.
595 + $cutoff = ( new DateTimeImmutable( 'now', $export_tz ) )->modify( '+2 years' )->format( 'Y-m-d' );
596 + $param['wh_booking_date2'] = $cutoff;
597 +
598 + // Export only approved bookings (FixIn: 2.0.11.1 / 8.5.2.3).
599 + $booking_is_ics_export_only_approved = get_bk_option( 'booking_is_ics_export_only_approved' );
600 + $param['wh_approved'] = ( 'On' === $booking_is_ics_export_only_approved ) ? '1' : '';
601 +
602 + // '' | 'imported' | 'plugin' (FixIn: 8.8.3.19 / 2.0.20.2).
603 + if ( 'imported' === get_bk_option( 'booking_is_ics_export_imported_bookings' ) ) {
604 + $param['wh_sync_gid'] = 'imported';
605 + }
606 + if ( 'plugin' === get_bk_option( 'booking_is_ics_export_imported_bookings' ) ) {
607 + $param['wh_sync_gid'] = 'plugin';
608 + }
609 +
610 + $_REQUEST['tab'] = 'vm_calendar'; // FixIn: 10.12.3.2 / 8.5.2.15 / 2.0.11.2.
611 +
177 612 // Get array of bookings.
178 613 $bookings_arr = wpbc_api_get_bookings_arr( $param );
614 + $export_resource_id = isset( $param['wh_booking_type'] ) ? intval( $param['wh_booking_type'] ) : 0;
615 + $bookings_arr = wpbc_ics_export_filter_parent_resource_bookings( $bookings_arr, $export_resource_id );
179 616
617 + ob_start();
180 618
181 - ob_start();
182 -
183 - // create the ical object
619 + // Create the iCal object.
184 620 $icalobj = new ZCiCal();
185 -
186 -
621 +
622 + // Calendar headers (library adds VERSION/PRODID; we add refresh hints).
623 + $datanode = new ZCiCalDataNode( 'REFRESH-INTERVAL;VALUE=DURATION:PT10M' ); // 10 minutes.
624 + $icalobj->curnode->data[ $datanode->getName() ] = $datanode;
625 + $datanode = new ZCiCalDataNode( 'X-PUBLISHED-TTL:PT10M' ); // 10 minutes.
626 + $icalobj->curnode->data[ $datanode->getName() ] = $datanode;
627 +
628 + $all_day_export = ( 'On' === get_wpbm_option( 'wpbm_is_export_only_full_days' ) ); // OTA toggle.
629 +
630 + // Host for UIDs (global uniqueness).
631 + $host = wp_parse_url( home_url(), PHP_URL_HOST );
632 + if ( empty( $host ) ) {
633 + $host = 'example.org';
634 + }
635 + if ( 'localhost' === $host ) {
636 + $host = 'localhost.localdomain';
637 + }
638 +
639 + // Track VTIMEZONE additions per TZID.
640 + $__wpbc_added_vtimezones = array();
641 +
187 642 foreach ( $bookings_arr['bookings'] as $bk_id => $bk_arr ) {
188 643
189 - // create the event within the ical object
644 + // Create the master event within the iCal object.
190 645 $eventobj = new ZCiCalNode( 'VEVENT', $icalobj->curnode );
191 646
192 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
193 - // SUMMARY [Title]
194 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
195 -
196 - if ( function_exists( 'get_title_for_showing_in_day' ) ) { // Get title of booking pipiline in "Calendar Overview" page
197 -
198 - if ( true ) { // admin
199 - $what_show_in_day_template = get_bk_option( 'booking_default_title_in_day_for_calendar_view_mode' );
200 - } else { // front-end
201 - $what_show_in_day_template = get_bk_option( 'booking_default_title_in_day_for_timeline_front_end' );
647 + // ---------------------------------------------------------------------------------------------
648 + // SUMMARY
649 + // ---------------------------------------------------------------------------------------------
650 + if ( function_exists( 'get_title_for_showing_in_day' ) ) {
651 + $what_show_in_day_template = get_bk_option( 'booking_default_title_in_day_for_calendar_view_mode' );
652 + $title = get_title_for_showing_in_day( $bk_id, $bookings_arr['bookings'], $what_show_in_day_template );
653 + } else {
654 + $title = ( isset( $bk_arr->form_data['name'] ) ? ' ' . esc_textarea( $bk_arr->form_data['name'] ) : '' );
655 + $title .= ( isset( $bk_arr->form_data['firstname'] ) ? ' ' . esc_textarea( $bk_arr->form_data['firstname'] )
656 + : '' );
657 + $title .= ( isset( $bk_arr->form_data['secondname'] )
658 + ? ' ' . esc_textarea( $bk_arr->form_data['secondname'] ) : '' );
659 + $title .= ( isset( $bk_arr->form_data['lastname'] ) ? ' ' . esc_textarea( $bk_arr->form_data['lastname'] )
660 + : '' );
661 + }
662 +
663 + $is_hide_details = get_wpbm_option( 'wpbm_is_hide_details' );
664 + if ( 'On' === $is_hide_details ) {
665 + $title = '';
666 + }
667 + $title = ltrim( (string) $title ); // optional: avoid leading space.
668 + $title_ics = wpbc_ics_escape_text( wpbm_esc_to_plain_text( $title ) );
669 + $eventobj->addNode( new ZCiCalDataNode( 'SUMMARY:' . $title_ics ) );
670 +
671 + // ---------------------------------------------------------------------------------------------
672 + // DESCRIPTION
673 + // ---------------------------------------------------------------------------------------------
674 + $description_raw = ( 'On' === $is_hide_details ) ? '' : $bk_arr->form_show;
675 + $description_raw = ltrim( (string) $description_raw ); // remove leading whitespace/newlines.
676 + $description_ics = wpbc_ics_escape_text( wpbm_esc_to_plain_text( $description_raw ) );
677 + $description_ics = preg_replace('/\s+/', ' ', $description_ics);
678 + $eventobj->addNode( new ZCiCalDataNode( 'DESCRIPTION:' . $description_ics ) );
679 + // ---------------------------------------------------------------------------------------------
680 + // START & END (master span from dates_short)
681 + // ---------------------------------------------------------------------------------------------
682 + $event_start = $bk_arr->dates_short[0];
683 + if ( ( count( $bk_arr->dates_short ) > 1 ) && ( '-' === $bk_arr->dates_short[1] ) ) {
684 + $event_end = $bk_arr->dates_short[2];
685 + $event_end = wpbm_get_next_date_if_it_full_date( $event_end ); // make end exclusive for YYYY-mm-dd
686 + $period_start_index = 3;
687 + } else { // comma or single day.
688 + $event_end = $bk_arr->dates_short[0];
689 + $event_end = wpbm_get_next_date_if_it_full_date( $event_end );
690 + $period_start_index = 1;
691 + }
692 +
693 + if ( $all_day_export ) {
694 + // OTA all-day mode (end exclusive).
695 + $event_start = substr( $event_start, 0, 10 ); // Y-m-d
696 + $event_end = substr( $event_end, 0, 10 ); // Y-m-d
697 + if ( $event_start === $event_end ) {
698 + $event_end = wpbc_add_days_ymd( $event_end, 1, $export_tz );
202 699 }
203 - //$title = esc_textarea( get_title_for_showing_in_day( $bk_id, $bookings_arr['bookings'], $what_show_in_day_template ) );
204 - $title = get_title_for_showing_in_day( $bk_id, $bookings_arr['bookings'], $what_show_in_day_template ); //FixIn: 2.0.1.6.1
205 -
206 700 } else {
207 -
208 - $title = ( ( isset( $bk_arr->form_data['name'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['name'] ) : '' )
209 - . ( ( isset( $bk_arr->form_data['firstname'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['firstname'] ) : '' )
210 - . ( ( isset( $bk_arr->form_data['secondname'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['secondname'] ) : '' )
211 - . ( ( isset( $bk_arr->form_data['lastname'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['lastname'] ) : '' );
701 + // Timed mode: normalize seconds to :00 for clean slots.
702 + if ( strlen( $event_start ) > 10 ) {
703 + $event_start = wpbc_normalize_sec_sql( $event_start, $export_tz );
704 + }
705 + if ( strlen( $event_end ) > 10 ) {
706 + $event_end = wpbc_normalize_sec_sql( $event_end, $export_tz );
707 + }
212 708 }
213 709
214 - $title = wpbm_esc_to_plain_text( $title ); //FixIn: 2.0.1.6.1
710 + // Add VTIMEZONE once (timed mode with TZID).
711 + if ( ! $all_day_export && ! empty( $tzid ) ) {
712 + wpbc_add_vtimezone_once( $icalobj, $tzid, substr( $event_start, 0, 4 ), substr( $event_end, 0, 4 ), $__wpbc_added_vtimezones );
713 + }
215 714
216 -//$title = substr($title, 0, 5);
217 - $eventobj->addNode( new ZCiCalDataNode( 'SUMMARY:' . trim( $title ) ) );
715 + // DTSTART / DTEND for the master VEVENT (coerced so DTEND > DTSTART).
716 + if ( $all_day_export ) {
717 + list( $event_start, $event_end ) = wpbc_ics_coerce_order( $event_start, $event_end, $export_tz, true );
718 + $eventobj->addNode( new ZCiCalDataNode( 'DTSTART;VALUE=DATE:' . preg_replace( '/[^0-9]/', '', $event_start ) ) );
719 + $eventobj->addNode( new ZCiCalDataNode( 'DTEND;VALUE=DATE:' . preg_replace( '/[^0-9]/', '', $event_end ) ) );
720 + } else {
721 + list( $event_start_fixed, $event_end_fixed ) = wpbc_ics_coerce_order( $event_start, $event_end, $export_tz, false );
722 + if ( ! empty( $tzid ) ) {
723 + $eventobj->addNode( new ZCiCalDataNode( 'DTSTART;TZID=' . $tzid . ':' . wpbc_to_ics_local( $event_start_fixed, $export_tz ) ) );
724 + $eventobj->addNode( new ZCiCalDataNode( 'DTEND;TZID=' . $tzid . ':' . wpbc_to_ics_local( $event_end_fixed, $export_tz ) ) );
725 + } else {
726 + $eventobj->addNode( new ZCiCalDataNode( 'DTSTART:' . wpbc_to_ics_utc( $event_start_fixed, $export_tz ) ) );
727 + $eventobj->addNode( new ZCiCalDataNode( 'DTEND:' . wpbc_to_ics_utc( $event_end_fixed, $export_tz ) ) );
728 + }
729 + }
218 730
219 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
220 - // DESCRIPTION
221 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
222 - $description = $bk_arr->form_show;
223 - //$description = html_entity_decode( sanitize_text_field( $description ) );
224 - //$description = trim( wp_specialchars_decode( esc_html( stripslashes( $description ) ), ENT_QUOTES ) );
731 + // ---------------------------------------------------------------------------------------------
732 + // STATUS / TRANSP
733 + // ---------------------------------------------------------------------------------------------
734 + $ics_status = 'TENTATIVE';
735 + if ( ! empty( $bk_arr->trash ) ) {
736 + $ics_status = 'CANCELLED';
737 + } elseif ( ! empty( $bk_arr->dates[0]->approved ) ) {
738 + $ics_status = 'CONFIRMED';
739 + }
740 + $eventobj->addNode( new ZCiCalDataNode( 'STATUS:' . $ics_status ) );
741 + $eventobj->addNode( new ZCiCalDataNode( 'TRANSP:OPAQUE' ) );
225 742
226 - $description = wpbm_esc_to_plain_text( $description );
227 -//debuge($description . '!');die;
228 -//$description = substr($description, 0, 1);
229 - $description = preg_replace( array( "/\n/" ), array( '' ), $description );
230 -//debuge( ZCiCal::formatContent( $description ) );die;
231 - $eventobj->addNode( new ZCiCalDataNode( 'DESCRIPTION:' . ZCiCal::formatContent( $description ) ) );
232 -
233 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
234 - // START & END DATES
235 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
236 - $event_start = $bk_arr->dates_short[ 0 ];
237 - if ( ( count($bk_arr->dates_short) > 1 ) && ( '-' === $bk_arr->dates_short[ 1 ] ) ) {
238 -
239 - $event_end = $bk_arr->dates_short[ 2 ];
240 -
241 - $event_end = wpbm_get_next_date_if_it_full_date( $event_end );
242 -
243 - $period_start_index = 3;
244 - } else { // comma
245 - $event_end = $bk_arr->dates_short[ 0 ];
246 -
247 - $event_end = wpbm_get_next_date_if_it_full_date( $event_end );
743 + // ---------------------------------------------------------------------------------------------
744 + // Expand extra dates/ranges into standalone VEVENTs (better than RDATE/RRULE for OTA/clients)
745 + // ---------------------------------------------------------------------------------------------
746 + $segments = array(); // each: ['start_sql' => 'Y-m-d[ H:i:s]', 'end_sql' => 'Y-m-d[ H:i:s]'|null]
747 + $prev = '';
748 + $current = null;
248 749
249 - $period_start_index = 1;
750 + foreach ( $bk_arr->dates_short as $i => $tok ) {
751 + if ( $i < $period_start_index ) {
752 + continue;
753 + }
754 + if ( ',' !== $tok && '-' !== $tok ) {
755 + if ( '-' === $prev ) {
756 + $tok_norm = wpbm_get_next_date_if_it_full_date( $tok ); // range end (exclusive if Y-m-d)
757 + } else {
758 + $tok_norm = wpbm_get_only_date_if_it_full_date( $tok ); // start or single
759 + }
760 + if ( '-' === $prev ) {
761 + if ( $current ) {
762 + $current['end_sql'] = $tok_norm;
763 + }
764 + } else {
765 + $current = array( 'start_sql' => $tok_norm, 'end_sql' => null );
766 + }
767 + } elseif ( ',' === $tok ) {
768 + if ( $current ) {
769 + $segments[] = $current;
770 + $current = null;
771 + }
772 + }
773 + $prev = $tok;
250 774 }
251 -
252 - // add start date
253 - $eventobj->addNode( new ZCiCalDataNode( "DTSTART:" . ZCiCal::fromSqlDateTime( $event_start ) ) );
775 + if ( $current ) {
776 + $segments[] = $current;
777 + }
254 778
255 - // add end date
256 - $eventobj->addNode( new ZCiCalDataNode( "DTEND:" . ZCiCal::fromSqlDateTime( $event_end ) ) );
257 -
258 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
259 - // RDATE - Rule for other different dates.
260 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
261 - //
262 - // RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,
263 - // 19960404T010000Z/PT3H
264 - //
265 - $rdate = array();
266 - $previos_value = '';
267 - foreach ( $bk_arr->dates_short as $short_ind => $bk_date ) {
268 -
269 - if ( $short_ind < $period_start_index )
270 - continue;
271 -
272 - if ( ( $bk_date !== ',' ) && ( $bk_date !== '-' ) ) {
779 + if ( ! empty( $segments ) ) {
780 + // Duration basis for timed events.
781 + $master_duration = 0;
782 + if ( ! $all_day_export ) {
783 + $basis_start = isset( $event_start_fixed ) ? $event_start_fixed : $event_start;
784 + $basis_end = isset( $event_end_fixed ) ? $event_end_fixed : $event_end;
785 + $dts = DateTime::createFromFormat( 'Y-m-d H:i:s', $basis_start, $export_tz );
786 + $dte = DateTime::createFromFormat( 'Y-m-d H:i:s', $basis_end, $export_tz );
787 + $master_duration = ( $dts && $dte && $dte > $dts ) ? ( $dte->getTimestamp() - $dts->getTimestamp() )
788 + : 3600;
789 + }
273 790
274 - if ( $previos_value == '-' ) {
275 - $bk_date = wpbm_get_next_date_if_it_full_date( $bk_date );
791 + $seg_idx = 0;
792 + foreach ( $segments as $s ) {
793 + ++ $seg_idx;
794 + $ev = new ZCiCalNode( 'VEVENT', $icalobj->curnode );
795 +
796 + if ( $all_day_export ) {
797 + $start_d = substr( $s['start_sql'], 0, 10 );
798 + $end_d = ! empty( $s['end_sql'] ) ? substr( $s['end_sql'], 0, 10 ) : $start_d;
799 + list( $start_d, $end_d ) = wpbc_ics_coerce_order( $start_d, $end_d, $export_tz, true );
800 +
801 + $ev->addNode( new ZCiCalDataNode( 'DTSTART;VALUE=DATE:' . preg_replace( '/[^0-9]/', '', $start_d ) ) );
802 + $ev->addNode( new ZCiCalDataNode( 'DTEND;VALUE=DATE:' . preg_replace( '/[^0-9]/', '', $end_d ) ) );
803 +
276 804 } else {
277 - $bk_date = wpbm_get_only_date_if_it_full_date( $bk_date );
805 + $start_sql = ( strlen( $s['start_sql'] ) > 10 )
806 + ? wpbc_normalize_sec_sql( $s['start_sql'], $export_tz ) : $s['start_sql'];
807 + if ( ! empty( $s['end_sql'] ) ) {
808 + $end_sql = ( strlen( $s['end_sql'] ) > 10 )
809 + ? wpbc_normalize_sec_sql( $s['end_sql'], $export_tz ) : $s['end_sql'];
810 + } else {
811 + $dt = DateTime::createFromFormat( 'Y-m-d H:i:s', $start_sql, $export_tz );
812 + if ( $dt ) {
813 + $dt->modify( '+' . $master_duration . ' seconds' );
814 + $end_sql = $dt->format( 'Y-m-d H:i:s' );
815 + } else {
816 + $end_sql = $start_sql;
817 + }
818 + }
819 +
820 + // Ensure DTEND > DTSTART for each segment.
821 + list( $start_sql, $end_sql ) = wpbc_ics_coerce_order( $start_sql, $end_sql, $export_tz, false );
822 +
823 + if ( ! empty( $tzid ) ) {
824 + $ev->addNode( new ZCiCalDataNode( 'DTSTART;TZID=' . $tzid . ':' . wpbc_to_ics_local( $start_sql, $export_tz ) ) );
825 + $ev->addNode( new ZCiCalDataNode( 'DTEND;TZID=' . $tzid . ':' . wpbc_to_ics_local( $end_sql, $export_tz ) ) );
826 + } else {
827 + $ev->addNode( new ZCiCalDataNode( 'DTSTART:' . wpbc_to_ics_utc( $start_sql, $export_tz ) ) );
828 + $ev->addNode( new ZCiCalDataNode( 'DTEND:' . wpbc_to_ics_utc( $end_sql, $export_tz ) ) );
829 + }
278 830 }
279 -
280 - $rdate[]= ZCiCal::fromSqlDateTime( $bk_date );
281 831
282 - } else if ( ( $bk_date === '-' ) && ( count($rdate) > 0 ) ) {
283 -
284 - $rdate[] = '/'; // PERIOD
285 -
286 - } else if ( ( $bk_date === ',' ) && ( count($rdate) > 0 ) ) {
287 -
288 - $rdate[] = ','; // Other date
289 -
832 + // Copy common fields.
833 + $ev->addNode( new ZCiCalDataNode( 'SUMMARY:' . $title_ics ) );
834 + $ev->addNode( new ZCiCalDataNode( 'DESCRIPTION:' . $description_ics ) );
835 + $ev->addNode( new ZCiCalDataNode( 'STATUS:' . $ics_status ) );
836 + $ev->addNode( new ZCiCalDataNode( 'TRANSP:OPAQUE' ) );
837 +
838 + // Unique UID per extra VEVENT.
839 + $uid_extra = $bk_id . '-' . $seg_idx . '@' . $host;
840 + $ev->addNode( new ZCiCalDataNode( 'UID:' . $uid_extra ) );
841 +
842 + // Timestamps in UTC Z.
843 + $ev->addNode( new ZCiCalDataNode( 'DTSTAMP:' . wpbc_to_ics_utc( null, $export_tz ) ) );
844 + $ev->addNode( new ZCiCalDataNode( 'CREATED:' . wpbc_to_ics_utc( $bk_arr->modification_date, $export_tz ) ) );
845 + $ev->addNode( new ZCiCalDataNode( 'LAST-MODIFIED:' . wpbc_to_ics_utc( $bk_arr->modification_date, $export_tz ) ) );
290 846 }
291 - $previos_value = $bk_date;
292 847 }
293 - if ( ! empty( $rdate ) ) {
294 - $rdate = implode( '', $rdate );
295 - // add RDATE
296 - $eventobj->addNode( new ZCiCalDataNode( "RDATE;VALUE=PERIOD:" . $rdate ) );
297 -
298 - }
299 848
300 -
301 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
302 - // UID
303 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
304 - $uid = $event_start . '_' . $bk_id . "@demo.icalendar.org";
305 - $eventobj->addNode( new ZCiCalDataNode( "UID:" . $uid ) );
306 -
307 - // DTSTAMP is a required item in VEVENT
308 - $eventobj->addNode( new ZCiCalDataNode( "DTSTAMP:" . ZCiCal::fromSqlDateTime() ) );
849 + // ---------------------------------------------------------------------------------------------
850 + // UID (master)
851 + // ---------------------------------------------------------------------------------------------
852 + $uid = $bk_id . '@' . $host; // stable UID not tied to start time.
853 + $eventobj->addNode( new ZCiCalDataNode( 'UID:' . $uid ) );
309 854
310 - $eventobj->addNode( new ZCiCalDataNode( "CREATED:" . ZCiCal::fromSqlDateTime( $bk_arr->modification_date ) ) );
311 - $eventobj->addNode( new ZCiCalDataNode( "LAST-MODIFIED:" . ZCiCal::fromSqlDateTime( $bk_arr->modification_date ) ) );
312 -
313 -
314 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
315 - // STATUS:CONFIRMED ( "TENTATIVE" / "CONFIRMED" / "CANCELLED" ) // This property defines the overall status for EVENT
316 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
317 -
318 - $ics_status = 'TENTATIVE';
319 - if ( ! ( empty( $bk_arr->trash ) ) ) {
320 - $ics_status = 'CANCELLED';
321 - } elseif ( ! ( empty( $bk_arr->dates[0]->approved ) ) ) {
322 - $ics_status = 'CONFIRMED';
855 + // DTSTAMP / CREATED / LAST-MODIFIED in UTC Z.
856 + $eventobj->addNode( new ZCiCalDataNode( 'DTSTAMP:' . wpbc_to_ics_utc( null, $export_tz ) ) );
857 + $eventobj->addNode( new ZCiCalDataNode( 'CREATED:' . wpbc_to_ics_utc( $bk_arr->modification_date, $export_tz ) ) );
858 + $eventobj->addNode( new ZCiCalDataNode( 'LAST-MODIFIED:' . wpbc_to_ics_utc( $bk_arr->modification_date, $export_tz ) ) );
859 +
860 + // ---------------------------------------------------------------------------------------------
861 + // ATTENDEE (optional)
862 + // ---------------------------------------------------------------------------------------------
863 + if ( isset( $bk_arr->form_data['email'] ) && ( 'On' !== $is_hide_details ) ) {
864 + $cn_title = ( isset( $bk_arr->form_data['name'] ) ? ' ' . esc_textarea( $bk_arr->form_data['name'] ) : '' );
865 + $cn_title .= ( isset( $bk_arr->form_data['firstname'] )
866 + ? ' ' . esc_textarea( $bk_arr->form_data['firstname'] ) : '' );
867 + $cn_title .= ( isset( $bk_arr->form_data['secondname'] )
868 + ? ' ' . esc_textarea( $bk_arr->form_data['secondname'] ) : '' );
869 + $cn_title .= ( isset( $bk_arr->form_data['lastname'] )
870 + ? ' ' . esc_textarea( $bk_arr->form_data['lastname'] ) : '' );
871 +
872 + $cn_clean = trim( wpbm_esc_to_plain_text( $cn_title ) );
873 + $cn_clean = addcslashes( $cn_clean, "\\,;\"" ); // escape per RFC
874 + $attendee_email = $bk_arr->form_data['email'];
875 + $eventobj->addNode( new ZCiCalDataNode( 'ATTENDEE;CN="' . $cn_clean . '":MAILTO:' . $attendee_email ) );
323 876 }
324 - $eventobj->addNode( new ZCiCalDataNode( "STATUS:" . $ics_status ) );
325 -
326 -
327 - // RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=WE,SA
328 - // LOCATION:South Australia\, Australia
329 - // SEQUENCE:1 // defines the revision sequence number of the calendar component within a sequence of revisions
330 - // TRANSP:OPAQUE // This property defines whether or not an event is transparent to busy time searches ;Default value is OPAQUE
331 - // OPAQUE - Blocks or opaque on busy time searches.
332 - // TRANSPARENT - Transparent on busy time searches.
333 -
334 877 }
335 878
336 - echo $icalobj->export(); // write iCalendar feed to stdout
879 + // Output iCalendar text.
880 + // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
881 + echo $icalobj->export();
337 882
338 883 $ics_export = ob_get_contents();
339 -
884 + $ics_export = trim( $ics_export );
340 885 ob_end_clean();
341 886
342 887 return $ics_export;
343 888 }
@@ -342,32 +887,33 @@
342 887 return $ics_export;
343 888 }
344 889
345 890
891 +
346 892 /** Get Next day in MySQL format without time, if current day its Full day - ending with 00:00:00
347 - *
893 + *
348 894 * @param string $mysql_date - 2017-07-12 00:00:00
349 895 * @return string - 2017-07-13
350 896 */
351 897 function wpbm_get_next_date_if_it_full_date( $mysql_date ) {
352 -
353 - // Check if this date ending with 00:00:00 its means that it full day,
898 +
899 + // Check if this date ending with 00:00:00 its means that it full day,
354 900 // so we need to add 24 hours for ending at 23:59:59 (basically 00:00:00 of next day) instead of at start of current day 00:00:00
355 901 if ( ( strlen( $mysql_date ) > 10 ) // , like 2017-07-12 00:00:00
356 - && ( substr( $mysql_date, 11 ) == '00:00:00' )
902 + && ( substr( $mysql_date, 11 ) == '00:00:00' )
357 903 ) {
358 904
359 905 $mysql_date = date_i18n( "Y-m-d 00:00:00", strtotime( $mysql_date ) );
360 906 $mysql_date = strtotime( $mysql_date );
361 - $mysql_date = date_i18n( "Y-m-d", strtotime( '+1 day', $mysql_date ) );
362 - }
907 + $mysql_date = date_i18n( "Y-m-d", strtotime( '+1 day', $mysql_date ) );
908 + }
363 909
364 - return $mysql_date;
910 + return $mysql_date;
365 911 }
366 912
367 913
368 914 /** Get day in MySQL format without time, if current day its Full day - ending with 00:00:00
369 - *
915 + *
370 916 * @param string $mysql_date - 2017-07-12 00:00:00
371 917 * @return string - 2017-07-12
372 918 */
373 919 function wpbm_get_only_date_if_it_full_date( $mysql_date ) {
@@ -372,11 +918,11 @@
372 918 */
373 919 function wpbm_get_only_date_if_it_full_date( $mysql_date ) {
374 920
375 921 if ( ( strlen( $mysql_date ) > 10 ) // , like 2017-07-12 00:00:00
376 - && ( substr( $mysql_date, 11) == '00:00:00' )
377 - ) {
922 + && ( substr( $mysql_date, 11) == '00:00:00' )
923 + ) {
378 924 $mysql_date = substr( $mysql_date, 0, 10 );
379 925 }
380 926
381 927 return $mysql_date;
382 -}
928 +}