modify( '+' . dayslot_min_days( $ctx ) . ' days' ); $end = $today->modify( '+' . (int) $s['max_days'] . ' days' ); $limit = dayslot_daily_limit( $ctx ); $per_day = has_filter( 'dayslot_day_limit' ); $pool = dayslot_pool_key( $ctx ); $occ = dayslot_occupancy_all(); $closed = dayslot_closed_weekdays( $ctx ); $blocked = array_flip( dayslot_blocked_dates() ); $slots = dayslot_blocked_slots(); $out = array(); // When rescheduling, the order's own place on its date is free for it. $own = $ctx['exclude_order'] ? wc_get_order( $ctx['exclude_order'] ) : null; $own = $own ? (string) $own->get_meta( DAYSLOT_META ) : ''; for ( $day = $start, $i = 0; $day <= $end && $i < 800; $day = $day->modify( '+1 day' ), $i++ ) { $key = $day->format( 'Y-m-d' ); if ( in_array( (int) $day->format( 'w' ), $closed, true ) || isset( $blocked[ $key ] ) ) { $out[] = $key; continue; } $busy = isset( $occ[ $key ][ $pool ] ) ? $occ[ $key ][ $pool ] : null; if ( $busy && $key === $own ) { $busy = dayslot_occupancy( $key, $ctx ); } $day_limit = $per_day ? dayslot_day_limit( $key, $ctx ) : $limit; if ( $day_limit > 0 && $busy && $busy['total'] >= $day_limit ) { $out[] = $key; continue; } // Only days with bookings, blocked times, or today (minimum notice) can run out of times. if ( dayslot_feature_on( 'times' ) && dayslot_mode_has_times( $ctx['mode'] ) && ( $busy || 0 === $i || isset( $slots[ $key ] ) || isset( $slots[ strtolower( $day->format( 'D' ) ) ] ) || $key === $today->format( 'Y-m-d' ) || ! empty( $ctx['check_all'] ) ) ) { if ( ! dayslot_date_has_free_time( $key, $ctx ) ) { $out[] = $key; } } } $out = apply_filters( 'dayslot_unavailable_dates', $out, $ctx, $start->format( 'Y-m-d' ), $end->format( 'Y-m-d' ) ); return array_values( array_unique( $out ) ); } /** * Is at least one time option still free on this date? */ function dayslot_date_has_free_time( $date, $ctx ) { // for_dates: rules about the cart (e.g. a minimum order) don't make a date unavailable, only its times. $ctx['for_dates'] = true; foreach ( dayslot_time_options( $date, $ctx ) as $group ) { foreach ( $group['options'] as $option ) { if ( $option['available'] ) { return true; } } } return false; } /** * Why a date can't be chosen at checkout (for the shop owner), or '' when it can. */ function dayslot_day_closed_reason( $date, $ctx = array() ) { $ctx = dayslot_ctx( $ctx ); $dt = dayslot_parse_date( $date ); if ( ! $dt ) { return ''; } $days = dayslot_min_days( $ctx ); if ( $dt < dayslot_today()->modify( '+' . $days . ' days' ) ) { /* translators: %d: number of days */ return sprintf( __( '"Preparation time" in General is %d days.', 'dayslot' ), $days ); } if ( in_array( (int) $dt->format( 'w' ), dayslot_closed_weekdays( $ctx ), true ) ) { return __( 'This weekday is closed in the settings.', 'dayslot' ); } if ( in_array( $date, dayslot_blocked_dates(), true ) ) { return __( 'The day is blocked (calendar or "Days off").', 'dayslot' ); } $limit = dayslot_day_limit( $date, $ctx ); if ( $limit > 0 && dayslot_occupancy( $date, $ctx )['total'] >= $limit ) { /* translators: %d: orders per day */ return sprintf( __( 'The limit of %d orders per day is reached.', 'dayslot' ), $limit ); } $reason = (string) apply_filters( 'dayslot_day_closed_reason', '', $date, $ctx ); if ( '' !== $reason ) { return $reason; } if ( in_array( $date, (array) apply_filters( 'dayslot_unavailable_dates', array(), $ctx, $date, $date ), true ) ) { return __( 'A Pro rule closes it (e.g. an imported calendar).', 'dayslot' ); } if ( ! dayslot_feature_on( 'times' ) || ! dayslot_mode_has_times( $ctx['mode'] ) || dayslot_date_has_free_time( $date, $ctx ) ) { return ''; } if ( 'delivery' !== $ctx['mode'] ) { return __( 'No free time is left on this day.', 'dayslot' ); } $cutoff = dayslot_notice_cutoff(); $blocked = dayslot_blocked_slots(); $late = 0; $closed = 0; $all = 0; foreach ( dayslot_variants() as $v ) { foreach ( dayslot_variant_slots( $v ) as $key ) { $start = dayslot_slot_start( $date, $key ); if ( $start && dayslot_night_next_day( $v, $key ) ) { $start = $start->modify( '+1 day' ); } ++$all; if ( ! $start || $start < $cutoff ) { ++$late; } elseif ( dayslot_slot_is_blocked( $date, $key, $blocked, $v['id'] ) ) { ++$closed; } } } if ( ! $all ) { return __( 'No kind of delivery is switched on (Delivery tab).', 'dayslot' ); } return sprintf( /* translators: 1: number of times, 2: minutes, 3: number of times, 4: number of times */ __( 'No free time is left: %1$d times are too soon ("Minimum notice" %2$d min), %3$d are blocked, %4$d are full.', 'dayslot' ), $late, max( 0, (int) dayslot_get_settings()['slot_prep_minutes'] ), $closed, $all - $late - $closed ); } /** * Check a choice. Returns '' if OK, or an error message for the customer. */ function dayslot_validate_choice( $choice, $ctx = array(), $admin = false ) { $ctx = dayslot_ctx( $ctx ); $s = dayslot_get_settings(); $date = $choice['date']; if ( ! isset( dayslot_modes()[ $ctx['mode'] ] ) ) { return __( 'Please choose how you want to receive the order.', 'dayslot' ); } if ( '' === $date ) { return 'yes' === $s['required'] ? $s['required_message'] : ''; } $dt = dayslot_parse_date( $date ); if ( ! $dt ) { return $s['unavailable_message']; } if ( ! $admin ) { $today = dayslot_today(); $min = $today->modify( '+' . dayslot_min_days( $ctx ) . ' days' ); $max = $today->modify( '+' . (int) $s['max_days'] . ' days' ); if ( $dt < $min || $dt > $max ) { return $s['unavailable_message']; } if ( in_array( (int) $dt->format( 'w' ), dayslot_closed_weekdays( $ctx ), true ) || in_array( $date, dayslot_blocked_dates(), true ) ) { return $s['unavailable_message']; } $extra = apply_filters( 'dayslot_unavailable_dates', array(), $ctx, $date, $date ); if ( in_array( $date, (array) $extra, true ) ) { return $s['unavailable_message']; } $limit = dayslot_day_limit( $date, $ctx ); if ( $limit > 0 && dayslot_occupancy( $date, $ctx )['total'] >= $limit ) { return $s['unavailable_message']; } } $error = ''; if ( ! dayslot_feature_on( 'times' ) || ! dayslot_mode_has_times( $ctx['mode'] ) ) { $error = ''; } elseif ( '' === $choice['slot'] ) { $error = $s['slot_required_message']; } else { $found = dayslot_find_option( $date, $choice['slot'], $choice['variant'], $ctx ); if ( ! $found || ( ! $admin && ! $found[1]['available'] ) ) { $error = $s['slot_full_message']; } } return (string) apply_filters( 'dayslot_validate_choice', $error, $choice, $ctx, $admin ); } /** * Data the picker needs. "pools" holds unavailable dates per capacity pool. */ function dayslot_picker_data( $products = null, $exclude_order = 0 ) { $s = dayslot_get_settings(); $pools = array(); $mins = array(); foreach ( dayslot_capacity_pools() as $key => $pool_ctx ) { $pool_ctx['items'] = $products; $pool_ctx['exclude_order'] = $exclude_order; $pools[ $key ] = dayslot_unavailable_dates( $pool_ctx ); $mins[ $key ] = dayslot_min_days( $pool_ctx ); } $modes = array(); foreach ( dayslot_modes() as $id => $label ) { $modes[] = array( 'id' => $id, 'label' => $label, 'note' => (string) apply_filters( 'dayslot_mode_note', '', $id ), ); } // Days that cost more (Sundays are marked in JS): holidays in the booking window. $today = dayslot_today()->format( 'Y-m-d' ); $holidays = array_values( array_filter( dayslot_holidays(), function ( $d ) use ( $today ) { return $d >= $today; } ) ); $style = dayslot_style_settings(); $data = array( 'modes' => $modes, 'pools' => $pools, 'minDays' => $mins, 'maxDays' => (int) $s['max_days'], 'today' => $today, 'firstDay' => (int) get_option( 'start_of_week', 1 ), 'monthNames' => dayslot_calendar_words()['months'], 'dateMonths' => dayslot_calendar_words()['dateMonths'], 'dayNames' => dayslot_calendar_words()['days'], 'dayShort' => dayslot_calendar_words()['daysShort'], 'holidays' => $holidays, 'feeDays' => dayslot_day_types_cost_more(), 'specialDays' => (object) apply_filters( 'dayslot_special_days', array() ), 'autoSelect' => 'yes' === $s['auto_select'], 'required' => 'yes' === $s['required'], 'labels' => array( 'method' => $s['method_label'], 'date' => $s['label'], 'time' => $s['time_label'], ), 'times' => dayslot_feature_on( 'times' ), 'modeStyle' => $style['mode_style'], 'timeStyle' => $style['time_style'], 'dateStyle' => $style['date_style'], 'match' => 'yes' === $style['match'], 'styleSet' => dayslot_style_fixed( $style ), 'fullTimes' => $s['full_times'], 'fullContact' => $s['full_contact'], 'slotsUrl' => class_exists( 'WC_AJAX' ) ? WC_AJAX::get_endpoint( 'dayslot_slots' ) : '', 'excludeOrder' => (int) $exclude_order, 'i18n' => array( 'chooseTime' => __( '— choose time —', 'dayslot' ), 'pickDate' => __( 'Choose a date first', 'dayslot' ), 'full' => __( 'full', 'dayslot' ), /* translators: %d: number of free places */ 'left' => __( 'left: %d', 'dayslot' ), 'loading' => __( 'Loading…', 'dayslot' ), 'noSlots' => __( 'No free times on this day. Please choose another date.', 'dayslot' ), 'prev' => __( 'Previous month', 'dayslot' ), 'next' => __( 'Next month', 'dayslot' ), 'higher' => __( 'Higher price on this day', 'dayslot' ), 'unavail' => __( 'not available', 'dayslot' ), 'selected' => __( 'selected', 'dayslot' ), 'pickTime' => __( 'Choose time', 'dayslot' ), 'pickDay' => __( '— choose a date —', 'dayslot' ), 'pickPoint' => __( 'Pickup point', 'dayslot' ), 'nearMe' => __( 'Show the nearest to me first', 'dayslot' ), 'pickupShort' => __( 'Pickup', 'dayslot' ), 'deliveryShort' => __( 'Delivery', 'dayslot' ), 'pickType' => __( 'Choose the kind of delivery', 'dayslot' ), ), ); return apply_filters( 'dayslot_picker_data', $data, $products, $exclude_order ); } /** * Do Sundays / holidays cost more than weekdays for any delivery option? array( 'sunday' => bool, 'holiday' => bool ). */ function dayslot_day_types_cost_more() { $out = array( 'sunday' => false, 'holiday' => false, ); foreach ( dayslot_variants() as $v ) { $holiday = '' === (string) $v['price_holiday'] ? $v['price_sunday'] : $v['price_holiday']; if ( (float) $v['price_sunday'] > (float) $v['price_weekday'] ) { $out['sunday'] = true; } if ( (float) $holiday > (float) $v['price_weekday'] ) { $out['holiday'] = true; } } return $out; } /** * Capacity pools: array( key => ctx ). Core has one; the Pro add-on adds pickup. */ function dayslot_capacity_pools() { return apply_filters( 'dayslot_capacity_pools', array( 'delivery' => dayslot_ctx() ) ); }