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 +656 -181 2.0.18 → 2.1.21 View file →
@@ -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,225 +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 - $is_hide_details = get_wpbm_option( 'wpbm_is_hide_details' );
259 - if ( 'On' === $is_hide_details ) { //FixIn: 2.0.12.3
663 + $is_hide_details = get_wpbm_option( 'wpbm_is_hide_details' );
664 + if ( 'On' === $is_hide_details ) {
260 665 $title = '';
261 666 }
262 - $title = wpbm_esc_to_plain_text( $title ); //FixIn: 2.0.1.6.1
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 ) );
263 670
264 -//$title = substr($title, 0, 5);
265 - $eventobj->addNode( new ZCiCalDataNode( 'SUMMARY:' . trim( $title ) ) );
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 + }
266 692
267 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
268 - // DESCRIPTION
269 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
270 - $description = $bk_arr->form_show;
271 -// if ( ( ! empty($bk_arr->dates) ) && ( $bk_arr->dates[0]->approved ) ){
272 -// $description .= '<br>Status: Approved';
273 -// } else {
274 -// $description .= '<br>Status: Pending';
275 -// }
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 + }
276 709
277 - //$description = html_entity_decode( sanitize_text_field( $description ) );
278 - //$description = trim( wp_specialchars_decode( esc_html( stripslashes( $description ) ), ENT_QUOTES ) );
279 - if ( 'On' === $is_hide_details ) { //FixIn: 2.0.12.3
280 - $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 );
281 713 }
282 - $description = wpbm_esc_to_plain_text( $description );
283 714
284 -//debuge($description . '!');die;
285 -//$description = substr($description, 0, 1);
286 - $description = preg_replace( array( "/\n/" ), array( '' ), $description );
287 -//debuge( ZCiCal::formatContent( $description ) );die;
288 - $eventobj->addNode( new ZCiCalDataNode( 'DESCRIPTION:' . ZCiCal::formatContent( $description ) ) );
289 -
290 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
291 - // START & END DATES
292 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
293 - $event_start = $bk_arr->dates_short[ 0 ];
294 - if ( ( count($bk_arr->dates_short) > 1 ) && ( '-' === $bk_arr->dates_short[ 1 ] ) ) {
295 -
296 - $event_end = $bk_arr->dates_short[ 2 ];
297 -
298 - $event_end = wpbm_get_next_date_if_it_full_date( $event_end );
299 -
300 - $period_start_index = 3;
301 - } else { // comma
302 - $event_end = $bk_arr->dates_short[ 0 ];
303 -
304 - $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 + }
305 730
306 - $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';
307 739 }
308 -
309 - // add start date
310 - $eventobj->addNode( new ZCiCalDataNode( "DTSTART:" . ZCiCal::fromSqlDateTime( $event_start ) ) );
740 + $eventobj->addNode( new ZCiCalDataNode( 'STATUS:' . $ics_status ) );
741 + $eventobj->addNode( new ZCiCalDataNode( 'TRANSP:OPAQUE' ) );
311 742
312 - // add end date
313 - $eventobj->addNode( new ZCiCalDataNode( "DTEND:" . ZCiCal::fromSqlDateTime( $event_end ) ) );
314 -
315 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
316 - // RDATE - Rule for other different dates.
317 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
318 - //
319 - // RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,
320 - // 19960404T010000Z/PT3H
321 - //
322 - $rdate = array();
323 - $previos_value = '';
324 - foreach ( $bk_arr->dates_short as $short_ind => $bk_date ) {
325 -
326 - 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 ) {
327 752 continue;
328 -
329 - if ( ( $bk_date !== ',' ) && ( $bk_date !== '-' ) ) {
330 -
331 - if ( $previos_value == '-' ) {
332 - $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)
333 757 } else {
334 - $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
335 759 }
336 -
337 - $rdate[]= ZCiCal::fromSqlDateTime( $bk_date );
338 -
339 - } else if ( ( $bk_date === '-' ) && ( count($rdate) > 0 ) ) {
340 -
341 - $rdate[] = '/'; // PERIOD
342 -
343 - } else if ( ( $bk_date === ',' ) && ( count($rdate) > 0 ) ) {
344 -
345 - $rdate[] = ','; // Other date
346 -
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 + }
347 772 }
348 - $previos_value = $bk_date;
773 + $prev = $tok;
349 774 }
350 - if ( ! empty( $rdate ) ) {
351 - $rdate = implode( '', $rdate );
352 - // add RDATE
353 - $eventobj->addNode( new ZCiCalDataNode( "RDATE;VALUE=PERIOD:" . $rdate ) );
354 -
775 + if ( $current ) {
776 + $segments[] = $current;
355 777 }
356 778
357 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
358 - // Timezone
359 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
360 - /* Timezone must be a supported PHP timezone (see http://php.net/manual/en/timezones.php )
361 - * Note: multi-word timezones must use underscore "_" separator
362 - * Example: $tzid = "America/New_York";
363 - */
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 + }
364 790
365 - $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 );
366 795
367 - if ( ! empty( $tzid ) ) {
368 - // Add timezone data
369 - 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 + }
370 847 }
371 848
372 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
373 - // UID
374 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
375 - $uid = $event_start . '_' . $bk_id . "@demo.icalendar.org";
376 - $eventobj->addNode( new ZCiCalDataNode( "UID:" . $uid ) );
377 -
378 - // DTSTAMP is a required item in VEVENT
379 - $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 ) );
380 854
381 - $eventobj->addNode( new ZCiCalDataNode( "CREATED:" . ZCiCal::fromSqlDateTime( $bk_arr->modification_date ) ) );
382 - $eventobj->addNode( new ZCiCalDataNode( "LAST-MODIFIED:" . ZCiCal::fromSqlDateTime( $bk_arr->modification_date ) ) );
383 -
384 -
385 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
386 - // STATUS:CONFIRMED ( "TENTATIVE" / "CONFIRMED" / "CANCELLED" ) // This property defines the overall status for EVENT
387 - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
388 -
389 - $ics_status = 'TENTATIVE';
390 - if ( ! ( empty( $bk_arr->trash ) ) ) {
391 - $ics_status = 'CANCELLED';
392 - } elseif ( ! ( empty( $bk_arr->dates[0]->approved ) ) ) {
393 - $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 ) );
394 876 }
395 - $eventobj->addNode( new ZCiCalDataNode( "STATUS:" . $ics_status ) ); //FixIn: 2.0.11.3 //FixIn: 2.0.12.2
396 -
397 -
398 - // RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=WE,SA
399 - // LOCATION:South Australia\, Australia
400 - // SEQUENCE:1 // defines the revision sequence number of the calendar component within a sequence of revisions
401 - // TRANSP:OPAQUE // This property defines whether or not an event is transparent to busy time searches ;Default value is OPAQUE
402 - // OPAQUE - Blocks or opaque on busy time searches.
403 - // TRANSPARENT - Transparent on busy time searches.
404 -
405 877 }
406 878
407 - echo $icalobj->export(); // write iCalendar feed to stdout
879 + // Output iCalendar text.
880 + // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
881 + echo $icalobj->export();
408 882
409 883 $ics_export = ob_get_contents();
410 -
884 + $ics_export = trim( $ics_export );
411 885 ob_end_clean();
412 886
413 887 return $ics_export;
414 888 }
@@ -413,32 +887,33 @@
413 887 return $ics_export;
414 888 }
415 889
416 890
891 +
417 892 /** Get Next day in MySQL format without time, if current day its Full day - ending with 00:00:00
418 - *
893 + *
419 894 * @param string $mysql_date - 2017-07-12 00:00:00
420 895 * @return string - 2017-07-13
421 896 */
422 897 function wpbm_get_next_date_if_it_full_date( $mysql_date ) {
423 -
424 - // 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,
425 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
426 901 if ( ( strlen( $mysql_date ) > 10 ) // , like 2017-07-12 00:00:00
427 - && ( substr( $mysql_date, 11 ) == '00:00:00' )
902 + && ( substr( $mysql_date, 11 ) == '00:00:00' )
428 903 ) {
429 904
430 905 $mysql_date = date_i18n( "Y-m-d 00:00:00", strtotime( $mysql_date ) );
431 906 $mysql_date = strtotime( $mysql_date );
432 - $mysql_date = date_i18n( "Y-m-d", strtotime( '+1 day', $mysql_date ) );
433 - }
907 + $mysql_date = date_i18n( "Y-m-d", strtotime( '+1 day', $mysql_date ) );
908 + }
434 909
435 - return $mysql_date;
910 + return $mysql_date;
436 911 }
437 912
438 913
439 914 /** Get day in MySQL format without time, if current day its Full day - ending with 00:00:00
440 - *
915 + *
441 916 * @param string $mysql_date - 2017-07-12 00:00:00
442 917 * @return string - 2017-07-12
443 918 */
444 919 function wpbm_get_only_date_if_it_full_date( $mysql_date ) {
@@ -443,11 +918,11 @@
443 918 */
444 919 function wpbm_get_only_date_if_it_full_date( $mysql_date ) {
445 920
446 921 if ( ( strlen( $mysql_date ) > 10 ) // , like 2017-07-12 00:00:00
447 - && ( substr( $mysql_date, 11) == '00:00:00' )
448 - ) {
922 + && ( substr( $mysql_date, 11) == '00:00:00' )
923 + ) {
449 924 $mysql_date = substr( $mysql_date, 0, 10 );
450 925 }
451 926
452 927 return $mysql_date;
453 -}
928 +}