<?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 &mdash; %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'], ) ); } }