id = 'dayslot_delivery'; $this->instance_id = absint( $instance_id ); $this->method_title = __( 'Delivery by date (Dayslot)', 'dayslot' ); $this->method_description = __( 'Price comes from the time option the customer chooses and the day type (Delivery calendar → Settings → Delivery options). Pickup is free.', 'dayslot' ); $this->supports = array( 'shipping-zones', 'instance-settings', 'instance-settings-modal' ); $this->instance_form_fields = array( 'title' => array( 'title' => __( 'Name at checkout', 'dayslot' ), 'type' => 'text', 'default' => dayslot_get_settings()['shipping_title'], ), ); // Only per-zone settings are used; reading the global ones would cost a database query on every page with a cart. $this->title = $this->instance_id ? $this->get_option( 'title', dayslot_get_settings()['shipping_title'] ) : dayslot_get_settings()['shipping_title']; $this->enabled = 'yes'; add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) ); } public function calculate_shipping( $package = array() ) { $choice = isset( $package['dayslot_choice'] ) && is_array( $package['dayslot_choice'] ) ? wp_parse_args( $package['dayslot_choice'], dayslot_empty_choice() ) : dayslot_session_get(); $modes = dayslot_modes(); if ( 'pickup' === $choice['mode'] && isset( $modes['pickup'] ) ) { $this->add_rate( array( 'id' => $this->get_rate_id( 'pickup' ), 'label' => $modes['pickup'], 'cost' => 0, 'package' => $package, 'meta_data' => array( '_dayslot_parts' => '[]' ), ) ); return; } $parts = dayslot_price_parts( $choice, $package ); $cost = 0; $text = array(); foreach ( $parts as $part ) { $cost += (float) $part['amount']; $text[] = $part['label'] . ': ' . dayslot_money( $part['amount'] ); } $label = $this->title; if ( ! $parts ) { /* translators: %s: shipping method name */ $label = sprintf( __( '%s (price after choosing the date and time)', 'dayslot' ), $this->title ); } $meta = array( '_dayslot_parts' => wp_json_encode( $parts ) ); if ( $text ) { $meta[ __( 'Price', 'dayslot' ) ] = implode( ' + ', $text ); } $this->add_rate( array( 'id' => $this->get_rate_id(), 'label' => $label, 'cost' => $cost, 'package' => $package, 'meta_data' => $meta, ) ); } } } ); add_filter( 'woocommerce_shipping_methods', function ( $methods ) { $methods['dayslot_delivery'] = 'DAYSLOT_Shipping_Method'; return $methods; } ); /** * Put the choice into each shipping package so WooCommerce recalculates when it changes. */ add_filter( 'woocommerce_cart_shipping_packages', function ( $packages ) { $choice = dayslot_session_get(); foreach ( $packages as &$package ) { $package['dayslot_choice'] = $choice; } return $packages; } ); /** * Optionally show only our method when it is available. */ add_filter( 'woocommerce_package_rates', function ( $rates ) { if ( 'yes' !== dayslot_get_settings()['hide_other_shipping'] ) { return $rates; } $ours = array(); foreach ( $rates as $id => $rate ) { if ( 'dayslot_delivery' === $rate->get_method_id() ) { $ours[ $id ] = $rate; } } return $ours ? $ours : $rates; }, 100 ); /** * Keep the price breakdown on the order. */ add_action( 'woocommerce_checkout_create_order_shipping_item', function ( $item, $package_key, $package, $order ) { if ( 'dayslot_delivery' !== $item->get_method_id() ) { return; } $parts = json_decode( (string) $item->get_meta( '_dayslot_parts' ), true ); $order->update_meta_data( DAYSLOT_PRICE_META, is_array( $parts ) ? $parts : array() ); }, 10, 4 ); /** * Right after activation: add our method to every shipping zone (once). */ add_action( 'admin_init', function () { if ( ! get_option( 'dayslot_add_shipping_method' ) || ! current_user_can( 'manage_woocommerce' ) || ! class_exists( 'WC_Shipping_Zones' ) ) { return; } delete_option( 'dayslot_add_shipping_method' ); $zones = WC_Shipping_Zones::get_zones(); $zones[] = array( 'zone_id' => 0 ); foreach ( $zones as $info ) { $zone = WC_Shipping_Zones::get_zone( (int) $info['zone_id'] ); if ( ! $zone ) { continue; } $has = false; foreach ( $zone->get_shipping_methods() as $method ) { if ( 'dayslot_delivery' === $method->id ) { $has = true; } } if ( ! $has ) { $zone->add_shipping_method( 'dayslot_delivery' ); } } } ); // The price breakdown on the shipping line is for the plugin, not for people (shown nicely in the order summary). add_filter( 'woocommerce_hidden_order_itemmeta', function ( $keys ) { $keys[] = '_dayslot_parts'; return $keys; } ); /** * The free delivery rule for a choice: array( above => order value from which delivery is free (0 = never), * waive => part types that become free (null = the whole delivery price) ). Pro makes it depend on the kind of * delivery and the day (filter "dayslot_free_delivery"). */ function dayslot_free_delivery_rule( $choice ) { $s = dayslot_get_settings(); $rule = array( 'above' => (float) $s['free_above'], 'always' => 'yes' === $s['free_always'], 'waive' => null, ); return (array) apply_filters( 'dayslot_free_delivery', $rule, $choice ); } /** * While a date of an existing order is changed, that order counts as "the cart" (its value, not the cart's). * Pass the order to set it; no argument returns it (or null). */ function dayslot_worth_order( $order = false ) { static $current = null; if ( false !== $order ) { $current = $order ? $order : null; } return $current; } /** * Value of the products in the cart (what free delivery and small orders compare with), or null outside a cart. */ function dayslot_cart_worth() { $order = dayslot_worth_order(); if ( $order ) { $worth = 0.0; foreach ( $order->get_items() as $item ) { $worth += (float) $item->get_total(); } return apply_filters( 'dayslot_cart_worth', $worth ); } if ( ! function_exists( 'WC' ) || ! WC()->cart ) { return apply_filters( 'dayslot_cart_worth', null ); } $worth = 0.0; foreach ( WC()->cart->get_cart() as $item ) { // The same value WooCommerce gives the shipping package ("contents_cost": products without tax). $worth += isset( $item['line_total'] ) ? (float) $item['line_total'] : 0.0; } return apply_filters( 'dayslot_cart_worth', $worth ); } /** * Free delivery from an order value, and a charge for small orders (products worth, from the cart package). */ add_filter( 'dayslot_price_parts', function ( $parts, $choice, $package ) { if ( 'delivery' !== $choice['mode'] || ! $parts || ! isset( $package['contents_cost'] ) ) { return $parts; } $s = dayslot_get_settings(); $worth = (float) $package['contents_cost']; $rule = dayslot_free_delivery_rule( $choice ); $is = ! empty( $rule['always'] ) || ( $rule['above'] > 0 && $worth >= $rule['above'] ); $free = 0.0; if ( $is ) { foreach ( $parts as $part ) { if ( null === $rule['waive'] || in_array( $part['type'], (array) $rule['waive'], true ) ) { $free += (float) $part['amount']; } } } if ( $free > 0 ) { $parts[] = array( 'type' => 'free', /* translators: %s: order value, e.g. $100.00 */ 'label' => ! empty( $rule['always'] ) ? __( 'Free delivery', 'dayslot' ) : sprintf( __( 'Free delivery from %s', 'dayslot' ), dayslot_money( $rule['above'] ) ), 'amount' => -1 * $free, ); } elseif ( ! $is && (float) $s['small_below'] > 0 && (float) $s['small_fee'] > 0 && $worth < (float) $s['small_below'] ) { $parts[] = array( 'type' => 'small', /* translators: %s: order value, e.g. $30.00 */ 'label' => sprintf( __( 'Order under %s', 'dayslot' ), dayslot_money( $s['small_below'] ) ), 'amount' => (float) $s['small_fee'], ); } return $parts; }, 100, 3 ); // The kinds of delivery that are free for this cart say "free" instead of their price. add_filter( 'dayslot_time_options', function ( $groups, $date, $ctx ) { if ( 'delivery' !== $ctx['mode'] || ! $groups ) { return $groups; } $worth = dayslot_cart_worth(); if ( null === $worth ) { return $groups; } foreach ( $groups as $i => $g ) { $rule = dayslot_free_delivery_rule( array( 'date' => $date, 'slot' => '', 'variant' => $g['variant'], 'mode' => 'delivery', 'location' => $ctx['location'], ) ); $is = ! empty( $rule['always'] ) || ( $rule['above'] > 0 && $worth > 0 && $worth >= $rule['above'] ); if ( $is && null === $rule['waive'] ) { $groups[ $i ]['price'] = 0; $groups[ $i ]['priceText'] = __( 'free', 'dayslot' ); } } return $groups; }, 90, 3 ); // "Delivery" button: tell customers about free delivery. add_filter( 'dayslot_mode_note', function ( $note, $mode ) { $free = (float) dayslot_get_settings()['free_above']; if ( 'delivery' === $mode && 'yes' === dayslot_get_settings()['free_always'] ) { $text = __( 'free delivery', 'dayslot' ); return $note ? $note . ' · ' . $text : $text; } if ( 'delivery' !== $mode || $free <= 0 ) { return $note; } /* translators: %s: order value, e.g. $100.00 */ $text = sprintf( __( 'free from %s', 'dayslot' ), dayslot_money( $free ) ); return $note ? $note . ' · ' . $text : $text; }, 10, 2 );