Skip to content

Commit

Permalink
Keep live rates enabled even with WC Shipping active (#2812)
Browse files Browse the repository at this point in the history
  • Loading branch information
waclawjacek authored Sep 23, 2024
1 parent ebf747f commit 5cd7545
Show file tree
Hide file tree
Showing 11 changed files with 897 additions and 92 deletions.
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*** WooCommerce Shipping & Tax Changelog ***

= 2.8.2 - 2024-xx-xx =
* Fix - Keep live rates enabled for eligible stores when WCS&T is active alongside WooCommerce Shipping.
* Tweak - Hide shipping migration banner for all stores not eligible to buy shipping labels.
* Tweak - Try WooCommerce Shipping modal copy.

Expand Down
356 changes: 356 additions & 0 deletions classes/class-wc-connect-compatibility-wcshipping-packages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
<?php

/**
* Replaces saved package data with WooCommerce Shipping's if it is active.
*
* Redirects reads of and writes to wc_connect_options[packages] to wcshipping_options[packages].
*
* @since x.x.x
*/
class WC_Connect_Compatibility_WCShipping_Packages {

/**
* Number WCShipping uses to indicate data migration from WCS&T has completed.
*
* @var int
*/
const WCSHIP_DATA_MIGRATION_COMPLETED = 14;

/**
* Mapping of WCShipping keys => WCS&T keys.
*
* @var array
*/
const WCSHIPPING_TO_WCSERVICES_KEY_MAP = array(
'boxWeight' => 'box_weight',
'dimensions' => 'inner_dimensions',
'maxWeight' => 'max_weight',
);

/**
* Keys that are allowed in packages mapped to the WCS&T package data format.
*
* Other keys will be removed.
*
* @var array
*/
const KEYS_USED_BY_WCSERVICES = array(
'box_weight',
'inner_dimensions',
'is_letter',
'is_user_defined',
'max_weight',
'name',
);

/**
* Keys that are allowed in packages mapped to the WCShipping package data format.
*
* Other keys will be removed.
*
* @var array
*/
const KEYS_USED_BY_WCSHIPPING = array(
'boxWeight',
'dimensions',
'id',
'is_user_defined',
'maxWeight',
'name',
'type',
);

/**
* Registers all, some, or no hooks based on store configuration.
*
* @return void
*/
public static function maybe_enable() {
// Don't do anything if WooCommerce Shipping is not active.
if ( ! WC_Connect_Loader::is_wc_shipping_activated() ) {
return;
}

self::register_rest_controller_hooks();

$is_migration_to_wcshipping_completed = self::WCSHIP_DATA_MIGRATION_COMPLETED === (int) get_option( 'wcshipping_migration_state' );
if ( $is_migration_to_wcshipping_completed ) {
self::register_option_overwriting_hooks();
}
}

/**
* Enqueue REST controller registration after WCS&T has finished initializing its other controllers.
*
* @return void
*/
public static function register_rest_controller_hooks() {
add_action( 'wcservices_rest_api_init', array( self::class, 'register_wcshipping_compatibility_rest_controller' ) );
}

/**
* Registers hooks intercepting reads/writes to "wc_connect_options".
*
* This is done to replace the keys "packages" and "predefined_packages" with values from WCShipping's options
* after doing some mapping.
*
* @return void
*/
public static function register_option_overwriting_hooks() {
// Intercept reads of "wc_connect_options[packages]" and "wc_connect_options[predefined_packages]".
add_filter( 'option_wc_connect_options', array( self::class, 'intercept_packages_read' ) );
add_filter( 'option_wc_connect_options', array( self::class, 'intercept_predefined_packages_read' ) );

// Intercept updates to "wc_connect_options[packages]" and "wc_connect_options[predefined_packages]".
add_action( 'pre_update_option_wc_connect_options', array( self::class, 'intercept_packages_update' ), 10, 2 );
add_action( 'pre_update_option_wc_connect_options', array( self::class, 'intercept_predefined_packages_update' ), 10, 2 );
}

/**
* Replaces `wc_connect_options[packages]` with mapped values from `wcshipping_options[packages]`.
*
* Leaves the rest of `wc_connect_options` intact.
*
* @param mixed $wc_connect_options "wc_connect_options" value from the WP options table.
*
* @return mixed
*/
public static function intercept_packages_read( $wc_connect_options ) {
$wcshipping_options = get_option( 'wcshipping_options' );

if ( is_array( $wcshipping_options ) && isset( $wcshipping_options['packages'] ) ) {
$wc_connect_options['packages'] = self::map_packages_to_wcservices_format( $wcshipping_options['packages'] );
}

return $wc_connect_options;
}

/**
* Replaces `wc_connect_options[predefined_packages]` with values from `wcshipping_options[predefined_packages]`.
*
* Leaves the rest of `wc_connect_options` intact.
*
* @param mixed $wc_connect_options "wc_connect_options" value from the WP options table.
*
* @return mixed
*/
public static function intercept_predefined_packages_read( $wc_connect_options ) {
$wcshipping_options = get_option( 'wcshipping_options' );

if ( is_array( $wcshipping_options ) && isset( $wcshipping_options['predefined_packages'] ) ) {
$wc_connect_options['predefined_packages'] = $wcshipping_options['predefined_packages'];
}

return $wc_connect_options;
}

/**
* Saves the mapped value of `wc_connect_options[packages]` to `wcshipping_options[packages]`.
*
* Reverts `wc_connect_options[packages]` to old value so that only the packages
* in `wcshipping_options` get updated.
*
* Leaves the rest of `wcshipping_options` intact.
*
* @param mixed $value New value for "wc_connect_options" to extract packages from.
* @param mixed $old_value Old value of "wc_connect_options".
*
* @return array `$value` with the `packages` field reverted to current DB value to prevent updating.
*/
public static function intercept_packages_update( $value, $old_value ) {
$wcshipping_options = get_option( 'wcshipping_options' );

if ( ! empty( $value['packages'] ) ) {
$wcshipping_options['packages'] = self::map_packages_to_wcshipping_format( $value['packages'] );
} else {
$wcshipping_options['packages'] = array();
}

update_option( 'wcshipping_options', $wcshipping_options );

/*
* Prevent update of WCS&T's packages so that only `wcshipping_options` get updated.
*/
$value['packages'] = $old_value['packages'];

return $value;
}

/**
* Saves the mapped value of `wc_connect_options[predefined_packages]` to `wcshipping_options[predefined_packages]`.
*
* Reverts `wc_connect_options[predefined_packages]` to old value so that only the predefined packages
* in `wcshipping_options` get updated.
*
* Leaves the rest of `wcshipping_options` intact.
*
* @param mixed $value New value for "wc_connect_options" to extract predefined packages from.
* @param mixed $old_value Old value of "wc_connect_options".
*
* @return array `$value` with the `predefined_packages` field reverted to current DB value to prevent updating.
*/
public static function intercept_predefined_packages_update( $value, $old_value ) {
$wcshipping_options = get_option( 'wcshipping_options' );

if ( ! empty( $value['predefined_packages'] ) ) {
$wcshipping_options['predefined_packages'] = $value['predefined_packages'];
} else {
$wcshipping_options['predefined_packages'] = array();
}

update_option( 'wcshipping_options', $wcshipping_options );

/*
* Prevent update of WCS&T's predefined packages so that only `wcshipping_options` get updated.
*/
$value['predefined_packages'] = $old_value['predefined_packages'];

return $value;
}

/**
* Register a REST controller that reads "wc_connect_options".
*
* We do this because if WCShipping is active, it registers its own controller under /wc/v1/connect/packages
* that accesses "wcshipping_options". For the purpose of the WCS&T settings page, we still want the page
* accessing `wc_connect_options` that we'll possibly overwrite with the option read/write-intercepting filters
* if migration of options from WCS&T to WCShipping has been completed.
*
* This is so that we can always modify the value of "wc_connect_options" but leave the value of
* "wcshipping_options" intact.
*
* If migration has been completed, the controller will overwrite the value of "wc_connect_options[packages]" with
* WCShipping's packages.
*
* If migration hasn't been completed, it will return the value of "wc_connect_options[packages]" with no changes.
*
* @see self::register_option_overwriting_hooks
*
* @param WC_Connect_Loader $loader WCS&T's main class.
*/
public static function register_wcshipping_compatibility_rest_controller( WC_Connect_Loader $loader ) {
require_once __DIR__ . '/class-wc-rest-connect-wcshipping-compatibility-packages-controller.php';
$rest_wcshipping_package_compatibility_controller = new WC_REST_Connect_WCShipping_Compatibility_Packages_Controller(
$loader->get_api_client(),
$loader->get_service_settings_store(),
$loader->get_logger(),
$loader->get_service_schemas_store()
);
$rest_wcshipping_package_compatibility_controller->register_routes();
}

/**
* Maps package data from WCShipping's to WCS&T's format.
*
* @param array $custom_packages The custom packages to map from WCShipping's to WCS&T's format.
*
* @return array
*/
public static function map_packages_to_wcservices_format( $custom_packages ) {
$old_custom_packages = $custom_packages;

foreach ( $custom_packages as &$package ) {
$package = self::rename_keys( $package, self::WCSHIPPING_TO_WCSERVICES_KEY_MAP );
$package = self::map_type_to_is_letter( $package );
$package = self::unset_unused_keys( $package, self::KEYS_USED_BY_WCSERVICES );
}

return apply_filters(
'wcservices_map_packages_to_wcservices_format',
$custom_packages,
$old_custom_packages
);
}

/**
* Maps package data from WCS&T's to WCShipping's format.
*
* @param array $custom_packages The custom packages to map from WCS&T's to WCShipping's format.
*
* @return array
*/
public static function map_packages_to_wcshipping_format( $custom_packages ) {
$old_custom_packages = $custom_packages;

foreach ( $custom_packages as &$package ) {
$package = self::rename_keys( $package, array_flip( self::WCSHIPPING_TO_WCSERVICES_KEY_MAP ) );
$package = self::map_is_letter_to_type( $package );
$package = self::unset_unused_keys( $package, self::KEYS_USED_BY_WCSHIPPING );
}

return apply_filters(
'wcservices_map_packages_to_wcshipping_format',
$custom_packages,
$old_custom_packages
);
}

/**
* Renames keys according to provided key map then unsets the original keys.
*
* @param array $package Package data.
* @param array $key_map Mapping to follow.
*
* @return array
*/
private static function rename_keys( $package, $key_map ) {
foreach ( $key_map as $source => $target ) {
if ( isset( $package[ $source ] ) ) {
$package[ $target ] = $package[ $source ];
unset( $package[ $source ] );
}
}

return $package;
}

/**
* Unsets keys that aren't in `$allowed_keys`.
*
* @param array $package Package data.
* @param array $allowed_keys Keys that will be left in the array, if present. Other keys are unset.
*
* @return array
*/
private static function unset_unused_keys( $package, $allowed_keys ) {
return array_intersect_key( $package, array_flip( $allowed_keys ) );
}

/**
* Maps a package's "type" prop ("box"/"envelope") to "is_letter" (true/false).
*
* "type" is the format used by WCShipping.
* "is_letter" is the format used by WCS&T.
*
* @param array $package Package data.
*
* @return array
*/
private static function map_type_to_is_letter( $package ) {
if ( isset( $package['type'] ) ) {
$package['is_letter'] = 'envelope' === $package['type'];
}
unset( $package['type'] );

return $package;
}

/**
* Maps a package's "is_letter" prop (true/false) to "type" ("box"/"envelope").
*
* "type" is the format used by WCShipping.
* "is_letter" is the format used by WCS&T.
*
* @param array $package Package data.
*
* @return array
*/
private static function map_is_letter_to_type( $package ) {
if ( isset( $package['is_letter'] ) ) {
$package['type'] = $package['is_letter'] ? 'envelope' : 'box';
}
unset( $package['is_letter'] );

return $package;
}
}
7 changes: 6 additions & 1 deletion classes/class-wc-connect-settings-pages.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Servi
* @return array
*/
public function get_sections( $shipping_tabs ) {
// If WC Shipping is active, it will register its page instead.
if ( WC_Connect_Loader::is_wc_shipping_activated() ) {
return $shipping_tabs;
}

if ( ! is_array( $shipping_tabs ) ) {
$shipping_tabs = array();
}
Expand Down Expand Up @@ -125,7 +130,7 @@ public function output_shipping_settings_screen() {
$carrier_information = current(
array_filter(
$extra_args['carrier_accounts'],
function( $carrier ) {
function ( $carrier ) {
return $carrier->type === $carrier;
}
)
Expand Down
Loading

0 comments on commit 5cd7545

Please sign in to comment.