File "class-location-single.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/orderable/inc/modules/location/class-location-single.php
File size: 34.44 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Single location class.
*
* @package Orderable/Classes
*/
defined( 'ABSPATH' ) || exit;
/**
* Single location class.
*/
class Orderable_Location_Single {
/**
* Location data.
*
* @var array
*/
public $location_data = array(
'location_id' => 0,
'override_default_open_hours' => false,
'asap_date' => '',
'asap_time' => '',
'address_line_1' => '',
'address_line_2' => '',
'city' => '',
'country_state' => '',
'postcode_zip' => '',
'open_hours' => array(),
'enable_default_holidays' => true,
'pickup_hours_same_as_delivery' => true,
);
/**
* Constructor.
*
* @param int|array|null $location Location ID or row data.
*/
public function __construct( $location = null ) {
$location_data = array();
// Get selected location ID if none is passed in.
if ( empty( $location ) ) {
$location = Orderable_Location::get_selected_location_id();
}
// Get the main location data.
if ( empty( $location ) ) {
$location_data['open_hours'] = Orderable_Location::get_default_open_hours();
} else {
$location_data = is_numeric( $location ) ? Orderable_Location::get_location_data( $location ) : $location;
}
$this->location_data = wp_parse_args( $location_data, $this->location_data );
/**
* Action to run after the location object is initialized.
*
* @since 1.13.0
*/
do_action( 'orderable_location_object_init', $this );
}
/**
* Get location ID.
*
* @return int
*/
public function get_location_id() {
return absint( $this->location_data['location_id'] );
}
/**
* Get location name.
*
* @return int
*/
public function get_title() {
return $this->location_data['title'];
}
/**
* Is service enabled?
*
* @param string $service_type Service type (delivery/pickup).
*
* @return bool
*/
public function is_service_enabled( $service_type ) {
return ! empty( $this->location_data[ $service_type ] );
}
/**
* Get services.
*
* @return array
*/
public function get_services() {
$services = array();
if ( $this->is_service_enabled( 'delivery' ) ) {
$services[] = 'delivery';
}
if ( $this->is_service_enabled( 'pickup' ) ) {
$services[] = 'pickup';
}
return $services;
}
/**
* Does the location have any services enabled.
*
* @return bool
*/
public function has_services() {
return ! empty( $this->get_services() );
}
/**
* Get lead time period.
*
* @return string
*/
public function get_lead_time_period() {
return ! empty( $this->location_data['lead_time_period'] ) ? $this->location_data['lead_time_period'] : 'days';
}
/**
* Get lead time.
*
* @param bool $in_seconds In seconds.
*
* @return int
*/
public function get_lead_time( $in_seconds = false ) {
$lead_time = ! empty( $this->location_data['lead_time'] ) ? absint( $this->location_data['lead_time'] ) : 0;
if ( 0 === $lead_time || ! $in_seconds ) {
/**
* Filter to modify the lead time.
*
* @since 1.9.0
*/
return apply_filters( 'orderable_location_get_lead_time', $lead_time );
}
$lead_time_period = $this->get_lead_time_period();
if ( 'days' === $lead_time_period ) {
$lead_time *= 86400;
} elseif ( 'hours' === $lead_time_period ) {
$lead_time *= 3600;
} elseif ( 'minutes' === $lead_time_period ) {
$lead_time *= 60;
}
/**
* Filter to modify the lead time.
*
* @since 1.9.0
*/
return apply_filters( 'orderable_location_get_lead_time', $lead_time );
}
/**
* Get preorder days.
*
* @return int
*/
public function get_preorder_days() {
return isset( $this->location_data['preorder'] ) ? absint( $this->location_data['preorder'] ) : 7;
}
/**
* Get delivery days calculation method.
*
* @return string
*/
public function get_delivery_calculation_method() {
return ! empty( $this->location_data['delivery_days_calculation_method'] ) ? $this->location_data['delivery_days_calculation_method'] : 'all';
}
/**
* Get override default open hours setting.
*
* @return bool
*/
public function get_override_default_open_hours() {
return ! in_array( $this->location_data['override_default_open_hours'], array( false, '0' ), true );
}
/**
* Get enable default holidays setting.
*
* @return bool
*/
public function get_enable_default_holidays() {
return ! in_array( $this->location_data['enable_default_holidays'], array( false, '0' ), true );
}
/**
* Get pickup hours same as delivery setting.
*
* @return bool
*/
public function get_pickup_hours_same_as_delivery() {
return ! in_array( $this->location_data['pickup_hours_same_as_delivery'], array( false, '0' ), true );
}
/**
* Get ASAP settings
*
* @return array
*/
public function get_asap_settings() {
return array(
'date' => '1' === $this->location_data['asap_date'],
'time' => '1' === $this->location_data['asap_time'],
);
}
/**
* Get service days.
*
* @param string $service_type Service type (delivery/pickup).
*
* @return array
*/
public function get_service_days( $service_type = 'delivery' ) {
$days = Orderable_Timings::get_days_of_the_week();
$settings = $this->get_service_hours( $service_type, false, true );
$service_days = array();
if ( empty( $settings ) ) {
return $service_days;
}
foreach ( $settings as $setting_row ) {
if ( empty( $setting_row['days'] ) ) {
continue;
}
foreach ( $setting_row['days'] as $day_number ) {
$service_days[ $day_number ] = $days[ $day_number ];
}
}
return $service_days;
}
/**
* Get service hours.
*
* @param null $service_type Service type (delivery/pickup).
* @param bool $is_admin Is this an admin request? If so, collect all data for location.
* @param bool $skip_zone Skip the zone ID.
*
* @return array
*/
public function get_service_hours( $service_type = null, $is_admin = false, $skip_zone = false ) {
$zone_id = Orderable_Location_Zones::get_selected_shipping_zone_id();
if ( false === $zone_id && ! $is_admin ) {
/**
* Filter to modify the service hours.
*
* @param array $service_hours The service hours.
* @param Orderable_Location_Single $location Current location object.
* @param string|null $service_type The service type.
* @param bool $is_admin Is this an admin request?
* @param bool $skip_zone Skip the zone ID.
*
* @since 1.14.0
*/
return apply_filters( 'orderable_get_service_hours', array(), $this, $service_type, $is_admin, $skip_zone );
}
// Switch service type to 'delivery' if pickup hours are the same as delivery.
$original_service_type = $service_type;
$service_type = ! $is_admin && 'pickup' === $service_type && $this->get_pickup_hours_same_as_delivery() ? 'delivery' : $service_type;
$location_id = $this->get_location_id();
$cache_key = "orderable_time_slots_{$location_id}";
if ( $original_service_type ) {
$cache_key .= "_{$original_service_type}";
}
if ( ! $skip_zone && false !== $zone_id ) {
$cache_key .= "_{$zone_id}";
}
$cached_service_hours = wp_cache_get( $cache_key );
if ( false !== $cached_service_hours ) {
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
return apply_filters( 'orderable_get_service_hours', $cached_service_hours, $this, $service_type, $is_admin, $skip_zone );
}
global $wpdb;
$query = "SELECT DISTINCT
ts.*
FROM
{$wpdb->prefix}orderable_location_time_slots ts
LEFT JOIN
{$wpdb->prefix}orderable_location_delivery_zones_lookup l
ON ts.location_id = l.location_id AND ts.time_slot_id = l.time_slot_id
WHERE
ts.location_id = %d";
$query_params = array(
$location_id,
);
if ( $service_type ) {
$query .= ' AND ts.service_type = %s';
$query_params[] = $service_type;
}
// Zone doesn't matter for pickup.
if ( 'pickup' !== $original_service_type && ! empty( $zone_id ) && ! $skip_zone ) {
$query .= ' AND (l.zone_id = %d OR ts.has_zones = 0)';
$query_params[] = $zone_id;
}
$service_hours = $wpdb->get_results(
$wpdb->prepare(
$query, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$query_params
),
ARRAY_A
);
if ( empty( $service_hours ) ) {
$service_hours = array();
} else {
foreach ( $service_hours as &$service_hour ) {
$service_hour['days'] = (array) maybe_unserialize( $service_hour['days'] );
$service_hour['from'] = maybe_unserialize( $service_hour['time_from'] );
$service_hour['to'] = maybe_unserialize( $service_hour['time_to'] );
}
}
wp_cache_set( $cache_key, $service_hours, '', ORDERABLE_CACHE_EXPIRATION_TIME );
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
return apply_filters( 'orderable_get_service_hours', $service_hours, $this, $service_type, $is_admin, $skip_zone );
}
/**
* Get services on day.
*
* @param int $timestamp Timestamp of specific day at 00:00am (GMT).
*
* @return array
*/
public function get_services_on_day( $timestamp ) {
$timestamp_adjusted = Orderable_Timings::get_timestamp_adjusted( $timestamp );
$services_on_day = array();
$services = $this->get_services();
if ( empty( $services ) ) {
return $services_on_day;
}
$day_to_check = absint( date( 'w', $timestamp_adjusted ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
foreach ( $services as $service ) {
$service_days = $this->get_service_days( $service );
$services_on_day[ $service ] = isset( $service_days[ $day_to_check ] ) && ! $this->is_holiday( $timestamp, $service );
}
return $services_on_day;
}
/**
* Get open days.
*
* @return array
*/
public function get_open_days() {
static $open_days_cache;
if ( ! empty( $open_days_cache[ $this->get_location_id() ] ) ) {
// phpcs:ignore WooCommerce.Commenting.CommentHooks
return apply_filters( 'orderable_location_get_open_days', $open_days_cache[ $this->get_location_id() ], $this );
}
$open_days = array();
$open_hours_settings = $this->get_open_hours();
$days_of_the_week = Orderable_Timings::get_days_of_the_week();
foreach ( $open_hours_settings as $day => $open_hour ) {
if ( ! empty( $open_hour['enabled'] ) ) {
$open_days[ $day ] = $days_of_the_week[ $day ];
}
}
/**
* Filter location open days.
*
* @since 1.8.0
* @hook orderable_location_get_open_days
* @param array $open_days Location open days.
* @param Orderable_Location_Single $location Location object.
*/
$open_days = apply_filters( 'orderable_location_get_open_days', $open_days, $this );
$open_days_cache[ $this->get_location_id() ] = $open_days;
return $open_days;
}
/**
* Is the location open?
*
* @param int $timestamp Timestamp (GMT).
*
* @return bool
*/
public function is_open( $timestamp ) {
$date_time = Orderable_Timings::get_date_time_by_timestamp( $timestamp );
$open_days = $this->get_open_days();
return array_key_exists( $date_time->format( 'w' ), $open_days );
}
/**
* Get open hours.
*
* @return array
*/
public function get_open_hours() {
static $open_hours_cache;
if ( ! empty( $open_hours_cache[ $this->get_location_id() ] ) ) {
// phpcs:ignore WooCommerce.Commenting.CommentHooks
return apply_filters( 'orderable_location_get_open_hours', $open_hours_cache[ $this->get_location_id() ], $this );
}
if ( $this->get_override_default_open_hours() ) {
$open_hours = maybe_unserialize( $this->location_data['open_hours'] );
$open_hours = ! empty( $open_hours ) ? $open_hours : array();
} else {
$open_hours = Orderable_Location::get_default_open_hours();
}
/**
* Filter location open hours.
*
* @since 1.8.0
* @hook orderable_location_get_open_hours
* @param array $open_hours Location open hours.
* @param Orderable_Location_Single $location Location object.
*/
$open_hours = apply_filters( 'orderable_location_get_open_hours', $open_hours, $this );
$open_hours_cache[ $this->get_location_id() ] = $open_hours;
return $open_hours;
}
/**
* Get upcoming open hours.
*
* @return array
*/
public function get_upcoming_open_hours() {
$open_hours = array();
$days = Orderable_Timings::get_days_of_the_week( 'full', 0 );
$open_hours_settings = $this->get_open_hours();
$current_day = absint( current_time( 'w', true ) );
$tense = 'last';
foreach ( $days as $index => $day ) {
$day_settings = isset( $open_hours_settings[ $index ] ) ? $open_hours_settings[ $index ] : null;
if ( empty( $day_settings ) ) {
continue;
}
if ( $index === $current_day ) {
$tense = 'this';
}
$day_name_en = date( 'l', strtotime( "Sunday +{$index} days" ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$datetime = new DateTime( $tense . ' ' . $day_name_en, wp_timezone() );
$timestamp = $datetime->getTimestamp();
$timestamp_adjusted = Orderable_Timings::get_timestamp_adjusted( $timestamp );
$services_on_day = $this->get_services_on_day( $timestamp );
$hours = __( 'Closed', 'orderable' );
$is_holiday = $this->is_holiday( $timestamp );
$is_holiday = $is_holiday && empty( array_filter( $services_on_day ) );
$open = ! empty( $day_settings['enabled'] ) && $day_settings['enabled'];
if ( $open && $is_holiday ) {
$hours = __( 'Holiday', 'orderable' );
} elseif ( $open && ! $is_holiday ) {
$from = sprintf( '%s:%s %s', $day_settings['from']['hour'], $day_settings['from']['minute'], $day_settings['from']['period'] );
$to = sprintf( '%s:%s %s', $day_settings['to']['hour'], $day_settings['to']['minute'], $day_settings['to']['period'] );
$hours = sprintf( '%s — %s', $from, $to );
}
$open_hours[ $index ] = array(
'day' => $day,
'date' => date( 'd', $timestamp_adjusted ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
'hours' => $hours,
'is_closed' => ! $open,
'services' => $services_on_day,
);
}
/**
* Filter location upcoming open hours.
*
* @since 1.8.0
* @hook orderable_upcoming_open_hours
* @param array $open_hours Location upcoming open hours.
* @param Orderable_Location_Single $location Location object.
*/
return apply_filters( 'orderable_upcoming_open_hours', $open_hours, $this );
}
/**
* Get slots for service type and day of the week.
*
* @param int $timestamp Timestamp (GMT).
* @param string $type Service type (delivery|pickup).
*
* @return array
*/
public function get_slots( $timestamp, $type = 'delivery' ) {
$slots = array();
if ( empty( $timestamp ) ) {
return $slots;
}
$settings = $this->get_service_hours( $type );
if ( empty( $settings ) ) {
return $slots;
}
$date_time = Orderable_Timings::get_date_time_by_timestamp( $timestamp );
$current_timestamp = time();
$timestamp = ! $timestamp ? $current_timestamp : $timestamp;
$day_number = (int) $date_time->format( 'w' ); // 0 (Sunday) through 6 (Saturday).
foreach ( $settings as $setting_key => $setting_row ) {
$days = array_map( 'absint', $setting_row['days'] );
if ( ! in_array( $day_number, $days, true ) ) {
continue;
}
$slots = array(
'all-day' => array(
'formatted' => __( 'All Day', 'orderable' ),
'value' => 'all-day',
'timestamp' => $timestamp,
'setting_key' => $setting_key,
'setting_row' => $setting_row,
),
);
break;
}
if ( has_filter( 'orderable_get_slots' ) ) {
_deprecated_hook( 'orderable_get_slots', '1.8.0', 'orderable_location_get_slots' );
/**
* Filter location slots.
*
* @since 1.0.0
* @hook orderable_get_slots
* @deprecated 1.8.0 Use orderable_location_get_slots instead.
*
* @param array $slots Location slots.
* @param int $timestamp Timestamp (GMT).
* @param string $type The service type. Either 'delivery' or 'pickup'.
* @param Orderable_Location_Single $location Location object.
*/
$slots = apply_filters( 'orderable_get_slots', $slots, $timestamp, $type, $this );
}
/**
* Filter location slots.
*
* @since 1.8.0
* @hook orderable_location_get_slots
*
* @param array $slots Location slots.
* @param int $timestamp Timestamp (GMT).
* @param string $type The service type. Either 'delivery' or 'pickup'.
* @param Orderable_Location_Single $location Location object.
*/
return apply_filters( 'orderable_location_get_slots', $slots, $timestamp, $type, $this );
}
/**
* Get holidays.
*
* @param null $type Service type (delivery/pickup).
* @param bool $include_defaults Include default holidays.
*
* @return array
*/
public function get_holidays( $type = null, $include_defaults = true ) {
static $holidays_cache;
if ( ! empty( $holidays_cache[ $this->get_location_id() ] ) ) {
// phpcs:ignore WooCommerce.Commenting.CommentHooks
return apply_filters( 'orderable_location_get_holidays', $holidays_cache[ $this->get_location_id() ], $type, $include_defaults, $this );
}
global $wpdb;
$holidays = $include_defaults && $this->get_enable_default_holidays() ? (array) Orderable_Settings::get_setting( 'holidays' ) : array();
$sql = "SELECT
holiday_id,
date_from 'from',
date_to 'to',
services,
repeat_yearly 'repeat'
FROM
{$wpdb->orderable_location_holidays} holidays
INNER JOIN
{$wpdb->orderable_locations} locations
ON
locations.location_id = holidays.location_id
WHERE
locations.location_id = %d";
$holidays_query = $wpdb->get_results(
$wpdb->prepare(
$sql, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$this->get_location_id()
),
ARRAY_A
);
/**
* Filter location holidays query result.
*
* @since 1.14.0
*/
$holidays_query = apply_filters( 'orderable_location_holidays_query_result', $holidays_query, $this );
$holidays_query = ! empty( $holidays_query ) ? $holidays_query : array();
$holidays = array_merge( $holidays, $holidays_query );
if ( ! empty( $holidays ) ) {
$today = new DateTime( 'now', wp_timezone() );
$today_timestamp = $today->getTimestamp();
foreach ( $holidays as $index => $holiday ) {
if ( empty( $holiday['from'] ) ) {
continue;
}
$holidays[ $index ]['services'] = maybe_unserialize( $holiday['services'] );
$holidays[ $index ]['timestamps'] = array();
$from = DateTime::createFromFormat( 'Y-m-d H:i:s', $holiday['from'] . ' 00:00:00', wp_timezone() );
$to = ! empty( $holiday['to'] ) ? DateTime::createFromFormat( 'Y-m-d H:i:s', $holiday['to'] . ' 00:00:00', wp_timezone() ) : clone $from;
// Add one minute so last slot is included.
$to->modify( '+1 minute' );
// Add years to from and to if this holiday repeats and is in the past.
if ( $today_timestamp > $to->getTimestamp() && ! empty( $holiday['repeat'] ) ) {
// Calculate how many years have past and append 1 additional.
$today_datetime = new DateTime();
$today_datetime->setTimestamp( $today_timestamp );
$interval = $today_datetime->diff( $to );
$year = intval( $interval->format( '%Y' ) ) + 1;
$from->modify( '+' . $year . ' year' );
$to->modify( '+' . $year . ' year' );
$holidays[ $index ]['from'] = $from->format( 'Y-m-d' );
$holidays[ $index ]['to'] = ! empty( $holidays[ $index ]['to'] ) ? $to->format( 'Y-m-d' ) : '';
}
$range = new DatePeriod(
$from,
new DateInterval( 'P1D' ), // Every 1 day.
$to
);
if ( empty( $range ) ) {
continue;
}
foreach ( $range as $time ) {
$holidays[ $index ]['timestamps'][] = $time->getTimestamp();
}
}
}
/**
* Filter location holidays.
*
* @since 1.8.0
* @hook orderable_location_get_holidays
* @param array $holidays Location holidays.
* @param string|null $type Service type (delivery/pickup).
* @param bool $include_defaults Include default holidays.
* @param Orderable_Location_Single $location Location object.
*/
$holidays = apply_filters( 'orderable_location_get_holidays', $holidays, $type, $include_defaults, $this );
$holidays_cache[ $this->get_location_id() ] = $holidays;
return $holidays;
}
/**
* Is this timestamp a holiday?
*
* @param int $timestamp Timestamp (GMT).
* @param string|null $type Service type (delivery/pickup).
*
* @return bool
*/
public function is_holiday( $timestamp, $type = null ) {
$is_holiday = false;
$holidays = $this->get_holidays();
// If no holidays are set, then this isn't a holiday.
if ( ! empty( $holidays ) ) {
foreach ( $holidays as $holiday ) {
// If no timestamps or not assigned to any service, continue.
if ( empty( $holiday['timestamps'] ) || empty( $holiday['services'] ) ) {
continue;
}
// If timestamp isn't a holiday, continue.
if ( ! in_array( $timestamp, $holiday['timestamps'], true ) ) {
continue;
}
// If we want to check a specific delivery type, check that
// service is assigned to this holiday. Otherwise, continue.
if ( $type && ! in_array( $type, $holiday['services'], true ) ) {
continue;
}
// If we got here, then this is indeed a holiday.
$is_holiday = true;
break;
}
}
/**
* Filter if is a holiday.
*
* @since 1.8.0
* @hook orderable_location_is_holiday
* @param bool $is_holiday Is this timestamp a holiday?
* @param int $timestamp Timestamp (GMT).
* @param string|null $type Service type (delivery/pickup).
* @param Orderable_Location_Single $location Location object.
*/
return apply_filters( 'orderable_location_is_holiday', $is_holiday, $timestamp, $type, $this );
}
/**
* Get dates available for service type.
*
* @param string $type Type. Can be 'delivery' or 'pickup'.
* @param bool $ignore_needs_shipping Ignore check for WC()->cart->needs_shipping().
*
* @return array|bool Array when dates are available, "true" when no date selection required, "false" when no dates available.
*/
public function get_service_dates( $type = false, $ignore_needs_shipping = false ) {
$cache_key = 'orderable_service_dates_' . md5( $type . (int) $ignore_needs_shipping . wp_json_encode( WC()->cart ) . $this->get_location_id() );
$cached_result = wp_cache_get( $cache_key );
if ( false !== $cached_result ) {
return $cached_result;
}
/**
* Filter whether ignoring check for WC()->cart->needs_shipping() when
* getting service dates.
*
* @since 1.14.0
* @hook orderable_location_service_dates_ignore_needs_shipping
* @param bool $ignore_needs_shipping Ignore check for WC()->cart->needs_shipping(). Default: false.
* @param string $service_type It can be 'delivery' or 'pickup'.
* @return bool New value
*/
$ignore_needs_shipping = apply_filters( 'orderable_location_service_dates_ignore_needs_shipping', $ignore_needs_shipping, $type );
if ( ! $ignore_needs_shipping && ! WC()->cart->needs_shipping() ) {
// For backwards compatibility.
$result = apply_filters_deprecated( 'orderable-service-dates', array( true, $type ), '1.8.0', 'orderable_location_service_dates' );
// Return true. No service is required.
/**
* Filter orderable service dates.
*
* @since 1.8.0
* @hook orderable_location_service_dates
* @see Orderable_Location_Single::get_service_dates()
*/
$service_dates = apply_filters( 'orderable_location_service_dates', $result, $type, $this );
wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
return $service_dates;
}
$type = ! $type ? Orderable_Services::get_selected_service( false ) : $type;
$service_dates = array();
if ( ! $type ) {
// For backwards compatibility.
$result = apply_filters_deprecated( 'orderable-service-dates', array( false, $type ), '1.8.0', 'orderable_location_service_dates' );
// Return false. A service should be selected.
// @todo Check if this should be true when no shipping method is selected yet.
/**
* Filter orderable service dates.
*
* @since 1.8.0
* @hook orderable_location_service_dates
* @see Orderable_Location_Single::get_service_dates()
*/
$service_dates = apply_filters( 'orderable_location_service_dates', $result, $type, $this );
wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
return $service_dates;
}
$services = $this->get_services();
if ( ! in_array( $type, $services, true ) ) {
// For backwards compatibility.
$result = apply_filters_deprecated( 'orderable-service-dates', array( false, $type ), '1.8.0', 'orderable_location_service_dates' );
/**
* Filter orderable service dates.
*
* @since 1.8.0
* @hook orderable_location_service_dates
* @see Orderable_Location_Single::get_service_dates()
*/
$service_dates = apply_filters( 'orderable_location_service_dates', $result, $type, $this );
wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
return $service_dates;
}
$min_max_method = $this->get_delivery_calculation_method();
$lead_time_period = $this->get_lead_time_period();
$lead_days = 'days' === $lead_time_period ? $this->get_lead_time() : 0;
$preorder_days = $this->get_preorder_days();
$service_days = $this->get_service_days( $type );
$start_date = new DateTime( 'now', wp_timezone() );
$start_date->setTime( 0, 0 ); // Set time to midnight 00:00:00.
$date_range = new ArrayIterator( array( $start_date ) );
$counted_lead_days = 0;
$counted_preorder_days = 0;
if ( ! empty( $service_days ) ) {
/**
* Filter the max index date.
*
* Since the condition to break the loop that
* searchs for service dates relies on the
* preorder days limit, we check against $max_index_date
* to prevent an infinite loop case preorder days
* limit fail multiple times.
*
* @since 1.8.1
* @hook orderable_location_max_index_date
* @param int $max_index_date The max index date value. Default: 365.
* @param string $type The type. Can be 'delivery' or 'pickup'.
* @param Orderable_Location_Single $location The location.
* @return int New value
*/
$max_index_date = apply_filters( 'orderable_location_max_index_date', 365, $type, $this );
foreach ( $date_range as $index => $date ) {
// Check to avoid an infinite loop.
if ( $index > $max_index_date ) {
break;
}
// If we're at the preorder day limit, break the loop.
if ( $counted_preorder_days > $preorder_days ) {
break;
}
$timestamp = $date->getTimestamp();
$week_day = $date->format( 'w' );
$date_range->append( clone $date->modify( '+1 day' ) );
// If calculation method is 'all', we want to increase
// the counters for all days.
if ( 'all' === $min_max_method ) {
$counted_lead_days ++;
$counted_preorder_days ++;
// We aren't ready to start serving up dates yet as we
// haven't counted the number of lead days required.
if ( $lead_days >= $counted_lead_days ) {
continue;
}
}
// If calculation method is 'weekdays' (Weekdays), we want to
// increase the counters here if the day is a weekday. This
// happens before checking if it's a service day.
if ( 'weekdays' === $min_max_method ) {
if ( Orderable_Timings::is_weekday( $timestamp ) ) {
$counted_lead_days ++;
$counted_preorder_days ++;
}
// We aren't ready to start serving up dates yet as we
// haven't counted the number of lead days required.
if ( $lead_days >= $counted_lead_days ) {
continue;
}
}
// If this date is a holiday, add a date to the array and continue.
if ( $this->is_holiday( $timestamp, $type ) ) {
continue;
}
// If calculation method is 'open' (Open Days), we want to
// increase the counters here if the store is open. This
// happens before checking if it's a service day.
if ( 'open' === $min_max_method ) {
if ( $this->is_open( $timestamp ) ) {
$counted_lead_days ++;
$counted_preorder_days ++;
// We aren't ready to start serving up dates yet as we
// haven't counted the number of lead days required.
if ( $lead_days >= $counted_lead_days ) {
continue;
}
}
}
// If this is not a service day, skip it.
if ( ! in_array( (int) $week_day, array_keys( $service_days ), true ) ) {
continue;
}
// If calculation method is 'service' (Service Days), we want to increase
// the counters here, as any date past this point is a service day.
if ( 'service' === $min_max_method ) {
$counted_lead_days ++;
$counted_preorder_days ++;
// We aren't ready to start serving up dates yet as we
// haven't counted the number of lead days required.
if ( $lead_days >= $counted_lead_days ) {
continue;
}
}
/**
* Filter whether a date is available for ordering.
*
* @since 1.8.0
* @hook orderable_date_available
* @see Orderable_Location_Single::get_service_dates()
*/
if ( ! apply_filters( 'orderable_date_available', true, $timestamp, $type, $this ) ) {
continue;
}
$format = get_option( 'date_format' );
$slots = $this->get_slots( $timestamp, $type );
if ( empty( $slots ) ) {
continue;
}
$service_dates[] = array(
'timestamp' => $timestamp,
'datetime' => date( $format, $timestamp ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
'formatted' => Orderable_Timings::get_formatted_date( $timestamp ),
'slots' => $slots,
);
}
}
// If empty, return false; no dates available.
// Otherwise, return dates.
$service_dates = empty( $service_dates ) ? false : $service_dates;
// For backwards compatibility.
$service_dates = apply_filters_deprecated( 'orderable-service-dates', array( $service_dates, $type ), '1.8.0', 'orderable_location_service_dates' );
/**
* Filter orderable service dates.
*
* @param array|bool $service_dates The service dates available. If true,
* the service doesn't require date/time selection.
* If false, a service should be selected.
* @param string $type The type of service. E.g. delivery, pickup.
* @param Orderable_Location_Single $location The location instance.
*
* @return array|bool New value
* @since 1.8.0
* @hook orderable_location_service_dates
*/
$service_dates = apply_filters( 'orderable_location_service_dates', $service_dates, $type, $this );
wp_cache_set( $cache_key, $service_dates, '', ORDERABLE_CACHE_EXPIRATION_TIME );
return $service_dates;
}
/**
* Check if location has service dates for this service type.
*
* @param string $service_type Service type (delivery/pickup).
*
* @return bool
*/
public function has_service_dates( $service_type ) {
$service_dates = $this->get_service_dates( $service_type, true );
return ! empty( $service_dates );
}
/**
* Get location address.
*
* @return array
*/
public function get_address() {
$address = array(
'address_line_1' => $this->location_data['address_line_1'],
'address_line_2' => $this->location_data['address_line_2'],
'city' => $this->location_data['city'],
'country_state' => $this->location_data['country_state'],
'postcode_zip' => $this->location_data['postcode_zip'],
);
return $address;
}
/**
* Get formatted address.
*
* @return string
*/
public function get_formatted_address() {
$address = $this->get_address();
$country_state = $address['country_state'];
if ( strstr( $country_state, ':' ) ) {
$country_state = explode( ':', $country_state );
$country = current( $country_state );
$state = end( $country_state );
} else {
$country = $country_state;
$state = '';
}
$state = empty( WC()->countries->get_states( $country )[ $state ] ) ? $state : WC()->countries->get_states( $country )[ $state ];
$country = empty( WC()->countries->get_countries()[ $country ] ) ? $country : WC()->countries->get_countries()[ $country ];
$data = array(
'address_1' => $address['address_line_1'],
'address_2' => $address['address_line_2'],
'city' => $address['city'],
'state' => $state,
'country' => $country,
'postcode' => $address['postcode_zip'],
);
return WC()->countries->get_formatted_address( $data, ', ' );
}
/**
* Check if location has a specific zone set.
*
* @param int $zone_id Zone ID.
* @param bool $allow_empty Allow slots with no zone set.
*
* @return bool
*/
public function has_zone( $zone_id, $allow_empty = false ) {
global $wpdb;
$location_id = $this->get_location_id();
$cache_key = "has_zone_{$location_id}_{$zone_id}_{$allow_empty}";
$cache_result = wp_cache_get( $cache_key );
if ( false !== $cache_result ) {
return (bool) $cache_result;
}
$allow_empty_clause = $allow_empty ? 'OR (l.zone_id IS NULL AND ts.has_zones = 0)' : '';
$query = $wpdb->prepare(
"SELECT
COUNT(*)
FROM
{$wpdb->prefix}orderable_location_delivery_zones_lookup l
LEFT JOIN
{$wpdb->prefix}orderable_location_time_slots ts
ON l.time_slot_id = ts.time_slot_id
WHERE
l.location_id = %d
AND
( l.zone_id = %d {$allow_empty_clause} )",
$location_id,
$zone_id
);
$result = $wpdb->get_var( $query );
$has_zone = $result > 0;
wp_cache_set( $cache_key, (int) $has_zone );
return $has_zone;
}
/**
* Update location title.
*
* @param string $title New title.
*/
public function update_title( $title ) {
global $wpdb;
$this->location_data['title'] = $title;
wp_update_post(
array(
'ID' => $this->location_data['post_id'],
'post_title' => $title,
)
);
$wpdb->update(
$wpdb->prefix . 'orderable_locations',
array(
'title' => $title,
),
array(
'location_id' => $this->location_data['location_id'],
)
);
}
}