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 +662 -181 2.0.11 → 2.1.21 View file →
@@ -131,13 +131,13 @@
131 131 $is_func_disabled = in_array( 'set_time_limit', $disabled );
132 132 if ( ! $is_func_disabled && ! ini_get( 'safe_mode' ) ) {
133 133 @set_time_limit( 0 );
134 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 +// }
135 139
136 - if ( function_exists( 'get_magic_quotes_runtime' ) && get_magic_quotes_runtime() && version_compare( phpversion(), '5.4', '<' ) ) {
137 - set_magic_quotes_runtime( 0 );
138 - }
139 -
140 140 @session_write_close();
141 141 if ( function_exists( 'apache_setenv' ) ) {
142 142 @apache_setenv( 'no-gzip', 1 );
143 143 }
@@ -149,9 +149,9 @@
149 149 ////////////////////////////////////////////////////////////////////////////////////////////////////////////
150 150 // Set Headers before file download
151 151 ////////////////////////////////////////////////////////////////////////////////////////////////////////////
152 152 $file = array();
153 - $file['content_type'] = 'text/calendar';
153 + $file['content_type'] = 'text/calendar; charset=UTF-8';
154 154 $file['name'] = 'wpbm.ics';
155 155
156 156 if ( function_exists( 'mb_detect_encoding' ) ) //FixIn: 2.0.5.3
157 157 $text_encoding = mb_detect_encoding( $ics_export );
@@ -172,9 +172,9 @@
172 172 header( "Robots: none" . "\n" );
173 173 @header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 200 OK' . "\n" );
174 174 header( "Content-Type: " . $file['content_type'] . "\n" );
175 175 header( "Content-Description: File Transfer" . "\n" );
176 - header( "Content-Disposition: attachment; filename=\"" . $file['name'] . "\"" . "\n" );
176 + header( "Content-Disposition: inline; filename=\"" . $file['name'] . "\"" . "\n" );
177 177 header( "Content-Transfer-Encoding: binary" . "\n" );
178 178
179 179 if ( (int) $file['size'] > 0 )
180 180 header( "Content-Length: " . $file['size'] . "\n" );
@@ -190,219 +190,699 @@
190 190 }
191 191 add_action( 'template_redirect', 'wpbm_make_export_ics_feeds' );
192 192
193 193
194 +// ---------------------------------------------------------------------------------------------------------------------
195 +// Helpers for ICS export:
196 +// ---------------------------------------------------------------------------------------------------------------------
194 197
195 -/** Define export ICS here
196 - * 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
197 204 */
198 -function wpbm_export_ics_feed__wpbm_ics( $param = array( 'wh_booking_type' => '1', 'wh_trash' => '' ) ) { //FixIn: 2.0.2.3
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.
199 210
200 - 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' ) ) {
201 570 return '';
571 + }
202 572
203 -// // Start date of getting bookings
204 -// $real_date = strtotime( '-1 year' );
205 -// $wh_booking_date = date_i18n( "Y-m-d", $real_date );
206 -// $param['wh_booking_date'] = $wh_booking_date;
207 - // End date of getting bookings
208 - $real_date = strtotime( '+2 years' ); //FixIn: 2.0.7.1
209 - $wh_booking_date2 = date_i18n( "Y-m-d", $real_date );
210 - $param['wh_booking_date2' ] = $wh_booking_date2;
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
211 577
212 - // Export only approved bookings //FixIn: 2.0.11.1 //FixIn: 8.5.2.3
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).
213 599 $booking_is_ics_export_only_approved = get_bk_option( 'booking_is_ics_export_only_approved' );
214 - if ( 'On' == $booking_is_ics_export_only_approved ) {
215 - $param['wh_approved'] = '1';
216 - } else {
217 - $param['wh_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';
218 605 }
606 + if ( 'plugin' === get_bk_option( 'booking_is_ics_export_imported_bookings' ) ) {
607 + $param['wh_sync_gid'] = 'plugin';
608 + }
219 609
220 - $_REQUEST['view_mode']= 'vm_calendar'; //FixIn: 8.5.2.15 2.0.11.2
610 + $_REQUEST['tab'] = 'vm_calendar'; // FixIn: 10.12.3.2 / 8.5.2.15 / 2.0.11.2.
611 +
221 612 // Get array of bookings.
222 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 );
223 616
617 + ob_start();
224 618
225 - ob_start();
226 -
227 - // create the ical object
619 + // Create the iCal object.
228 620 $icalobj = new ZCiCal();
229 -
230 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 +
231 642 foreach ( $bookings_arr['bookings'] as $bk_id => $bk_arr ) {
232 643
233 - // create the event within the ical object
644 + // Create the master event within the iCal object.
234 645 $eventobj = new ZCiCalNode( 'VEVENT', $icalobj->curnode );
235 646
236 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
237 - // SUMMARY [Title]
238 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
239 -
240 - if ( function_exists( 'get_title_for_showing_in_day' ) ) { // Get title of booking pipiline in "Calendar Overview" page
241 -
242 - if ( true ) { // admin
243 - $what_show_in_day_template = get_bk_option( 'booking_default_title_in_day_for_calendar_view_mode' );
244 - } else { // front-end
245 - $what_show_in_day_template = get_bk_option( 'booking_default_title_in_day_for_timeline_front_end' );
246 - }
247 - //$title = esc_textarea( get_title_for_showing_in_day( $bk_id, $bookings_arr['bookings'], $what_show_in_day_template ) );
248 - $title = get_title_for_showing_in_day( $bk_id, $bookings_arr['bookings'], $what_show_in_day_template ); //FixIn: 2.0.1.6.1
249 -
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 );
250 653 } else {
251 -
252 - $title = ( ( isset( $bk_arr->form_data['name'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['name'] ) : '' )
253 - . ( ( isset( $bk_arr->form_data['firstname'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['firstname'] ) : '' )
254 - . ( ( isset( $bk_arr->form_data['secondname'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['secondname'] ) : '' )
255 - . ( ( isset( $bk_arr->form_data['lastname'] ) ) ? ' ' . esc_textarea( $bk_arr->form_data['lastname'] ) : '' );
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 + : '' );
256 661 }
257 662
258 - $title = wpbm_esc_to_plain_text( $title ); //FixIn: 2.0.1.6.1
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 ) );
259 670
260 -//$title = substr($title, 0, 5);
261 - $eventobj->addNode( new ZCiCalDataNode( 'SUMMARY:' . trim( $title ) ) );
262 -
263 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
671 + // ---------------------------------------------------------------------------------------------
264 672 // DESCRIPTION
265 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
266 - $description = $bk_arr->form_show;
267 -// if ( ( ! empty($bk_arr->dates) ) && ( $bk_arr->dates[0]->approved ) ){
268 -// $description .= '<br>Status: Approved';
269 -// } else {
270 -// $description .= '<br>Status: Pending';
271 -// }
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 + }
272 692
273 - //$description = html_entity_decode( sanitize_text_field( $description ) );
274 - //$description = trim( wp_specialchars_decode( esc_html( stripslashes( $description ) ), ENT_QUOTES ) );
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 );
699 + }
700 + } else {
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 + }
708 + }
275 709
276 - $description = wpbm_esc_to_plain_text( $description );
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 + }
277 714
278 -//debuge($description . '!');die;
279 -//$description = substr($description, 0, 1);
280 - $description = preg_replace( array( "/\n/" ), array( '' ), $description );
281 -//debuge( ZCiCal::formatContent( $description ) );die;
282 - $eventobj->addNode( new ZCiCalDataNode( 'DESCRIPTION:' . ZCiCal::formatContent( $description ) ) );
283 -
284 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
285 - // START & END DATES
286 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
287 - $event_start = $bk_arr->dates_short[ 0 ];
288 - if ( ( count($bk_arr->dates_short) > 1 ) && ( '-' === $bk_arr->dates_short[ 1 ] ) ) {
289 -
290 - $event_end = $bk_arr->dates_short[ 2 ];
291 -
292 - $event_end = wpbm_get_next_date_if_it_full_date( $event_end );
293 -
294 - $period_start_index = 3;
295 - } else { // comma
296 - $event_end = $bk_arr->dates_short[ 0 ];
297 -
298 - $event_end = wpbm_get_next_date_if_it_full_date( $event_end );
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 + }
299 730
300 - $period_start_index = 1;
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';
301 739 }
302 -
303 - // add start date
304 - $eventobj->addNode( new ZCiCalDataNode( "DTSTART:" . ZCiCal::fromSqlDateTime( $event_start ) ) );
740 + $eventobj->addNode( new ZCiCalDataNode( 'STATUS:' . $ics_status ) );
741 + $eventobj->addNode( new ZCiCalDataNode( 'TRANSP:OPAQUE' ) );
305 742
306 - // add end date
307 - $eventobj->addNode( new ZCiCalDataNode( "DTEND:" . ZCiCal::fromSqlDateTime( $event_end ) ) );
308 -
309 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
310 - // RDATE - Rule for other different dates.
311 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
312 - //
313 - // RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,
314 - // 19960404T010000Z/PT3H
315 - //
316 - $rdate = array();
317 - $previos_value = '';
318 - foreach ( $bk_arr->dates_short as $short_ind => $bk_date ) {
319 -
320 - if ( $short_ind < $period_start_index )
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;
749 +
750 + foreach ( $bk_arr->dates_short as $i => $tok ) {
751 + if ( $i < $period_start_index ) {
321 752 continue;
322 -
323 - if ( ( $bk_date !== ',' ) && ( $bk_date !== '-' ) ) {
324 -
325 - if ( $previos_value == '-' ) {
326 - $bk_date = wpbm_get_next_date_if_it_full_date( $bk_date );
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)
327 757 } else {
328 - $bk_date = wpbm_get_only_date_if_it_full_date( $bk_date );
758 + $tok_norm = wpbm_get_only_date_if_it_full_date( $tok ); // start or single
329 759 }
330 -
331 - $rdate[]= ZCiCal::fromSqlDateTime( $bk_date );
332 -
333 - } else if ( ( $bk_date === '-' ) && ( count($rdate) > 0 ) ) {
334 -
335 - $rdate[] = '/'; // PERIOD
336 -
337 - } else if ( ( $bk_date === ',' ) && ( count($rdate) > 0 ) ) {
338 -
339 - $rdate[] = ','; // Other date
340 -
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 + }
341 772 }
342 - $previos_value = $bk_date;
773 + $prev = $tok;
343 774 }
344 - if ( ! empty( $rdate ) ) {
345 - $rdate = implode( '', $rdate );
346 - // add RDATE
347 - $eventobj->addNode( new ZCiCalDataNode( "RDATE;VALUE=PERIOD:" . $rdate ) );
348 -
775 + if ( $current ) {
776 + $segments[] = $current;
349 777 }
350 778
351 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
352 - // Timezone
353 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
354 - /* Timezone must be a supported PHP timezone (see http://php.net/manual/en/timezones.php )
355 - * Note: multi-word timezones must use underscore "_" separator
356 - * Example: $tzid = "America/New_York";
357 - */
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 + }
358 790
359 - $tzid = get_bk_option( 'booking_gcal_timezone' );
791 + $seg_idx = 0;
792 + foreach ( $segments as $s ) {
793 + ++ $seg_idx;
794 + $ev = new ZCiCalNode( 'VEVENT', $icalobj->curnode );
360 795
361 - if ( ! empty( $tzid ) ) {
362 - // Add timezone data
363 - ZCTimeZoneHelper::getTZNode( substr( $event_start, 0, 4 ), substr( $event_end, 0, 4 ), $tzid, $icalobj->curnode ); //FixIn: 2.0.3.3
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 +
804 + } else {
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 + }
830 + }
831 +
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 ) ) );
846 + }
364 847 }
365 848
366 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
367 - // UID
368 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
369 - $uid = $event_start . '_' . $bk_id . "@demo.icalendar.org";
370 - $eventobj->addNode( new ZCiCalDataNode( "UID:" . $uid ) );
371 -
372 - // DTSTAMP is a required item in VEVENT
373 - $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 ) );
374 854
375 - $eventobj->addNode( new ZCiCalDataNode( "CREATED:" . ZCiCal::fromSqlDateTime( $bk_arr->modification_date ) ) );
376 - $eventobj->addNode( new ZCiCalDataNode( "LAST-MODIFIED:" . ZCiCal::fromSqlDateTime( $bk_arr->modification_date ) ) );
377 -
378 -
379 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
380 - // STATUS:CONFIRMED ( "TENTATIVE" / "CONFIRMED" / "CANCELLED" ) // This property defines the overall status for EVENT
381 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
382 -
383 - $ics_status = 'TENTATIVE';
384 - if ( ! ( empty( $bk_arr->trash ) ) ) {
385 - $ics_status = 'CANCELLED';
386 - } elseif ( ! ( empty( $bk_arr->dates[0]->approved ) ) ) {
387 - $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 ) );
388 876 }
389 -// $eventobj->addNode( new ZCiCalDataNode( "STATUS:" . $ics_status ) ); //FixIn: 2.0.11.3
390 -
391 -
392 - // RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=WE,SA
393 - // LOCATION:South Australia\, Australia
394 - // SEQUENCE:1 // defines the revision sequence number of the calendar component within a sequence of revisions
395 - // TRANSP:OPAQUE // This property defines whether or not an event is transparent to busy time searches ;Default value is OPAQUE
396 - // OPAQUE - Blocks or opaque on busy time searches.
397 - // TRANSPARENT - Transparent on busy time searches.
398 -
399 877 }
400 878
401 - echo $icalobj->export(); // write iCalendar feed to stdout
879 + // Output iCalendar text.
880 + // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
881 + echo $icalobj->export();
402 882
403 883 $ics_export = ob_get_contents();
404 -
884 + $ics_export = trim( $ics_export );
405 885 ob_end_clean();
406 886
407 887 return $ics_export;
408 888 }
@@ -407,32 +887,33 @@
407 887 return $ics_export;
408 888 }
409 889
410 890
891 +
411 892 /** Get Next day in MySQL format without time, if current day its Full day - ending with 00:00:00
412 - *
893 + *
413 894 * @param string $mysql_date - 2017-07-12 00:00:00
414 895 * @return string - 2017-07-13
415 896 */
416 897 function wpbm_get_next_date_if_it_full_date( $mysql_date ) {
417 -
418 - // 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,
419 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
420 901 if ( ( strlen( $mysql_date ) > 10 ) // , like 2017-07-12 00:00:00
421 - && ( substr( $mysql_date, 11 ) == '00:00:00' )
902 + && ( substr( $mysql_date, 11 ) == '00:00:00' )
422 903 ) {
423 904
424 905 $mysql_date = date_i18n( "Y-m-d 00:00:00", strtotime( $mysql_date ) );
425 906 $mysql_date = strtotime( $mysql_date );
426 - $mysql_date = date_i18n( "Y-m-d", strtotime( '+1 day', $mysql_date ) );
427 - }
907 + $mysql_date = date_i18n( "Y-m-d", strtotime( '+1 day', $mysql_date ) );
908 + }
428 909
429 - return $mysql_date;
910 + return $mysql_date;
430 911 }
431 912
432 913
433 914 /** Get day in MySQL format without time, if current day its Full day - ending with 00:00:00
434 - *
915 + *
435 916 * @param string $mysql_date - 2017-07-12 00:00:00
436 917 * @return string - 2017-07-12
437 918 */
438 919 function wpbm_get_only_date_if_it_full_date( $mysql_date ) {
@@ -437,11 +918,11 @@
437 918 */
438 919 function wpbm_get_only_date_if_it_full_date( $mysql_date ) {
439 920
440 921 if ( ( strlen( $mysql_date ) > 10 ) // , like 2017-07-12 00:00:00
441 - && ( substr( $mysql_date, 11) == '00:00:00' )
442 - ) {
922 + && ( substr( $mysql_date, 11) == '00:00:00' )
923 + ) {
443 924 $mysql_date = substr( $mysql_date, 0, 10 );
444 925 }
445 926
446 927 return $mysql_date;
447 -}
928 +}