<?php
namespace Automattic\WCShipping\Packages;
use Automattic\WCShipping\Connect\WC_Connect_Service_Settings_Store;
use Automattic\WCShipping\Connect\WC_Connect_Package_Settings;
use Automattic\WCShipping\Exceptions\RESTRequestException;
use Automattic\WCShipping\WCShippingRESTController;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
class PackagesRESTController extends WCShippingRESTController {
protected $rest_base = 'packages';
private $settings_store;
/**
* @var WC_Connect_Package_Settings
*/
protected $package_settings;
public function __construct( WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Package_Settings $package_settings ) {
$this->settings_store = $settings_store;
$this->package_settings = $package_settings;
}
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get' ),
'permission_callback' => array( $this, 'ensure_rest_permission' ),
),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'post' ),
'permission_callback' => array( $this, 'ensure_rest_permission' ),
),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => 'PUT',
'callback' => array( $this, 'put' ),
'permission_callback' => array( $this, 'ensure_rest_permission' ),
),
)
);
register_rest_route(
$this->namespace,
// Accepts alphanumeric, underscores, hyphens, and digits.
'/' . $this->rest_base . '/(?P<type>custom|predefined)/(?P<id>[a-zA-Z0-9_-]+)',
array(
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete' ),
'permission_callback' => array( $this, 'ensure_rest_permission' ),
'args' => array(
'type' => array(
'required' => true,
'type' => 'string',
'enum' => array( 'custom', 'predefined' ),
'validate_callback' => function ( $param ) {
return in_array( $param, array( 'custom', 'predefined' ), true );
},
'sanitize_callback' => 'sanitize_text_field',
),
'id' => array(
'required' => true,
'type' => 'string',
'validate_callback' => function ( $param ) {
return (bool) preg_match( '/^[a-zA-Z0-9_-]+$/', $param );
},
'sanitize_callback' => 'sanitize_text_field',
),
),
),
)
);
}
/**
* Get the package settings.
*
* @return WP_Error|WP_REST_Response
*/
public function get( WP_REST_Request $request ) {
$package_settings = $this->package_settings->get( $request->get_param( 'features_supported_by_client' ) );
$packages_as_arrays = ( new PackagesAsArraysSanitizer( $package_settings['formData']['custom'], false ) )->to_packages_as_api_arrays();
return rest_ensure_response(
array(
'success' => true,
'storeOptions' => $package_settings['storeOptions'],
'packages' => array(
'saved' => array(
'custom' => $packages_as_arrays,
'predefined' => $package_settings['formData']['predefined'],
),
'predefined' => $package_settings['formSchema']['predefined'],
),
)
);
}
/**
* Update the existing custom and predefined packages.
*
* @param WP_REST_Request $request The request body contains the custom/predefined packages to replace.
* @return WP_REST_Response
*/
public function put( $request ) {
$packages = $request->get_json_params();
if ( isset( $packages['custom'] ) ) {
try {
$this->settings_store->update_packages( $packages['custom'] );
} catch ( PackageValidationException $e ) {
return $e->get_error_response();
}
}
if ( isset( $packages['predefined'] ) ) {
$this->settings_store->update_predefined_packages( $packages['predefined'] );
}
return rest_ensure_response(
array(
'predefined' => $this->settings_store->get_predefined_packages(),
'custom' => $this->settings_store->get_packages(),
)
);
}
/**
* Create custom and/or predefined packages.
*
* @param WP_REST_Request $request The request body contains the custom/predefined packages to create.
* @return WP_Error|WP_REST_Response
*/
public function post( $request ) {
$packages = $request->get_json_params();
$custom_packages = isset( $packages['custom'] ) ? $packages['custom'] : array();
$predefined_packages = isset( $packages['predefined'] ) ? $packages['predefined'] : array();
// Handle new custom packages. The custom packages are structured as an array of packages as dictionaries.
if ( ! empty( $custom_packages ) ) {
// Validate that the new custom packages have unique names.
$map_package_name = function ( $package ) {
return $package['name'];
};
$custom_package_names = array_map( $map_package_name, $custom_packages );
$unique_custom_package_names = array_unique( $custom_package_names );
if ( count( $unique_custom_package_names ) < count( $custom_package_names ) ) {
$duplicate_package_names = array_diff_assoc( $custom_package_names, $unique_custom_package_names );
$error = array(
'code' => 'duplicate_custom_package_names',
'message' => __( 'The new custom package names are not unique.', 'woocommerce-shipping' ),
'data' => array( 'package_names' => array_values( $duplicate_package_names ) ),
);
return new WP_REST_Response( $error, 400 );
}
// Validate that the new custom packages do not have the same names as existing custom packages.
$existing_custom_packages = $this->settings_store->get_packages();
$existing_custom_package_names = array_map( $map_package_name, $existing_custom_packages );
$duplicate_package_names = array_intersect( $existing_custom_package_names, $custom_package_names );
if ( ! empty( $duplicate_package_names ) ) {
$error = array(
'code' => 'duplicate_custom_package_names_of_existing_packages',
'message' => __( 'At least one of the new custom packages has the same name as existing packages.', 'woocommerce-shipping' ),
'data' => array( 'package_names' => array_values( $duplicate_package_names ) ),
);
return new WP_REST_Response( $error, 400 );
}
try {
// If no duplicate custom packages, create the given packages.
$this->settings_store->create_packages( $custom_packages );
} catch ( PackageValidationException $e ) {
return $e->get_error_response();
}
}
// Handle new predefined packages. The predefined packages are structured as a dictionary from carrier name to
// an array of package names.
if ( ! empty( $predefined_packages ) ) {
$duplicate_package_names_by_carrier = array();
// Validate that the new predefined packages have unique names for each carrier.
foreach ( $predefined_packages as $carrier => $package_names ) {
$unique_package_names = array_unique( $package_names );
if ( count( $unique_package_names ) < count( $package_names ) ) {
$duplicate_package_names = array_diff_assoc( $package_names, $unique_package_names );
$duplicate_package_names_by_carrier[ $carrier ] = array_values( $duplicate_package_names );
}
}
if ( ! empty( $duplicate_package_names_by_carrier ) ) {
$error = array(
'code' => 'duplicate_predefined_package_names',
'message' => __( 'At least one of the new carrier package names is not unique.', 'woocommerce-shipping' ),
'data' => array( 'package_names_by_carrier' => $duplicate_package_names_by_carrier ),
);
return new WP_REST_Response( $error, 400 );
}
// Validate that the new predefined packages for each carrier do not have the same names as existing predefined packages.
$existing_predefined_packages = $this->settings_store->get_predefined_packages();
if ( ! empty( $existing_predefined_packages ) ) {
foreach ( $existing_predefined_packages as $carrier => $existing_package_names ) {
$new_package_names = isset( $predefined_packages[ $carrier ] ) ? $predefined_packages[ $carrier ] : array();
$duplicate_package_names = array_intersect( $existing_package_names, $new_package_names );
if ( ! empty( $duplicate_package_names ) ) {
$duplicate_package_names_by_carrier[ $carrier ] = array_values( $duplicate_package_names );
}
}
}
if ( ! empty( $duplicate_package_names_by_carrier ) ) {
$error = array(
'code' => 'duplicate_predefined_package_names_of_existing_packages',
'message' => __( 'At least one of the new predefined packages has the same name as an existing package.', 'woocommerce-shipping' ),
'data' => array( 'package_names_by_carrier' => $duplicate_package_names_by_carrier ),
);
return new WP_REST_Response( $error, 400 );
}
// If no duplicate predefined packages, create the given packages.
$this->settings_store->create_predefined_packages( $predefined_packages );
}
return rest_ensure_response(
array(
'predefined' => $this->settings_store->get_predefined_packages(),
'custom' => $this->settings_store->get_packages(),
)
);
}
/**
* Delete a saved package.
*
* @param WP_REST_Request $request The request parameters contain the package type and id to delete.
* @return WP_Error|WP_REST_Response
*/
public function delete( WP_REST_Request $request ) {
try {
list( $type, $id ) = $this->get_and_check_request_params( $request, array( 'type', 'id' ) );
} catch ( RESTRequestException $error ) {
return rest_ensure_response( $error->get_error_response() );
}
if ( 'custom' === $type ) {
$packages = $this->settings_store->get_packages();
$found = false;
foreach ( $packages as $key => $package ) {
if ( $package['id'] === $id ) {
unset( $packages[ $key ] );
$found = true;
}
}
if ( ! $found ) {
return rest_ensure_response(
array(
'success' => false,
'error' => array(
'code' => 'package_not_found',
'message' => __( 'Custom package not found.', 'woocommerce-shipping' ),
),
)
);
}
// Reindex the array since we've just plugged a hole.
$packages = array_values( $packages );
try {
$this->settings_store->update_packages( $packages );
} catch ( PackageValidationException $e ) {
return $e->get_error_response();
}
} else {
$predefined_packages = $this->settings_store->get_predefined_packages();
$found = false;
foreach ( $predefined_packages as $carrier => $package_names ) {
if ( false !== ( $key = array_search( $id, $package_names, true ) ) ) {
unset( $predefined_packages[ $carrier ][ $key ] );
$predefined_packages[ $carrier ] = array_values( $predefined_packages[ $carrier ] );
if ( empty( $predefined_packages[ $carrier ] ) ) {
unset( $predefined_packages[ $carrier ] );
}
$found = true;
break;
}
}
if ( ! $found ) {
return rest_ensure_response(
array(
'success' => false,
'error' => array(
'code' => 'package_not_found',
'message' => __( 'Predefined package not found.', 'woocommerce-shipping' ),
),
)
);
}
try {
$this->settings_store->update_predefined_packages( $predefined_packages );
} catch ( PackageValidationException $e ) {
return $e->get_error_response();
}
}
return rest_ensure_response(
array(
'success' => true,
'predefined' => $this->settings_store->get_predefined_packages(),
'custom' => $this->settings_store->get_packages(),
)
);
}
}