/home/crealab/cars.brainware.com.co/wp-content/plugins/jet-booking/includes/tools.php
<?php

namespace JET_ABAF;

class Tools {

	/**
	 * Date format JS to PHP.
	 *
	 * Returns PHP date format from JavaScript format.
	 *
	 * @access public
	 *
	 * @param null  $format JS date format.
	 * @param array $mask   Provided transform mask.
	 *
	 * @return mixed|string|string[]
	 */
	public static function date_format_js_to_php( $format = null, $mask = [] ) {

		if ( ! $format ) {
			return '';
		}

		$mask = ! empty( $mask ) ? $mask : [
			'/HH{1}/'   => 'H',
			'/hh{1}/'   => 'h',
			'/YYYY{1}/' => 'Y',
			'/YY{1}/'   => 'y',
			'/MMMM{1}/' => 'F',
			'/MMM{1}/'  => 'M',
			'/MM{1}/'   => 'm',
			'/M{1}/'    => 'n',
			'/mm{1}/'   => 'i',
			'/DD{1}/'   => 'd',
			'/D{1}/'    => 'j',
			'/dddd{1}/' => 'l',
			'/ddd{1}/'  => 'D',
		];

		foreach ( $mask as $key => $value ) {
			$format = preg_replace( $key, $value, $format );
		}

		return $format;

	}

	/**
	 * Date format PHP to JS.
	 *
	 * Returns JavaScript date format from PHP format.
	 *
	 * @access public
	 *
	 * @param null  $format PHP date format.
	 * @param array $mask   Provided transform mask.
	 *
	 * @return mixed|string|string[]
	 */
	public static function date_format_php_to_js( $format = null, $mask = [] ) {

		if ( ! $format ) {
			return '';
		}

		$mask = ! empty( $mask ) ? $mask : [
			'/H{1}/' => 'HH',
			'/h{1}/' => 'hh',
			'/Y{1}/' => 'YYYY',
			'/y{1}/' => 'YY',
			'/M{1}/' => 'MMM',
			'/n{1}/' => 'M',
			'/m{1}/' => 'MM',
			'/F{1}/' => 'MMMM',
			'/d{1}/' => 'DD',
			'/D{1}/' => 'ddd',
			'/j{1}/' => 'D',
			'/l{1}/' => 'dddd',
			'/i{1}/' => 'mm',
			'/g{1}/' => 'hh',
		];

		foreach ( $mask as $key => $value ) {
			$format = preg_replace( $key, $value, $format );
		}

		return $format;

	}

	/**
	 * Get post types for js.
	 *
	 * Returns all post types list to use in JavaScript components
	 *
	 * @since 3.2.0
	 *
	 * @param false $placeholder Placeholder value.
	 * @param false $key         Array key.
	 *
	 * @return array
	 */
	public function get_post_types_for_js( $placeholder = false, $key = false ) {

		$post_types = get_post_types( [], 'objects' );
		$types_list = $this->prepare_list_for_js( $post_types, 'name', 'label', $key );

		if ( $placeholder && is_array( $placeholder ) ) {
			$types_list = array_merge( [ $placeholder ], $types_list );
		}

		return $types_list;

	}

	/**
	 * Prepare list for js.
	 *
	 * Prepare passed array for using in JavaScript options.
	 *
	 * @since 3.2.0
	 *
	 * @param array $array     Initial list of posts.
	 * @param null  $value_key List value key.
	 * @param null  $label_key List label key.
	 * @param false $key       Array key.
	 *
	 * @return array
	 */
	public static function prepare_list_for_js( $array = [], $value_key = null, $label_key = null, $key = false ) {

		$result = [];

		if ( ! is_array( $array ) || empty( $array ) ) {
			return $result;
		}

		$array_key = false;

		foreach ( $array as $index => $item ) {
			$value = null;
			$label = null;

			if ( is_object( $item ) ) {
				$value = $item->$value_key;
				$label = $item->$label_key;

				if ( $key ) {
					$array_key = $item->$key;
				}
			} elseif ( is_array( $item ) ) {
				$value = $item[ $value_key ];
				$label = $item[ $label_key ];

				if ( $key ) {
					$array_key = $item[ $key ];
				}
			} else {
				if ( ARRAY_A === $value_key ) {
					$value = $index;
				} else {
					$value = $item;
				}

				$label = $item;

				if ( $key ) {
					$array_key = $index;
				}
			}

			if ( $key && false !== $array_key ) {
				$result[ $array_key ] = [
					'value' => $value,
					'label' => $label,
				];
			} else {
				$result[] = [
					'value' => $value,
					'label' => $label,
				];
			}
		}

		return $result;

	}

	/**
	 * Get booking posts.
	 *
	 * Returns list of all created bookings posts.
	 *
	 * @since  2.6.1
	 * @since  3.1.1 Added $args parameter.
	 *
	 * @param array $args Booking post arguments.
	 *
	 * @return array|int[]|\WP_Post[]
	 */
	public function get_booking_posts( $args = [] ) {

		$post_type = jet_abaf()->settings->get( 'apartment_post_type' );

		if ( ! $post_type ) {
			return [];
		}

		$defaults = apply_filters( 'jet-booking/tools/post-type-args', [
			'post_type'      => $post_type,
			'posts_per_page' => - 1,
		] );

		$args  = wp_parse_args( $args, $defaults );
		$posts = get_posts( $args );

		if ( ! $posts ) {
			return [];
		}

		return $posts;

	}

	/**
	 * Get unavailable apartments.
	 *
	 * @since 2.0.0
	 * @since 2.6.1 New handling.
	 * @since 3.1.0 Moved to tools class.
	 *
	 * @param string $from Range start date in timestamp.
	 * @param string $to   Range end date in timestamp.
	 *
	 * @return array
	 * @throws \Exception
	 */
	public function get_unavailable_apartments( $from, $to ) {

		$args  = apply_filters( 'jet-booking/tools/booking-posts-args', [] );
		$posts = $this->get_booking_posts( $args );

		if ( empty( $posts ) ) {
			return [];
		}

		$booked_apartments = [];

		foreach ( $posts as $post ) {
			$invalid_dates = $this->get_invalid_dates_in_range( $from, $to, $post->ID );

			if ( ! empty( $invalid_dates ) ) {
				$booked_apartments[] = $post->ID;
			}
		}

		return $booked_apartments;

	}

	/**
	 * Get invalid dates in range.
	 *
	 * Returns list of booked, disabled and off dates in defined range.
	 *
	 * @since  2.5.5
	 * @since  2.6.1 Added `$instance_id` parameter.
	 * @since  2.7.1 Checkout only compatibility.
	 * @access public
	 *
	 * @param string        $from        First date of range in timestamp.
	 * @param string        $to          Last date of range in timestamp.
	 * @param string|number $instance_id Booking instance ID.
	 *
	 * @return array
	 * @throws \Exception
	 */
	public function get_invalid_dates_in_range( $from, $to, $instance_id ) {

		$period        = $this->get_booking_period( $from, $to );
		$booked_dates  = jet_abaf()->settings->get_off_dates( $instance_id );
		$disabled_days = jet_abaf()->settings->get_days_by_rule( $instance_id );
		$booked_range  = [];

		foreach ( $period as $key => $value ) {
			if ( in_array( $value->format( 'Y-m-d' ), $booked_dates ) || in_array( $value->format( 'w' ), $disabled_days ) ) {
				$booked_range[] = $value->format( 'Y-m-d' );
			}
		}

		sort( $booked_range );

		$days_off = jet_abaf()->settings->get_booking_days_off( $instance_id );

		if ( jet_abaf()->settings->checkout_only_allowed( $instance_id ) ) {
			if ( false !== ( $index = array_search( date( 'Y-m-d', $to ), $booked_range ) ) && 0 === $index && ! in_array( $booked_range[ $index ], $days_off ) && ! in_array( date( 'N', $to ), $disabled_days ) ) {
				unset( $booked_range[ $index ] );
			}
		}

		return array_values( $booked_range );

	}

	/**
	 * Get field default value.
	 *
	 * Returns check in check out field default values.
	 *
	 * @since  3.0.0
	 * @access public
	 *
	 * @param string     $value   Initial value.
	 * @param string     $format  Date format.
	 * @param string|int $post_id Queried post ID.
	 *
	 * @return array
	 * @throws \Exception
	 */
	public function get_field_default_value( $value, $format, $post_id ) {

		$check_in_days  = jet_abaf()->settings->get_days_by_rule( $post_id, 'check_in' );
		$check_out_days = jet_abaf()->settings->get_days_by_rule( $post_id, 'check_out' );

		if ( ! empty( $check_in_days ) && 7 > count( $check_in_days ) || ! empty( $check_out_days ) && 7 > count( $check_out_days ) || jet_abaf()->settings->get( 'weekly_bookings' ) ) {
			return [];
		}

		$result         = [];
		$store_type     = jet_abaf()->settings->get( 'filters_store_type' );
		$searched_dates = jet_abaf()->stores->get_store( $store_type )->get( 'searched_dates' );
		$value          = $value ?: $searched_dates;

		if ( ! trim( $value ) ) {
			return $result;
		}

		$value = explode( ' - ', $value );

		if ( ! empty( $value[0] ) && $this->is_valid_timestamp( $value[0] ) && ! empty( $value[1] ) && $this->is_valid_timestamp( $value[1] ) ) {
			$checkin  = date( 'Y-m-d', $value[0] );
			$checkout = date( 'Y-m-d', $value[1] );

			if ( $checkin === $checkout && jet_abaf()->settings->is_per_nights_booking( $post_id ) ) {
				return $result;
			}

			$period_start     = new \DateTime( $checkin );
			$period_end       = new \DateTime( $checkout );
			$period           = jet_abaf()->settings->is_per_nights_booking( $post_id ) ? $period_start->diff( $period_end ) : $period_start->diff( $period_end->modify( '+1 day' ) );
			$min_days         = jet_abaf()->settings->get_config_setting( $post_id, 'min_days' );
			$max_days         = jet_abaf()->settings->get_config_setting( $post_id, 'max_days' );
			$start_day_offset = jet_abaf()->settings->get_config_setting( $post_id, 'start_day_offset' );

			$price          = new Price( $post_id );
			$seasonal_price = $price->seasonal_price->get_price();
			$in_season      = false;

			if ( ! empty( $seasonal_price ) ) {
				foreach ( $seasonal_price as $season ) {
					if ( ! isset( $season['enable_config'] ) || ! filter_var( $season['enable_config'], FILTER_VALIDATE_BOOLEAN ) ) {
						continue;
					}

					if ( $value[0] >= $season['start'] && $value[0] <= $season['end'] || $value[1] >= $season['start'] && $value[1] <= $season['end'] || $season['start'] >= $value[0] && $season['start'] <= $value[0] || $season['end'] >= $value[0] && $season['end'] <= $value[0] ) {
						$in_season = true;

						if ( ! empty( $season['min_days'] ) && $period->days < $season['min_days'] || ! empty( $season['max_days'] ) && $period->days > $season['max_days'] || ! empty( $season['start_day_offset'] ) && $period->days <= $season['start_day_offset'] ) {
							return $result;
						}
					}
				}
			}

			if ( ! $in_season && ( $min_days && $period->days < $min_days || $max_days && $period->days > $max_days || $period->days <= $start_day_offset ) ) {
				return $result;
			}

			$booked_range = $this->get_invalid_dates_in_range( $value[0], $value[1], $post_id );

			if ( $checkin >= date( 'Y-m-d' ) && ! ( in_array( $checkin, $booked_range ) && in_array( $checkout, $booked_range ) ) ) {
				if ( in_array( $checkin, $booked_range ) ) {
					$checkin = strtotime( end( $booked_range ) . ' + 1 day' );
					reset( $booked_range );
				} else {
					$checkin = $value[0];
				}

				if ( in_array( $checkout, $booked_range ) ) {
					$checkout = strtotime( $booked_range[0] . ' - 1 day' );
				} else {
					if ( ! empty( $booked_range ) && ! in_array( date( 'Y-m-d', $value[0] ), $booked_range ) ) {
						$checkout = strtotime( $booked_range[0] . ' - 1 day' );
					} else {
						$checkout = $value[1];
					}
				}

				$format = self::date_format_js_to_php( $format );

				$result['checkin']  = date( $format, $checkin );
				$result['checkout'] = jet_abaf()->settings->is_one_day_bookings( $post_id ) ? $result['checkin'] : date( $format, $checkout );
			}
		}

		return $result;

	}

	/**
	 * Booked dates.
	 *
	 * Returns booked dates list.
	 *
	 * @since  1.0.0
	 *
	 * @return array List of booked dates.
	 * @throws \Exception
	 */
	public function get_booked_dates( $post_id ) {

		$bookings = jet_abaf()->db->get_future_bookings( $post_id );

		if ( empty( $bookings ) ) {
			return [];
		}

		$units           = jet_abaf()->db->get_apartment_units( $post_id );
		$units_num       = ! empty( $units ) ? count( $units ) : 0;
		$weekly_bookings = jet_abaf()->settings->is_weekly_bookings( $post_id );
		$week_offset     = jet_abaf()->settings->get_config_setting( $post_id, 'week_offset' );
		$skip_statuses   = jet_abaf()->statuses->invalid_statuses();
		$skip_statuses[] = jet_abaf()->statuses->temporary_status();
		$dates           = [];

		if ( ! $units_num || 1 === $units_num ) {
			foreach ( $bookings as $booking ) {
				if ( ! empty( $booking['status'] ) && in_array( $booking['status'], $skip_statuses ) ) {
					continue;
				}

				$from = new \DateTime( date( 'F d, Y', $booking['check_in_date'] ) );
				$to   = new \DateTime( date( 'F d, Y', $booking['check_out_date'] ) );

				if ( $weekly_bookings && ! $week_offset || ! jet_abaf()->settings->is_per_nights_booking( $post_id ) ) {
					$to = $to->modify( '+1 day' );
				}

				if ( $to->format( 'Y-m-d' ) === $from->format( 'Y-m-d' ) ) {
					$dates[] = $from->format( 'Y-m-d' );
				} else {
					$period = new \DatePeriod( $from, new \DateInterval( 'P1D' ), $to );

					foreach ( $period as $key => $value ) {
						$dates[] = $value->format( 'Y-m-d' );
					}
				}
			}
		} else {
			$booked_units = [];

			foreach ( $bookings as $booking ) {
				if ( ! empty( $booking['status'] ) && in_array( $booking['status'], $skip_statuses ) ) {
					continue;
				}

				$from = new \DateTime( date( 'F d, Y', $booking['check_in_date'] ) );
				$to   = new \DateTime( date( 'F d, Y', $booking['check_out_date'] ) );

				if ( $weekly_bookings && ! $week_offset || ! jet_abaf()->settings->is_per_nights_booking( $post_id ) ) {
					$to = $to->modify( '+1 day' );
				}

				if ( $to->format( 'Y-m-d' ) === $from->format( 'Y-m-d' ) ) {
					if ( empty( $booked_units[ $from->format( 'Y-m-d' ) ] ) ) {
						$booked_units[ $from->format( 'Y-m-d' ) ] = 1;
					} else {
						$booked_units[ $from->format( 'Y-m-d' ) ] ++;
					}
				} else {
					$period = new \DatePeriod( $from, new \DateInterval( 'P1D' ), $to );

					foreach ( $period as $key => $value ) {
						if ( empty( $booked_units[ $value->format( 'Y-m-d' ) ] ) ) {
							$booked_units[ $value->format( 'Y-m-d' ) ] = 1;
						} else {
							$booked_units[ $value->format( 'Y-m-d' ) ] ++;
						}
					}
				}
			}

			foreach ( $booked_units as $date => $booked_units_num ) {
				if ( $units_num <= $booked_units_num ) {
					$dates[] = $date;
				}
			}
		}

		return $dates;

	}

	/**
	 * Get next booked dates.
	 *
	 * Returns list of dates that booked next.
	 *
	 * @param array           $booked_dates Booked dates list.
	 * @param int|string|null $post_id      Booking instance ID.
	 *
	 * @return array
	 */
	public function get_next_booked_dates( $booked_dates = [], $post_id = null ) {

		$result = [];

		if ( ! jet_abaf()->settings->checkout_only_allowed( $post_id ) ) {
			return $result;
		}

		foreach ( $booked_dates as $index => $date ) {
			$next_date = date( 'Y-m-d', strtotime( $date ) + DAY_IN_SECONDS );
			$prev_date = date( 'Y-m-d', strtotime( $date ) - DAY_IN_SECONDS );

			if ( ! in_array( $next_date, $booked_dates ) && ! in_array( $prev_date, $booked_dates ) ) {
				$result[] = $next_date;
			}
		}

		return $result;

	}

	/**
	 * Returns booking period data.
	 *
	 * @since 3.2.1
	 *
	 * @param int|string $from Start period date in timestamp format.
	 * @param int|string $to   End period date in timestamp format.
	 *
	 * @return \DatePeriod
	 * @throws \Exception
	 */
	public function get_booking_period( $from, $to ) {

		$start = new \DateTime( date( 'Y-m-d', $from ) );
		$end   = new \DateTime( date( 'Y-m-d', $to ) );

		return new \DatePeriod( $start, new \DateInterval( 'P1D' ), $end->modify( '+1 day' ) );

	}

	/**
	 * Check in booking period containing disables days.
	 *
	 * @since 3.2.1
	 *
	 * @param array $booking Booking data.
	 *
	 * @return bool
	 * @throws \Exception
	 */
	public function is_booking_period_available( $booking ) {

		$disabled_days  = jet_abaf()->settings->get_days_by_rule( $booking['apartment_id'] );
		$days_off       = jet_abaf()->settings->get_booking_days_off( $booking['apartment_id'] );
		$booking_period = $this->get_booking_period( $booking['check_in_date'], $booking['check_out_date'] );
		$invalid_days   = [];

		foreach ( $booking_period as $key => $value ) {
			if ( in_array( $value->format( 'w' ), $disabled_days ) || in_array( $value->format( 'Y-m-d' ), $days_off ) ) {
				$invalid_days[] = $value->format( 'Y-m-d' );
			}
		}

		return empty( $invalid_days );

	}

	/**
	 * Is valid timestamp.
	 *
	 * Check if is valid timestamp
	 *
	 * @since 3.2.0
	 *
	 * @param mixed $timestamp Date in timestamp format.
	 *
	 * @return boolean
	 */
	public static function is_valid_timestamp( $timestamp ) {

		if ( is_array( $timestamp ) || is_object( $timestamp ) ) {
			return false;
		}

		return ( ( string ) ( int ) $timestamp === $timestamp || ( int ) $timestamp === $timestamp ) && ( $timestamp <= PHP_INT_MAX ) && ( $timestamp >= ~PHP_INT_MAX );

	}

}