PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Blocks / Shipping / ShippingController.php
woocommerce / src / Blocks / Shipping Last commit date
PickupLocation.php 1 year ago ShippingController.php 7 months ago
ShippingController.php
620 lines
1 <?php
2 namespace Automattic\WooCommerce\Blocks\Shipping;
3
4 use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
5 use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
6 use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
7 use Automattic\WooCommerce\Enums\ProductTaxStatus;
8 use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
9 use Automattic\WooCommerce\Utilities\ArrayUtil;
10 use WC_Customer;
11 use WC_Shipping_Rate;
12 use WC_Tracks;
13
14 /**
15 * ShippingController class.
16 *
17 * @internal
18 */
19 class ShippingController {
20
21 /**
22 * Script handle used for enqueueing the scripts needed for managing the Local Pickup Shipping Settings.
23 */
24 private const LOCAL_PICKUP_ADMIN_JS_HANDLE = 'wc-shipping-method-pickup-location';
25
26 /**
27 * Instance of the asset API.
28 *
29 * @var AssetApi
30 */
31 protected $asset_api;
32
33 /**
34 * Instance of the asset data registry.
35 *
36 * @var AssetDataRegistry
37 */
38 protected $asset_data_registry;
39
40 /**
41 * Whether local pickup is enabled.
42 *
43 * @var bool
44 */
45 private $local_pickup_enabled;
46
47 /**
48 * Constructor.
49 *
50 * @param AssetApi $asset_api Instance of the asset API.
51 * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
52 */
53 public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry ) {
54 $this->asset_api = $asset_api;
55 $this->asset_data_registry = $asset_data_registry;
56 $this->local_pickup_enabled = LocalPickupUtils::is_local_pickup_enabled();
57 }
58
59 /**
60 * Initialization method.
61 */
62 public function init() {
63 if ( is_admin() ) {
64 $this->asset_data_registry->add(
65 'countryStates',
66 function () {
67 return WC()->countries->get_states();
68 }
69 );
70 }
71 $this->asset_data_registry->add( 'shippingCostRequiresAddress', get_option( 'woocommerce_shipping_cost_requires_address', false ) === 'yes' );
72 add_action( 'rest_api_init', array( $this, 'register_settings' ) );
73 add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
74 add_action( 'admin_footer', array( $this, 'hydrate_client_settings' ), 0 );
75 add_action( 'woocommerce_load_shipping_methods', array( $this, 'register_local_pickup' ) );
76 add_filter( 'woocommerce_local_pickup_methods', array( $this, 'register_local_pickup_method' ) );
77 add_filter( 'woocommerce_order_hide_shipping_address', array( $this, 'hide_shipping_address_for_local_pickup' ), 10 );
78 add_filter( 'woocommerce_customer_taxable_address', array( $this, 'filter_taxable_address' ) );
79 add_filter( 'woocommerce_shipping_settings', array( $this, 'remove_shipping_settings' ) );
80 add_filter( 'woocommerce_shipping_packages', array( $this, 'filter_shipping_packages' ) );
81 add_filter( 'pre_update_option_woocommerce_pickup_location_settings', array( $this, 'flush_cache' ) );
82 add_filter( 'pre_update_option_pickup_location_pickup_locations', array( $this, 'flush_cache' ) );
83 add_filter( 'woocommerce_shipping_packages', array( $this, 'remove_shipping_if_no_address' ), 11 );
84 add_filter( 'woocommerce_order_shipping_to_display', array( $this, 'show_local_pickup_details' ), 10, 2 );
85 add_action( 'rest_pre_serve_request', array( $this, 'track_local_pickup' ), 10, 4 );
86 }
87
88 /**
89 * Inject collection details onto the order received page.
90 *
91 * @param string $return_value Return value.
92 * @param \WC_Order $order Order object.
93 * @return string
94 */
95 public function show_local_pickup_details( $return_value, $order ) {
96 // Confirm order is valid before proceeding further.
97 if ( ! $order instanceof \WC_Order ) {
98 return $return_value;
99 }
100
101 $shipping_method_ids = ArrayUtil::select( $order->get_shipping_methods(), 'get_method_id', ArrayUtil::SELECT_BY_OBJECT_METHOD );
102 $shipping_method_id = current( $shipping_method_ids );
103
104 // Ensure order used pickup location method, otherwise bail.
105 if ( 'pickup_location' !== $shipping_method_id ) {
106 return $return_value;
107 }
108
109 $shipping_method = current( $order->get_shipping_methods() );
110 $details = $shipping_method->get_meta( 'pickup_details' );
111 $location = $shipping_method->get_meta( 'pickup_location' );
112 $address = $shipping_method->get_meta( 'pickup_address' );
113 $cost = $shipping_method->get_total();
114
115 $lines = array();
116
117 if ( $location ) {
118 $lines[] = sprintf(
119 // Translators: %s location name.
120 __( 'Collection from <strong>%s</strong>:', 'woocommerce' ),
121 $location
122 );
123 }
124
125 if ( $address ) {
126 $lines[] = nl2br( esc_html( str_replace( ',', ', ', $address ) ) );
127 }
128
129 if ( $details ) {
130 $lines[] = wp_kses_post( $details );
131 }
132
133 if ( $cost > 0 ) {
134 $tax_display = get_option( 'woocommerce_tax_display_cart' );
135 $tax = $shipping_method->get_total_tax();
136
137 // Format cost with tax handling.
138 if ( 'excl' === $tax_display ) {
139 // Show pickup cost excluding tax.
140 $formatted_cost = wc_price( $cost, array( 'currency' => $order->get_currency() ) );
141 if ( (float) $tax > 0 && $order->get_prices_include_tax() ) {
142 /**
143 * Hook to add tax label to pickup cost.
144 *
145 * @since 6.0.0
146 * @param string $tax_label Tax label.
147 * @param \WC_Order $order Order object.
148 * @param string $tax_display Tax display.
149 * @return string
150 */
151 $formatted_cost .= apply_filters(
152 'woocommerce_order_shipping_to_display_tax_label',
153 '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>',
154 $order,
155 $tax_display
156 );
157 }
158 } else {
159 // Show pickup cost including tax.
160 $formatted_cost = wc_price(
161 (float) $cost + (float) $tax,
162 array( 'currency' => $order->get_currency() )
163 );
164 if ( (float) $tax > 0 && ! $order->get_prices_include_tax() ) {
165 /**
166 * Hook to add tax label to pickup cost.
167 *
168 * @since 6.0.0
169 * @param string $tax_label Tax label.
170 * @param \WC_Order $order Order object.
171 * @param string $tax_display Tax display.
172 * @return string
173 */
174 $formatted_cost .= apply_filters(
175 'woocommerce_order_shipping_to_display_tax_label',
176 '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>',
177 $order,
178 $tax_display
179 );
180 }
181 }
182
183 $lines[] = '<br>' . sprintf(
184 // Translators: %s is the formatted price.
185 __( 'Pickup cost: %s', 'woocommerce' ),
186 $formatted_cost
187 );
188 }
189
190 // If nothing is available, return original.
191 if ( empty( $lines ) ) {
192 return $return_value;
193 }
194
195 // Join all the lines with a <br> separator.
196 return implode( '<br>', $lines );
197 }
198
199 /**
200 * When using the cart and checkout blocks this method is used to adjust core shipping settings via a filter hook.
201 *
202 * @param array $settings The default WC shipping settings.
203 * @return array|mixed The filtered settings.
204 */
205 public function remove_shipping_settings( $settings ) {
206 if ( CartCheckoutUtils::is_cart_block_default() ) {
207 foreach ( $settings as $index => $setting ) {
208 if ( 'woocommerce_enable_shipping_calc' === $setting['id'] ) {
209 $settings[ $index ]['desc_tip'] = sprintf(
210 /* translators: %s: URL to the documentation. */
211 __( 'This feature is not available when using the <a href="%s">Cart and checkout blocks</a>. Shipping will be calculated at checkout.', 'woocommerce' ),
212 'https://woocommerce.com/document/woocommerce-store-editing/customizing-cart-and-checkout/'
213 );
214 $settings[ $index ]['disabled'] = true;
215 $settings[ $index ]['value'] = 'no';
216 break;
217 }
218 }
219 }
220
221 return $settings;
222 }
223
224 /**
225 * Register Local Pickup settings for rest api.
226 */
227 public function register_settings() {
228 register_setting(
229 'options',
230 'woocommerce_pickup_location_settings',
231 array(
232 'type' => 'object',
233 'description' => 'WooCommerce Local Pickup Method Settings',
234 'default' => array(),
235 'show_in_rest' => array(
236 'name' => 'pickup_location_settings',
237 'schema' => array(
238 'type' => 'object',
239 'properties' => array(
240 'enabled' => array(
241 'description' => __( 'If enabled, this method will appear on the block based checkout.', 'woocommerce' ),
242 'type' => 'string',
243 'enum' => array( 'yes', 'no' ),
244 ),
245 'title' => array(
246 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
247 'type' => 'string',
248 ),
249 'tax_status' => array(
250 'description' => __( 'If a cost is defined, this controls if taxes are applied to that cost.', 'woocommerce' ),
251 'type' => 'string',
252 'enum' => array( ProductTaxStatus::TAXABLE, ProductTaxStatus::NONE ),
253 ),
254 'cost' => array(
255 'description' => __( 'Optional cost to charge for local pickup.', 'woocommerce' ),
256 'type' => 'string',
257 ),
258 ),
259 ),
260 ),
261 )
262 );
263 register_setting(
264 'options',
265 'pickup_location_pickup_locations',
266 array(
267 'type' => 'array',
268 'description' => 'WooCommerce Local Pickup Locations',
269 'default' => array(),
270 'show_in_rest' => array(
271 'name' => 'pickup_locations',
272 'schema' => array(
273 'type' => 'array',
274 'items' => array(
275 'type' => 'object',
276 'properties' => array(
277 'name' => array(
278 'type' => 'string',
279 ),
280 'address' => array(
281 'type' => 'object',
282 'properties' => array(
283 'address_1' => array(
284 'type' => 'string',
285 ),
286 'city' => array(
287 'type' => 'string',
288 ),
289 'state' => array(
290 'type' => 'string',
291 ),
292 'postcode' => array(
293 'type' => 'string',
294 ),
295 'country' => array(
296 'type' => 'string',
297 ),
298 ),
299 ),
300 'details' => array(
301 'type' => 'string',
302 ),
303 'enabled' => array(
304 'type' => 'boolean',
305 ),
306 ),
307 ),
308 ),
309 ),
310 )
311 );
312 }
313
314 /**
315 * Hydrate client settings
316 */
317 public function hydrate_client_settings() {
318 if ( ! wp_script_is( self::LOCAL_PICKUP_ADMIN_JS_HANDLE, 'enqueued' ) ) {
319 // Only hydrate the settings if the script dependent on them is enqueued.
320 return;
321 }
322
323 $locations = get_option( 'pickup_location_pickup_locations', array() );
324
325 $formatted_pickup_locations = array();
326 foreach ( $locations as $location ) {
327 $formatted_pickup_locations[] = array(
328 'name' => $location['name'],
329 'address' => $location['address'],
330 'details' => $location['details'],
331 'enabled' => wc_string_to_bool( $location['enabled'] ),
332 );
333 }
334
335 $has_legacy_pickup = false;
336
337 // Get all shipping zones.
338 $shipping_zones = \WC_Shipping_Zones::get_zones( 'admin' );
339 $international_shipping_zone = new \WC_Shipping_Zone( 0 );
340
341 // Loop through each shipping zone.
342 foreach ( $shipping_zones as $shipping_zone ) {
343 // Get all registered rates for this shipping zone.
344 $shipping_methods = $shipping_zone['shipping_methods'];
345 // Loop through each registered rate.
346 foreach ( $shipping_methods as $shipping_method ) {
347 if ( 'local_pickup' === $shipping_method->id && 'yes' === $shipping_method->enabled ) {
348 $has_legacy_pickup = true;
349 break 2;
350 }
351 }
352 }
353
354 foreach ( $international_shipping_zone->get_shipping_methods( true ) as $shipping_method ) {
355 if ( 'local_pickup' === $shipping_method->id ) {
356 $has_legacy_pickup = true;
357 break;
358 }
359 }
360
361 $settings = array(
362 'pickupLocationSettings' => LocalPickupUtils::get_local_pickup_settings(),
363 'pickupLocations' => $formatted_pickup_locations,
364 'readonlySettings' => array(
365 'hasLegacyPickup' => $has_legacy_pickup,
366 'storeCountry' => WC()->countries->get_base_country(),
367 'storeState' => WC()->countries->get_base_state(),
368 ),
369 );
370
371 wp_add_inline_script(
372 self::LOCAL_PICKUP_ADMIN_JS_HANDLE,
373 sprintf(
374 'var hydratedScreenSettings = %s;',
375 wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
376 ),
377 'before'
378 );
379 }
380 /**
381 * Load admin scripts.
382 */
383 public function admin_scripts() {
384 $this->asset_api->register_script( self::LOCAL_PICKUP_ADMIN_JS_HANDLE, 'assets/client/blocks/wc-shipping-method-pickup-location.js', array(), true );
385 }
386
387 /**
388 * Registers the Local Pickup shipping method used by the Checkout Block.
389 */
390 public function register_local_pickup() {
391 if ( CartCheckoutUtils::is_checkout_block_default() ) {
392 $wc_instance = WC();
393 if ( is_object( $wc_instance ) && method_exists( $wc_instance, 'shipping' ) && is_object( $wc_instance->shipping ) && method_exists( $wc_instance->shipping, 'register_shipping_method' ) ) {
394 $wc_instance->shipping->register_shipping_method( new PickupLocation() );
395 } else {
396 wc_get_logger()->error( 'Error registering pickup location: WC()->shipping->register_shipping_method is not available', array( 'source' => 'shipping-controller' ) );
397 }
398 }
399 }
400
401 /**
402 * Declares the Pickup Location shipping method as a Local Pickup method for WooCommerce.
403 *
404 * @param array $methods Shipping method ids.
405 * @return array
406 */
407 public function register_local_pickup_method( $methods ) {
408 $methods[] = 'pickup_location';
409 return $methods;
410 }
411
412 /**
413 * Hides the shipping address on the order confirmation page when local pickup is selected.
414 *
415 * @param array $pickup_methods Method ids.
416 * @return array
417 */
418 public function hide_shipping_address_for_local_pickup( $pickup_methods ) {
419 return array_merge( $pickup_methods, LocalPickupUtils::get_local_pickup_method_ids() );
420 }
421
422 /**
423 * Everytime we save or update local pickup settings, we flush the shipping
424 * transient group.
425 *
426 * @param array $settings The setting array we're saving.
427 * @return array $settings The setting array we're saving.
428 */
429 public function flush_cache( $settings ) {
430 \WC_Cache_Helper::get_transient_version( 'shipping', true );
431 return $settings;
432 }
433 /**
434 * Filter the location used for taxes based on the chosen pickup location.
435 *
436 * @param array $address Location args.
437 * @return array
438 */
439 public function filter_taxable_address( $address ) {
440
441 if ( null === WC()->session ) {
442 return $address;
443 }
444 // We only need to select from the first package, since pickup_location only supports a single package.
445 $chosen_method = current( WC()->session->get( 'chosen_shipping_methods', array() ) ) ?? '';
446 $chosen_method_id = explode( ':', $chosen_method )[0];
447 $chosen_method_instance = explode( ':', $chosen_method )[1] ?? 0;
448
449 // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
450 if ( $chosen_method_id && true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && in_array( $chosen_method_id, LocalPickupUtils::get_local_pickup_method_ids(), true ) ) {
451 $pickup_locations = get_option( 'pickup_location_pickup_locations', array() );
452 $pickup_location = $pickup_locations[ $chosen_method_instance ] ?? array();
453
454 if ( isset( $pickup_location['address'], $pickup_location['address']['country'] ) && ! empty( $pickup_location['address']['country'] ) ) {
455 $address = array(
456 $pickup_locations[ $chosen_method_instance ]['address']['country'],
457 $pickup_locations[ $chosen_method_instance ]['address']['state'],
458 $pickup_locations[ $chosen_method_instance ]['address']['postcode'],
459 $pickup_locations[ $chosen_method_instance ]['address']['city'],
460 );
461 }
462 }
463
464 return $address;
465 }
466
467 /**
468 * Local Pickup requires all packages to support local pickup. This is because the entire order must be picked up
469 * so that all packages get the same tax rates applied during checkout.
470 *
471 * If a shipping package does not support local pickup (e.g. if disabled by an extension), this filters the option
472 * out for all packages. This will in turn disable the "pickup" toggle in Block Checkout.
473 *
474 * @param array $packages Array of shipping packages.
475 * @return array
476 */
477 public function filter_shipping_packages( $packages ) {
478 // Check all packages for an instance of a collectable shipping method.
479 $valid_packages = array_filter(
480 $packages,
481 function ( $package ) {
482 $shipping_method_ids = ArrayUtil::select( $package['rates'] ?? array(), 'get_method_id', ArrayUtil::SELECT_BY_OBJECT_METHOD );
483 return ! empty( array_intersect( LocalPickupUtils::get_local_pickup_method_ids(), $shipping_method_ids ) );
484 }
485 );
486
487 // Remove pickup location from rates arrays if not all packages can be picked up or support local pickup.
488 if ( count( $valid_packages ) !== count( $packages ) ) {
489 $packages = array_map(
490 function ( $package ) {
491 if ( ! is_array( $package['rates'] ) ) {
492 $package['rates'] = array();
493 return $package;
494 }
495 $package['rates'] = array_filter(
496 $package['rates'],
497 function ( $rate ) {
498 return ! in_array( $rate->get_method_id(), LocalPickupUtils::get_local_pickup_method_ids(), true );
499 }
500 );
501 return $package;
502 },
503 $packages
504 );
505 }
506
507 return $packages;
508 }
509
510 /**
511 * Remove shipping (i.e. delivery, not local pickup) if "Hide shipping costs until an address is entered" is enabled,
512 * and no address has been entered yet.
513 *
514 * Only applies to block checkout because pickup is chosen separately to shipping in that context.
515 *
516 * @param array $packages Array of shipping packages.
517 * @return array
518 */
519 public function remove_shipping_if_no_address( $packages ) {
520 if ( 'shortcode' === WC()->cart->cart_context ) {
521 return $packages;
522 }
523
524 $shipping_cost_requires_address = wc_string_to_bool( get_option( 'woocommerce_shipping_cost_requires_address', 'no' ) );
525
526 // Return early here for a small performance gain if we don't need to hide shipping costs until an address is entered.
527 if ( ! $shipping_cost_requires_address ) {
528 return $packages;
529 }
530
531 $customer = WC()->customer;
532
533 if ( $customer instanceof WC_Customer && $customer->has_full_shipping_address() ) {
534 return $packages;
535 }
536
537 return array_map(
538 function ( $package ) {
539 // Package rates is always an array due to a check in core.
540 $package['rates'] = array_filter(
541 $package['rates'],
542 function ( $rate ) {
543 return $rate instanceof WC_Shipping_Rate && in_array( $rate->get_method_id(), LocalPickupUtils::get_local_pickup_method_ids(), true );
544 }
545 );
546 return $package;
547 },
548 $packages
549 );
550 }
551
552 /**
553 * Track local pickup settings changes via Store API
554 *
555 * @param bool $served Whether the request has already been served.
556 * @param \WP_REST_Response $result The response object.
557 * @param \WP_REST_Request $request The request object.
558 * @return bool
559 */
560 public function track_local_pickup( $served, $result, $request ) {
561 if ( '/wp/v2/settings' !== $request->get_route() ) {
562 return $served;
563 }
564 // Param name here comes from the show_in_rest['name'] value when registering the setting.
565 if ( ! $request->get_param( 'pickup_location_settings' ) && ! $request->get_param( 'pickup_locations' ) ) {
566 return $served;
567 }
568
569 $event_name = 'local_pickup_save_changes';
570
571 $settings = $request->get_param( 'pickup_location_settings' );
572 $locations = $request->get_param( 'pickup_locations' );
573
574 $data = array(
575 'local_pickup_enabled' => 'yes' === $settings['enabled'] ? true : false,
576 'title' => __( 'Pickup', 'woocommerce' ) === $settings['title'],
577 'price' => '' === $settings['cost'] ? true : false,
578 'cost' => '' === $settings['cost'] ? 0 : $settings['cost'],
579 'taxes' => $settings['tax_status'],
580 'total_pickup_locations' => count( $locations ),
581 'pickup_locations_enabled' => count(
582 array_filter(
583 $locations,
584 function ( $location ) {
585 return $location['enabled']; }
586 )
587 ),
588 );
589
590 WC_Tracks::record_event( $event_name, $data );
591
592 return $served;
593 }
594
595 /**
596 * Check if legacy local pickup is activated in any of the shipping zones or in the Rest of the World zone.
597 *
598 * @since 8.8.0
599 *
600 * @return bool
601 */
602 public static function is_legacy_local_pickup_active() {
603 $rest_of_the_world = \WC_Shipping_Zones::get_zone_by( 'zone_id', 0 );
604 $shipping_zones = \WC_Shipping_Zones::get_zones();
605 $rest_of_the_world_data = $rest_of_the_world->get_data();
606 $rest_of_the_world_data['shipping_methods'] = $rest_of_the_world->get_shipping_methods();
607 array_unshift( $shipping_zones, $rest_of_the_world_data );
608
609 foreach ( $shipping_zones as $zone ) {
610 foreach ( $zone['shipping_methods'] as $method ) {
611 if ( 'local_pickup' === $method->id && $method->is_enabled() ) {
612 return true;
613 }
614 }
615 }
616
617 return false;
618 }
619 }
620