init
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WC_Connect_Account_Settings {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Payment_Methods_Store
|
||||
*/
|
||||
protected $payment_methods_store;
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_Service_Settings_Store $settings_store,
|
||||
WC_Connect_Payment_Methods_Store $payment_methods_store
|
||||
) {
|
||||
$this->settings_store = $settings_store;
|
||||
$this->payment_methods_store = $payment_methods_store;
|
||||
}
|
||||
|
||||
|
||||
public function get() {
|
||||
$payment_methods_warning = false;
|
||||
$payment_methods_success = $this->payment_methods_store->fetch_payment_methods_from_connect_server();
|
||||
|
||||
if ( ! $payment_methods_success ) {
|
||||
$payment_methods_warning = __( 'There was a problem updating your saved credit cards.', 'woocommerce-services' );
|
||||
}
|
||||
|
||||
$connection_owner = WC_Connect_Jetpack::get_connection_owner();
|
||||
$connection_owner_wpcom_data = WC_Connect_Jetpack::get_connection_owner_wpcom_data();
|
||||
$last_box_id = get_user_meta( get_current_user_id(), 'wc_connect_last_box_id', true );
|
||||
$last_box_id = $last_box_id === 'individual' ? '' : $last_box_id;
|
||||
$last_service_id = get_user_meta( get_current_user_id(), 'wc_connect_last_service_id', true );
|
||||
$last_carrier_id = get_user_meta( get_current_user_id(), 'wc_connect_last_carrier_id', true );
|
||||
$wcshipping_migration_state = intval( get_option( 'wcshipping_migration_state' ) );
|
||||
|
||||
return array(
|
||||
'storeOptions' => $this->settings_store->get_store_options(),
|
||||
'formData' => $this->settings_store->get_account_settings(),
|
||||
'formMeta' => array(
|
||||
'can_manage_payments' => $this->settings_store->can_user_manage_payment_methods(),
|
||||
'can_edit_settings' => true,
|
||||
'master_user_name' => $connection_owner ? $connection_owner->display_name : '',
|
||||
'master_user_login' => $connection_owner ? $connection_owner->user_login : '',
|
||||
'master_user_wpcom_login' => $connection_owner_wpcom_data ? $connection_owner_wpcom_data['login'] : '',
|
||||
'master_user_email' => $connection_owner_wpcom_data ? $connection_owner_wpcom_data['email'] : '',
|
||||
'payment_methods' => $this->payment_methods_store->get_payment_methods(),
|
||||
'add_payment_method_url' => $this->payment_methods_store->get_add_payment_method_url(),
|
||||
'warnings' => array( 'payment_methods' => $payment_methods_warning ),
|
||||
'is_eligible_to_migrate' => $this->settings_store->is_eligible_for_migration(),
|
||||
'wcshipping_migration_state' => $wcshipping_migration_state,
|
||||
),
|
||||
'userMeta' => array(
|
||||
'last_box_id' => $last_box_id,
|
||||
'last_service_id' => $last_service_id,
|
||||
'last_carrier_id' => $last_carrier_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! defined( 'WOOCOMMERCE_CONNECT_SERVER_URL' ) ) {
|
||||
define( 'WOOCOMMERCE_CONNECT_SERVER_URL', 'https://api.woocommerce.com/' );
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_API_Client_Live' ) ) {
|
||||
require_once plugin_basename( 'class-wc-connect-api-client.php' );
|
||||
|
||||
class WC_Connect_API_Client_Live extends WC_Connect_API_Client {
|
||||
|
||||
protected function request( $method, $path, $body = array() ) {
|
||||
|
||||
// TODO - incorporate caching for repeated identical requests
|
||||
if ( ! is_array( $body ) ) {
|
||||
return new WP_Error(
|
||||
'request_body_should_be_array',
|
||||
__( 'Unable to send request to WooCommerce Shipping & Tax server. Body must be an array.', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
|
||||
$url = trailingslashit( WOOCOMMERCE_CONNECT_SERVER_URL );
|
||||
$url = apply_filters( 'wc_connect_server_url', $url );
|
||||
$url = trailingslashit( $url ) . ltrim( $path, '/' );
|
||||
|
||||
// Add useful system information to requests that contain bodies
|
||||
if ( in_array( $method, array( 'POST', 'PUT' ) ) ) {
|
||||
$body = $this->request_body( $body );
|
||||
$body = wp_json_encode( apply_filters( 'wc_connect_api_client_body', $body ) );
|
||||
|
||||
if ( ! $body ) {
|
||||
return new WP_Error(
|
||||
'unable_to_json_encode_body',
|
||||
__( 'Unable to encode body for request to WooCommerce Shipping & Tax server.', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$headers = $this->request_headers();
|
||||
if ( is_wp_error( $headers ) ) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
$http_timeout = 60; // 1 minute
|
||||
if ( function_exists( 'wc_set_time_limit' ) ) {
|
||||
wc_set_time_limit( $http_timeout + 10 );
|
||||
}
|
||||
$args = array(
|
||||
'headers' => $headers,
|
||||
'method' => $method,
|
||||
'body' => $body,
|
||||
'redirection' => 0,
|
||||
'compress' => true,
|
||||
'timeout' => $http_timeout,
|
||||
);
|
||||
$args = apply_filters( 'wc_connect_request_args', $args );
|
||||
|
||||
$response = wp_remote_request( $url, $args );
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
|
||||
// If the received response is not JSON, return the raw response.
|
||||
$content_type = wp_remote_retrieve_header( $response, 'content-type' );
|
||||
if ( false === strpos( $content_type, 'application/json' ) ) {
|
||||
if ( 200 != $response_code ) {
|
||||
return new WP_Error(
|
||||
'wcc_server_error',
|
||||
sprintf(
|
||||
__( 'Error: The WooCommerce Shipping & Tax server returned HTTP code: %d', 'woocommerce-services' ),
|
||||
$response_code
|
||||
),
|
||||
array(
|
||||
'response_status_code' => $response_code,
|
||||
)
|
||||
);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response_body = wp_remote_retrieve_body( $response );
|
||||
if ( ! empty( $response_body ) ) {
|
||||
$response_body = json_decode( $response_body );
|
||||
}
|
||||
|
||||
if ( 200 != $response_code ) {
|
||||
if ( empty( $response_body ) ) {
|
||||
return new WP_Error(
|
||||
'wcc_server_empty_response',
|
||||
sprintf(
|
||||
__( 'Error: The WooCommerce Shipping & Tax server returned ( %d ) and an empty response body.', 'woocommerce-services' ),
|
||||
$response_code
|
||||
),
|
||||
array(
|
||||
'response_status_code' => $response_code,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$error = property_exists( $response_body, 'error' ) ? $response_body->error : '';
|
||||
$message = property_exists( $response_body, 'message' ) ? $response_body->message : '';
|
||||
$data = property_exists( $response_body, 'data' ) ? (array) $response_body->data : array();
|
||||
|
||||
$data['response_status_code'] = $response_code;
|
||||
|
||||
return new WP_Error(
|
||||
'wcc_server_error_response',
|
||||
sprintf(
|
||||
/* translators: %1$s: error code, %2$s: error message, %3$d: HTTP response code */
|
||||
__( 'Error: The WooCommerce Shipping & Tax server returned: %1$s %2$s ( %3$d )', 'woocommerce-services' ),
|
||||
$error,
|
||||
$message,
|
||||
$response_code
|
||||
),
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
<?php
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_API_Client' ) ) {
|
||||
|
||||
abstract class WC_Connect_API_Client {
|
||||
const API_VERSION = WOOCOMMERCE_CONNECT_SERVER_API_VERSION;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Services_Validator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Loader
|
||||
*/
|
||||
protected $wc_connect_loader;
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_Service_Schemas_Validator $validator,
|
||||
WC_Connect_Loader $wc_connect_loader
|
||||
) {
|
||||
|
||||
$this->validator = $validator;
|
||||
$this->wc_connect_loader = $wc_connect_loader;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the available services for this site from the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_service_schemas() {
|
||||
$response_body = $this->request( 'POST', '/services', array( 'settings' => array( 'wcship_migration_supported' => true ) ) );
|
||||
|
||||
if ( is_wp_error( $response_body ) ) {
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
$result = $this->validator->validate_service_schemas( $response_body );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the settings for a given service with the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @param $service_slug
|
||||
* @param $service_settings
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function validate_service_settings( $service_slug, $service_settings ) {
|
||||
// Make sure the service slug only contains dashes, underscores or letters
|
||||
if ( 1 === preg_match( '/[^a-z_\-]/i', $service_slug ) ) {
|
||||
return new WP_Error( 'invalid_service_slug', __( 'Invalid WooCommerce Shipping & Tax service slug provided', 'woocommerce-services' ) );
|
||||
}
|
||||
|
||||
return $this->request( 'POST', "/services/{$service_slug}/settings", array( 'service_settings' => $service_settings ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the server's expected contents array, for rates requests.
|
||||
*
|
||||
* @param $package Package provided to WC_Shipping_Method::calculate_shipping()
|
||||
*
|
||||
* @return array|WP_Error {
|
||||
* @type float $height Product height.
|
||||
* @type float $width Product width.
|
||||
* @type float $length Product length.
|
||||
* @type int $product_id Product ID (or Variation ID).
|
||||
* @type int $quantity Quantity of product in shipment.
|
||||
* @type float $weight Product weight.
|
||||
* }
|
||||
*/
|
||||
public function build_shipment_contents( $package ) {
|
||||
$contents = array();
|
||||
|
||||
foreach ( $package['contents'] as $package_item ) {
|
||||
$product = $package_item['data'];
|
||||
$quantity = $package_item['quantity'];
|
||||
|
||||
if ( ( $quantity > 0 ) && $product->needs_shipping() ) {
|
||||
|
||||
if ( ! $product->has_weight() ) {
|
||||
return new WP_Error(
|
||||
'product_missing_weight',
|
||||
sprintf(
|
||||
__( 'Product ( ID: %d ) did not include a weight. Shipping rates cannot be calculated.', 'woocommerce-services' ),
|
||||
$product->get_id()
|
||||
),
|
||||
array( 'product_id' => $product->get_id() )
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
! $product->get_length() ||
|
||||
! $product->get_height() ||
|
||||
! $product->get_width()
|
||||
) {
|
||||
return new WP_Error(
|
||||
'product_missing_dimension',
|
||||
sprintf(
|
||||
__( 'Product ( ID: %d ) is missing a dimension value. Shipping rates cannot be calculated.', 'woocommerce-services' ),
|
||||
$product->get_id()
|
||||
),
|
||||
array( 'product_id' => $product->get_id() )
|
||||
);
|
||||
}
|
||||
|
||||
$weight = $product->get_weight();
|
||||
$height = $product->get_height();
|
||||
$length = $product->get_length();
|
||||
$width = $product->get_width();
|
||||
|
||||
$contents[] = array(
|
||||
'height' => (float) $height,
|
||||
'product_id' => $product->get_id(),
|
||||
'length' => (float) $length,
|
||||
'quantity' => $package_item['quantity'],
|
||||
'weight' => (float) $weight,
|
||||
'width' => (float) $width,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets shipping rates (for checkout) from the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @param $services All settings for all services we want rates for
|
||||
* @param $package Package provided to WC_Shipping_Method::calculate_shipping()
|
||||
* @param $custom_boxes array of custom boxes definitions (objects)
|
||||
* @param $predefined_boxes array of enabled predefined box IDs (strings)
|
||||
*
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_shipping_rates( $services, $package, $custom_boxes, $predefined_boxes ) {
|
||||
// First, build the contents array
|
||||
// each item needs to specify quantity, weight, length, width and height
|
||||
$contents = $this->build_shipment_contents( $package );
|
||||
|
||||
if ( is_wp_error( $contents ) ) {
|
||||
return $contents;
|
||||
}
|
||||
|
||||
if ( empty( $contents ) ) {
|
||||
return new WP_Error(
|
||||
'nothing_to_ship',
|
||||
__( 'No shipping rate could be calculated. No items in the package are shippable.', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
|
||||
// Then, make the request
|
||||
$body = array(
|
||||
'contents' => $contents,
|
||||
'destination' => $package['destination'],
|
||||
'services' => $services,
|
||||
'boxes' => $custom_boxes,
|
||||
'predefined_boxes' => $predefined_boxes,
|
||||
);
|
||||
|
||||
return $this->request( 'POST', '/shipping/rates', $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send rates request information to track subscription events
|
||||
*
|
||||
* @param array $services Array of service settings for shipping methods.
|
||||
*
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function track_subscription_event( $services ) {
|
||||
return $this->request( 'POST', '/subscriptions/checkout', array( 'services' => $services ) );
|
||||
}
|
||||
|
||||
public function send_shipping_label_request( $body ) {
|
||||
return $this->request( 'POST', '/shipping/label', $body );
|
||||
}
|
||||
|
||||
public function send_address_normalization_request( $body ) {
|
||||
return $this->request( 'POST', '/shipping/address/normalize', $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the WooCommerce Shipping & Tax server for an array of payment methods
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function get_payment_methods() {
|
||||
return $this->request( 'POST', '/payment/methods' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve Sift configurations.
|
||||
*
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_sift_configuration() {
|
||||
return $this->request( 'GET', '/payment/sift' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets shipping rates (for labels) from the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @param array $request - array(
|
||||
* 'packages' => array(
|
||||
* array(
|
||||
* 'id' => 'box_1',
|
||||
* 'height' => 10,
|
||||
* 'length' => 10,
|
||||
* 'width' => 10,
|
||||
* 'weight' => 10,
|
||||
* ),
|
||||
* array(
|
||||
* 'id' => 'box_2',
|
||||
* 'box_id' => 'medium_flat_box_top',
|
||||
* 'weight' => 5,
|
||||
* ),
|
||||
* ...
|
||||
* ),
|
||||
* 'carrier' => 'usps',
|
||||
* 'origin' => array(
|
||||
* 'address' => '132 Hawthorne St',
|
||||
* 'address_2' => '',
|
||||
* 'city' => 'San Francisco',
|
||||
* 'state' => 'CA',
|
||||
* 'postcode' => '94107',
|
||||
* 'country' => 'US',
|
||||
* ),
|
||||
* 'destination' => array(
|
||||
* 'address' => '1550 Snow Creek Dr',
|
||||
* 'address_2' => '',
|
||||
* 'city' => 'Park City',
|
||||
* 'state' => 'UT',
|
||||
* 'postcode' => '84060',
|
||||
* 'country' => 'US',
|
||||
* ),
|
||||
* )
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_label_rates( $request ) {
|
||||
return $this->request( 'POST', '/shipping/label/rates', $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a PDF with the set of dummy labels specified in the request
|
||||
*
|
||||
* @param $request
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_labels_preview_pdf( $request ) {
|
||||
return $this->request( 'POST', 'shipping/labels/preview', $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a PDF with the requested shipping labels in it
|
||||
*
|
||||
* @param $request
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_labels_print_pdf( $request ) {
|
||||
return $this->request( 'POST', 'shipping/labels/print', $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the shipping label status (refund status, tracking code, etc)
|
||||
*
|
||||
* @param $label_id integer
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_label_status( $label_id ) {
|
||||
return $this->request( 'GET', '/shipping/label/' . $label_id . '?get_refund=true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the shipping label status (refund status, tracking code, etc)
|
||||
*
|
||||
* @param $order_id integer
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function anonymize_order( $order_id ) {
|
||||
return $this->request( 'POST', '/privacy/order/' . $order_id . '/anonymize' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a refund for a given shipping label
|
||||
*
|
||||
* @param $label_id integer
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function send_shipping_label_refund_request( $label_id ) {
|
||||
return $this->request( 'POST', '/shipping/label/' . $label_id . '/refund' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured carrier accounts
|
||||
*
|
||||
* @param $request
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_carrier_accounts() {
|
||||
return $this->request( 'GET', '/shipping/carriers' );
|
||||
}
|
||||
/**
|
||||
* Disconnects the provided carrier account
|
||||
*
|
||||
* @param $carrier_id
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function disconnect_carrier_account( $carrier_id ) {
|
||||
return $this->request( 'DELETE', '/shipping/carrier/' . $carrier_id );
|
||||
}
|
||||
/**
|
||||
* Register a new carrier account
|
||||
*
|
||||
* @param $body
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function create_shipping_carrier_account( $body ) {
|
||||
return $this->request( 'POST', '/shipping/carrier', $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of the subscriptions for WooCommerce.com linked account.
|
||||
*
|
||||
* @param $body
|
||||
* @param object|WP_Error
|
||||
*/
|
||||
public function get_wccom_subscriptions() {
|
||||
return $this->request( 'POST', '/subscriptions' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all carriers we support for registration. This end point
|
||||
* returns a list of "fields" that we use to register the carrier
|
||||
* account.
|
||||
*
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function get_carrier_types() {
|
||||
return $this->request( 'GET', '/shipping/carrier-types' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the connection to the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function auth_test() {
|
||||
return $this->request( 'GET', '/connection/test' );
|
||||
}
|
||||
|
||||
/** Heartbeat test.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function is_alive() {
|
||||
return $this->request( 'GET', '' );
|
||||
}
|
||||
|
||||
/** Heartbeat test with a transient cache.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function is_alive_cached() {
|
||||
$connect_server_is_alive_transient = get_transient( 'connect_server_is_alive_transient' );
|
||||
if ( false !== $connect_server_is_alive_transient ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$is_alive_request = $this->is_alive();
|
||||
$new_is_alive = ! is_wp_error( $is_alive_request );
|
||||
if ( $new_is_alive ) {
|
||||
set_transient( 'connect_server_is_alive_transient', true, MINUTE_IN_SECONDS );
|
||||
}
|
||||
return $new_is_alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a subscrption with WCCOM API.
|
||||
*
|
||||
* @param string $subscription_key Product Key on WCCOM.
|
||||
* @return WP_Error|Array API Response.
|
||||
*/
|
||||
public function activate_subscription( $subscription_key ) {
|
||||
$activation_response = WC_Helper_API::post(
|
||||
'activate',
|
||||
array(
|
||||
'authenticated' => true,
|
||||
'body' => wp_json_encode(
|
||||
array(
|
||||
'product_key' => $subscription_key,
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return $activation_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @param $method
|
||||
* @param $path
|
||||
* @param $body
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
abstract protected function request( $method, $path, $body = array() );
|
||||
|
||||
/**
|
||||
* Proxy an HTTP request through the WCS Server
|
||||
*
|
||||
* @param $path Path of proxy route
|
||||
* @param $args WP_Http request args
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function proxy_request( $path, $args ) {
|
||||
$proxy_url = trailingslashit( WOOCOMMERCE_CONNECT_SERVER_URL );
|
||||
$proxy_url .= ltrim( $path, '/' );
|
||||
|
||||
$authorization = $this->authorization_header();
|
||||
if ( is_wp_error( $authorization ) ) {
|
||||
return $authorization;
|
||||
}
|
||||
$args['headers']['Authorization'] = $authorization;
|
||||
|
||||
$http_timeout = 60; // 1 minute
|
||||
|
||||
if ( function_exists( 'wc_set_time_limit' ) ) {
|
||||
wc_set_time_limit( $http_timeout + 10 );
|
||||
}
|
||||
|
||||
$args['timeout'] = $http_timeout;
|
||||
|
||||
$response = wp_remote_request( $proxy_url, $args );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds useful WP/WC/WCC information to request bodies
|
||||
*
|
||||
* @param array $initial_body
|
||||
* @return array
|
||||
*/
|
||||
protected function request_body( $initial_body = array() ) {
|
||||
$default_body = array(
|
||||
'settings' => array(),
|
||||
);
|
||||
$body = array_merge( $default_body, $initial_body );
|
||||
|
||||
// Add interesting fields to the body of each request
|
||||
$body['settings'] = wp_parse_args(
|
||||
$body['settings'],
|
||||
array(
|
||||
'store_guid' => $this->get_guid(),
|
||||
'base_city' => WC()->countries->get_base_city(),
|
||||
'base_country' => WC()->countries->get_base_country(),
|
||||
'base_state' => WC()->countries->get_base_state(),
|
||||
'base_postcode' => WC()->countries->get_base_postcode(),
|
||||
'currency' => get_woocommerce_currency(),
|
||||
'dimension_unit' => strtolower( get_option( 'woocommerce_dimension_unit' ) ),
|
||||
'weight_unit' => strtolower( get_option( 'woocommerce_weight_unit' ) ),
|
||||
'wcs_version' => WC_Connect_Loader::get_wcs_version(),
|
||||
'jetpack_version' => 'embed-' . WC_Connect_Jetpack::get_jetpack_connection_package_version(),
|
||||
'is_atomic' => WC_Connect_Jetpack::is_atomic_site(),
|
||||
'wc_version' => WC()->version,
|
||||
'wp_version' => get_bloginfo( 'version' ),
|
||||
'last_services_update' => WC_Connect_Options::get_option( 'services_last_update', 0 ),
|
||||
'last_heartbeat' => WC_Connect_Options::get_option( 'last_heartbeat', 0 ),
|
||||
'last_rate_request' => WC_Connect_Options::get_option( 'last_rate_request', 0 ),
|
||||
'active_services' => $this->wc_connect_loader->get_active_services(),
|
||||
'disable_stats' => WC_Connect_Jetpack::is_staging_site(),
|
||||
'taxes_enabled' => wc_tax_enabled() && 'yes' === get_option( 'wc_connect_taxes_enabled' ),
|
||||
)
|
||||
);
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates headers for our request to the WooCommerce Shipping & Tax Server
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
protected function request_headers() {
|
||||
$authorization = $this->authorization_header();
|
||||
if ( is_wp_error( $authorization ) ) {
|
||||
return $authorization;
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
$locale = strtolower( str_replace( '_', '-', get_locale() ) );
|
||||
$locale_elements = explode( '-', $locale );
|
||||
$lang = $locale_elements[0];
|
||||
$headers['Accept-Language'] = $locale . ',' . $lang;
|
||||
$headers['Content-Type'] = 'application/json; charset=utf-8';
|
||||
$headers['Accept'] = 'application/vnd.woocommerce-connect.v' . static::API_VERSION;
|
||||
$headers['Authorization'] = $authorization;
|
||||
|
||||
$wc_helper_auth_info = WC_Connect_Functions::get_wc_helper_auth_info();
|
||||
if ( ! is_wp_error( $wc_helper_auth_info ) ) {
|
||||
$headers['X-Woo-Signature'] = $this->request_signature_wccom( $wc_helper_auth_info['access_token_secret'], 'subscriptions', 'GET', array() );
|
||||
$headers['X-Woo-Access-Token'] = $wc_helper_auth_info['access_token'];
|
||||
$headers['X-Woo-Site-Id'] = $wc_helper_auth_info['site_id'];
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
|
||||
protected function authorization_header() {
|
||||
$token = WC_Connect_Jetpack::get_blog_access_token();
|
||||
$token = apply_filters( 'wc_connect_jetpack_access_token', $token );
|
||||
if ( ! $token || empty( $token->secret ) ) {
|
||||
return new WP_Error(
|
||||
'missing_token',
|
||||
__( 'Unable to send request to WooCommerce Shipping & Tax server. WordPress.com token is missing', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( false === strpos( $token->secret, '.' ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_token',
|
||||
__( 'Unable to send request to WooCommerce Shipping & Tax server. WordPress.com token is malformed.', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
|
||||
list( $token_key, $token_secret ) = explode( '.', $token->secret );
|
||||
$token_key = sprintf( '%s:%d:%d', $token_key, Constants::get_constant( 'JETPACK__API_VERSION' ), $token->external_user_id );
|
||||
$time_diff = (int) Jetpack_Options::get_option( 'time_diff' );
|
||||
$timestamp = time() + $time_diff;
|
||||
$nonce = wp_generate_password( 10, false );
|
||||
|
||||
$signature = $this->request_signature( $token_key, $token_secret, $timestamp, $nonce, $time_diff );
|
||||
if ( is_wp_error( $signature ) ) {
|
||||
return $signature;
|
||||
}
|
||||
|
||||
$auth = array(
|
||||
'token' => $token_key,
|
||||
'timestamp' => $timestamp,
|
||||
'nonce' => $nonce,
|
||||
'signature' => $signature,
|
||||
);
|
||||
|
||||
$header_pieces = array();
|
||||
foreach ( $auth as $key => $value ) {
|
||||
$header_pieces[] = sprintf( '%s="%s"', $key, $value );
|
||||
}
|
||||
|
||||
$authorization = 'X_JP_Auth ' . join( ' ', $header_pieces );
|
||||
return $authorization;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a signature for WCCOM API request validation.
|
||||
*
|
||||
* @param string $token_secret
|
||||
* @param string $endpoint
|
||||
* @param string $method
|
||||
* @param array $body
|
||||
* @return string
|
||||
*/
|
||||
protected function request_signature_wccom( $token_secret, $endpoint, $method, $body = array() ) {
|
||||
$request_url = WC_Helper_API::url( $endpoint );
|
||||
|
||||
$data = array(
|
||||
'host' => parse_url( $request_url, PHP_URL_HOST ), // host URL.
|
||||
'request_uri' => parse_url( $request_url, PHP_URL_PATH ), // endpoint URL.
|
||||
'method' => $method,
|
||||
);
|
||||
|
||||
if ( ! empty( $body ) ) {
|
||||
$data['body'] = $body;
|
||||
}
|
||||
|
||||
return hash_hmac( 'sha256', wp_json_encode( $data ), $token_secret );
|
||||
}
|
||||
|
||||
protected function request_signature( $token_key, $token_secret, $timestamp, $nonce, $time_diff ) {
|
||||
$local_time = $timestamp - $time_diff;
|
||||
if ( $local_time < time() - 600 || $local_time > time() + 300 ) {
|
||||
return new WP_Error(
|
||||
'invalid_signature',
|
||||
__( 'Unable to send request to WooCommerce Shipping & Tax server. The timestamp generated for the signature is too old.', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
|
||||
$normalized_request_string = join(
|
||||
"\n",
|
||||
array(
|
||||
$token_key,
|
||||
$timestamp,
|
||||
$nonce,
|
||||
)
|
||||
) . "\n";
|
||||
|
||||
return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) );
|
||||
}
|
||||
|
||||
private function get_guid() {
|
||||
$guid = WC_Connect_Options::get_option( 'store_guid', false );
|
||||
if ( false === $guid ) {
|
||||
$guid = $this->generate_guid();
|
||||
WC_Connect_Options::update_option( 'store_guid', $guid );
|
||||
}
|
||||
|
||||
return $guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a GUID.
|
||||
* This code is based of a snippet found in https://github.com/alixaxel/phunction,
|
||||
* which was referenced in http://php.net/manual/en/function.com-create-guid.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generate_guid() {
|
||||
return strtolower(
|
||||
sprintf(
|
||||
'%04X%04X-%04X-%04X-%04X-%04X%04X%04X',
|
||||
mt_rand( 0, 65535 ),
|
||||
mt_rand( 0, 65535 ),
|
||||
mt_rand( 0, 65535 ),
|
||||
mt_rand( 16384, 20479 ),
|
||||
mt_rand( 32768, 49151 ),
|
||||
mt_rand( 0, 65535 ),
|
||||
mt_rand( 0, 65535 ),
|
||||
mt_rand( 0, 65535 )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* Validates the shipping info in the cart.
|
||||
*/
|
||||
class WC_Connect_Cart_Validation {
|
||||
|
||||
/**
|
||||
* Needed to keep track of the current package being processed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $current_package;
|
||||
|
||||
/**
|
||||
* Register actions when required instead of using constructor.
|
||||
*/
|
||||
public function register_actions() {
|
||||
add_action( 'woocommerce_store_api_cart_errors', [ $this, 'add_api_cart_errors' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register filters when required instead of using constructor.
|
||||
*/
|
||||
public function register_filters() {
|
||||
add_filter( 'woocommerce_cart_no_shipping_available_html', [ $this, 'error_no_shipping_available_html' ] );
|
||||
add_filter( 'woocommerce_shipping_package_name', [ $this, 'set_current_package' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* We use this filter to store the current package array because it's not passed to the woocommerce_cart_no_shipping_available_html filter.
|
||||
*
|
||||
* @param string $package_name Package Name.
|
||||
* @param int $i Index.
|
||||
* @param array $package Current package we need to store.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function set_current_package( $package_name, $i, $package ) {
|
||||
$this->current_package = $package;
|
||||
return $package_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* More friendly error message when WCS has an error.
|
||||
*
|
||||
* @param string $error_html Generic error message from WC.
|
||||
* @return string
|
||||
*/
|
||||
public function error_no_shipping_available_html( $error_html ) {
|
||||
foreach ( WC()->shipping()->load_shipping_methods( $this->current_package ) as $shipping_method ) {
|
||||
if ( $shipping_method instanceof WC_Connect_Shipping_Method ) {
|
||||
// We have to always force validation to run because WC_Shipping could cache package rates.
|
||||
$shipping_method->is_valid_package_destination( $this->current_package );
|
||||
$errors = $shipping_method->get_package_validation_errors();
|
||||
if ( $errors->has_errors() ) {
|
||||
return $errors->get_error_message();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $error_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the error on the first load at Cart and Checkout page that has cart block or checkout block.
|
||||
*
|
||||
* @param \WP_Error $cart_errors List of errors in the cart.
|
||||
* @param \WC_Cart $cart Cart object.
|
||||
*/
|
||||
public function add_api_cart_errors( $cart_errors, $cart ) {
|
||||
|
||||
if ( WC_Connect_Functions::is_store_api_call() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$all_notices = wc_get_notices();
|
||||
|
||||
$notices = array();
|
||||
foreach ( $all_notices as $type => $type_notices ) {
|
||||
if ( is_array( $type_notices ) && 'error' === $type ) {
|
||||
$notices = array_merge( $notices, $type_notices );
|
||||
}
|
||||
}
|
||||
|
||||
$added_notices = array();
|
||||
if ( ! empty( $notices ) ) {
|
||||
$i = 1;
|
||||
foreach ( $notices as $notice ) {
|
||||
if ( ! in_array( $notice['notice'], $added_notices ) ) {
|
||||
$added_notices[] = $notice['notice'];
|
||||
$cart_errors->add( 'notice_' . $i, $notice['notice'] );
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* A class for working around the quirks and different versions of WordPress/WooCommerce
|
||||
* This is for versions higher than 2.6 (3.0 and higher)
|
||||
*/
|
||||
|
||||
// No direct access please.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Compatibility_WC30' ) ) {
|
||||
/**
|
||||
* WC_Connect_Compatibility class.
|
||||
*/
|
||||
class WC_Connect_Compatibility_WC30 extends WC_Connect_Compatibility {
|
||||
|
||||
/**
|
||||
* Return the order admin screen
|
||||
*
|
||||
* @return string The order admin screen
|
||||
*/
|
||||
public function get_order_admin_screen() {
|
||||
return 'shop_order';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to initialize the global $theorder object, mostly used during order meta boxes rendering.
|
||||
*
|
||||
* @param WC_Order|WP_Post $post_or_order_object Post or order object.
|
||||
*
|
||||
* @return bool|WC_Order|WC_Order_Refund
|
||||
*/
|
||||
public function init_theorder_object( $post_or_order_object ) {
|
||||
if ( $post_or_order_object instanceof WC_Order ) {
|
||||
return $post_or_order_object;
|
||||
}
|
||||
|
||||
return wc_get_order( $post_or_order_object->ID );
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* A class for working around the quirks and different versions of WordPress/WooCommerce
|
||||
* This is for versions 6.9 and higher
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
// No direct access please.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Compatibility_WC69' ) ) {
|
||||
/**
|
||||
* WC_Connect_Compatibility class.
|
||||
*/
|
||||
class WC_Connect_Compatibility_WC69 extends WC_Connect_Compatibility {
|
||||
|
||||
/**
|
||||
* Return the order admin screen
|
||||
*
|
||||
* @return string The order admin screen
|
||||
*/
|
||||
public function get_order_admin_screen(): string {
|
||||
return OrderUtil::get_order_admin_screen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to initialize the global $theorder object, mostly used during order meta boxes rendering.
|
||||
*
|
||||
* @param WC_Order|WP_Post $post_or_order_object Post or order object.
|
||||
*
|
||||
* @return bool|WC_Order|WC_Order_Refund WC_Order object.
|
||||
*/
|
||||
public function init_theorder_object( $post_or_order_object ) {
|
||||
return OrderUtil::init_theorder_object( $post_or_order_object );
|
||||
}
|
||||
}
|
||||
}
|
||||
+356
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* A class for working around the quirks and different versions of WordPress/WooCommerce
|
||||
* This is the base class. Its static members auto-select the correct version to use.
|
||||
*/
|
||||
|
||||
// No direct access please.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Compatibility' ) ) {
|
||||
/**
|
||||
* WC_Connect_Compatibility class.
|
||||
*/
|
||||
abstract class WC_Connect_Compatibility {
|
||||
|
||||
/**
|
||||
* WC_Connect_Compatibility.
|
||||
*
|
||||
* @var WC_Connect_Compatibility
|
||||
*/
|
||||
private static $singleton;
|
||||
|
||||
/**
|
||||
* Woocommerce version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $version = WC_VERSION;
|
||||
|
||||
/**
|
||||
* WC_Connect_Compatibility singleton instance.
|
||||
*
|
||||
* @return WC_Connect_Compatibility
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( is_null( self::$singleton ) ) {
|
||||
self::$singleton = self::select_compatibility();
|
||||
}
|
||||
|
||||
return self::$singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return subclass for active version of WooCommerce.
|
||||
*
|
||||
* @return WC_Connect_Compatibility subclass for active version of WooCommerce
|
||||
*/
|
||||
private static function select_compatibility() {
|
||||
if ( version_compare( self::$version, '6.9.0', '<' ) ) {
|
||||
require_once 'class-wc-connect-compatibility-wc30.php';
|
||||
|
||||
return new WC_Connect_Compatibility_WC30();
|
||||
} else {
|
||||
require_once 'class-wc-connect-compatibility-wc69.php';
|
||||
|
||||
return new WC_Connect_Compatibility_WC69();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite default WooCommerce Version.
|
||||
*
|
||||
* @param string $value WooCommerce Version.
|
||||
* @return void
|
||||
*/
|
||||
public static function set_version( $value ) {
|
||||
self::$singleton = null;
|
||||
self::$version = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert to current WooCommerce Version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset_version() {
|
||||
self::$singleton = null;
|
||||
self::$version = WC_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the order admin screen
|
||||
*
|
||||
* @return string The order admin screen
|
||||
*/
|
||||
abstract public function get_order_admin_screen();
|
||||
|
||||
/**
|
||||
* Helper function to initialize the global $theorder object, mostly used during order meta boxes rendering.
|
||||
*
|
||||
* @param WC_Order|WP_Post $post_or_order_object Post or order object.
|
||||
*
|
||||
* @return bool|WC_Order|WC_Order_Refund.
|
||||
*/
|
||||
abstract public function init_theorder_object( $post_or_order_object );
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Connect_Continents' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_Connect_Continents {
|
||||
|
||||
/**
|
||||
* Return the list of countries and states for a given continent.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param string $continent_code
|
||||
* @return array|mixed Response data, ready for insertion into collection data.
|
||||
*/
|
||||
public function get_continent( $continent_code = false ) {
|
||||
$continents = WC()->countries->get_continents();
|
||||
$countries = WC()->countries->get_countries();
|
||||
$states = WC()->countries->get_states();
|
||||
$locale_info = include WC()->plugin_path() . '/i18n/locale-info.php';
|
||||
$data = array();
|
||||
|
||||
if ( ! array_key_exists( $continent_code, $continents ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$continent_list = $continents[ $continent_code ];
|
||||
|
||||
$continent = array(
|
||||
'code' => $continent_code,
|
||||
'name' => $continent_list['name'],
|
||||
);
|
||||
|
||||
$local_countries = array();
|
||||
foreach ( $continent_list['countries'] as $country_code ) {
|
||||
if ( isset( $countries[ $country_code ] ) ) {
|
||||
$country = array(
|
||||
'code' => $country_code,
|
||||
'name' => $countries[ $country_code ],
|
||||
);
|
||||
|
||||
// If we have detailed locale information include that in the response
|
||||
if ( array_key_exists( $country_code, $locale_info ) ) {
|
||||
// Defensive programming against unexpected changes in locale-info.php
|
||||
$country_data = wp_parse_args(
|
||||
$locale_info[ $country_code ],
|
||||
array(
|
||||
'currency_code' => 'USD',
|
||||
'currency_pos' => 'left',
|
||||
'decimal_sep' => '.',
|
||||
'dimension_unit' => 'in',
|
||||
'num_decimals' => 2,
|
||||
'thousand_sep' => ',',
|
||||
'weight_unit' => 'lbs',
|
||||
)
|
||||
);
|
||||
|
||||
$country = array_merge( $country_data, $country );
|
||||
}
|
||||
|
||||
$local_states = array();
|
||||
if ( isset( $states[ $country_code ] ) ) {
|
||||
foreach ( $states[ $country_code ] as $state_code => $state_name ) {
|
||||
$local_states[] = array(
|
||||
'code' => $state_code,
|
||||
'name' => $state_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
$country['states'] = $local_states;
|
||||
|
||||
// Allow only desired keys (e.g. filter out tax rates)
|
||||
$allowed = array(
|
||||
'code',
|
||||
'currency_code',
|
||||
'currency_pos',
|
||||
'decimal_sep',
|
||||
'dimension_unit',
|
||||
'name',
|
||||
'num_decimals',
|
||||
'states',
|
||||
'thousand_sep',
|
||||
'weight_unit',
|
||||
);
|
||||
$country = array_intersect_key( $country, array_flip( $allowed ) );
|
||||
|
||||
$local_countries[] = $country;
|
||||
}
|
||||
}
|
||||
|
||||
$continent['countries'] = $local_countries;
|
||||
return $continent;
|
||||
}
|
||||
|
||||
|
||||
public function get() {
|
||||
$continents = array();
|
||||
foreach ( array_keys( WC()->countries->get_continents() ) as $continent_code ) {
|
||||
$continents[] = $this->get_continent( $continent_code, null );
|
||||
}
|
||||
|
||||
return $continents;
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* A class for custom surcharge.
|
||||
*/
|
||||
|
||||
// No direct access please.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Custom_Surcharge' ) ) {
|
||||
/**
|
||||
* WC_Connect_Custom_Surcharge class.
|
||||
*/
|
||||
class WC_Connect_Custom_Surcharge {
|
||||
|
||||
/**
|
||||
* Initialize the class and set up action hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'woocommerce_cart_calculate_fees', array( static::class, 'add_us_co_retail_delivery_fee' ), 10 );
|
||||
}
|
||||
/**
|
||||
* Add US Colorado Retail Delivery Fee Tax.
|
||||
* Uses the WooCommerce fees API `$cart->add_fee()`.
|
||||
*
|
||||
* Colorado Retail Delivery Fee Tax:
|
||||
* https://www.avalara.com/blog/en/north-america/2022/10/what-you-need-to-know-about-the-colorado-retail-delivery-fee-now.html
|
||||
*
|
||||
* RDF fee is DISABLED by default - not all business are required to charge the fee.
|
||||
* To apply the fee use `wc_services_apply_us_co_retail_delivery_fee` filter.
|
||||
* Change boolian flag to `true`
|
||||
* Example: `add_filter( 'wc_services_apply_us_co_retail_delivery_fee', '__return_true' );`
|
||||
*
|
||||
* @param WC_Cart $cart WooCommerce Cart object.
|
||||
*/
|
||||
public static function add_us_co_retail_delivery_fee( $cart ) {
|
||||
|
||||
/**
|
||||
* Filter should Retail Delivery Fee be applied.
|
||||
* Default: false.
|
||||
*
|
||||
* @since 2.9.0
|
||||
*
|
||||
* @param bool Should the Retail Delivery Fee be applied.
|
||||
* @param WC_Cart WooCommerce cart object.
|
||||
*/
|
||||
if ( ! apply_filters( 'wc_services_enable_us_co_retail_delivery_fee', false, $cart ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false === ( $cart instanceof WC_Cart ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not apply RDF if the customer is not in US Colorado.
|
||||
if (
|
||||
'US' !== WC()->customer->get_shipping_country()
|
||||
|| 'CO' !== WC()->customer->get_shipping_state()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not apply RDF when every item in order is exempt from Colorado sales tax.
|
||||
if (
|
||||
! is_array( $cart->get_cart_contents_taxes() )
|
||||
|| 0 === count( $cart->get_cart_contents_taxes() )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not apply RDF if all shipping methods use Local Pickup.
|
||||
if ( 0 === count(
|
||||
array_diff(
|
||||
wc_get_chosen_shipping_method_ids(),
|
||||
/**
|
||||
* Filters local pickup shipping methods.
|
||||
* Copied from WooCommerce core to maintain compatability.
|
||||
*
|
||||
* @since 6.8.0
|
||||
* @param string[] $local_pickup_methods Local pickup shipping method IDs.
|
||||
*/
|
||||
apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) )
|
||||
)
|
||||
) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not apply RDF if all products are virtual.
|
||||
if ( ! $cart->needs_shipping() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for manipulate the custom surcharge.
|
||||
*
|
||||
* As of July 1, 2024 till June 30, 2025 RDF is 29 cents per order
|
||||
* RDF is subject to sales tax.
|
||||
* https://www.avalara.com/blog/en/north-america/2022/10/what-you-need-to-know-about-the-colorado-retail-delivery-fee-now.html.
|
||||
*
|
||||
* @since 2.9.0
|
||||
*
|
||||
* @param array Custom surcharge info.
|
||||
* @param WC_Cart WooCommerce cart object.
|
||||
*/
|
||||
$fee_info = apply_filters(
|
||||
'wc_services_apply_us_co_retail_delivery_fee',
|
||||
array(
|
||||
'value' => 0.29,
|
||||
'text' => __( 'Retail Delivery Fee', 'woocommerce_services' ),
|
||||
),
|
||||
$cart
|
||||
);
|
||||
|
||||
if (
|
||||
! empty( $fee_info['text'] ) &&
|
||||
isset( $fee_info['value'] ) && is_numeric( $fee_info['value'] )
|
||||
) {
|
||||
$cart->add_fee( $fee_info['text'], floatval( $fee_info['value'] ), true, 'standard' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Debug_Tools' ) ) {
|
||||
|
||||
class WC_Connect_Debug_Tools {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
function __construct( WC_Connect_API_Client $api_client ) {
|
||||
$this->api_client = $api_client;
|
||||
|
||||
add_filter( 'woocommerce_debug_tools', array( $this, 'woocommerce_debug_tools' ) );
|
||||
}
|
||||
|
||||
function woocommerce_debug_tools( $tools ) {
|
||||
$tools['test_wcc_connection'] = array(
|
||||
'name' => __( 'Test your WooCommerce Shipping & Tax connection', 'woocommerce-services' ),
|
||||
'button' => __( 'Test Connection', 'woocommerce-services' ),
|
||||
'desc' => __( 'This will test your WooCommerce Shipping & Tax connection to ensure everything is working correctly', 'woocommerce-services' ),
|
||||
'callback' => array( $this, 'test_connection' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Only show this tool for stores not based in California
|
||||
*/
|
||||
if ( 'CA' !== WC()->countries->get_base_state() ) {
|
||||
$tools['delete_ca_taxes'] = array(
|
||||
'name' => __( 'Delete California tax rates', 'woocommerce-services' ),
|
||||
'button' => __( 'Delete CA tax rates', 'woocommerce-services' ),
|
||||
'desc' => sprintf( '<strong class="red">%1$s</strong> %2$s %3$s %4$s <a href="https://woocommerce.com/document/woocommerce-shipping-and-tax/woocommerce-tax/#jan-2022-ca-notice" target="_blank">%5$s</a>', __( 'Note:', 'woocommerce-services' ), __( 'This option will delete ALL of your "CA" tax rates where the tax name ends with " Tax" (case insensitive).', 'woocommerce-services' ), '<br>', __( 'A backup CSV of all existing tax rates will be made before the deletion process runs.', 'woocommerce-services' ), __( 'Additional information.', 'woocommerce-services' ) ),
|
||||
'callback' => array( $this, 'delete_california_tax_rates' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only show when object cache is disabled - the tool doesn't work when object cache is enabled.
|
||||
*/
|
||||
if ( ! wp_using_ext_object_cache() ) {
|
||||
$tools['delete_cached_tax_server_responses'] = array(
|
||||
'name' => __( 'Delete WooCommerce Tax cached tax rate responses', 'woocommerce-services' ),
|
||||
'button' => __( 'Delete cached Tax transients', 'woocommerce-services' ),
|
||||
'desc' => __( 'Deletes the all the transients in your database that represent cached Tax Rates responses', 'woocommerce-services' ),
|
||||
'callback' => array( $this, 'delete_cached_tax_server_responses' ),
|
||||
);
|
||||
}
|
||||
|
||||
return $tools;
|
||||
}
|
||||
|
||||
function test_connection() {
|
||||
$test_request = $this->api_client->auth_test();
|
||||
if ( $test_request && ! is_wp_error( $test_request ) && $test_request->authorized ) {
|
||||
echo '<div class="updated inline"><p>' . esc_html__( 'Your site is successfully communicating to the WooCommerce Shipping & Tax API.', 'woocommerce-services' ) . '</p></div>';
|
||||
} else {
|
||||
echo '<div class="error inline"><p>'
|
||||
. esc_html__( 'ERROR: Your site has a problem connecting to the WooCommerce Shipping & Tax API. Please make sure your WordPress.com connection is working.', 'woocommerce-services' )
|
||||
. '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Back up all existing tax rates from the database in a CSV file.
|
||||
* Then, if successfully backed up, loop through the tax rates
|
||||
* in the database and delete rates where:
|
||||
* tax_rate_country = 'US' and
|
||||
* tax_rate_state = 'CA' and
|
||||
* tax_rate_name LIKE '% Tax'
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function delete_california_tax_rates() {
|
||||
$backed_up = WC_Connect_Functions::backup_existing_tax_rates();
|
||||
|
||||
if ( ! $backed_up ) {
|
||||
echo '<div class="error inline"><p>';
|
||||
echo esc_html__( 'ERROR: The "CA" tax rate deletion process was cancelled because the existing tax rates could not be backed up.', 'woocommerce-services' );
|
||||
echo '</p></div>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$found_ca_rates = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates
|
||||
WHERE tax_rate_country = %s AND tax_rate_state = %s AND tax_rate_name LIKE %s
|
||||
",
|
||||
'US',
|
||||
'CA',
|
||||
'% Tax'
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
/**
|
||||
* If no rates were found, output a message and return
|
||||
*/
|
||||
if ( empty( $found_ca_rates ) ) {
|
||||
echo '<div class="updated inline"><p>';
|
||||
echo esc_html__( 'No "CA" tax rates were found.', 'woocommerce-services' );
|
||||
echo '</p></div>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$deleted_count = 0;
|
||||
foreach ( $found_ca_rates as $rate ) {
|
||||
if ( empty( $rate['tax_rate_id'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WC_Tax::_delete_tax_rate( $rate['tax_rate_id'] );
|
||||
$deleted_count ++;
|
||||
}
|
||||
|
||||
echo '<div class="updated inline"><p>';
|
||||
echo sprintf( esc_html__( 'Successfully deleted %1$d rows from the database.', 'woocommerce-services' ), intval( $deleted_count ) );
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the all the transients in the database that represent cached Tax Rates responses.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function delete_cached_tax_server_responses() {
|
||||
global $wpdb;
|
||||
|
||||
$deleted_count = absint(
|
||||
$wpdb->query(
|
||||
"DELETE FROM {$wpdb->options} WHERE option_name LIKE '%tj\_tax\_%';"
|
||||
)
|
||||
);
|
||||
|
||||
echo '<div class="updated inline"><p>';
|
||||
echo sprintf( esc_html__( 'Successfully deleted %1$d transients from the database.', 'woocommerce-services' ), intval( $deleted_count ) );
|
||||
echo '</p></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Show admin notices when errors occur
|
||||
*/
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Error_Notice' ) ) {
|
||||
|
||||
class WC_Connect_Error_Notice {
|
||||
|
||||
|
||||
private static $inst = null;
|
||||
|
||||
public static function instance() {
|
||||
if ( null === self::$inst ) {
|
||||
self::$inst = new WC_Connect_Error_Notice();
|
||||
}
|
||||
|
||||
return self::$inst;
|
||||
}
|
||||
|
||||
public function enable_notice( $error = true ) {
|
||||
WC_Connect_Options::update_option( 'error_notice', $error );
|
||||
}
|
||||
|
||||
public function disable_notice() {
|
||||
WC_Connect_Options::update_option( 'error_notice', false );
|
||||
}
|
||||
|
||||
public function render_notice() {
|
||||
$error_notice = filter_input( INPUT_GET, 'wc-connect-error-notice', FILTER_SANITIZE_ENCODED );
|
||||
if ( 'disable' === $error_notice ) {
|
||||
WC_Connect_Options::update_option( 'error_notice', false );
|
||||
$url = remove_query_arg( 'wc-connect-error-notice' );
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $this->notice_enabled() ) {
|
||||
$this->show_notice();
|
||||
}
|
||||
}
|
||||
|
||||
private function notice_enabled() {
|
||||
return WC_Connect_Options::get_option( 'error_notice', false );
|
||||
}
|
||||
|
||||
private function show_notice() {
|
||||
$link_status = admin_url( 'admin.php?page=wc-status&tab=connect' );
|
||||
$link_dismiss = add_query_arg( array( 'wc-connect-error-notice' => 'disable' ) );
|
||||
$error = $this->notice_enabled();
|
||||
|
||||
if ( ! is_wp_error( $error ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = false;
|
||||
|
||||
if (
|
||||
'product_missing_weight' === $error->get_error_code() ||
|
||||
'product_missing_dimension' === $error->get_error_code()
|
||||
) {
|
||||
$error_data = $error->get_error_data();
|
||||
$id = $error_data['product_id'];
|
||||
$product = wc_get_product( $id );
|
||||
|
||||
if (
|
||||
! $product ||
|
||||
( $product->has_weight() &&
|
||||
$product->get_length() &&
|
||||
$product->get_height() &&
|
||||
$product->get_width()
|
||||
)
|
||||
) {
|
||||
$this->disable_notice();
|
||||
return;
|
||||
}
|
||||
|
||||
$product_name = $product->get_name();
|
||||
$product_id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
|
||||
$message = sprintf(
|
||||
__( '<strong>"%2$s" is missing weight, length, width, or height.</strong><br />Shipping rates cannot be calculated. <a href="%1$s">Enter dimensions and weight for %2$s</a> so your customers can purchase this item.', 'woocommerce-services' ),
|
||||
get_edit_post_link( $product_id ),
|
||||
$product_name
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $message ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allowed_html = array(
|
||||
'a' => array( 'href' => array() ),
|
||||
'strong' => array(),
|
||||
'br' => array(),
|
||||
);
|
||||
?>
|
||||
<div class='notice notice-error' style="position: relative;">
|
||||
<a href="<?php echo esc_url( $link_dismiss ); ?>" style="text-decoration: none;" class="notice-dismiss" title="<?php esc_attr_e( 'Dismiss this notice', 'woocommerce-services' ); ?>"></a>
|
||||
<p><?php echo wp_kses( $message, $allowed_html ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
echo '';
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Extension_Compatibility' ) ) {
|
||||
class WC_Connect_Extension_Compatibility {
|
||||
/**
|
||||
* Function called when a new tracking number is added to the order
|
||||
*
|
||||
* @param $order_id - order ID
|
||||
* @param $carrier_id - carrier ID, as returned on the label objects returned by the server
|
||||
* @param $tracking_number - tracking number string
|
||||
*/
|
||||
public static function on_new_tracking_number( $order_id, $carrier_id, $tracking_number ) {
|
||||
// call WooCommerce Shipment Tracking if it's installed
|
||||
if ( function_exists( 'wc_st_add_tracking_number' ) ) {
|
||||
// note: the only carrier ID we use at the moment is 'usps', which is the same in WC_ST, but this might require a mapping
|
||||
wc_st_add_tracking_number( $order_id, $tracking_number, $carrier_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if WooCommerce Shipping & Tax should email the tracking details, or if another extension is taking care of that already
|
||||
*
|
||||
* @param $order_id - order ID
|
||||
* @return boolean true if WCS should send the tracking info, false otherwise
|
||||
*/
|
||||
public static function should_email_tracking_details( $order_id ) {
|
||||
if ( function_exists( 'wc_shipment_tracking' ) ) {
|
||||
$shipment_tracking = wc_shipment_tracking();
|
||||
if ( property_exists( $shipment_tracking, 'actions' )
|
||||
&& method_exists( $shipment_tracking->actions, 'get_tracking_items' ) ) {
|
||||
$shipment_tracking_items = $shipment_tracking->actions->get_tracking_items( $order_id );
|
||||
if ( ! empty( $shipment_tracking_items ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Functions' ) ) {
|
||||
class WC_Connect_Functions {
|
||||
/**
|
||||
* Checks if the potentially expensive Shipping/Tax API requests should be sent
|
||||
* based on the context in which they are initialized.
|
||||
*
|
||||
* @return bool true if the request can be sent, false otherwise
|
||||
*/
|
||||
public static function should_send_cart_api_request() {
|
||||
// Allow if this is an API call to store/cart endpoint. Provides compatibility with WooCommerce Blocks.
|
||||
return self::is_store_api_call() || ! (
|
||||
// Skip for carts loaded from session in the dashboard.
|
||||
( is_admin() && did_action( 'woocommerce_cart_loaded_from_session' ) ) ||
|
||||
// Skip during Jetpack API requests.
|
||||
( ! empty( $_SERVER['REQUEST_URI'] ) && false !== strpos( $_SERVER['REQUEST_URI'], 'jetpack/v4/' ) ) || // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
// Skip during REST API or XMLRPC requests.
|
||||
( defined( 'REST_REQUEST' ) || defined( 'REST_API_REQUEST' ) || defined( 'XMLRPC_REQUEST' ) ) ||
|
||||
// Skip during Jetpack REST API proxy requests.
|
||||
( isset( $_GET['rest_route'] ) && isset( $_GET['_for'] ) && ( 'jetpack' === $_GET['_for'] ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WC Helper authorization information to use with WC Connect Server requests( e.g. site ID, access token).
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function get_wc_helper_auth_info() {
|
||||
if ( class_exists( 'WC_Helper_Options' ) && is_callable( 'WC_Helper_Options::get' ) ) {
|
||||
$helper_auth_data = WC_Helper_Options::get( 'auth' );
|
||||
}
|
||||
|
||||
// It's possible for WC_Helper_Options::get() to return false, throw error if this is the case.
|
||||
if ( ! $helper_auth_data ) {
|
||||
return new WP_Error(
|
||||
'missing_wccom_auth',
|
||||
__( 'WooCommerce Helper auth is missing', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
return $helper_auth_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are currently in Rest API request for the wc/store/cart or wc/store/checkout API call.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_store_api_call() {
|
||||
if ( ! WC()->is_rest_api_request() && empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
|
||||
return false;
|
||||
}
|
||||
$rest_route = $GLOBALS['wp']->query_vars['rest_route'];
|
||||
|
||||
// Use regex to check any route that has "wc/store" with any of these text : "cart", "checkout", or "batch"
|
||||
// Example : wc/store/v3/batch
|
||||
preg_match( '/wc\/store\/v[0-9]{1,}\/(batch|cart|checkout)/', $rest_route, $route_matches, PREG_OFFSET_CAPTURE );
|
||||
|
||||
return ( ! empty( $route_matches ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is a cart page or has woocommerce cart block.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_cart() {
|
||||
if ( is_cart() || self::has_cart_block() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is a checkout page or has woocommerce checkout block.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_checkout() {
|
||||
if ( is_checkout() || self::has_checkout_block() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page has woocommerce cart block.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_cart_block() {
|
||||
// To support WP < 5.0.0, we need to check if `has_block` exists first as has_block only being introduced on WP 5.0.0.
|
||||
if ( function_exists( 'has_block' ) ) {
|
||||
return has_block( 'woocommerce/cart' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page has woocommerce checkout block.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_checkout_block() {
|
||||
// To support WP < 5.0.0, we need to check if `has_block` exists first as has_block only being introduced on WP 5.0.0.
|
||||
if ( function_exists( 'has_block' ) ) {
|
||||
return has_block( 'woocommerce/checkout' );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page has woocommerce cart or checkout block.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_cart_or_checkout_block() {
|
||||
if ( self::has_checkout_block() || self::has_cart_block() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current user has permissions to manage shipping labels.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function user_can_manage_labels() {
|
||||
/**
|
||||
* @since 1.25.14
|
||||
*/
|
||||
return apply_filters( 'wcship_user_can_manage_labels', current_user_can( 'manage_woocommerce' ) || current_user_can( 'wcship_manage_labels' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports existing tax rates to a CSV and clears the table.
|
||||
*
|
||||
* Ported from TaxJar's plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/42cd4cd0/taxjar-woocommerce.php#L75
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function backup_existing_tax_rates() {
|
||||
global $wpdb;
|
||||
|
||||
// Export Tax Rates
|
||||
$rates = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates
|
||||
ORDER BY tax_rate_order
|
||||
LIMIT %d, %d
|
||||
",
|
||||
0,
|
||||
10000
|
||||
)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
$header =
|
||||
__( 'Country Code', 'woocommerce' ) . ',' .
|
||||
__( 'State Code', 'woocommerce' ) . ',' .
|
||||
__( 'ZIP/Postcode', 'woocommerce' ) . ',' .
|
||||
__( 'City', 'woocommerce' ) . ',' .
|
||||
__( 'Rate %', 'woocommerce' ) . ',' .
|
||||
__( 'Tax Name', 'woocommerce' ) . ',' .
|
||||
__( 'Priority', 'woocommerce' ) . ',' .
|
||||
__( 'Compound', 'woocommerce' ) . ',' .
|
||||
__( 'Shipping', 'woocommerce' ) . ',' .
|
||||
__( 'Tax Class', 'woocommerce' ) . "\n";
|
||||
|
||||
echo $header; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
foreach ( $rates as $rate ) {
|
||||
if ( $rate->tax_rate_country ) {
|
||||
echo esc_attr( $rate->tax_rate_country );
|
||||
} else {
|
||||
echo '*';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
if ( $rate->tax_rate_state ) {
|
||||
echo esc_attr( $rate->tax_rate_state );
|
||||
} else {
|
||||
echo '*';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
$locations = $wpdb->get_col( $wpdb->prepare( "SELECT location_code FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE location_type='postcode' AND tax_rate_id = %d ORDER BY location_code", $rate->tax_rate_id ) );
|
||||
|
||||
if ( $locations ) {
|
||||
echo esc_attr( implode( '; ', $locations ) );
|
||||
} else {
|
||||
echo '*';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
$locations = $wpdb->get_col( $wpdb->prepare( "SELECT location_code FROM {$wpdb->prefix}woocommerce_tax_rate_locations WHERE location_type='city' AND tax_rate_id = %d ORDER BY location_code", $rate->tax_rate_id ) );
|
||||
if ( $locations ) {
|
||||
echo esc_attr( implode( '; ', $locations ) );
|
||||
} else {
|
||||
echo '*';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
if ( $rate->tax_rate ) {
|
||||
echo esc_attr( $rate->tax_rate );
|
||||
} else {
|
||||
echo '0';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
if ( $rate->tax_rate_name ) {
|
||||
echo esc_attr( $rate->tax_rate_name );
|
||||
} else {
|
||||
echo '*';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
if ( $rate->tax_rate_priority ) {
|
||||
echo esc_attr( $rate->tax_rate_priority );
|
||||
} else {
|
||||
echo '1';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
if ( $rate->tax_rate_compound ) {
|
||||
echo esc_attr( $rate->tax_rate_compound );
|
||||
} else {
|
||||
echo '0';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
if ( $rate->tax_rate_shipping ) {
|
||||
echo esc_attr( $rate->tax_rate_shipping );
|
||||
} else {
|
||||
echo '0';
|
||||
}
|
||||
|
||||
echo ',';
|
||||
|
||||
echo "\n";
|
||||
} // End foreach().
|
||||
|
||||
$csv = ob_get_clean();
|
||||
$upload_dir = wp_upload_dir();
|
||||
$backed_up = file_put_contents( $upload_dir['basedir'] . '/taxjar-wc_tax_rates-' . date( 'm-d-Y' ) . '-' . time() . '.csv', $csv );
|
||||
|
||||
return (bool) $backed_up;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the uploads directory and return all backed up
|
||||
* tax rate files.
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public static function get_backed_up_tax_rate_files() {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$pattern = $upload_dir['basedir'] . '/taxjar-wc_tax_rates-*.csv';
|
||||
$found_files = glob( $pattern );
|
||||
|
||||
if ( empty( $found_files ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$files = [];
|
||||
foreach ( $found_files as $file ) {
|
||||
$filename = basename( $file );
|
||||
$files[ $filename ] = $upload_dir['baseurl'] . '/' . $filename;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Help_View' ) ) {
|
||||
|
||||
class WC_Connect_Help_View {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $service_schemas_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $service_settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_TaxJar_Integration
|
||||
*/
|
||||
protected $taxjar_integration;
|
||||
|
||||
/**
|
||||
* @array
|
||||
*/
|
||||
protected $fieldsets;
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_Service_Schemas_Store $service_schemas_store,
|
||||
WC_Connect_TaxJar_Integration $taxjar_integration,
|
||||
WC_Connect_Service_Settings_Store $service_settings_store,
|
||||
WC_Connect_Logger $logger
|
||||
) {
|
||||
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
$this->service_settings_store = $service_settings_store;
|
||||
$this->logger = $logger;
|
||||
$this->taxjar_integration = $taxjar_integration;
|
||||
|
||||
add_filter( 'woocommerce_admin_status_tabs', array( $this, 'status_tabs' ) );
|
||||
add_action( 'woocommerce_admin_status_content_connect', array( $this, 'page' ) );
|
||||
}
|
||||
|
||||
protected function get_health_items() {
|
||||
$health_items = array();
|
||||
|
||||
// WooCommerce
|
||||
// Only one of the following should present
|
||||
// Check that WooCommerce is at least 2.6 or higher (feature-plugin only)
|
||||
// Check that WooCommerce base_country is set
|
||||
$base_country = WC()->countries->get_base_country();
|
||||
if ( version_compare( WC()->version, WOOCOMMERCE_CONNECT_MINIMUM_WOOCOMMERCE_VERSION, '<' ) ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => sprintf(
|
||||
__( 'WooCommerce %1$s or higher is required (You are running %2$s)', 'woocommerce-services' ),
|
||||
WOOCOMMERCE_CONNECT_MINIMUM_WOOCOMMERCE_VERSION,
|
||||
WC()->version
|
||||
),
|
||||
);
|
||||
} elseif ( empty( $base_country ) ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => __( 'Please set Base Location in WooCommerce Settings > General', 'woocommerce-services' ),
|
||||
);
|
||||
} else {
|
||||
$health_item = array(
|
||||
'state' => 'success',
|
||||
'message' => sprintf(
|
||||
__( 'WooCommerce %s is configured correctly', 'woocommerce-services' ),
|
||||
WC()->version
|
||||
),
|
||||
);
|
||||
}
|
||||
$health_items['woocommerce'] = $health_item;
|
||||
|
||||
if ( WC_Connect_Jetpack::is_offline_mode() ) {
|
||||
$health_item = array(
|
||||
'state' => 'warning',
|
||||
'message' => __( 'This site is working in offline mode. This mode is activated when running the site on a local machine or if developer mode is enabled', 'woocommerce-services' ),
|
||||
);
|
||||
} elseif ( ! WC_Connect_Jetpack::is_connected() ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => __( 'Not connected to WordPress.com', 'woocommerce-services' ),
|
||||
);
|
||||
} elseif ( WC_Connect_Jetpack::is_staging_site() ) {
|
||||
$health_item = array(
|
||||
'state' => 'warning',
|
||||
'message' => __( 'This site was identified as a staging site', 'woocommerce-services' ),
|
||||
);
|
||||
} else {
|
||||
$health_item = array(
|
||||
'state' => 'success',
|
||||
'message' => __( 'Connected to WordPress.com', 'woocommerce-services' ),
|
||||
);
|
||||
}
|
||||
$health_items['wpcom_connection'] = $health_item;
|
||||
|
||||
// Automated taxes status
|
||||
$health_items['automated_taxes'] = $this->get_tax_health_item();
|
||||
|
||||
// Lastly, do the WooCommerce Shipping & Tax health check
|
||||
// Check that we have schema
|
||||
// Check that we are able to talk to the WooCommerce Shipping & Tax server
|
||||
$schemas = $this->service_schemas_store->get_service_schemas();
|
||||
$last_fetch_timestamp = $this->service_schemas_store->get_last_fetch_timestamp();
|
||||
$health_items['woocommerce_services'] = array(
|
||||
'timestamp' => $last_fetch_timestamp,
|
||||
'has_service_schemas' => ! is_null( $schemas ),
|
||||
'error_threshold' => 3 * DAY_IN_SECONDS,
|
||||
'warning_threshold' => DAY_IN_SECONDS,
|
||||
);
|
||||
|
||||
return $health_items;
|
||||
}
|
||||
|
||||
protected function is_shipping_loaded() {
|
||||
return ! in_array( 'woocommerce-shipping/woocommerce-shipping.php', get_option( 'active_plugins' ) );
|
||||
}
|
||||
|
||||
protected function get_services_items() {
|
||||
$available_service_method_ids = $this->service_schemas_store->get_all_shipping_method_ids();
|
||||
if ( empty( $available_service_method_ids ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$service_items = array();
|
||||
|
||||
$enabled_services = $this->service_settings_store->get_enabled_services();
|
||||
|
||||
foreach ( (array) $enabled_services as $enabled_service ) {
|
||||
$last_failed_request_timestamp = intval( WC_Connect_Options::get_shipping_method_option( 'failure_timestamp', -1, $enabled_service->method_id, $enabled_service->instance_id ) );
|
||||
|
||||
$service_settings_url = esc_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-settings',
|
||||
'tab' => 'shipping',
|
||||
'instance_id' => $enabled_service->instance_id,
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
)
|
||||
);
|
||||
|
||||
// Figure out if the service has any settings saved at all
|
||||
$service_settings = $this->service_settings_store->get_service_settings( $enabled_service->method_id, $enabled_service->instance_id );
|
||||
if ( empty( $service_settings ) ) {
|
||||
$state = 'error';
|
||||
$message = __( 'Setup for this service has not yet been completed', 'woocommerce-services' );
|
||||
} elseif ( -1 === $last_failed_request_timestamp ) {
|
||||
$state = 'warning';
|
||||
$message = __( 'No rate requests have yet been made for this service', 'woocommerce-services' );
|
||||
} elseif ( 0 === $last_failed_request_timestamp ) {
|
||||
$state = 'success';
|
||||
$message = __( 'The most recent rate request was successful', 'woocommerce-services' );
|
||||
} else {
|
||||
$state = 'error';
|
||||
$message = __( 'The most recent rate request failed', 'woocommerce-services' );
|
||||
}
|
||||
|
||||
$subtitle = sprintf(
|
||||
__( '%s Shipping Zone', 'woocommerce-services' ),
|
||||
$enabled_service->zone_name
|
||||
);
|
||||
|
||||
$service_items[] = (object) array(
|
||||
'title' => $enabled_service->title,
|
||||
'subtitle' => $subtitle,
|
||||
'state' => $state,
|
||||
'message' => $message,
|
||||
'timestamp' => $last_failed_request_timestamp,
|
||||
'url' => $service_settings_url,
|
||||
);
|
||||
}
|
||||
|
||||
return $service_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last 10 lines from the WooCommerce Shipping & Tax log by feature, if it exists
|
||||
*/
|
||||
protected function get_debug_log_data( $feature = '' ) {
|
||||
$data = new stdClass();
|
||||
$data->key = '';
|
||||
$data->file = null;
|
||||
$data->tail = array();
|
||||
|
||||
if ( ! method_exists( 'WC_Admin_Status', 'scan_log_files' ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$log_prefix = 'wc\-services';
|
||||
|
||||
if ( ! empty( $feature ) ) {
|
||||
$log_prefix .= '\-' . $feature;
|
||||
}
|
||||
|
||||
$logs = WC_Admin_Status::scan_log_files();
|
||||
$latest_file_date = 0;
|
||||
|
||||
foreach ( $logs as $log_key => $log_file ) {
|
||||
if ( ! preg_match( '/' . $log_prefix . '\-(?:\d{4}\-\d{2}\-\d{2}\-)?[0-9a-f]{32}\-log/', $log_key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$log_file_path = WC_LOG_DIR . $log_file;
|
||||
$file_date = filemtime( $log_file_path );
|
||||
|
||||
if ( $latest_file_date < $file_date ) {
|
||||
$latest_file_date = $file_date;
|
||||
$data->file = $log_file_path;
|
||||
$data->key = $log_key;
|
||||
}
|
||||
}
|
||||
|
||||
if ( null !== $data->file ) {
|
||||
$complete_log = file( $data->file );
|
||||
$data->tail = array_slice( $complete_log, -10 );
|
||||
}
|
||||
|
||||
$line_count = count( $data->tail );
|
||||
if ( $line_count < 1 ) {
|
||||
$log_tail = array( __( 'Log is empty', 'woocommerce-services' ) );
|
||||
} else {
|
||||
$log_tail = $data->tail;
|
||||
}
|
||||
|
||||
return array(
|
||||
'tail' => implode( $log_tail ),
|
||||
'url' => $url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-status',
|
||||
'tab' => 'logs',
|
||||
'log_file' => $data->key,
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
),
|
||||
'count' => $line_count,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the WooCommerce System Status Tabs to add connect
|
||||
*
|
||||
* @param array $tabs
|
||||
* @return array
|
||||
*/
|
||||
public function status_tabs( $tabs ) {
|
||||
if ( ! is_array( $tabs ) ) {
|
||||
$tabs = array();
|
||||
}
|
||||
$tabs['connect'] = _x( 'WooCommerce Shipping & Tax', 'The WooCommerce Shipping & Tax brandname', 'woocommerce-services' );
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data bootstrap for the help page
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_form_data() {
|
||||
return array(
|
||||
'health_items' => $this->get_health_items(),
|
||||
'services' => $this->get_services_items(),
|
||||
'logging_enabled' => $this->logger->is_logging_enabled(),
|
||||
'debug_enabled' => $this->logger->is_debug_enabled(),
|
||||
'logs' => array(
|
||||
'shipping' => $this->get_debug_log_data( 'shipping' ),
|
||||
'taxes' => $this->get_debug_log_data( 'taxes' ),
|
||||
'other' => $this->get_debug_log_data(),
|
||||
),
|
||||
'tax_rate_backups' => WC_Connect_Functions::get_backed_up_tax_rate_files(),
|
||||
'is_shipping_loaded' => $this->is_shipping_loaded(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes the bootstrap, enqueues the script and styles for the help page
|
||||
*/
|
||||
public function page() {
|
||||
?>
|
||||
<h2>
|
||||
<?php esc_html_e( 'WooCommerce Shipping & Tax Status', 'woocommerce-services' ); ?>
|
||||
</h2>
|
||||
<?php
|
||||
|
||||
do_action(
|
||||
'enqueue_wc_connect_script',
|
||||
'wc-connect-admin-status',
|
||||
array(
|
||||
'formData' => $this->get_form_data(),
|
||||
)
|
||||
);
|
||||
|
||||
do_action(
|
||||
'enqueue_wc_connect_script',
|
||||
'wc-connect-admin-test-print',
|
||||
array(
|
||||
'isShippingLoaded' => $this->is_shipping_loaded(),
|
||||
'storeOptions' => $this->service_settings_store->get_store_options(),
|
||||
'paperSize' => $this->service_settings_store->get_preferred_paper_size(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function get_tax_health_item() {
|
||||
$store_country = WC()->countries->get_base_country();
|
||||
if ( ! $this->taxjar_integration->is_supported_country( $store_country ) ) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'settings_link_type' => '',
|
||||
'message' => sprintf( __( 'Your store\'s country (%s) is not supported. Automated taxes functionality is disabled', 'woocommerce-services' ), $store_country ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_Taxjar' ) ) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'settings_link_type' => '',
|
||||
'message' => __( 'TaxJar extension detected. Automated taxes functionality is disabled', 'woocommerce-services' ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! wc_tax_enabled() ) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'settings_link_type' => 'general',
|
||||
'message' => __( 'The core WooCommerce taxes functionality is disabled. Please ensure the "Enable tax rates and calculations" setting is turned "on" in the WooCommerce settings page', 'woocommerce-services' ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $this->taxjar_integration->is_enabled() ) {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'settings_link_type' => 'tax',
|
||||
'message' => __( 'The automated taxes functionality is disabled. Enable the "Automated taxes" setting on the WooCommerce settings page', 'woocommerce-services' ),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'success',
|
||||
'settings_link_type' => 'tax',
|
||||
'message' => __( 'Automated taxes are enabled', 'woocommerce-services' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\Jetpack\Connection\Package_Version;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Automattic\Jetpack\Status\Host;
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Jetpack' ) ) {
|
||||
class WC_Connect_Jetpack {
|
||||
const JETPACK_PLUGIN_SLUG = 'woocommerce-services';
|
||||
|
||||
public static function get_connection_manager() {
|
||||
return new Manager( self::JETPACK_PLUGIN_SLUG );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Blog Token.
|
||||
*
|
||||
* @return stdClass|WP_Error
|
||||
*/
|
||||
public static function get_blog_access_token() {
|
||||
return self::get_connection_manager()->get_tokens()->get_access_token();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get if Jetpack is in offline mode
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_offline_mode() {
|
||||
$status = new Status();
|
||||
|
||||
return $status->is_offline_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get if Jetpack is connected (aka active).
|
||||
*
|
||||
* @deprecated 2.3.0 Use self::is_connected() instead.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_active() {
|
||||
return self::is_connected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get if the current Jetpack website is marked as staging
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_staging_site() {
|
||||
$jetpack_status = new Status();
|
||||
|
||||
return $jetpack_status->in_safe_mode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get whether the current site is an Atomic site
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_atomic_site() {
|
||||
return ( new Host() )->is_woa_site();
|
||||
}
|
||||
|
||||
public static function get_connection_owner_wpcom_data() {
|
||||
$connection_owner = self::get_connection_owner();
|
||||
|
||||
if ( ! $connection_owner ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::get_connection_manager()->get_connected_user_data( $connection_owner->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the Jetpack connection owner, IF we are connected
|
||||
*
|
||||
* @return WP_User | false
|
||||
*/
|
||||
public static function get_connection_owner() {
|
||||
if ( ! self::is_connected() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return self::get_connection_manager()->get_connection_owner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a Tracks event
|
||||
*
|
||||
* @param $user
|
||||
* @param $event_type
|
||||
* @param
|
||||
*/
|
||||
public static function tracks_record_event( $user, $event_type, $data ) {
|
||||
$tracking = new Automattic\Jetpack\Tracking();
|
||||
|
||||
return $tracking->tracks_record_event( $user, $event_type, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current user is the site's Jetpack connection owner.
|
||||
*
|
||||
* @return bool Whether the current user is the Jetpack connection owner.
|
||||
*/
|
||||
public static function is_current_user_connection_owner() {
|
||||
return self::get_connection_manager()->is_connection_owner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if both the blog and a blog owner account are connected to Jetpack.
|
||||
*
|
||||
* @return bool Whether or nor Jetpack is connected
|
||||
*/
|
||||
public static function is_connected() {
|
||||
return self::get_connection_manager()->is_connected() &&
|
||||
self::get_connection_manager()->has_connected_owner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the site to Jetpack.
|
||||
* This code performs a redirection, so anything executed after it will be ignored.
|
||||
*
|
||||
* @param $redirect_url
|
||||
*/
|
||||
public static function connect_site( $redirect_url ) {
|
||||
$connection_manager = self::get_connection_manager();
|
||||
|
||||
// Register the site to wp.com.
|
||||
if ( ! $connection_manager->is_connected() ) {
|
||||
$result = $connection_manager->try_registration();
|
||||
if ( is_wp_error( $result ) ) {
|
||||
wp_die( esc_html( $result->get_error_message() ), 'wc_services_jetpack_register_site_failed', 500 );
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect the user to the Jetpack user connection flow.
|
||||
add_filter( 'jetpack_use_iframe_authorization_flow', '__return_false' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- URL generated by the Jetpack Connection package.
|
||||
wp_redirect(
|
||||
add_query_arg(
|
||||
array( 'from' => self::JETPACK_PLUGIN_SLUG ),
|
||||
$connection_manager->get_authorization_url( null, $redirect_url )
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jetpack Connection package version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_jetpack_connection_package_version() {
|
||||
return Package_Version::PACKAGE_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WPCOM or self-hosted site ID.
|
||||
*
|
||||
* @return int|WP_Error
|
||||
*/
|
||||
public static function get_wpcom_site_id() {
|
||||
return Manager::get_site_id();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Label_Reports' ) ) {
|
||||
include_once WC()->plugin_path() . '/includes/admin/reports/class-wc-admin-report.php';
|
||||
|
||||
class WC_Connect_Label_Reports extends WC_Admin_Report {
|
||||
const LABELS_TRANSIENT_KEY = 'wcs_label_reports';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $settings_store;
|
||||
|
||||
public function __construct( WC_Connect_Service_Settings_Store $settings_store ) {
|
||||
$this->settings_store = $settings_store;
|
||||
}
|
||||
|
||||
public function get_export_button() {
|
||||
$current_range = ! empty( $_GET['range'] ) ? sanitize_text_field( $_GET['range'] ) : '7day';
|
||||
?>
|
||||
<a
|
||||
href="#"
|
||||
download="report-shipping-labels-<?php echo esc_attr( $current_range ); ?>-<?php echo esc_html( date_i18n( 'Y-m-d', current_time( 'timestamp' ) ) ); ?>.csv"
|
||||
class="export_csv"
|
||||
data-export="table"
|
||||
>
|
||||
<?php esc_html_e( 'Export CSV', 'woocommerce-services' ); ?>
|
||||
</a>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function compare_label_dates_desc( $label_a, $label_b ) {
|
||||
return $label_b['created'] - $label_a['created'];
|
||||
}
|
||||
|
||||
private function get_all_labels() {
|
||||
global $wpdb;
|
||||
|
||||
$table_name = OrderUtil::get_table_for_order_meta();
|
||||
$id_column = OrderUtil::custom_orders_table_usage_is_enabled() ? 'order_id' : 'post_id';
|
||||
$db_results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'SELECT %i, meta_value FROM %i WHERE meta_key = %s',
|
||||
$id_column,
|
||||
$table_name,
|
||||
'wc_connect_labels'
|
||||
)
|
||||
);
|
||||
|
||||
$results = array();
|
||||
|
||||
foreach ( $db_results as $meta ) {
|
||||
$labels = maybe_unserialize( $meta->meta_value );
|
||||
|
||||
if ( ! is_array( $labels ) ) {
|
||||
$labels = $this->settings_store->try_deserialize_labels_json( $meta->meta_value );
|
||||
}
|
||||
|
||||
if ( empty( $labels ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $labels as $label ) {
|
||||
$results[] = array_merge( $label, array( 'order_id' => $meta->{$id_column} ) );
|
||||
}
|
||||
}
|
||||
|
||||
usort( $results, array( $this, 'compare_label_dates_desc' ) );
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function query_labels() {
|
||||
$all_labels = get_transient( self::LABELS_TRANSIENT_KEY );
|
||||
if ( false === $all_labels ) {
|
||||
$all_labels = $this->get_all_labels();
|
||||
// set transient with ttl of 30 minutes
|
||||
set_transient( self::LABELS_TRANSIENT_KEY, $all_labels, 1800 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate timestamps to JS timestamps.
|
||||
*
|
||||
* The start_date is set to the beginning of the day (midnight) of the start_date property, converted to milliseconds.
|
||||
* The end_date is set to the end of the day (one millisecond before midnight) of the end_date property, converted to milliseconds.
|
||||
* This ensures that the date range includes the entire days specified by start_date and end_date.
|
||||
*/
|
||||
$start_date = strtotime( 'midnight', $this->start_date ) * 1000;
|
||||
$end_date = strtotime( 'tomorrow', $this->end_date ) * 1000 - 1;
|
||||
|
||||
$results = array();
|
||||
foreach ( $all_labels as $label ) {
|
||||
$created = $label['created'];
|
||||
if ( $created > $end_date ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// labels are sorted in descending order, so if we reached the end, break the loop
|
||||
if ( $created < $start_date ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( isset( $label['error'] ) || // ignore the error labels
|
||||
! isset( $label['rate'] ) ) { // labels where purchase hasn't completed for any reason
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore labels with complete refunds
|
||||
if ( isset( $label['refund'] ) ) {
|
||||
$refund = (array) $label['refund'];
|
||||
if ( isset( $refund['status'] ) && 'completed' === $refund['status'] ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$results[] = $label;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function output_report() {
|
||||
$ranges = array(
|
||||
'year' => __( 'Year', 'woocommerce-services' ),
|
||||
'last_month' => __( 'Last month', 'woocommerce-services' ),
|
||||
'month' => __( 'This month', 'woocommerce-services' ),
|
||||
'7day' => __( 'Last 7 days', 'woocommerce-services' ),
|
||||
);
|
||||
|
||||
$current_range = ! empty( $_GET['range'] ) ? sanitize_text_field( $_GET['range'] ) : '7day';
|
||||
|
||||
if ( ! in_array( $current_range, array( 'custom', 'year', 'last_month', 'month', '7day' ) ) ) {
|
||||
$current_range = '7day';
|
||||
}
|
||||
|
||||
$this->check_current_range_nonce( $current_range );
|
||||
$this->calculate_current_range( $current_range );
|
||||
|
||||
$hide_sidebar = true;
|
||||
|
||||
include WC()->plugin_path() . '/includes/admin/views/html-report-by-date.php';
|
||||
}
|
||||
|
||||
private function get_edit_order_link( $post_id ) {
|
||||
$order = wc_get_order( $post_id );
|
||||
if ( ! is_a( $order, 'WC_Order' ) ) {
|
||||
return null;
|
||||
}
|
||||
return '<a href="' . $order->get_edit_order_url() . '">' . $order->get_order_number() . '</a>';
|
||||
}
|
||||
|
||||
private function get_label_refund_status( $label ) {
|
||||
if ( ! isset( $label['refund'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$refund = (array) $label['refund'];
|
||||
|
||||
if ( isset( $refund['status'] ) &&
|
||||
( 'rejected' === $refund['status'] || 'complete' === $refund['status'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return __( 'Requested', 'woocommerce-services' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main chart.
|
||||
*/
|
||||
public function get_main_chart() {
|
||||
$labels = $this->query_labels();
|
||||
|
||||
?>
|
||||
<table class="widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<?php esc_html_e( 'Time', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php esc_html_e( 'Order', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php esc_html_e( 'Price', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php esc_html_e( 'Service', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php esc_html_e( 'Refund', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<?php if ( ! empty( $labels ) ) : ?>
|
||||
<tbody>
|
||||
<?php foreach ( $labels as $label ) : ?>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php echo esc_html( get_date_from_gmt( date( 'Y-m-d H:i:s', intval( $label['created'] / 1000 ) ) ) ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php
|
||||
echo wp_kses(
|
||||
$this->get_edit_order_link( $label['order_id'] ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
);
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo wp_kses_post( wc_price( $label['rate'] ) ); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo esc_html( $label['service_name'] ); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo esc_html( $this->get_label_refund_status( $label ) ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<?php
|
||||
$total = array_sum( wp_list_pluck( $labels, 'rate' ) );
|
||||
?>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e( 'Total', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php echo count( $labels ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php echo wp_kses_post( wc_price( $total ) ); ?>
|
||||
</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><?php esc_html_e( 'No labels found for this period', 'woocommerce-services' ); ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Logger' ) ) {
|
||||
|
||||
class WC_Connect_Logger {
|
||||
|
||||
/**
|
||||
* @var WC_Logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
private $is_logging_enabled = false;
|
||||
private $is_debug_enabled = false;
|
||||
|
||||
private $feature;
|
||||
|
||||
public function __construct( WC_Logger $logger, $feature = '' ) {
|
||||
|
||||
$this->logger = $logger;
|
||||
$this->feature = strtolower( $feature );
|
||||
|
||||
$this->is_logging_enabled = WC_Connect_Options::get_option( 'debug_logging_enabled', false );
|
||||
$this->is_debug_enabled = WC_Connect_Options::get_option( 'debug_display_enabled', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a message with optional context for logging.
|
||||
*
|
||||
* @param string|WP_Error $message Either a string message, or WP_Error object.
|
||||
* @param string $context Optional. Context for the logged message.
|
||||
* @return string The formatted log message.
|
||||
*/
|
||||
protected function format_message( $message, $context = '' ) {
|
||||
|
||||
$formatted_message = $message;
|
||||
|
||||
if ( is_wp_error( $message ) ) {
|
||||
$formatted_message = $message->get_error_code() . ' ' . $message->get_error_message();
|
||||
}
|
||||
|
||||
if ( ! empty( $context ) ) {
|
||||
$formatted_message .= ' (' . $context . ')';
|
||||
}
|
||||
|
||||
return $formatted_message;
|
||||
}
|
||||
|
||||
public function enable_logging() {
|
||||
WC_Connect_Options::update_option( 'debug_logging_enabled', true );
|
||||
$this->is_logging_enabled = true;
|
||||
$this->log( 'Logging enabled' );
|
||||
}
|
||||
|
||||
public function disable_logging() {
|
||||
$this->log( 'Logging disabled' );
|
||||
WC_Connect_Options::update_option( 'debug_logging_enabled', false );
|
||||
$this->is_logging_enabled = false;
|
||||
}
|
||||
|
||||
public function enable_debug() {
|
||||
WC_Connect_Options::update_option( 'debug_display_enabled', true );
|
||||
$this->is_debug_enabled = true;
|
||||
$this->log( 'Debug enabled' );
|
||||
}
|
||||
|
||||
public function disable_debug() {
|
||||
$this->log( 'Debug disabled' );
|
||||
WC_Connect_Options::update_option( 'debug_display_enabled', false );
|
||||
$this->is_debug_enabled = false;
|
||||
}
|
||||
|
||||
public function is_debug_enabled() {
|
||||
return $this->is_debug_enabled;
|
||||
}
|
||||
|
||||
public function is_logging_enabled() {
|
||||
return $this->is_logging_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log debug by printing it as notice when debugging is enabled.
|
||||
*
|
||||
* @param string $message Debug message.
|
||||
* @param string $type Notice type.
|
||||
*/
|
||||
public function debug( $message, $type = 'notice' ) {
|
||||
if ( $this->is_debug_enabled() && ! wc_has_notice( $message, $type ) ) {
|
||||
wc_add_notice( $message, $type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs messages even if debugging is disabled
|
||||
*
|
||||
* @param string $message Message to log
|
||||
* @param string $context Optional context (e.g. a class or function name)
|
||||
*/
|
||||
public function error( $message, $context = '' ) {
|
||||
WC_Connect_Error_Notice::instance()->enable_notice( $message );
|
||||
$this->log( $message, $context, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs messages to file and error_log if WP_DEBUG
|
||||
*
|
||||
* @param WP_Error|string $message Message to log
|
||||
* @param string $context Optional context (e.g. a class or function name)
|
||||
*/
|
||||
public function log( $message, $context = '', $force = false ) {
|
||||
$log_message = $this->format_message( $message, $context );
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
error_log( $log_message );
|
||||
}
|
||||
|
||||
if ( ! $this->is_logging_enabled() && ! $force ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$log_file = 'wc-services';
|
||||
|
||||
if ( ! empty( $this->feature ) ) {
|
||||
$log_file .= '-' . $this->feature;
|
||||
}
|
||||
|
||||
$this->logger->add( $log_file, $log_message );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* WooCommerce Shipping note: DHL live rates available.
|
||||
*
|
||||
* Only for legacy customers that had the feature available.
|
||||
*/
|
||||
|
||||
class WC_Connect_Note_DHL_Live_Rates_Available {
|
||||
|
||||
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
|
||||
|
||||
/**
|
||||
* Name of the note for use in the database.
|
||||
*/
|
||||
const NOTE_NAME = 'wc-services-dhl-live-rates-available';
|
||||
|
||||
/**
|
||||
* Maybe add note to inform WooCommerce Shipping users with legacy live rates about new DHL live rates.
|
||||
*
|
||||
* @param WC_Connect_Service_Schemas_Store $schemas Store schemas.
|
||||
*/
|
||||
public static function init( WC_Connect_Service_Schemas_Store $schemas ) {
|
||||
// If store has DHL Express live rates.
|
||||
$has_wc_services_dhl_express = in_array( 'wc_services_dhlexpress', $schemas->get_all_shipping_method_ids(), true );
|
||||
|
||||
if ( $has_wc_services_dhl_express ) {
|
||||
self::possibly_add_note();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the note.
|
||||
*
|
||||
* @return Automattic\WooCommerce\Admin\Notes\Note
|
||||
*/
|
||||
public static function get_note() {
|
||||
$note = new Automattic\WooCommerce\Admin\Notes\Note();
|
||||
|
||||
$note->set_title( __( 'DHL Express live rates are now available', 'woocommerce-services' ) );
|
||||
$note->set_content( __( 'Add DHL Express as a shipping method to selected shipping zones to display live rates at checkout.', 'woocommerce-services' ) );
|
||||
$note->set_type( Automattic\WooCommerce\Admin\Notes\Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_name( self::NOTE_NAME );
|
||||
$note->set_source( 'woocommerce-services' );
|
||||
$note->add_action(
|
||||
'go-to-shipping-zones',
|
||||
__( 'Go to shipping zones', 'woocommerce-services' ),
|
||||
admin_url( 'admin.php?page=wc-settings&tab=shipping' )
|
||||
);
|
||||
|
||||
return $note;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Nux' ) ) {
|
||||
|
||||
class WC_Connect_Nux {
|
||||
/**
|
||||
* Jetpack status constants.
|
||||
*/
|
||||
const JETPACK_NOT_CONNECTED = 'not-connected';
|
||||
const JETPACK_OFFLINE_MODE = 'offline-mode';
|
||||
const JETPACK_CONNECTED = 'connected';
|
||||
|
||||
const IS_NEW_LABEL_USER = 'wcc_is_new_label_user';
|
||||
|
||||
/**
|
||||
* Option name for dismissing success banner
|
||||
* after the JP connection flow
|
||||
*/
|
||||
const SHOULD_SHOW_AFTER_CXN_BANNER = 'should_display_nux_after_jp_cxn_banner';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Tracks
|
||||
*/
|
||||
protected $tracks;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Shipping_Label
|
||||
*/
|
||||
private $shipping_label;
|
||||
|
||||
function __construct( WC_Connect_Tracks $tracks, WC_Connect_Shipping_Label $shipping_label ) {
|
||||
$this->tracks = $tracks;
|
||||
$this->shipping_label = $shipping_label;
|
||||
|
||||
$this->init_pointers();
|
||||
}
|
||||
|
||||
private function get_notice_states() {
|
||||
$states = get_user_meta( get_current_user_id(), 'wc_connect_nux_notices', true );
|
||||
|
||||
if ( ! is_array( $states ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $states;
|
||||
}
|
||||
|
||||
public function is_notice_dismissed( $notice ) {
|
||||
$notices = $this->get_notice_states();
|
||||
|
||||
return isset( $notices[ $notice ] ) && $notices[ $notice ];
|
||||
}
|
||||
|
||||
public function dismiss_notice( $notice ) {
|
||||
$notices = $this->get_notice_states();
|
||||
$notices[ $notice ] = true;
|
||||
update_user_meta( get_current_user_id(), 'wc_connect_nux_notices', $notices );
|
||||
}
|
||||
|
||||
public function ajax_dismiss_notice() {
|
||||
if ( empty( $_POST['dismissible_id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_ajax_referer( 'wc_connect_dismiss_notice', 'nonce' );
|
||||
$this->dismiss_notice( sanitize_key( $_POST['dismissible_id'] ) );
|
||||
wp_die();
|
||||
}
|
||||
|
||||
private function init_pointers() {
|
||||
add_filter( 'wc_services_pointer_post.php', array( $this, 'register_order_page_labels_pointer' ) );
|
||||
add_filter( 'wc_services_pointer_post.php', array( $this, 'register_new_carrier_dhl_pointer' ) );
|
||||
}
|
||||
|
||||
public function show_pointers( $hook ) {
|
||||
/*
|
||||
Get admin pointers for the current admin page.
|
||||
*
|
||||
* @since 0.9.6
|
||||
*
|
||||
* @param array $pointers Array of pointers.
|
||||
*/
|
||||
$pointers = apply_filters( 'wc_services_pointer_' . $hook, array() );
|
||||
|
||||
if ( ! $pointers || ! is_array( $pointers ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dismissed_pointers = $this->get_dismissed_pointers();
|
||||
$valid_pointers = array();
|
||||
|
||||
foreach ( $pointers as $pointer ) {
|
||||
if ( ! in_array( $pointer['id'], $dismissed_pointers, true ) ) {
|
||||
$valid_pointers[] = $pointer;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $valid_pointers ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style( 'wp-pointer' );
|
||||
wp_localize_script( 'wc_services_admin_pointers', 'wcServicesAdminPointers', $valid_pointers );
|
||||
wp_enqueue_script( 'wc_services_admin_pointers' );
|
||||
}
|
||||
|
||||
public function get_dismissed_pointers() {
|
||||
$data = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true );
|
||||
if ( is_string( $data ) && 0 < strlen( $data ) ) {
|
||||
return explode( ',', $data );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss a WP pointer for the current user.
|
||||
*
|
||||
* @param string $pointer_to_dismiss Pointer ID to dismiss for the current user
|
||||
*/
|
||||
public function dismiss_pointer( $pointer_to_dismiss ) {
|
||||
$dismissed_pointers = $this->get_dismissed_pointers();
|
||||
|
||||
if ( in_array( $pointer_to_dismiss, $dismissed_pointers, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dismissed_pointers[] = $pointer_to_dismiss;
|
||||
$dismissed_data = implode( ',', $dismissed_pointers );
|
||||
update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed_data );
|
||||
}
|
||||
|
||||
public function is_new_labels_user() {
|
||||
$is_new_user = get_transient( self::IS_NEW_LABEL_USER );
|
||||
if ( false === $is_new_user ) {
|
||||
global $wpdb;
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key = %s LIMIT 1",
|
||||
'wc_connect_labels'
|
||||
)
|
||||
);
|
||||
$is_new_user = 0 === count( $results ) ? 'yes' : 'no';
|
||||
set_transient( self::IS_NEW_LABEL_USER, $is_new_user );
|
||||
}
|
||||
|
||||
return 'yes' === $is_new_user;
|
||||
}
|
||||
|
||||
public function register_order_page_labels_pointer( $pointers ) {
|
||||
// If the user is not new to labels, we should just dismiss this pointer
|
||||
if ( ! $this->is_new_labels_user() ) {
|
||||
$this->dismiss_pointer( 'wc_services_labels_metabox' );
|
||||
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
global $post;
|
||||
|
||||
if ( ! $this->shipping_label->should_show_meta_box( $post ) ) {
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
$supported_carriers = array( 'USPS' );
|
||||
if ( $this->shipping_label->is_dhl_express_available() ) {
|
||||
$supported_carriers[] = 'DHL';
|
||||
}
|
||||
|
||||
$pointers[] = array(
|
||||
'id' => 'wc_services_labels_metabox',
|
||||
'target' => '#woocommerce-order-label .button',
|
||||
'options' => array(
|
||||
'content' => sprintf(
|
||||
'<h3>%s</h3><p>%s</p>',
|
||||
__( 'Discounted Shipping Labels', 'woocommerce-services' ),
|
||||
sprintf( __( "When you're ready, purchase and print discounted labels from %s right here.", 'woocommerce-services' ), implode( ' or ', $supported_carriers ) )
|
||||
),
|
||||
'position' => array(
|
||||
'edge' => 'top',
|
||||
'align' => 'left',
|
||||
),
|
||||
),
|
||||
'dim' => true,
|
||||
);
|
||||
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
public function register_new_carrier_dhl_pointer( $pointers ) {
|
||||
// new user? no need to show this alert, `wc_services_labels_metabox` will take care of communicating about DHL
|
||||
if ( $this->is_new_labels_user() ) {
|
||||
$this->dismiss_pointer( 'wc_services_new_carrier_dhl_express' );
|
||||
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
// existing user? figure out if the order supports DHL, then let them know DHL is a new carrier!
|
||||
if ( ! $this->shipping_label->is_order_dhl_express_eligible() ) {
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
$pointers[] = array(
|
||||
'id' => 'wc_services_new_carrier_dhl_express',
|
||||
'target' => '#woocommerce-order-label .button',
|
||||
'options' => array(
|
||||
'content' => sprintf(
|
||||
'<h3>%s</h3><p>%s</p>',
|
||||
__( 'Discounted DHL Shipping Labels', 'woocommerce-services' ),
|
||||
__( 'WooCommerce Shipping now supports DHL labels for international shipments. Purchase and print discounted labels from DHL and USPS right here.', 'woocommerce-services' )
|
||||
),
|
||||
'position' => array(
|
||||
'edge' => 'top',
|
||||
'align' => 'left',
|
||||
),
|
||||
),
|
||||
'dim' => true,
|
||||
);
|
||||
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
public static function get_banner_type_to_display( $status = array() ) {
|
||||
if ( ! isset( $status['jetpack_connection_status'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
The NUX Flow:
|
||||
- Case 1: Jetpack not connected (with TOS or no TOS accepted):
|
||||
1. show_banner_before_connection()
|
||||
2. connect to JP
|
||||
3. show_banner_after_connection(), which sets the TOS acceptance in options
|
||||
- Case 2: Jetpack connected, no TOS
|
||||
1. show_tos_only_banner(), which accepts TOS on button click
|
||||
- Case 3: Jetpack connected, and TOS accepted
|
||||
This is an existing user. Do nothing.
|
||||
*/
|
||||
switch ( $status['jetpack_connection_status'] ) {
|
||||
case self::JETPACK_NOT_CONNECTED:
|
||||
return 'before_jetpack_connection';
|
||||
case self::JETPACK_CONNECTED:
|
||||
case self::JETPACK_OFFLINE_MODE:
|
||||
// Has the user just gone through our NUX connection flow?
|
||||
if ( isset( $status['should_display_after_cxn_banner'] ) && $status['should_display_after_cxn_banner'] ) {
|
||||
return 'after_jetpack_connection';
|
||||
}
|
||||
|
||||
// Has the user already accepted our TOS? Then do nothing.
|
||||
// Note: TOS is accepted during the after_connection banner
|
||||
if (
|
||||
isset( $status['tos_accepted'] )
|
||||
&& ! $status['tos_accepted']
|
||||
&& isset( $status['can_accept_tos'] )
|
||||
&& $status['can_accept_tos']
|
||||
) {
|
||||
return 'tos_only_banner';
|
||||
}
|
||||
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_jetpack_install_status() {
|
||||
if ( WC_Connect_Jetpack::is_offline_mode() ) {
|
||||
// activated, and dev mode on
|
||||
return self::JETPACK_OFFLINE_MODE;
|
||||
}
|
||||
|
||||
// dev mode off, check if connected
|
||||
if ( ! WC_Connect_Jetpack::is_connected() ) {
|
||||
return self::JETPACK_NOT_CONNECTED;
|
||||
}
|
||||
|
||||
return self::JETPACK_CONNECTED;
|
||||
}
|
||||
|
||||
public function should_display_nux_notice_on_screen( $screen ) {
|
||||
if ( // Display if on any of these admin pages.
|
||||
( // Products list.
|
||||
'product' === $screen->post_type
|
||||
&& 'edit' === $screen->base
|
||||
)
|
||||
|| ( // Orders list and edit order page when not using HPOS.
|
||||
'shop_order' === $screen->post_type
|
||||
&& in_array( $screen->base, array( 'edit', 'post' ), true )
|
||||
)
|
||||
|| ( // Orders list and edit order page when using HPOS.
|
||||
wc_get_page_screen_id( 'shop_order' ) === $screen->id
|
||||
)
|
||||
|| ( // WooCommerce settings.
|
||||
'woocommerce_page_wc-settings' === $screen->base
|
||||
)
|
||||
|| ( // WooCommerce featured extension page
|
||||
'woocommerce_page_wc-addons' === $screen->base
|
||||
&& isset( $_GET['section'] ) && 'featured' === $_GET['section']
|
||||
)
|
||||
|| ( // WooCommerce shipping extension page
|
||||
'woocommerce_page_wc-addons' === $screen->base
|
||||
&& isset( $_GET['section'] ) && 'shipping_methods' === $_GET['section']
|
||||
)
|
||||
|| 'plugins' === $screen->base
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developers.taxjar.com/api/reference/#countries
|
||||
*/
|
||||
public function is_taxjar_supported_country( $country_code ) {
|
||||
$taxjar_supported_countries = array_merge(
|
||||
array(
|
||||
'US',
|
||||
'CA',
|
||||
'AU',
|
||||
),
|
||||
WC()->countries->get_european_union_countries()
|
||||
);
|
||||
|
||||
return in_array( $country_code, $taxjar_supported_countries );
|
||||
}
|
||||
|
||||
public function should_display_nux_notice_for_current_store_locale() {
|
||||
$store_country = WC()->countries->get_base_country();
|
||||
|
||||
$supports_taxes = $this->is_taxjar_supported_country( $store_country );
|
||||
$supports_shipping = in_array( $store_country, array( 'US', 'CA' ) );
|
||||
|
||||
return $supports_shipping || $supports_taxes;
|
||||
}
|
||||
|
||||
public function get_feature_list_for_country( $country ) {
|
||||
$feature_list = false;
|
||||
$supports_taxes = $this->is_taxjar_supported_country( $country );
|
||||
$supports_labels = ( 'US' === $country );
|
||||
|
||||
$is_ppec_active = is_plugin_active( 'woocommerce-gateway-paypal-express-checkout/woocommerce-gateway-paypal-express-checkout.php' );
|
||||
$ppec_settings = get_option( 'woocommerce_ppec_paypal_settings', array() );
|
||||
$supports_payments = $is_ppec_active && ( ! isset( $ppec_settings['enabled'] ) || 'yes' === $ppec_settings['enabled'] );
|
||||
|
||||
if ( $supports_payments && $supports_taxes && $supports_labels ) {
|
||||
$feature_list = __( 'automated tax calculation, shipping label printing, and smoother payment setup', 'woocommerce-services' );
|
||||
} elseif ( $supports_payments && $supports_taxes ) {
|
||||
$feature_list = __( 'automated tax calculation and smoother payment setup', 'woocommerce-services' );
|
||||
} elseif ( $supports_taxes && $supports_labels ) {
|
||||
$feature_list = __( 'automated tax calculation and shipping label printing', 'woocommerce-services' );
|
||||
} elseif ( $supports_payments && $supports_labels ) {
|
||||
$feature_list = __( 'shipping label printing and smoother payment setup', 'woocommerce-services' );
|
||||
} elseif ( $supports_payments ) {
|
||||
$feature_list = __( 'smoother payment setup', 'woocommerce-services' );
|
||||
} elseif ( $supports_taxes ) {
|
||||
$feature_list = __( 'automated tax calculation', 'woocommerce-services' );
|
||||
} elseif ( $supports_labels ) {
|
||||
$feature_list = __( 'shipping label printing', 'woocommerce-services' );
|
||||
}
|
||||
|
||||
return $feature_list;
|
||||
}
|
||||
|
||||
public function get_jetpack_redirect_url() {
|
||||
$full_path = add_query_arg( array() );
|
||||
// Remove [...]/wp-admin so we can use admin_url().
|
||||
$new_index = strpos( $full_path, '/wp-admin' ) + strlen( '/wp-admin' );
|
||||
$path = substr( $full_path, $new_index );
|
||||
|
||||
return esc_url( admin_url( $path ) );
|
||||
}
|
||||
|
||||
public function set_up_nux_notices() {
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for plugin install and activate permissions to handle Jetpack on multisites:
|
||||
// Admins might not be able to install or activate plugins, but Jetpack might already have been installed by a superadmin.
|
||||
// If this is the case, the admin can connect the site on their own, and should be able to use WCS as ususal
|
||||
$jetpack_install_status = $this->get_jetpack_install_status();
|
||||
|
||||
$banner_to_display = self::get_banner_type_to_display(
|
||||
array(
|
||||
'jetpack_connection_status' => $jetpack_install_status,
|
||||
'tos_accepted' => WC_Connect_Options::get_option( 'tos_accepted' ),
|
||||
'can_accept_tos' => WC_Connect_Jetpack::is_current_user_connection_owner() || WC_Connect_Jetpack::is_offline_mode(),
|
||||
'should_display_after_cxn_banner' => WC_Connect_Options::get_option( self::SHOULD_SHOW_AFTER_CXN_BANNER ),
|
||||
)
|
||||
);
|
||||
|
||||
switch ( $banner_to_display ) {
|
||||
case 'before_jetpack_connection':
|
||||
wp_enqueue_script( 'wc_connect_banner' );
|
||||
add_action(
|
||||
'admin_post_register_woocommerce_services_jetpack',
|
||||
array( $this, 'register_woocommerce_services_jetpack' )
|
||||
);
|
||||
wp_enqueue_style( 'wc_connect_banner' );
|
||||
add_action( 'admin_notices', array( $this, 'show_banner_before_connection' ), 9 );
|
||||
break;
|
||||
case 'tos_only_banner':
|
||||
wp_enqueue_style( 'wc_connect_banner' );
|
||||
add_action( 'admin_notices', array( $this, 'show_tos_banner' ) );
|
||||
break;
|
||||
case 'after_jetpack_connection':
|
||||
wp_enqueue_style( 'wc_connect_banner' );
|
||||
add_action( 'admin_notices', array( $this, 'show_banner_after_connection' ) );
|
||||
break;
|
||||
}
|
||||
|
||||
add_action( 'wp_ajax_wc_connect_dismiss_notice', array( $this, 'ajax_dismiss_notice' ) );
|
||||
}
|
||||
|
||||
public function show_banner_before_connection() {
|
||||
if ( ! $this->should_display_nux_notice_for_current_store_locale() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->should_display_nux_notice_on_screen( get_current_screen() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove Jetpack's connect banners since we're showing our own.
|
||||
if ( class_exists( 'Jetpack_Connection_Banner' ) ) {
|
||||
$jetpack_banner = Jetpack_Connection_Banner::init();
|
||||
|
||||
remove_action( 'admin_notices', array( $jetpack_banner, 'render_banner' ) );
|
||||
remove_action( 'admin_notices', array( $jetpack_banner, 'render_connect_prompt_full_screen' ) );
|
||||
}
|
||||
|
||||
// Make sure that we wait until the button is clicked before displaying
|
||||
// the after_connection banner
|
||||
// so that we don't accept the TOS pre-maturely
|
||||
WC_Connect_Options::delete_option( self::SHOULD_SHOW_AFTER_CXN_BANNER );
|
||||
|
||||
$country = WC()->countries->get_base_country();
|
||||
/* translators: %s: list of features, potentially comma separated */
|
||||
$description_base = __( "WooCommerce Shipping & Tax is almost ready to go! Once you connect your site to WordPress.com you'll have access to %s.", 'woocommerce-services' );
|
||||
$feature_list = $this->get_feature_list_for_country( $country );
|
||||
$banner_content = array(
|
||||
'title' => __( 'Connect your site to activate WooCommerce Shipping & Tax', 'woocommerce-services' ),
|
||||
'description' => sprintf( $description_base, $feature_list ),
|
||||
'button_text' => __( 'Connect', 'woocommerce-services' ),
|
||||
'image_url' => plugins_url( 'images/wcs-notice.png', dirname( __FILE__ ) ),
|
||||
'should_show_terms' => true,
|
||||
);
|
||||
|
||||
$this->show_nux_banner( $banner_content );
|
||||
}
|
||||
|
||||
public function show_banner_after_connection() {
|
||||
if ( ! $this->should_display_nux_notice_for_current_store_locale() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->should_display_nux_notice_on_screen( get_current_screen() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Did the user just dismiss?
|
||||
if ( isset( $_GET['wcs-nux-notice'] ) && 'dismiss' === $_GET['wcs-nux-notice'] ) {
|
||||
// No longer need to keep track of whether the before connection banner was displayed.
|
||||
WC_Connect_Options::delete_option( self::SHOULD_SHOW_AFTER_CXN_BANNER );
|
||||
wp_safe_redirect( remove_query_arg( 'wcs-nux-notice' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// By going through the connection process, the user has accepted our TOS
|
||||
WC_Connect_Options::update_option( 'tos_accepted', true );
|
||||
|
||||
$this->tracks->opted_in( 'connection_banner' );
|
||||
|
||||
$country = WC()->countries->get_base_country();
|
||||
/* translators: %s: list of features, potentially comma separated */
|
||||
$description_base = __( 'You can now enjoy %s.', 'woocommerce-services' );
|
||||
$feature_list = $this->get_feature_list_for_country( $country );
|
||||
|
||||
$this->show_nux_banner(
|
||||
array(
|
||||
'title' => __( 'Setup complete.', 'woocommerce-services' ),
|
||||
'description' => esc_html( sprintf( $description_base, $feature_list ) ),
|
||||
'button_text' => __( 'Got it, thanks!', 'woocommerce-services' ),
|
||||
'button_link' => add_query_arg(
|
||||
array(
|
||||
'wcs-nux-notice' => 'dismiss',
|
||||
)
|
||||
),
|
||||
'image_url' => plugins_url(
|
||||
'images/wcs-notice.png',
|
||||
dirname( __FILE__ )
|
||||
),
|
||||
'should_show_terms' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function show_tos_banner() {
|
||||
if ( ! $this->should_display_nux_notice_for_current_store_locale() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->should_display_nux_notice_on_screen( get_current_screen() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $_GET['wcs-nux-tos'] ) && 'accept' === $_GET['wcs-nux-tos'] ) {
|
||||
WC_Connect_Options::update_option( 'tos_accepted', true );
|
||||
|
||||
$this->tracks->opted_in( 'tos_banner' );
|
||||
|
||||
wp_safe_redirect( remove_query_arg( 'wcs-nux-tos' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$country = WC()->countries->get_base_country();
|
||||
/* translators: %s: list of features, potentially comma separated */
|
||||
$description_base = __( "WooCommerce Shipping & Tax is almost ready to go! Once you connect your site to WordPress.com you'll have access to %s.", 'woocommerce-services' );
|
||||
$feature_list = $this->get_feature_list_for_country( $country );
|
||||
|
||||
$this->show_nux_banner(
|
||||
array(
|
||||
'title' => __( 'Connect your site to activate WooCommerce Shipping & Tax', 'woocommerce-services' ),
|
||||
'description' => esc_html( sprintf( $description_base, $feature_list ) ),
|
||||
'button_text' => __( 'Connect', 'woocommerce-services' ),
|
||||
'button_link' => add_query_arg(
|
||||
array(
|
||||
'wcs-nux-tos' => 'accept',
|
||||
)
|
||||
),
|
||||
'image_url' => plugins_url(
|
||||
'images/wcs-notice.png',
|
||||
dirname( __FILE__ )
|
||||
),
|
||||
'should_show_terms' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function show_nux_banner( $content ) {
|
||||
if ( isset( $content['dismissible_id'] ) && $this->is_notice_dismissed( sanitize_key( $content['dismissible_id'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice wcs-nux__notice <?php echo isset( $content['dismissible_id'] ) ? 'is-dismissible' : ''; ?>">
|
||||
<div class="wcs-nux__notice-logo <?php echo isset( $content['compact_logo'] ) && $content['compact_logo'] ? 'is-compact' : ''; ?>">
|
||||
<img class="wcs-nux__notice-logo-graphic" src="<?php echo esc_url( $content['image_url'] ); ?>">
|
||||
</div>
|
||||
<div class="wcs-nux__notice-content">
|
||||
<h1 class="wcs-nux__notice-content-title">
|
||||
<?php echo esc_html( $content['title'] ); ?>
|
||||
</h1>
|
||||
<p class="wcs-nux__notice-content-text">
|
||||
<?php echo esc_html( $content['description'] ); ?>
|
||||
</p>
|
||||
<?php if ( isset( $content['should_show_terms'] ) && $content['should_show_terms'] ) : ?>
|
||||
<p class="wcs-nux__notice-content-tos">
|
||||
<?php
|
||||
/* translators: %1$s example values include "Install Jetpack and CONNECT >", "Activate Jetpack and CONNECT >", "CONNECT >" */
|
||||
printf(
|
||||
wp_kses(
|
||||
__( 'By clicking "%1$s", you agree to our <a href="%2$s">Terms of Service</a> and have read our <a href="%3$s">Privacy Policy</a>.', 'woocommerce-services' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
esc_html( $content['button_text'] ),
|
||||
'https://wordpress.com/tos/',
|
||||
'https://automattic.com/privacy/'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php if ( isset( $content['button_link'] ) ) : ?>
|
||||
<a
|
||||
class="wcs-nux__notice-content-button button button-primary"
|
||||
href="<?php echo esc_url( $content['button_link'] ); ?>"
|
||||
>
|
||||
<?php echo esc_html( $content['button_text'] ); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<form action="<?php echo esc_attr( admin_url( 'admin-post.php' ) ); ?>" method="post">
|
||||
<input type="hidden" name="action" value="register_woocommerce_services_jetpack"/>
|
||||
<input type="hidden" name="redirect_url"
|
||||
value="<?php echo esc_url( $this->get_jetpack_redirect_url() ); ?>"/>
|
||||
<?php wp_nonce_field( 'wcs_nux_notice' ); ?>
|
||||
<button
|
||||
type="submit"
|
||||
class="woocommerce-services__connect-jetpack wcs-nux__notice-content-button button button-primary"
|
||||
>
|
||||
<?php echo esc_html( $content['button_text'] ); ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
if ( isset( $content['dismissible_id'] ) ) :
|
||||
// Add handler for dismissing banner. Only supports a single banner at a time
|
||||
wp_enqueue_script( 'wp-util' );
|
||||
?>
|
||||
<script>
|
||||
(
|
||||
function ($) {
|
||||
$('.wcs-nux__notice').on('click', '.notice-dismiss', function () {
|
||||
wp.ajax.post({
|
||||
action: 'wc_connect_dismiss_notice',
|
||||
dismissible_id: "<?php echo esc_js( $content['dismissible_id'] ); ?>",
|
||||
nonce: "<?php echo esc_js( wp_create_nonce( 'wc_connect_dismiss_notice' ) ); ?>"
|
||||
})
|
||||
})
|
||||
}
|
||||
)(jQuery)
|
||||
</script>
|
||||
<?php
|
||||
endif;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the site to Jetpack.
|
||||
*/
|
||||
public function register_woocommerce_services_jetpack() {
|
||||
check_admin_referer( 'wcs_nux_notice' );
|
||||
|
||||
$redirect_url = '';
|
||||
if ( isset( $_POST['redirect_url'] ) ) {
|
||||
$redirect_url = esc_url_raw( wp_unslash( $_POST['redirect_url'] ) );
|
||||
}
|
||||
|
||||
// Make sure we always display the after-connection banner
|
||||
// after the before_connection button is clicked
|
||||
WC_Connect_Options::update_option( self::SHOULD_SHOW_AFTER_CXN_BANNER, true );
|
||||
|
||||
WC_Connect_Jetpack::connect_site( $redirect_url );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Options' ) ) {
|
||||
class WC_Connect_Options {
|
||||
/**
|
||||
* An array that maps a grouped option type to an option name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $grouped_options = array(
|
||||
'compact' => 'wc_connect_options',
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns an array of option names for a given type.
|
||||
*
|
||||
* @param string $type The type of option to return. Defaults to 'compact'.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_option_names( $type = 'compact' ) {
|
||||
switch ( $type ) {
|
||||
case 'non_compact':
|
||||
return array(
|
||||
'error_notice',
|
||||
'services',
|
||||
'services_last_update',
|
||||
'last_heartbeat',
|
||||
'origin_address',
|
||||
'last_rate_request',
|
||||
'services_last_result_code',
|
||||
);
|
||||
case 'shipping_method':
|
||||
return array(
|
||||
'form_settings',
|
||||
'failure_timestamp',
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'tos_accepted',
|
||||
'store_guid',
|
||||
'debug_logging_enabled',
|
||||
'debug_display_enabled',
|
||||
'add_payment_method_url',
|
||||
'payment_methods',
|
||||
'account_settings',
|
||||
'paper_size',
|
||||
'packages',
|
||||
'predefined_packages',
|
||||
'shipping_methods_migrated',
|
||||
'should_display_nux_after_jp_cxn_banner',
|
||||
'needs_tax_environment_setup',
|
||||
'banner_ppec',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all options created by WooCommerce Shipping & Tax, including shipping method options
|
||||
*/
|
||||
public static function delete_all_options() {
|
||||
if ( defined( 'WOOCOMMERCE_CONNECT_DEV_SERVER_URL' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( self::$grouped_options as $group_key => $group ) {
|
||||
// delete legacy options
|
||||
foreach ( self::get_option_names( $group_key ) as $group_option ) {
|
||||
delete_option( "wc_connect_$group_option" );
|
||||
}
|
||||
|
||||
delete_option( $group );
|
||||
}
|
||||
|
||||
$non_compacts = self::get_option_names( 'non_compact' );
|
||||
foreach ( $non_compacts as $non_compact ) {
|
||||
delete_option( "wc_connect_$non_compact" );
|
||||
}
|
||||
|
||||
self::delete_all_shipping_methods_options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested option. Looks in wc_connect_options or wc_connect_$name as appropriate.
|
||||
*
|
||||
* @param string $name Option name
|
||||
* @param mixed $default (optional)
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_option( $name, $default = false ) {
|
||||
if ( self::is_valid( $name, 'non_compact' ) ) {
|
||||
return get_option( "wc_connect_$name", $default );
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$grouped_options ) as $group ) {
|
||||
if ( self::is_valid( $name, $group ) ) {
|
||||
return self::get_grouped_option( $group, $name, $default );
|
||||
}
|
||||
}
|
||||
|
||||
trigger_error( esc_html( sprintf( 'Invalid WooCommerce Shipping & Tax option name: %s', $name ), E_USER_WARNING ) );
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the single given option. Updates wc_connect_options or wc_connect_$name as appropriate.
|
||||
*
|
||||
* @param string $name Option name
|
||||
* @param mixed $value Option value
|
||||
*
|
||||
* @return bool Was the option successfully updated?
|
||||
*/
|
||||
public static function update_option( $name, $value ) {
|
||||
if ( self::is_valid( $name, 'non_compact' ) ) {
|
||||
return update_option( "wc_connect_$name", $value );
|
||||
}
|
||||
foreach ( array_keys( self::$grouped_options ) as $group ) {
|
||||
if ( self::is_valid( $name, $group ) ) {
|
||||
return self::update_grouped_option( $group, $name, $value );
|
||||
}
|
||||
}
|
||||
trigger_error( esc_html( sprintf( 'Invalid WooCommerce Shipping & Tax option name: %s', $name ), E_USER_WARNING ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given option. May be passed multiple option names as an array.
|
||||
* Updates wc_connect_options and/or deletes wc_connect_$name as appropriate.
|
||||
*
|
||||
* @param string|array $names
|
||||
*
|
||||
* @return bool Was the option successfully deleted?
|
||||
*/
|
||||
public static function delete_option( $names ) {
|
||||
$result = true;
|
||||
$names = (array) $names;
|
||||
if ( ! self::is_valid( $names ) ) {
|
||||
trigger_error( esc_html( sprintf( 'Invalid WooCommerce Shipping & Tax option names: %s', print_r( $names, 1 ) ), E_USER_WARNING ) );
|
||||
return false;
|
||||
}
|
||||
foreach ( array_intersect( $names, self::get_option_names( 'non_compact' ) ) as $name ) {
|
||||
if ( ! delete_option( "wc_connect_$name" ) ) {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
foreach ( array_keys( self::$grouped_options ) as $group ) {
|
||||
if ( ! self::delete_grouped_option( $group, $names ) ) {
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a shipping method option
|
||||
*
|
||||
* @param $name
|
||||
* @param $default
|
||||
* @param $service_id
|
||||
* @param $service_instance
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_shipping_method_option( $name, $default, $service_id, $service_instance = false ) {
|
||||
$option_name = self::get_shipping_method_option_name( $name, $service_id, $service_instance );
|
||||
|
||||
if ( ! $option_name ) {
|
||||
trigger_error( esc_html( sprintf( 'Invalid WooCommerce Shipping & Tax shipping method option name: %s', $name ), E_USER_WARNING ) );
|
||||
return $default;
|
||||
}
|
||||
|
||||
return get_option( $option_name, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a shipping method option
|
||||
*
|
||||
* @param $name
|
||||
* @param $value
|
||||
* @param $service_id
|
||||
* @param $service_instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function update_shipping_method_option( $name, $value, $service_id, $service_instance = false ) {
|
||||
$option_name = self::get_shipping_method_option_name( $name, $service_id, $service_instance );
|
||||
|
||||
if ( ! $option_name ) {
|
||||
trigger_error( esc_html( sprintf( 'Invalid WooCommerce Shipping & Tax shipping method option name: %s', $name ), E_USER_WARNING ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
return update_option( $option_name, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a shipping method option
|
||||
*
|
||||
* @param $name
|
||||
* @param $service_id
|
||||
* @param $service_instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete_shipping_method_option( $name, $service_id, $service_instance = false ) {
|
||||
$option_name = self::get_shipping_method_option_name( $name, $service_id, $service_instance );
|
||||
|
||||
if ( ! $option_name ) {
|
||||
trigger_error( esc_html( sprintf( 'Invalid WooCommerce Shipping & Tax shipping method option name: %s', $name ), E_USER_WARNING ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
return delete_option( $option_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all options related to a shipping method
|
||||
*
|
||||
* @param $service_id
|
||||
* @param $service_instance
|
||||
*/
|
||||
public static function delete_shipping_method_options( $service_id, $service_instance = false ) {
|
||||
$option_names = self::get_option_names( 'shipping_method' );
|
||||
|
||||
foreach ( $option_names as $name ) {
|
||||
delete_option( self::get_shipping_method_option_name( $name, $service_id, $service_instance ) );
|
||||
}
|
||||
}
|
||||
|
||||
private static function get_grouped_option( $group, $name, $default ) {
|
||||
$options = get_option( self::$grouped_options[ $group ] );
|
||||
if ( is_array( $options ) && isset( $options[ $name ] ) ) {
|
||||
return $options[ $name ];
|
||||
}
|
||||
|
||||
// make the grouped options backwards-compatible and migrate the old options
|
||||
$legacy_name = "wc_connect_$name";
|
||||
$legacy_option = get_option( $legacy_name, false );
|
||||
if ( ! $legacy_option ) {
|
||||
return $default;
|
||||
}
|
||||
if ( self::update_grouped_option( $group, $name, $legacy_option ) ) {
|
||||
delete_option( $legacy_name );
|
||||
}
|
||||
|
||||
return $legacy_option;
|
||||
}
|
||||
|
||||
private static function update_grouped_option( $group, $name, $value ) {
|
||||
$options = get_option( self::$grouped_options[ $group ] );
|
||||
if ( ! is_array( $options ) ) {
|
||||
$options = array();
|
||||
}
|
||||
$options[ $name ] = $value;
|
||||
return update_option( self::$grouped_options[ $group ], $options );
|
||||
}
|
||||
|
||||
private static function delete_grouped_option( $group, $names ) {
|
||||
$options = get_option( self::$grouped_options[ $group ], array() );
|
||||
$to_delete = array_intersect( $names, self::get_option_names( $group ), array_keys( $options ) );
|
||||
if ( $to_delete ) {
|
||||
foreach ( $to_delete as $name ) {
|
||||
unset( $options[ $name ] );
|
||||
}
|
||||
return update_option( self::$grouped_options[ $group ], $options );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the service id and optional instance, generates the option name
|
||||
*
|
||||
* @param $name
|
||||
* @param $service_id
|
||||
* @param $service_instance
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
private static function get_shipping_method_option_name( $name, $service_id, $service_instance = false ) {
|
||||
if ( ! in_array( $name, self::get_option_names( 'shipping_method' ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $service_instance ) {
|
||||
return 'woocommerce_' . $service_id . '_' . $name;
|
||||
}
|
||||
|
||||
return 'woocommerce_' . $service_id . '_' . $service_instance . '_' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the option name valid?
|
||||
*
|
||||
* @param string $name The name of the option
|
||||
* @param string $group The name of the group that the option is in. Defaults to compact.
|
||||
*
|
||||
* @return bool Is the option name valid?
|
||||
*/
|
||||
private static function is_valid( $name, $group = 'non_compact' ) {
|
||||
$group_keys = array_keys( self::$grouped_options );
|
||||
|
||||
if ( is_array( $name ) ) {
|
||||
$compact_names = array();
|
||||
foreach ( $group_keys as $_group ) {
|
||||
$compact_names = array_merge( $compact_names, self::get_option_names( $_group ) );
|
||||
}
|
||||
$result = array_diff( $name, self::get_option_names( 'non_compact' ), $compact_names );
|
||||
return empty( $result );
|
||||
}
|
||||
|
||||
if ( is_null( $group ) || 'non_compact' === $group ) {
|
||||
if ( in_array( $name, self::get_option_names( $group ) ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( array_keys( self::$grouped_options ) as $_group ) {
|
||||
if ( is_null( $group ) || $group === $_group ) {
|
||||
if ( in_array( $name, self::get_option_names( $_group ) ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all options of all shipping methods
|
||||
*/
|
||||
private static function delete_all_shipping_methods_options() {
|
||||
global $wpdb;
|
||||
$methods = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_methods " );
|
||||
|
||||
foreach ( (array) $methods as $method ) {
|
||||
self::delete_shipping_method_options( $method->method_id, $method->instance_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Order_Presenter' ) ) {
|
||||
|
||||
class WC_Connect_Order_Presenter {
|
||||
|
||||
/**
|
||||
* This function transform the WC_Order object to a representational JSON form for the react app.
|
||||
* This is based on WooCommerce v3's get_order API woocommerce/includes/legacy/api/v3/class-wc-api-orders.php
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @return array
|
||||
*/
|
||||
public function get_order_for_api( WC_Order $order ) {
|
||||
$decimal_point = 2;
|
||||
$order_data = array(
|
||||
'id' => $order->get_id(),
|
||||
'order_number' => $order->get_order_number(),
|
||||
'order_key' => $order->get_order_key(),
|
||||
'created_at' => $order->get_date_created() ? $order->get_date_created()->getTimestamp() : 0,
|
||||
'updated_at' => wc_format_datetime( $order->get_date_modified() ? $order->get_date_modified()->getTimestamp() : 0 ),
|
||||
'completed_at' => wc_format_datetime( $order->get_date_completed() ? $order->get_date_completed()->getTimestamp() : 0 ),
|
||||
'status' => $order->get_status(),
|
||||
'currency' => $order->get_currency(),
|
||||
'total' => wc_format_decimal( $order->get_total(), $decimal_point ),
|
||||
'subtotal' => wc_format_decimal( $order->get_subtotal(), $decimal_point ),
|
||||
'total_line_items_quantity' => $order->get_item_count(),
|
||||
'total_tax' => wc_format_decimal( $order->get_total_tax(), $decimal_point ),
|
||||
'total_shipping' => wc_format_decimal( $order->get_shipping_total(), $decimal_point ),
|
||||
'cart_tax' => wc_format_decimal( $order->get_cart_tax(), $decimal_point ),
|
||||
'shipping_tax' => wc_format_decimal( $order->get_shipping_tax(), $decimal_point ),
|
||||
'total_discount' => wc_format_decimal( $order->get_total_discount(), $decimal_point ),
|
||||
'shipping_methods' => $order->get_shipping_method(),
|
||||
'payment_details' => array(
|
||||
'method_id' => $order->get_payment_method(),
|
||||
'method_title' => $order->get_payment_method_title(),
|
||||
'paid' => ! is_null( $order->get_date_paid() ),
|
||||
),
|
||||
'billing_address' => array(
|
||||
'first_name' => $order->get_billing_first_name(),
|
||||
'last_name' => $order->get_billing_last_name(),
|
||||
'company' => $order->get_billing_company(),
|
||||
'address_1' => $order->get_billing_address_1(),
|
||||
'address_2' => $order->get_billing_address_2(),
|
||||
'city' => $order->get_billing_city(),
|
||||
'state' => $order->get_billing_state(),
|
||||
'postcode' => $order->get_billing_postcode(),
|
||||
'country' => $order->get_billing_country(),
|
||||
'email' => $order->get_billing_email(),
|
||||
'phone' => $order->get_billing_phone(),
|
||||
),
|
||||
'shipping_address' => array(
|
||||
'first_name' => $order->get_shipping_first_name(),
|
||||
'last_name' => $order->get_shipping_last_name(),
|
||||
'company' => $order->get_shipping_company(),
|
||||
'address_1' => $order->get_shipping_address_1(),
|
||||
'address_2' => $order->get_shipping_address_2(),
|
||||
'city' => $order->get_shipping_city(),
|
||||
'state' => $order->get_shipping_state(),
|
||||
'postcode' => $order->get_shipping_postcode(),
|
||||
'country' => $order->get_shipping_country(),
|
||||
),
|
||||
'note' => $order->get_customer_note(),
|
||||
'customer_ip' => $order->get_customer_ip_address(),
|
||||
'customer_user_agent' => $order->get_customer_user_agent(),
|
||||
'customer_id' => $order->get_user_id(),
|
||||
'view_order_url' => $order->get_view_order_url(),
|
||||
'line_items' => array(),
|
||||
'shipping_lines' => array(),
|
||||
'tax_lines' => array(),
|
||||
'fee_lines' => array(),
|
||||
'coupon_lines' => array(),
|
||||
);
|
||||
|
||||
// Add line items.
|
||||
/**
|
||||
* WC Order Item Product.
|
||||
*
|
||||
* @var WC_Order_Item_Product $item
|
||||
*/
|
||||
foreach ( $order->get_items() as $item_id => $item ) {
|
||||
$product = $item->get_product();
|
||||
$item_meta = $item->get_formatted_meta_data();
|
||||
|
||||
foreach ( $item_meta as $key => $values ) {
|
||||
$item_meta[ $key ]->label = $values->display_key;
|
||||
unset( $item_meta[ $key ]->display_key );
|
||||
unset( $item_meta[ $key ]->display_value );
|
||||
}
|
||||
|
||||
$line_item = array(
|
||||
'id' => $item_id,
|
||||
'subtotal' => wc_format_decimal( $order->get_line_subtotal( $item, false, false ), $decimal_point ),
|
||||
'subtotal_tax' => wc_format_decimal( $item->get_subtotal_tax(), $decimal_point ),
|
||||
'total' => wc_format_decimal( $order->get_line_total( $item, false, false ), $decimal_point ),
|
||||
'total_tax' => wc_format_decimal( $item->get_total_tax(), $decimal_point ),
|
||||
'price' => wc_format_decimal( $order->get_item_total( $item, false, false ), $decimal_point ),
|
||||
'quantity' => $item->get_quantity(),
|
||||
'tax_class' => $item->get_tax_class(),
|
||||
'name' => $item->get_name(),
|
||||
'product_id' => $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id(),
|
||||
'sku' => is_object( $product ) ? $product->get_sku() : null,
|
||||
'meta' => array_values( $item_meta ),
|
||||
);
|
||||
|
||||
$order_data['line_items'][] = $line_item;
|
||||
}
|
||||
|
||||
// Add shipping.
|
||||
foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) {
|
||||
$order_data['shipping_lines'][] = array(
|
||||
'id' => $shipping_item_id,
|
||||
'method_id' => $shipping_item->get_method_id(),
|
||||
'method_title' => $shipping_item->get_name(),
|
||||
'total' => wc_format_decimal( $shipping_item->get_total(), $decimal_point ),
|
||||
);
|
||||
}
|
||||
|
||||
// Add taxes.
|
||||
foreach ( $order->get_tax_totals() as $tax_code => $tax ) {
|
||||
$tax_line = array(
|
||||
'id' => $tax->id,
|
||||
'rate_id' => $tax->rate_id,
|
||||
'code' => $tax_code,
|
||||
'title' => $tax->label,
|
||||
'total' => wc_format_decimal( $tax->amount, $decimal_point ),
|
||||
'compound' => (bool) $tax->is_compound,
|
||||
);
|
||||
|
||||
$order_data['tax_lines'][] = $tax_line;
|
||||
}
|
||||
|
||||
// Add fees.
|
||||
foreach ( $order->get_fees() as $fee_item_id => $fee_item ) {
|
||||
$order_data['fee_lines'][] = array(
|
||||
'id' => $fee_item_id,
|
||||
'title' => $fee_item->get_name(),
|
||||
'tax_class' => $fee_item->get_tax_class(),
|
||||
'total' => wc_format_decimal( $order->get_line_total( $fee_item ), $decimal_point ),
|
||||
'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), $decimal_point ),
|
||||
);
|
||||
}
|
||||
|
||||
// Add coupons.
|
||||
/**
|
||||
* WC Order Item Coupon.
|
||||
*
|
||||
* @var WC_Order_Item_Coupon $coupon_item
|
||||
*/
|
||||
foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) {
|
||||
$coupon_line = array(
|
||||
'id' => $coupon_item_id,
|
||||
'code' => $coupon_item->get_code(),
|
||||
'amount' => wc_format_decimal( $coupon_item->get_discount(), $decimal_point ),
|
||||
);
|
||||
|
||||
$order_data['coupon_lines'][] = $coupon_line;
|
||||
}
|
||||
|
||||
return $order_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WC_Connect_Package_Settings {
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $service_schemas_store;
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_Service_Settings_Store $settings_store,
|
||||
WC_Connect_Service_Schemas_Store $service_schemas_store
|
||||
) {
|
||||
$this->settings_store = $settings_store;
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
}
|
||||
public function get() {
|
||||
return array(
|
||||
'storeOptions' => $this->settings_store->get_store_options(),
|
||||
'formSchema' => array(
|
||||
'custom' => $this->service_schemas_store->get_packages_schema(),
|
||||
'predefined' => $this->service_schemas_store->get_predefined_packages_schema(),
|
||||
),
|
||||
'formData' => array(
|
||||
'custom' => $this->settings_store->get_packages(),
|
||||
'predefined' => $this->settings_store->get_predefined_packages(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Payment_Gateway' ) ) {
|
||||
|
||||
class WC_Connect_Payment_Gateway extends WC_Payment_Gateway {
|
||||
|
||||
public function __construct( $settings ) {
|
||||
|
||||
foreach ( (array) $settings as $key => $value ) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
$this->init_settings();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Payment_Methods_Store' ) ) {
|
||||
|
||||
class WC_Connect_Payment_Methods_Store {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $service_settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct( WC_Connect_Service_Settings_Store $service_settings_store,
|
||||
WC_Connect_API_Client $api_client, WC_Connect_Logger $logger ) {
|
||||
|
||||
$this->service_settings_store = $service_settings_store;
|
||||
$this->api_client = $api_client;
|
||||
$this->logger = $logger;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch stored payment methods from server and store in options.
|
||||
*
|
||||
* @return bool Were payment methods successfully retrieved?
|
||||
*/
|
||||
public function fetch_payment_methods_from_connect_server() {
|
||||
|
||||
$response_body = $this->api_client->get_payment_methods();
|
||||
|
||||
if ( is_wp_error( $response_body ) ) {
|
||||
$this->logger->log( $response_body, __FUNCTION__ );
|
||||
return false;
|
||||
}
|
||||
|
||||
$validation = $this->validate_payment_methods_response( $response_body );
|
||||
if ( is_wp_error( $validation ) ) {
|
||||
$this->logger->log( sprintf( '[%s] %s', $validation->get_error_code(), $validation->get_error_message() ), __FUNCTION__ );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get add payment method url from response body.
|
||||
$add_payment_method_url = $this->get_add_payment_method_url_from_response_body( $response_body );
|
||||
$payment_methods = $this->get_payment_methods_from_response_body( $response_body );
|
||||
|
||||
// Store the payment methods and add payment method url.
|
||||
$this->update_add_payment_method_url( $add_payment_method_url );
|
||||
$this->update_payment_methods( $payment_methods );
|
||||
|
||||
$this->potentially_update_selected_payment_method_from_payment_methods( $payment_methods );
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function potentially_update_selected_payment_method_from_payment_methods( $payment_methods ) {
|
||||
$payment_method_ids = array();
|
||||
|
||||
foreach ( (array) $payment_methods as $payment_method ) {
|
||||
$payment_method_id = intval( $payment_method->payment_method_id );
|
||||
|
||||
if ( 0 !== $payment_method_id ) {
|
||||
$payment_method_ids[] = $payment_method_id;
|
||||
}
|
||||
}
|
||||
|
||||
// No payment methods at all? Clear anything we have stored.
|
||||
if ( 0 === count( $payment_method_ids ) ) {
|
||||
$this->service_settings_store->set_selected_payment_method_id( 0 );
|
||||
return;
|
||||
}
|
||||
|
||||
// Has the stored method ID been removed, or is there only one available? Select the first available one.
|
||||
$selected_payment_method_id = $this->service_settings_store->get_selected_payment_method_id();
|
||||
if (
|
||||
( $selected_payment_method_id || 1 === count( $payment_method_ids ) ) &&
|
||||
! in_array( $selected_payment_method_id, $payment_method_ids )
|
||||
) {
|
||||
$this->service_settings_store->set_selected_payment_method_id( $payment_method_ids[0] );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_payment_methods() {
|
||||
return WC_Connect_Options::get_option( 'payment_methods', array() );
|
||||
}
|
||||
|
||||
protected function update_payment_methods( $payment_methods ) {
|
||||
WC_Connect_Options::update_option( 'payment_methods', $payment_methods );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the response body is valid and contains the correct properties.
|
||||
*
|
||||
* @param object $response_body The response body object.
|
||||
* @return true|WP_Error Whether the response body is valid.
|
||||
*/
|
||||
protected function validate_payment_methods_response( $response_body ) {
|
||||
if ( ! is_object( $response_body ) ) {
|
||||
return new WP_Error( 'payment_method_response_body_type', __( 'Expected but did not receive object for response body.', 'woocommerce-services' ) );
|
||||
}
|
||||
|
||||
if ( ! property_exists( $response_body, 'payment_methods' ) ) {
|
||||
return new WP_Error( 'payment_method_response_body_missing_payment_methods', __( 'Expected but did not receive payment_methods in response body.', 'woocommerce-services' ) );
|
||||
}
|
||||
|
||||
if ( ! property_exists( $response_body, 'add_payment_method_url' ) ) {
|
||||
return new WP_Error( 'payment_method_response_body_missing_add_payment_method_url', __( 'Expected but did not receive add_payment_method_url in response body.', 'woocommerce-services' ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function get_payment_methods_from_response_body( $response_body ) {
|
||||
$payment_methods = $response_body->payment_methods;
|
||||
if ( ! is_array( $payment_methods ) ) {
|
||||
return new WP_Error( 'payment_methods_type', 'Expected but did not receive array for payment_methods.' );
|
||||
}
|
||||
|
||||
foreach ( $payment_methods as $payment_method ) {
|
||||
$required_keys = array( 'payment_method_id', 'name', 'card_type', 'card_digits', 'expiry' );
|
||||
foreach ( $required_keys as $required_key ) {
|
||||
if ( ! property_exists( $payment_method, $required_key ) ) {
|
||||
return new WP_Error( 'payment_methods_key_missing', 'Payment method is missing a required property' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $payment_methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to add a payment method from the response body.
|
||||
*
|
||||
* @param object $response_body The response body object.
|
||||
* @return string The URL to add a payment method.
|
||||
*/
|
||||
protected function get_add_payment_method_url_from_response_body( $response_body ) {
|
||||
return $response_body->add_payment_method_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to add a payment method.
|
||||
*
|
||||
* @return string The URL to add a payment method.
|
||||
*/
|
||||
public function get_add_payment_method_url() {
|
||||
return WC_Connect_Options::get_option( 'add_payment_method_url', '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the URL to add a payment method.
|
||||
*
|
||||
* @param string $add_payment_method_url The URL to add a payment method.
|
||||
* @return void
|
||||
*/
|
||||
protected function update_add_payment_method_url( $add_payment_method_url ) {
|
||||
WC_Connect_Options::update_option( 'add_payment_method_url', esc_url_raw( $add_payment_method_url ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_PayPal_EC' ) ) {
|
||||
|
||||
/**
|
||||
* Integrates with WooCommerce PayPal Checkout Payment Gateway,
|
||||
* modifying that plugin's behavior to facilitate authenticating requests
|
||||
* not by linking an account but via the WCS server through which we proxy.
|
||||
*/
|
||||
class WC_Connect_PayPal_EC {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
private $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Nux
|
||||
*/
|
||||
private $nux;
|
||||
|
||||
/**
|
||||
* Express Checkout API methods to proxy.
|
||||
*/
|
||||
private $methods_to_proxy = array( 'SetExpressCheckout', 'GetExpressCheckoutDetails', 'DoExpressCheckoutPayment' );
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Nux $nux ) {
|
||||
$this->api_client = $api_client;
|
||||
$this->nux = $nux;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if ( ! function_exists( 'wc_gateway_ppec' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ppec_plugin = wc_gateway_ppec();
|
||||
if ( ! property_exists( $ppec_plugin, 'settings' ) || empty( $ppec_plugin->settings ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->maybe_set_reroute_requests();
|
||||
|
||||
add_filter( 'woocommerce_paypal_express_checkout_settings', array( $this, 'adjust_form_fields' ) );
|
||||
$this->initialize_settings();
|
||||
$settings = $ppec_plugin->settings;
|
||||
|
||||
// Don't modify any PPEC plugin behavior if WCS request proxying is not enabled
|
||||
if ( 'yes' !== $settings->reroute_requests ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If empty, populate Sandbox and Live API Subject values with provided email
|
||||
if (
|
||||
empty( $settings->sandbox_api_subject ) &&
|
||||
empty( $settings->sandbox_api_username ) &&
|
||||
empty( $settings->api_username )
|
||||
) {
|
||||
$email = isset( $settings->email ) ? $settings->email : $settings->api_subject;
|
||||
$settings->api_subject = $email;
|
||||
$settings->sandbox_api_subject = $email;
|
||||
$settings->save();
|
||||
}
|
||||
|
||||
$username = $settings->get_active_api_credentials()->get_username();
|
||||
$subject = $settings->get_active_api_credentials()->get_subject();
|
||||
|
||||
// Proceed to attach PPEC-related hooks if email address is present but credentials are missing
|
||||
if ( empty( $username ) && ! empty( $subject ) ) {
|
||||
add_filter( 'woocommerce_paypal_express_checkout_request_body', array( $this, 'request_body' ) );
|
||||
|
||||
add_filter( 'option_woocommerce_ppec_paypal_settings', array( $this, 'adjust_settings' ) );
|
||||
add_filter( 'woocommerce_payment_gateway_supports', array( $this, 'ppec_supports' ), 10, 3 );
|
||||
|
||||
if ( 'live' === $settings->environment ) {
|
||||
// If PPEC order comes in, activate prompt to connect a PayPal account
|
||||
add_action( 'woocommerce_order_status_on-hold', array( $this, 'maybe_trigger_banner' ) );
|
||||
add_action( 'woocommerce_payment_complete', array( $this, 'maybe_trigger_banner' ) );
|
||||
|
||||
// Once a payment is received, show prompt to connect a PayPal account on certain screens
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_show_banner' ) );
|
||||
|
||||
add_filter( 'wc_services_pointer_post.php', array( $this, 'register_refund_pointer' ) );
|
||||
}
|
||||
add_filter( 'pre_option_wc_gateway_ppce_prompt_to_connect', '__return_empty_string' ); // Disable default PPEC notice.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach request proxying hook if it's an Express Checkout method
|
||||
*/
|
||||
public function request_body( $body ) {
|
||||
if ( in_array( $body['METHOD'], $this->methods_to_proxy ) ) {
|
||||
add_filter( 'pre_http_request', array( $this, 'proxy_request' ), 10, 3 );
|
||||
} else {
|
||||
remove_filter( 'pre_http_request', array( $this, 'proxy_request' ), 10, 3 );
|
||||
}
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reroute Express Checkout requests from the PPEC extension via WCS server to pick up API credentials
|
||||
*/
|
||||
public function proxy_request( $preempt, $r, $url ) {
|
||||
if ( ! preg_match( '/paypal.com\/nvp$/', $url ) ) {
|
||||
return $preempt;
|
||||
}
|
||||
|
||||
$settings = wc_gateway_ppec()->settings;
|
||||
return $this->api_client->proxy_request( 'paypal/nvp/' . $settings->environment, $r );
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit supported payment gateway features to payments alone
|
||||
*/
|
||||
public function ppec_supports( $supported, $feature, $gateway ) {
|
||||
return 'ppec_paypal' === $gateway->id ? 'products' === $feature : $supported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pointer clarifying the need to link an account before refunding payment
|
||||
*/
|
||||
public function register_refund_pointer( $pointers ) {
|
||||
$pointers[] = array(
|
||||
'id' => 'wc_services_refund_via_ppec',
|
||||
'target' => '.refund-actions > button:first-child',
|
||||
'options' => array(
|
||||
'content' => sprintf(
|
||||
'<h3>%s</h3><p>%s</p>',
|
||||
__( 'Link a PayPal account', 'woocommerce-services' ),
|
||||
sprintf(
|
||||
wp_kses(
|
||||
__( 'To issue refunds via PayPal Checkout, you will need to <a href="%s">link a PayPal account</a> with the email address that received this payment.', 'woocommerce-services' ),
|
||||
array( 'a' => array( 'href' => array() ) )
|
||||
),
|
||||
wc_gateway_ppec()->ips->get_signup_url( wc_gateway_ppec()->settings->environment )
|
||||
)
|
||||
),
|
||||
'position' => array(
|
||||
'edge' => 'bottom',
|
||||
'align' => 'top',
|
||||
),
|
||||
),
|
||||
'delayed_opening' => array(
|
||||
'show_button' => '.refund-items',
|
||||
'hide_button' => '.cancel-action',
|
||||
'animating_container' => '.wc-order-refund-items',
|
||||
'delegation_container' => '#woocommerce-order-items',
|
||||
),
|
||||
);
|
||||
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger banner to appear based on order paid with PPEC
|
||||
*/
|
||||
public function maybe_trigger_banner( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
$payment_method = $order ? $order->get_payment_method() : false;
|
||||
|
||||
if ( 'ppec_paypal' === $payment_method ) {
|
||||
WC_Connect_Options::update_option( 'banner_ppec', 'yes' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show banner if it has been triggered and if this screen is an appropriate place for it
|
||||
*/
|
||||
public function maybe_show_banner() {
|
||||
if ( 'yes' !== WC_Connect_Options::get_option( 'banner_ppec', null ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
$order = wc_get_order();
|
||||
$payment_method = $order ? $order->get_payment_method() : false;
|
||||
|
||||
if ( // Display if on any of these admin pages.
|
||||
( // Orders list.
|
||||
'shop_order' === $screen->post_type
|
||||
&& 'edit' === $screen->base
|
||||
)
|
||||
|| ( // Edit order page.
|
||||
'shop_order' === $screen->post_type
|
||||
&& 'post' === $screen->base
|
||||
&& 'ppec_paypal' === $payment_method
|
||||
)
|
||||
|| ( // WooCommerce » Settings » Payments.
|
||||
'woocommerce_page_wc-settings' === $screen->base
|
||||
&& isset( $_GET['tab'] ) && 'checkout' === $_GET['tab']
|
||||
)
|
||||
|| ( // WooCommerce » Extensions » Payments.
|
||||
'woocommerce_page_wc-addons' === $screen->base
|
||||
&& isset( $_GET['section'] ) && 'payment-gateways' === $_GET['section']
|
||||
)
|
||||
) {
|
||||
wp_enqueue_style( 'wc_connect_banner' );
|
||||
add_action( 'admin_notices', array( $this, 'banner' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a NUX banner prompting the merchant to link a PayPal account
|
||||
*/
|
||||
public function banner() {
|
||||
$this->nux->show_nux_banner(
|
||||
array(
|
||||
'title' => __( 'Link your PayPal account', 'woocommerce-services' ),
|
||||
'description' => esc_html( __( 'Link a new or existing PayPal account to make sure future orders are marked “Processing” instead of “On hold”, and so refunds can be issued without leaving WooCommerce.', 'woocommerce-services' ) ),
|
||||
'button_text' => __( 'Link account', 'woocommerce-services' ),
|
||||
'button_link' => wc_gateway_ppec()->ips->get_signup_url( 'live' ),
|
||||
'image_url' => plugins_url( 'images/cashier.svg', dirname( __FILE__ ) ),
|
||||
'should_show_jp' => false,
|
||||
'dismissible_id' => 'ppec',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize PPEC settings to their default values
|
||||
*/
|
||||
public function initialize_settings() {
|
||||
$settings = get_option( 'woocommerce_ppec_paypal_settings', array() );
|
||||
|
||||
if ( ! isset( $settings['reroute_requests'] ) ) {
|
||||
$settings['reroute_requests'] = 'no';
|
||||
} elseif ( 'no' === $settings['reroute_requests'] ) {
|
||||
return;
|
||||
} elseif ( ! isset( $settings['button_size'] ) ) { // Check if settings are initialized, represented by button_size as its absence would be first to affect the customer
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
$gateway = $payment_gateways['ppec_paypal'];
|
||||
|
||||
foreach ( $gateway->form_fields as $key => $form_field ) {
|
||||
if ( ! isset( $settings[ $key ] ) && isset( $form_field['default'] ) ) {
|
||||
$settings[ $key ] = $form_field['default'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_option( 'woocommerce_ppec_paypal_settings', $settings );
|
||||
wc_gateway_ppec()->settings->load( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force setting values that will work when proxying requests
|
||||
*/
|
||||
public function adjust_settings( $settings ) {
|
||||
$settings['paymentaction'] = 'sale';
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify PPEC settings form to include a toggle (and other accommodations) for WCS request proxying
|
||||
*/
|
||||
public function adjust_form_fields( $form_fields ) {
|
||||
$settings = wc_gateway_ppec()->settings;
|
||||
|
||||
// Modify form fields and descriptions depending on whether WCS request proxying is enabled
|
||||
if ( 'yes' === $settings->reroute_requests ) {
|
||||
$form_fields = $this->adjust_api_subject_form_field( $form_fields );
|
||||
|
||||
// Prevent user from changing Payment Action away from "Sale", the only option for which payments will work
|
||||
$form_fields['paymentaction']['disabled'] = true;
|
||||
$form_fields['paymentaction']['description'] = sprintf( __( '%s (Note that "authorizing payment only" requires linking a PayPal account.)', 'woocommerce-services' ), $form_fields['paymentaction']['description'] );
|
||||
|
||||
// Communicate WCS proxying and provide option to disable
|
||||
$reset_link = add_query_arg(
|
||||
array(
|
||||
'reroute_requests' => 'no',
|
||||
'nonce' => wp_create_nonce( 'reroute_requests' ),
|
||||
),
|
||||
wc_gateway_ppec()->get_admin_setting_link()
|
||||
);
|
||||
$api_creds_template = __( 'Payments will be authenticated by WooCommerce Shipping & Tax and directed to the following email address. To disable this feature and link a PayPal account, <a href="%s">click here</a>.', 'woocommerce-services' );
|
||||
if ( empty( $settings->api_username ) ) {
|
||||
$api_creds_text = sprintf( $api_creds_template, esc_url( add_query_arg( 'environment', 'live', $reset_link ) ) );
|
||||
$form_fields['api_credentials']['description'] = $api_creds_text;
|
||||
unset( $form_fields['api_username'], $form_fields['api_password'], $form_fields['api_signature'], $form_fields['api_certificate'] );
|
||||
}
|
||||
if ( empty( $settings->sandbox_api_username ) ) {
|
||||
$api_creds_text = sprintf( $api_creds_template, esc_url( add_query_arg( 'environment', 'sandbox', $reset_link ) ) );
|
||||
$form_fields['sandbox_api_credentials']['description'] = $api_creds_text;
|
||||
unset( $form_fields['sandbox_api_username'], $form_fields['sandbox_api_password'], $form_fields['sandbox_api_signature'], $form_fields['sandbox_api_certificate'] );
|
||||
}
|
||||
} else {
|
||||
// Provide option to enable request proxying
|
||||
$reset_link = add_query_arg(
|
||||
array(
|
||||
'reroute_requests' => 'yes',
|
||||
'nonce' => wp_create_nonce( 'reroute_requests' ),
|
||||
),
|
||||
wc_gateway_ppec()->get_admin_setting_link()
|
||||
);
|
||||
$api_creds_template = __( 'To authenticate payments with WooCommerce Shipping & Tax, <a href="%s">click here</a>.', 'woocommerce-services' );
|
||||
if ( empty( $settings->api_username ) ) {
|
||||
$api_creds_text = sprintf( $api_creds_template, esc_url( add_query_arg( 'environment', 'live', $reset_link ) ) );
|
||||
$form_fields['api_credentials']['description'] .= '<br /><br />' . $api_creds_text;
|
||||
}
|
||||
if ( empty( $settings->sandbox_api_username ) ) {
|
||||
$api_creds_text = sprintf( $api_creds_template, esc_url( add_query_arg( 'environment', 'sandbox', $reset_link ) ) );
|
||||
$form_fields['sandbox_api_credentials']['description'] .= '<br /><br />' . $api_creds_text;
|
||||
}
|
||||
}
|
||||
|
||||
return $form_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Present the "API Subject" setting in a way that's simpler, more comprehensible, and more appropriate to the way it's being used
|
||||
*/
|
||||
public function adjust_api_subject_form_field( $form_fields ) {
|
||||
$api_subject_title = __( 'Payment Email', 'woocommerce-services' );
|
||||
$form_fields['api_subject']['title'] = $api_subject_title;
|
||||
$form_fields['sandbox_api_subject']['title'] = $api_subject_title;
|
||||
|
||||
$api_subject_description = __( 'Enter your email address at which to accept payments. You\'ll need to link your own account in order to perform anything other than "sale" transactions.', 'woocommerce-services' );
|
||||
$form_fields['api_subject']['description'] = $api_subject_description;
|
||||
$form_fields['sandbox_api_subject']['description'] = $api_subject_description;
|
||||
|
||||
$api_subject_placeholder = __( 'Required', 'woocommerce-services' );
|
||||
$form_fields['api_subject']['placeholder'] = $api_subject_placeholder;
|
||||
$form_fields['sandbox_api_subject']['placeholder'] = $api_subject_placeholder;
|
||||
|
||||
return $form_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle reroute_requests setting change
|
||||
*/
|
||||
public function maybe_set_reroute_requests() {
|
||||
if (
|
||||
! isset( $_GET['page'] ) || 'wc-settings' !== $_GET['page'] ||
|
||||
empty( $_GET['reroute_requests'] ) ||
|
||||
empty( $_GET['nonce'] ) ||
|
||||
! wp_verify_nonce( $_GET['nonce'], 'reroute_requests' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = wc_gateway_ppec()->settings;
|
||||
$settings->reroute_requests = 'yes' === $_GET['reroute_requests'] ? 'yes' : 'no';
|
||||
if ( isset( $_GET['environment'] ) ) {
|
||||
$settings->environment = 'sandbox' === $_GET['environment'] ? 'sandbox' : 'live';
|
||||
}
|
||||
$settings->save();
|
||||
|
||||
wp_safe_redirect( wc_gateway_ppec()->get_admin_setting_link() );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
if ( class_exists( 'WC_Connect_Privacy' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_Connect_Privacy {
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
public function __construct( WC_Connect_Service_Settings_Store $settings_store, WC_Connect_API_Client $api_client ) {
|
||||
$this->settings_store = $settings_store;
|
||||
$this->api_client = $api_client;
|
||||
|
||||
add_action( 'admin_init', array( $this, 'add_privacy_message' ) );
|
||||
add_action( 'admin_notices', array( $this, 'add_erasure_notice' ) );
|
||||
add_filter( 'woocommerce_privacy_export_order_personal_data', array( $this, 'label_data_exporter' ), 10, 2 );
|
||||
add_action( 'woocommerce_privacy_before_remove_order_personal_data', array( $this, 'label_data_eraser' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the privacy message to display in the admin panel
|
||||
*/
|
||||
public function add_privacy_message() {
|
||||
if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$title = __( 'WooCommerce Shipping & Tax', 'woocommerce-services' );
|
||||
$content = wpautop(
|
||||
sprintf(
|
||||
wp_kses(
|
||||
__( 'By using this extension, you may be storing personal data or sharing data with external services. <a href="%s" target="_blank">Learn more about how this works, including what you may want to include in your privacy policy.</a>', 'woocommerce-services' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
),
|
||||
)
|
||||
),
|
||||
'https://jetpack.com/support/for-your-privacy-policy/#woocommerce-services'
|
||||
)
|
||||
);
|
||||
|
||||
wp_add_privacy_policy_content( $title, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* If WooCommerce order data erasure is enabled, display a warning on the erasure page
|
||||
*/
|
||||
public function add_erasure_notice() {
|
||||
$screen = get_current_screen();
|
||||
if ( 'tools_page_remove_personal_data' !== $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$erasure_enabled = wc_string_to_bool( get_option( 'woocommerce_erasure_request_removes_order_data', 'no' ) );
|
||||
if ( ! $erasure_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice notice-warning" style="position: relative;">
|
||||
<p><?php esc_html_e( 'Warning: Erasing personal data will cause the ability to reprint or refund WooCommerce Shipping & Tax shipping labels to be lost on the affected orders.', 'woocommerce-services' ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for woocommerce_privacy_export_order_personal_data that adds WCS personal data to the exported orders
|
||||
*
|
||||
* @param array $personal_data
|
||||
* @param object $order
|
||||
* @return array
|
||||
*/
|
||||
public function label_data_exporter( $personal_data, $order ) {
|
||||
$order_id = $order->get_id();
|
||||
$labels = $this->settings_store->get_label_order_meta_data( $order_id );
|
||||
|
||||
foreach ( $labels as $label ) {
|
||||
if ( empty( $label['tracking'] ) ) {
|
||||
continue;
|
||||
}
|
||||
$personal_data[] = array(
|
||||
'name' => __( 'Shipping label service', 'woocommerce-services' ),
|
||||
'value' => $label['service_name'],
|
||||
);
|
||||
$personal_data[] = array(
|
||||
'name' => __( 'Shipping label tracking number', 'woocommerce-services' ),
|
||||
'value' => $label['tracking'],
|
||||
);
|
||||
}
|
||||
|
||||
return $personal_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks into woocommerce_privacy_before_remove_order_personal_data to remove WCS personal data from orders
|
||||
*
|
||||
* @param WC_Order $order WC Order.
|
||||
*/
|
||||
public function label_data_eraser( $order ) {
|
||||
$order_id = $order->get_id();
|
||||
$labels = $this->settings_store->get_label_order_meta_data( $order_id );
|
||||
if ( empty( $labels ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $labels as $label_idx => $label ) {
|
||||
$labels[ $label_idx ]['tracking'] = '';
|
||||
$labels[ $label_idx ]['status'] = 'ANONYMIZED';
|
||||
}
|
||||
|
||||
$this->api_client->anonymize_order( $order_id );
|
||||
$order->update_meta_data( 'wc_connect_labels', $labels );
|
||||
$order->save();
|
||||
}
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Service_Schemas_Store' ) ) {
|
||||
|
||||
class WC_Connect_Service_Schemas_Store {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Logger $logger ) {
|
||||
|
||||
$this->api_client = $api_client;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function fetch_service_schemas_from_connect_server() {
|
||||
|
||||
$response_body = $this->api_client->get_service_schemas();
|
||||
|
||||
if ( is_wp_error( $response_body ) ) {
|
||||
$error_data = $response_body->get_error_data();
|
||||
if ( isset( $error_data['response_status_code'] ) ) {
|
||||
$this->update_last_fetch_result_code( $error_data['response_status_code'] );
|
||||
}
|
||||
$this->logger->log( $response_body, __FUNCTION__ );
|
||||
return false;
|
||||
}
|
||||
$this->update_last_fetch_result_code( '200' );
|
||||
|
||||
$this->logger->log( 'Successfully loaded service schemas from server response.', __FUNCTION__ );
|
||||
$this->update_last_fetch_timestamp();
|
||||
$this->maybe_update_heartbeat();
|
||||
|
||||
$old_schemas = $this->get_service_schemas();
|
||||
if ( $old_schemas == $response_body ) {
|
||||
// schemas weren't changed, but were fetched without problems
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we made it this far, it is safe to store the object
|
||||
return $this->update_service_schemas( $response_body );
|
||||
}
|
||||
|
||||
public function get_service_schemas() {
|
||||
return WC_Connect_Options::get_option( 'services', null );
|
||||
}
|
||||
|
||||
protected function update_service_schemas( $service_schemas ) {
|
||||
return WC_Connect_Options::update_option( 'services', $service_schemas );
|
||||
}
|
||||
|
||||
public function get_last_fetch_timestamp() {
|
||||
return WC_Connect_Options::get_option( 'services_last_update', null );
|
||||
}
|
||||
|
||||
protected function update_last_fetch_timestamp() {
|
||||
WC_Connect_Options::update_option( 'services_last_update', time() );
|
||||
}
|
||||
|
||||
public function get_last_fetch_result_code() {
|
||||
return WC_Connect_Options::get_option( 'services_last_result_code' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $result_status_code
|
||||
*/
|
||||
protected function update_last_fetch_result_code( $result_status_code ) {
|
||||
WC_Connect_Options::update_option( 'services_last_result_code', $result_status_code );
|
||||
}
|
||||
|
||||
protected function maybe_update_heartbeat() {
|
||||
$last_heartbeat = WC_Connect_Options::get_option( 'last_heartbeat' );
|
||||
$now = time();
|
||||
|
||||
if ( ! $last_heartbeat ) {
|
||||
$should_update = true;
|
||||
} else {
|
||||
$last_heartbeat = absint( $last_heartbeat );
|
||||
if ( $last_heartbeat > $now ) {
|
||||
// last heartbeat in the future? wacky
|
||||
$should_update = true;
|
||||
} else {
|
||||
$elapsed = $now - $last_heartbeat;
|
||||
$should_update = $elapsed > DAY_IN_SECONDS;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $should_update ) {
|
||||
WC_Connect_Options::update_option( 'last_heartbeat', $now );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all service ids of a specific type (e.g. shipping)
|
||||
*
|
||||
* @param string $type The type of services to return
|
||||
*
|
||||
* @return array An array of that type's service ids, or an empty array if no such type is known
|
||||
*/
|
||||
public function get_all_service_ids_of_type( $type ) {
|
||||
|
||||
if ( empty( $type ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
if ( ! is_object( $service_schemas ) || ! property_exists( $service_schemas, $type ) || ! is_array( $service_schemas->$type ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$service_schema_ids = array();
|
||||
foreach ( $service_schemas->$type as $service_schema ) {
|
||||
$service_schema_ids[] = $service_schema->id;
|
||||
}
|
||||
|
||||
return $service_schema_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all shipping method ids
|
||||
*
|
||||
* @return array|bool An array of supported shipping method ids or false if schema does not support method_id
|
||||
*/
|
||||
public function get_all_shipping_method_ids() {
|
||||
$shipping_method_ids = array();
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
if ( ! is_object( $service_schemas ) || ! property_exists( $service_schemas, 'shipping' ) || ! is_array( $service_schemas->shipping ) ) {
|
||||
return $shipping_method_ids;
|
||||
}
|
||||
|
||||
foreach ( $service_schemas->shipping as $service_schema ) {
|
||||
if ( ! property_exists( $service_schema, 'method_id' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$shipping_method_ids[] = $service_schema->method_id;
|
||||
}
|
||||
|
||||
return $shipping_method_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a particular service's schema given its id
|
||||
*
|
||||
* @param string $service_id The service id for which to return the schema
|
||||
*
|
||||
* @return object|null The service schema or null if no such id was found
|
||||
*/
|
||||
public function get_service_schema_by_id( $service_id ) {
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
if ( ! is_object( $service_schemas ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $service_schemas as $service_type => $service_type_service_schemas ) {
|
||||
$matches = wp_filter_object_list( $service_type_service_schemas, array( 'id' => $service_id ) );
|
||||
if ( $matches ) {
|
||||
return array_shift( $matches );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a particular service's schema given its method_id
|
||||
*
|
||||
* @param $method_id
|
||||
*
|
||||
* @return object|null The service schema or null if no such id was found
|
||||
*/
|
||||
public function get_service_schema_by_method_id( $method_id ) {
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
if ( ! is_object( $service_schemas ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $service_schemas as $service_type => $service_type_service_schemas ) {
|
||||
$matches = wp_filter_object_list( $service_type_service_schemas, array( 'method_id' => $method_id ) );
|
||||
if ( $matches ) {
|
||||
return array_shift( $matches );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a service's schema given its shipping zone instance
|
||||
*
|
||||
* @param string $instance_id The shipping zone instance id for which to return the schema
|
||||
*
|
||||
* @return object|null The service schema or null if no such instance was found
|
||||
*/
|
||||
public function get_service_schema_by_instance_id( $instance_id ) {
|
||||
global $wpdb;
|
||||
$method_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d;",
|
||||
$instance_id
|
||||
)
|
||||
);
|
||||
|
||||
return $this->get_service_schema_by_method_id( $method_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a service's schema given an id or shipping zone instance.
|
||||
*
|
||||
* @param string $id_or_instance_id String ID or numeric instance ID.
|
||||
* @return object|null Service schema on success, null on failure
|
||||
*/
|
||||
public function get_service_schema_by_id_or_instance_id( $id_or_instance_id ) {
|
||||
|
||||
if ( is_numeric( $id_or_instance_id ) ) {
|
||||
return $this->get_service_schema_by_instance_id( $id_or_instance_id );
|
||||
}
|
||||
|
||||
if ( ! empty( $id_or_instance_id ) ) {
|
||||
return $this->get_service_schema_by_method_id( $id_or_instance_id );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns packages schema
|
||||
*
|
||||
* @return object|null Packages schema on success, null on failure
|
||||
*/
|
||||
public function get_packages_schema() {
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
if ( ! is_object( $service_schemas ) || ! property_exists( $service_schemas, 'boxes' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $service_schemas->boxes;
|
||||
}
|
||||
|
||||
public function get_predefined_packages_schema() {
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
if ( ! is_object( $service_schemas ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$predefined_packages = array();
|
||||
foreach ( $service_schemas->shipping as $service_schema ) {
|
||||
if ( ! isset( $service_schema->packages ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$predefined_packages[ $service_schema->id ] = $service_schema->packages;
|
||||
}
|
||||
|
||||
return $predefined_packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WooCommerce Shipping and WooCommerce Tax migration enabled status
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_wcship_wctax_migration_enabled() {
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
|
||||
return is_object( $service_schemas ) && property_exists( $service_schemas, 'features' ) && property_exists( $service_schemas->features, 'wcshippingtax_upgrade_banner' ) && ! empty( $service_schemas->features->wcshippingtax_upgrade_banner );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WooCommerce Shipping and WooCommerce Tax upgrade banners
|
||||
*
|
||||
* @return object|null The banners schema or null if no such id was found
|
||||
*/
|
||||
public function get_wcship_wctax_upgrade_banner() {
|
||||
$service_schemas = $this->get_service_schemas();
|
||||
|
||||
if ( $this->is_wcship_wctax_migration_enabled() ) {
|
||||
return $service_schemas->features->wcshippingtax_upgrade_banner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Service_Schemas_Validator' ) ) {
|
||||
|
||||
class WC_Connect_Service_Schemas_Validator {
|
||||
|
||||
/**
|
||||
* Validates the overall passed services object (all service types and all services therein)
|
||||
*
|
||||
* @param object $services
|
||||
*
|
||||
* @return WP_Error|true
|
||||
*/
|
||||
public function validate_service_schemas( $service_schemas ) {
|
||||
|
||||
if ( ! is_object( $service_schemas ) ) {
|
||||
return new WP_Error(
|
||||
'outermost_container_not_object',
|
||||
'Malformed service schemas. Outermost container is not an object.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset( $service_schemas->shipping ) || ! is_array( $service_schemas->shipping ) ) {
|
||||
return new WP_Error(
|
||||
'service_type_not_ref_array',
|
||||
'Malformed service schemas. \'shipping\' does not reference an array.'
|
||||
);
|
||||
}
|
||||
|
||||
$service_counter = 0;
|
||||
foreach ( $service_schemas->shipping as $service_schema ) {
|
||||
if ( ! is_object( $service_schema ) ) {
|
||||
return new WP_Error(
|
||||
'service_not_ref_object',
|
||||
sprintf(
|
||||
'Malformed service schema. Service type \'shipping\' [%d] does not reference an object.',
|
||||
$service_counter
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->validate_service_schema( 'shipping', $service_counter, $service_schema );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$service_counter ++;
|
||||
}
|
||||
|
||||
if ( ! isset( $service_schemas->boxes ) || ! is_object( $service_schemas->boxes ) ) {
|
||||
return new WP_Error(
|
||||
'boxes_not_object',
|
||||
'Malformed service schemas. \'boxes\' is not an object.'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a particular service schema, especially the parts of the service that WC relies
|
||||
* on like id, method_title, method_description, etc
|
||||
*
|
||||
* @param string $service_type
|
||||
* @param integer $service_counter
|
||||
* @param object $service
|
||||
*
|
||||
* @return WP_Error|true
|
||||
*/
|
||||
protected function validate_service_schema( $service_type, $service_counter, $service_schema ) {
|
||||
$required_properties = array(
|
||||
'id' => 'string',
|
||||
'method_description' => 'string',
|
||||
'method_title' => 'string',
|
||||
'service_settings' => 'object',
|
||||
'form_layout' => 'array',
|
||||
);
|
||||
|
||||
foreach ( $required_properties as $required_property => $required_property_type ) {
|
||||
if ( ! property_exists( $service_schema, $required_property ) ) {
|
||||
return new WP_Error(
|
||||
'required_service_property_missing',
|
||||
sprintf(
|
||||
'Malformed service schema. Service type \'%s\' [%d] does not include a required \'%s\' property.',
|
||||
$service_type,
|
||||
$service_counter,
|
||||
$required_property
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$property_type = gettype( $service_schema->$required_property );
|
||||
if ( $required_property_type !== $property_type ) {
|
||||
return new WP_Error(
|
||||
'required_service_property_wrong_type',
|
||||
sprintf(
|
||||
'Malformed service schema. Service type \'%s\' [%d] property \'%s\' is a %s. Was expecting a %s.',
|
||||
$service_type,
|
||||
$service_counter,
|
||||
$service_schema->$required_property,
|
||||
$property_type,
|
||||
$required_property_type
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->validate_service_schema_settings( $service_schema->id, $service_schema->service_settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a particular service's service settings schema, especially the parts of the
|
||||
* service settings that WC relies on like type, required and properties
|
||||
*
|
||||
* @param string $service_id
|
||||
* @param object $service_settings
|
||||
*
|
||||
* @return WP_Error|true
|
||||
*/
|
||||
protected function validate_service_schema_settings( $service_id, $service_settings ) {
|
||||
$required_properties = array(
|
||||
'type' => 'string',
|
||||
'required' => 'array',
|
||||
'properties' => 'object',
|
||||
);
|
||||
|
||||
foreach ( $required_properties as $required_property => $required_property_type ) {
|
||||
if ( ! property_exists( $service_settings, $required_property ) ) {
|
||||
return new WP_Error(
|
||||
'service_settings_missing_required_property',
|
||||
sprintf(
|
||||
'The settings part of a service schema is malformed. Service \'%s\' service_settings do not include a required \'%s\' property.',
|
||||
$service_id,
|
||||
$required_property
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$property_type = gettype( $service_settings->$required_property );
|
||||
if ( $required_property_type !== $property_type ) {
|
||||
return new WP_Error(
|
||||
'service_settings_property_wrong_type',
|
||||
sprintf(
|
||||
"The settings part of a service schema is malformed. Service '%s' service_setting property '%s' is a %s. Was expecting a %s.",
|
||||
$service_id,
|
||||
$required_property,
|
||||
$property_type,
|
||||
$required_property_type
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->validate_service_settings_required_properties( $service_id, $service_settings->properties );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates a particular service's schema's required properties, especially the parts of the
|
||||
* properties that WC relies on and title
|
||||
*
|
||||
* @param string $service_id
|
||||
* @param object $service_settings_properties
|
||||
*
|
||||
* @return WP_Error|true
|
||||
*/
|
||||
protected function validate_service_settings_required_properties( $service_id, $service_settings_properties ) {
|
||||
$required_properties = array(
|
||||
'title',
|
||||
);
|
||||
|
||||
foreach ( $required_properties as $required_property ) {
|
||||
if ( ! property_exists( $service_settings_properties, $required_property ) ) {
|
||||
return new WP_Error(
|
||||
'service_properties_missing_required_property',
|
||||
sprintf(
|
||||
"The properties part of a service schema is malformed. Service '%s' service_settings properties do not include a required '%s' property.",
|
||||
$service_id,
|
||||
$required_property
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+670
@@ -0,0 +1,670 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Service_Settings_Store' ) ) {
|
||||
|
||||
class WC_Connect_Service_Settings_Store {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $service_schemas_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct( WC_Connect_Service_Schemas_Store $service_schemas_store, WC_Connect_API_Client $api_client, WC_Connect_Logger $logger ) {
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
$this->api_client = $api_client;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets woocommerce store options that are useful for all connect services
|
||||
*
|
||||
* @return object|array
|
||||
*/
|
||||
public function get_store_options() {
|
||||
$currency_symbol = sanitize_text_field( html_entity_decode( get_woocommerce_currency_symbol() ) );
|
||||
$dimension_unit = sanitize_text_field( strtolower( get_option( 'woocommerce_dimension_unit' ) ) );
|
||||
$weight_unit = sanitize_text_field( strtolower( get_option( 'woocommerce_weight_unit' ) ) );
|
||||
$base_location = wc_get_base_location();
|
||||
|
||||
return array(
|
||||
'currency_symbol' => $currency_symbol,
|
||||
'dimension_unit' => $this->translate_unit( $dimension_unit ),
|
||||
'weight_unit' => $this->translate_unit( $weight_unit ),
|
||||
'origin_country' => $base_location['country'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets connect account settings (e.g. payment method)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_account_settings() {
|
||||
$default = array(
|
||||
'selected_payment_method_id' => 0,
|
||||
'enabled' => true,
|
||||
);
|
||||
|
||||
$result = WC_Connect_Options::get_option( 'account_settings', $default );
|
||||
$result['paper_size'] = $this->get_preferred_paper_size();
|
||||
$result = array_merge( $default, $result );
|
||||
|
||||
if ( ! isset( $result['email_receipts'] ) ) {
|
||||
$result['email_receipts'] = true;
|
||||
}
|
||||
|
||||
if ( ! isset( $result['use_last_service'] ) ) {
|
||||
$result['use_last_service'] = false;
|
||||
}
|
||||
|
||||
if ( ! isset( $result['use_last_package'] ) ) {
|
||||
$result['use_last_package'] = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates connect account settings (e.g. payment method)
|
||||
*
|
||||
* @param array $settings
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function update_account_settings( $settings ) {
|
||||
// simple validation for now.
|
||||
if ( ! is_array( $settings ) ) {
|
||||
$this->logger->log( 'Array expected but not received', __FUNCTION__ );
|
||||
return false;
|
||||
}
|
||||
|
||||
$paper_size = $settings['paper_size'];
|
||||
$this->set_preferred_paper_size( $paper_size );
|
||||
unset( $settings['paper_size'] );
|
||||
|
||||
return WC_Connect_Options::update_option( 'account_settings', $settings );
|
||||
}
|
||||
|
||||
public function get_selected_payment_method_id() {
|
||||
$account_settings = $this->get_account_settings();
|
||||
return intval( $account_settings['selected_payment_method_id'] );
|
||||
}
|
||||
|
||||
public function set_selected_payment_method_id( $new_payment_method_id ) {
|
||||
$new_payment_method_id = intval( $new_payment_method_id );
|
||||
$account_settings = $this->get_account_settings();
|
||||
$old_payment_method_id = intval( $account_settings['selected_payment_method_id'] );
|
||||
if ( $old_payment_method_id === $new_payment_method_id ) {
|
||||
return;
|
||||
}
|
||||
$account_settings['selected_payment_method_id'] = $new_payment_method_id;
|
||||
$this->update_account_settings( $account_settings );
|
||||
}
|
||||
|
||||
public function can_user_manage_payment_methods() {
|
||||
return WC_Connect_Jetpack::is_offline_mode() || WC_Connect_Jetpack::is_current_user_connection_owner();
|
||||
}
|
||||
|
||||
public function get_origin_address() {
|
||||
$wc_address_fields = array();
|
||||
$wc_address_fields['company'] = html_entity_decode( get_bloginfo( 'name' ), ENT_QUOTES ); // HTML entities may be saved in the option.
|
||||
$wc_address_fields['name'] = wp_get_current_user()->display_name;
|
||||
$wc_address_fields['phone'] = '';
|
||||
|
||||
$wc_countries = WC()->countries;
|
||||
// WC 3.2 introduces ability to configure a full address in the settings
|
||||
// Use it for address defaults if available
|
||||
if ( method_exists( $wc_countries, 'get_base_address' ) ) {
|
||||
$wc_address_fields['country'] = $wc_countries->get_base_country();
|
||||
$wc_address_fields['state'] = $wc_countries->get_base_state();
|
||||
$wc_address_fields['address'] = $wc_countries->get_base_address();
|
||||
$wc_address_fields['address_2'] = $wc_countries->get_base_address_2();
|
||||
$wc_address_fields['city'] = $wc_countries->get_base_city();
|
||||
$wc_address_fields['postcode'] = $wc_countries->get_base_postcode();
|
||||
} else {
|
||||
$base_location = wc_get_base_location();
|
||||
$wc_address_fields['country'] = $base_location['country'];
|
||||
$wc_address_fields['state'] = $base_location['state'];
|
||||
$wc_address_fields['address'] = '';
|
||||
$wc_address_fields['address_2'] = '';
|
||||
$wc_address_fields['city'] = '';
|
||||
$wc_address_fields['postcode'] = '';
|
||||
}
|
||||
|
||||
$stored_address_fields = WC_Connect_Options::get_option( 'origin_address', array() );
|
||||
$merged_fields = is_array( $stored_address_fields ) ? array_merge( $wc_address_fields, $stored_address_fields ) : $wc_address_fields;
|
||||
$merged_fields['company'] = html_entity_decode( $merged_fields['company'], ENT_QUOTES ); // Decode again for any existing stores that had some html entities saved in the option.
|
||||
return $merged_fields;
|
||||
}
|
||||
|
||||
public function get_preferred_paper_size() {
|
||||
$paper_size = WC_Connect_Options::get_option( 'paper_size', '' );
|
||||
if ( $paper_size ) {
|
||||
return $paper_size;
|
||||
}
|
||||
// According to https://en.wikipedia.org/wiki/Letter_(paper_size) US, Mexico, Canada and Dominican Republic
|
||||
// use "Letter" size, and pretty much all the rest of the world use A4, so those are sensible defaults.
|
||||
$base_location = wc_get_base_location();
|
||||
if ( in_array( $base_location['country'], array( 'US', 'CA', 'MX', 'DO' ), true ) ) {
|
||||
return 'letter';
|
||||
}
|
||||
return 'a4';
|
||||
}
|
||||
|
||||
public function set_preferred_paper_size( $size ) {
|
||||
return WC_Connect_Options::update_option( 'paper_size', $size );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to recover faulty json string fields that might contain strings with unescaped quotes
|
||||
*
|
||||
* @param string $field_name
|
||||
* @param string $json
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function try_recover_invalid_json_string( $field_name, $json ) {
|
||||
$regex = '/"' . $field_name . '":"(.+?)","/';
|
||||
preg_match_all( $regex, $json, $match_groups );
|
||||
if ( 2 === count( $match_groups ) ) {
|
||||
foreach ( $match_groups[0] as $idx => $match ) {
|
||||
$value = $match_groups[1][ $idx ];
|
||||
$escaped_value = preg_replace( '/(?<!\\\)"/', '\\"', $value );
|
||||
$json = str_replace( $match, '"' . $field_name . '":"' . $escaped_value . '","', $json );
|
||||
}
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to recover faulty json string array fields that might contain strings with unescaped quotes
|
||||
*
|
||||
* @param string $field_name
|
||||
* @param string $json
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function try_recover_invalid_json_array( $field_name, $json ) {
|
||||
$regex = '/"' . $field_name . '":\["(.+?)"\]/';
|
||||
preg_match_all( $regex, $json, $match_groups );
|
||||
if ( 2 === count( $match_groups ) ) {
|
||||
foreach ( $match_groups[0] as $idx => $match ) {
|
||||
$array = $match_groups[1][ $idx ];
|
||||
$escaped_array = preg_replace( '/(?<![,\\\])"(?!,)/', '\\"', $array );
|
||||
$json = str_replace( '["' . $array . '"]', '["' . $escaped_array . '"]', $json );
|
||||
}
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function try_deserialize_labels_json( $label_data ) {
|
||||
// attempt to decode the JSON (legacy way of storing the labels data).
|
||||
$decoded_labels = json_decode( $label_data, true );
|
||||
if ( $decoded_labels ) {
|
||||
return $decoded_labels;
|
||||
}
|
||||
|
||||
$label_data = $this->try_recover_invalid_json_string( 'package_name', $label_data );
|
||||
$decoded_labels = json_decode( $label_data, true );
|
||||
if ( $decoded_labels ) {
|
||||
return $decoded_labels;
|
||||
}
|
||||
|
||||
$label_data = $this->try_recover_invalid_json_array( 'product_names', $label_data );
|
||||
$decoded_labels = json_decode( $label_data, true );
|
||||
if ( $decoded_labels ) {
|
||||
return $decoded_labels;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns labels for the specific order ID
|
||||
*
|
||||
* @param $order_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_label_order_meta_data( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order instanceof WC_Order ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$label_data = $order->get_meta( 'wc_connect_labels', true );
|
||||
// return an empty array if the data doesn't exist.
|
||||
if ( ! $label_data ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// labels stored as an array, return.
|
||||
if ( is_array( $label_data ) ) {
|
||||
return $label_data;
|
||||
}
|
||||
|
||||
return $this->try_deserialize_labels_json( $label_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the existing label data
|
||||
*
|
||||
* @param $order_id
|
||||
* @param $new_label_data
|
||||
*
|
||||
* @return array updated label info
|
||||
*/
|
||||
public function update_label_order_meta_data( $order_id, $new_label_data ) {
|
||||
$result = $new_label_data;
|
||||
$order = wc_get_order( $order_id );
|
||||
$labels_data = $this->get_label_order_meta_data( $order_id );
|
||||
foreach ( $labels_data as $index => $label_data ) {
|
||||
if ( $label_data['label_id'] === $new_label_data->label_id ) {
|
||||
$result = array_merge( $label_data, (array) $new_label_data );
|
||||
$labels_data[ $index ] = $result;
|
||||
|
||||
if ( ! isset( $label_data['tracking'] )
|
||||
&& isset( $result['tracking'] ) ) {
|
||||
WC_Connect_Extension_Compatibility::on_new_tracking_number( $order_id, $result['carrier_id'], $result['tracking'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
$order->update_meta_data( 'wc_connect_labels', $labels_data );
|
||||
$order->save();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new labels to the order
|
||||
*
|
||||
* @param $order_id
|
||||
* @param array $new_labels - labels to be added
|
||||
*/
|
||||
public function add_labels_to_order( $order_id, $new_labels ) {
|
||||
$labels_data = $this->get_label_order_meta_data( $order_id );
|
||||
$labels_data = array_merge( $new_labels, $labels_data );
|
||||
$order = wc_get_order( $order_id );
|
||||
$order->update_meta_data( 'wc_connect_labels', $labels_data );
|
||||
$order->save();
|
||||
}
|
||||
|
||||
public function update_origin_address( $address ) {
|
||||
return WC_Connect_Options::update_option( 'origin_address', $address );
|
||||
}
|
||||
|
||||
public function update_destination_address( $order_id, $api_address ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
$wc_address = $order->get_address( 'shipping' );
|
||||
|
||||
$new_address = array_merge( array(), (array) $wc_address, (array) $api_address );
|
||||
// rename address to address_1.
|
||||
$new_address['address_1'] = $new_address['address'];
|
||||
// remove api-specific fields.
|
||||
unset( $new_address['address'], $new_address['name'] );
|
||||
|
||||
foreach ( $new_address as $key => $value ) {
|
||||
if ( method_exists( $order, 'set_shipping_' . $key ) ) {
|
||||
call_user_func( array( $order, 'set_shipping_' . $key ), $value );
|
||||
}
|
||||
}
|
||||
|
||||
$order->update_meta_data( '_wc_connect_destination_normalized', true );
|
||||
$order->save();
|
||||
}
|
||||
|
||||
protected function sort_services( $a, $b ) {
|
||||
|
||||
if ( $a->zone_order === $b->zone_order ) {
|
||||
return ( $a->instance_id > $b->instance_id ) ? 1 : -1;
|
||||
}
|
||||
|
||||
if ( is_null( $a->zone_order ) ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( is_null( $b->zone_order ) ) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ( $a->instance_id > $b->instance_id ) ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the service type and id for each enabled WooCommerce Shipping & Tax service
|
||||
*
|
||||
* Shipping services also include instance_id and shipping zone id
|
||||
*
|
||||
* Note that at this time, only shipping services exist, but this method will
|
||||
* return other services in the future
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_enabled_services() {
|
||||
$shipping_services = $this->service_schemas_store->get_all_shipping_method_ids();
|
||||
if ( empty( $shipping_services ) ) {
|
||||
return array();
|
||||
}
|
||||
return $this->get_enabled_services_by_ids( $shipping_services );
|
||||
}
|
||||
|
||||
public function get_enabled_services_by_ids( $service_ids ) {
|
||||
if ( empty( $service_ids ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$enabled_services = array();
|
||||
|
||||
// Note: We use esc_sql here instead of prepare because we are using WHERE IN
|
||||
// https://codex.wordpress.org/Function_Reference/esc_sql.
|
||||
|
||||
$escaped_list = '';
|
||||
foreach ( $service_ids as $shipping_service ) {
|
||||
if ( ! empty( $escaped_list ) ) {
|
||||
$escaped_list .= ',';
|
||||
}
|
||||
$escaped_list .= "'" . esc_sql( $shipping_service ) . "'";
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared --- Need to use interpolated for the `IN()` condition
|
||||
$methods = $wpdb->get_results(
|
||||
"SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_methods " .
|
||||
"LEFT JOIN {$wpdb->prefix}woocommerce_shipping_zones " .
|
||||
"ON {$wpdb->prefix}woocommerce_shipping_zone_methods.zone_id = {$wpdb->prefix}woocommerce_shipping_zones.zone_id " .
|
||||
"WHERE method_id IN ({$escaped_list}) " .
|
||||
'ORDER BY zone_order, instance_id;'
|
||||
);
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
|
||||
if ( empty( $methods ) ) {
|
||||
return $enabled_services;
|
||||
}
|
||||
|
||||
foreach ( (array) $methods as $method ) {
|
||||
$service_schema = $this->service_schemas_store->get_service_schema_by_method_id( $method->method_id );
|
||||
$service_settings = $this->get_service_settings( $method->method_id, $method->instance_id );
|
||||
if ( is_object( $service_settings ) && property_exists( $service_settings, 'title' ) ) {
|
||||
$title = $service_settings->title;
|
||||
} elseif ( is_object( $service_schema ) && property_exists( $service_schema, 'method_title' ) ) {
|
||||
$title = $service_schema->method_title;
|
||||
} else {
|
||||
$title = _x( 'Unknown', 'A service with an unknown title and unknown method_title', 'woocommerce-services' );
|
||||
}
|
||||
$method->service_type = 'shipping';
|
||||
$method->title = $title;
|
||||
$method->zone_name = empty( $method->zone_name ) ? __( 'Rest of the World', 'woocommerce-services' ) : $method->zone_name;
|
||||
$enabled_services[] = $method;
|
||||
}
|
||||
|
||||
usort( $enabled_services, array( $this, 'sort_services' ) );
|
||||
return $enabled_services;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the shipping method ids have been migrated to the "wc_services_*" format and migrates them
|
||||
*/
|
||||
public function migrate_legacy_services() {
|
||||
if ( WC_Connect_Options::get_option( 'shipping_methods_migrated', false ) ) { // check if the method have already been migrated.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->service_schemas_store->fetch_service_schemas_from_connect_server() ) { // ensure the latest schemas are fetched.
|
||||
// No schemes exist this is a site that has nothing to migrate.
|
||||
WC_Connect_Options::update_option( 'shipping_methods_migrated', true );
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// old services used the id field instead of method_id.
|
||||
$shipping_service_ids = $this->service_schemas_store->get_all_service_ids_of_type( 'shipping' );
|
||||
$legacy_services = $this->get_enabled_services_by_ids( $shipping_service_ids );
|
||||
|
||||
foreach ( $legacy_services as $legacy_service ) {
|
||||
$service_id = $legacy_service->method_id;
|
||||
$instance_id = $legacy_service->instance_id;
|
||||
$service_schema = $this->service_schemas_store->get_service_schema_by_id( $service_id );
|
||||
$service_settings = $this->get_service_settings( $service_id, $instance_id );
|
||||
if ( ( is_array( $service_settings ) && ! $service_settings ) // check for an empty array.
|
||||
|| ( ! is_array( $service_settings ) && ! is_object( $service_settings ) ) ) { // settings are neither an array nor an object.
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_method_id = $service_schema->method_id;
|
||||
|
||||
$wpdb->update(
|
||||
"{$wpdb->prefix}woocommerce_shipping_zone_methods",
|
||||
array( 'method_id' => $new_method_id ),
|
||||
array(
|
||||
'instance_id' => $instance_id,
|
||||
'method_id' => $service_id,
|
||||
),
|
||||
array( '%s' ),
|
||||
array( '%d', '%s' )
|
||||
);
|
||||
|
||||
// update the migrated service settings.
|
||||
WC_Connect_Options::update_shipping_method_option( 'form_settings', $service_settings, $new_method_id, $instance_id );
|
||||
// delete the old service settings.
|
||||
WC_Connect_Options::delete_shipping_method_options( $service_id, $instance_id );
|
||||
}
|
||||
|
||||
WC_Connect_Options::update_option( 'shipping_methods_migrated', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a service's id and optional instance, returns the settings for that
|
||||
* service or an empty array
|
||||
*
|
||||
* @param string $service_id
|
||||
* @param integer $service_instance
|
||||
*
|
||||
* @return object|array
|
||||
*/
|
||||
public function get_service_settings( $service_id, $service_instance = false ) {
|
||||
return WC_Connect_Options::get_shipping_method_option( 'form_settings', array(), $service_id, $service_instance );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given id and possibly instance, validates the settings and, if they validate, saves them to options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function validate_and_possibly_update_settings( $settings, $id, $instance = false ) {
|
||||
|
||||
// Validate instance or at least id if no instance is given.
|
||||
if ( ! empty( $instance ) ) {
|
||||
$service_schema = $this->service_schemas_store->get_service_schema_by_instance_id( $instance );
|
||||
if ( ! $service_schema ) {
|
||||
return new WP_Error( 'bad_instance_id', __( 'An invalid service instance was received.', 'woocommerce-services' ) );
|
||||
}
|
||||
} else {
|
||||
$service_schema = $this->service_schemas_store->get_service_schema_by_method_id( $id );
|
||||
if ( ! $service_schema ) {
|
||||
return new WP_Error( 'bad_service_id', __( 'An invalid service ID was received.', 'woocommerce-services' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Validate settings with WCC server.
|
||||
$response_body = $this->api_client->validate_service_settings( $service_schema->id, $settings );
|
||||
|
||||
if ( is_wp_error( $response_body ) ) {
|
||||
// TODO - handle multiple error messages when the validation endpoint can return them
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
// On success, save the settings to the database and exit.
|
||||
WC_Connect_Options::update_shipping_method_option( 'form_settings', $settings, $id, $instance );
|
||||
// Invalidate shipping rates session cache.
|
||||
WC_Cache_Helper::get_transient_version( 'shipping', /* $refresh = */ true );
|
||||
do_action( 'wc_connect_saved_service_settings', $id, $instance, $settings );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a global list of packages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_packages() {
|
||||
return WC_Connect_Options::get_option( 'packages', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the global list of packages with a list of new packages
|
||||
*
|
||||
* @param array new_packages - packages to extend
|
||||
*/
|
||||
public function create_packages( $new_packages ) {
|
||||
if ( is_null( $new_packages ) ) {
|
||||
return;
|
||||
}
|
||||
$packages = $this->get_packages();
|
||||
$packages = array_merge( $packages, $new_packages );
|
||||
WC_Connect_Options::update_option( 'packages', $packages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the global list of packages
|
||||
*
|
||||
* @param array packages
|
||||
*/
|
||||
public function update_packages( $packages ) {
|
||||
WC_Connect_Options::update_option( 'packages', $packages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a global list of enabled predefined packages for all services
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_predefined_packages() {
|
||||
return WC_Connect_Options::get_option( 'predefined_packages', array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of enabled predefined packages for the specified service
|
||||
*
|
||||
* @param $service_id
|
||||
* @return array
|
||||
*/
|
||||
public function get_predefined_packages_for_service( $service_id ) {
|
||||
$packages = $this->get_predefined_packages();
|
||||
if ( ! isset( $packages[ $service_id ] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $packages[ $service_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the global list of enabled predefined packages with a list of new packages
|
||||
*
|
||||
* @param array new_packages - packages to extend
|
||||
*/
|
||||
public function create_predefined_packages( $new_packages ) {
|
||||
if ( is_null( $new_packages ) ) {
|
||||
return;
|
||||
}
|
||||
$packages = $this->get_predefined_packages();
|
||||
$packages = array_merge_recursive( $packages, $new_packages );
|
||||
WC_Connect_Options::update_option( 'predefined_packages', $packages );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the global list of enabled predefined packages for all services
|
||||
*
|
||||
* @param array packages
|
||||
*/
|
||||
public function update_predefined_packages( $packages ) {
|
||||
WC_Connect_Options::update_option( 'predefined_packages', $packages );
|
||||
}
|
||||
|
||||
public function get_package_lookup() {
|
||||
$lookup = array();
|
||||
|
||||
$custom_packages = $this->get_packages();
|
||||
foreach ( $custom_packages as $custom_package ) {
|
||||
$lookup[ $custom_package['name'] ] = $custom_package;
|
||||
}
|
||||
|
||||
$predefined_packages_schema = $this->service_schemas_store->get_predefined_packages_schema();
|
||||
if ( is_null( $predefined_packages_schema ) ) {
|
||||
return $lookup;
|
||||
}
|
||||
|
||||
foreach ( $predefined_packages_schema as $service_id => $groups ) {
|
||||
foreach ( $groups as $group ) {
|
||||
foreach ( $group->definitions as $predefined ) {
|
||||
$lookup[ $predefined->id ] = (array) $predefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $lookup;
|
||||
}
|
||||
|
||||
public function is_eligible_for_migration() {
|
||||
$migration_state = intval( get_option( 'wcshipping_migration_state', 0 ) );
|
||||
|
||||
// If the migration state is greater than "COMPLETED", then we can assume that the next part of the migration
|
||||
// state is being handled by WooCommerce Shipping.
|
||||
if ( $migration_state > WC_Connect_WCST_To_WCShipping_Migration_State_Enum::COMPLETED ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide the migration notification if the site has any active shipping methods defined by WCS&T.
|
||||
if ( ! empty( $this->get_enabled_services() ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$migration_dismissed = false;
|
||||
if ( isset( $_COOKIE[ WC_Connect_Loader::MIGRATION_DISMISSAL_COOKIE_KEY ] ) && (int) $_COOKIE[ WC_Connect_Loader::MIGRATION_DISMISSAL_COOKIE_KEY ] === 1 ) {
|
||||
$migration_dismissed = true;
|
||||
}
|
||||
|
||||
$migration_pending = ! $migration_state || WC_Connect_WCST_To_WCShipping_Migration_State_Enum::COMPLETED !== $migration_state;
|
||||
$migration_enabled = $this->service_schemas_store->is_wcship_wctax_migration_enabled();
|
||||
|
||||
return $migration_pending && $migration_enabled && ! $migration_dismissed;
|
||||
}
|
||||
|
||||
private function translate_unit( $value ) {
|
||||
switch ( $value ) {
|
||||
case 'kg':
|
||||
return __( 'kg', 'woocommerce-services' );
|
||||
case 'g':
|
||||
return __( 'g', 'woocommerce-services' );
|
||||
case 'lbs':
|
||||
return __( 'lbs', 'woocommerce-services' );
|
||||
case 'oz':
|
||||
return __( 'oz', 'woocommerce-services' );
|
||||
case 'm':
|
||||
return __( 'm', 'woocommerce-services' );
|
||||
case 'cm':
|
||||
return __( 'cm', 'woocommerce-services' );
|
||||
case 'mm':
|
||||
return __( 'mm', 'woocommerce-services' );
|
||||
case 'in':
|
||||
return __( 'in', 'woocommerce-services' );
|
||||
case 'yd':
|
||||
return __( 'yd', 'woocommerce-services' );
|
||||
default:
|
||||
$this->logger->log( 'Unexpected measurement unit: ' . $value, __FUNCTION__ );
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Settings_Pages' ) ) {
|
||||
|
||||
class WC_Connect_Settings_Pages {
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $service_schemas_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Continents
|
||||
*/
|
||||
protected $continents;
|
||||
|
||||
/**
|
||||
* @var string;
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @var string;
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Service_Schemas_Store $service_schemas_store ) {
|
||||
$this->id = 'connect';
|
||||
$this->label = _x( 'WooCommerce Shipping', 'The WooCommerce Shipping & Tax brandname', 'woocommerce-services' );
|
||||
$this->continents = new WC_Connect_Continents();
|
||||
$this->api_client = $api_client;
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
|
||||
add_filter( 'woocommerce_get_sections_shipping', array( $this, 'get_sections' ), 30 );
|
||||
add_action( 'woocommerce_settings_shipping', array( $this, 'output_settings_screen' ), 5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sections.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
$shipping_tabs['woocommerce-services-settings'] = __( 'WooCommerce Shipping', 'woocommerce-services' );
|
||||
return $shipping_tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the settings.
|
||||
*/
|
||||
public function output_settings_screen() {
|
||||
global $current_section;
|
||||
|
||||
if ( 'woocommerce-services-settings' !== $current_section ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'woocommerce_get_settings_shipping', '__return_empty_array' );
|
||||
$this->output_shipping_settings_screen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes the bootstrap, enqueues the script and styles for the settings page
|
||||
*/
|
||||
public function output_shipping_settings_screen() {
|
||||
// hiding the save button because the react container has its own.
|
||||
global $hide_save_button;
|
||||
$hide_save_button = true;
|
||||
|
||||
if ( WC_Connect_Jetpack::is_offline_mode() ) {
|
||||
if ( WC_Connect_Jetpack::is_connected() ) {
|
||||
$message = __( 'Note: Your site is connected but WooCommerce Shipping & Tax is configured to work in offline mode. Please disable offline mode.', 'woocommerce-services' );
|
||||
} else {
|
||||
$message = __( 'Note: WooCommerce Shipping & Tax is configured to work in offline mode. This site will not be able to obtain payment methods from WooCommerce Shipping & Tax production servers.', 'woocommerce-services' );
|
||||
}
|
||||
?>
|
||||
<div class="wc-connect-admin-dev-notice">
|
||||
<p>
|
||||
<?php echo esc_html( $message ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
$extra_args = array(
|
||||
'live_rates_types' => $this->service_schemas_store->get_all_shipping_method_ids(),
|
||||
);
|
||||
|
||||
$carriers_response = $this->api_client->get_carrier_accounts();
|
||||
|
||||
if ( ! is_wp_error( $carriers_response ) && ! empty( $carriers_response->carriers ) ) {
|
||||
$extra_args['carrier_accounts'] = $carriers_response->carriers;
|
||||
}
|
||||
|
||||
// check the helper auth before calling wccom subscription api.
|
||||
if ( ! is_wp_error( WC_Connect_Functions::get_wc_helper_auth_info() ) ) {
|
||||
$subscriptions_usage_response = $this->api_client->get_wccom_subscriptions();
|
||||
|
||||
if ( ! is_wp_error( $subscriptions_usage_response ) && ! empty( $subscriptions_usage_response->subscriptions ) ) {
|
||||
$extra_args['subscriptions'] = $subscriptions_usage_response->subscriptions;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $_GET['from_order'] ) ) {
|
||||
$from_order = sanitize_text_field( $_GET['from_order'] );
|
||||
$extra_args['order_id'] = $from_order;
|
||||
$extra_args['order_href'] = get_edit_post_link( $from_order );
|
||||
}
|
||||
|
||||
if ( ! empty( $_GET['carrier'] ) ) {
|
||||
$carrier = sanitize_text_field( $_GET['carrier'] );
|
||||
$extra_args['carrier'] = $carrier;
|
||||
$extra_args['continents'] = $this->continents->get();
|
||||
|
||||
$carrier_information = array();
|
||||
if ( ! empty( $extra_args['carrier_accounts'] ) ) {
|
||||
$carrier_information = current(
|
||||
array_filter(
|
||||
$extra_args['carrier_accounts'],
|
||||
function ( $carrier ) {
|
||||
return $carrier->type === $carrier;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( ! empty( $carrier_information ) ) {
|
||||
?>
|
||||
<h2>
|
||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=woocommerce-services-settings' ) ); ?>"><?php esc_html_e( 'WooCommerce Shipping & Tax', 'woocommerce-services' ); ?></a> >
|
||||
<span><?php echo esc_html( $carrier_information->carrier ); ?></span>
|
||||
</h2>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
do_action( 'enqueue_wc_connect_script', 'wc-connect-shipping-settings', $extra_args );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Shipping_Label' ) ) {
|
||||
|
||||
class WC_Connect_Shipping_Label {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $service_schemas_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Account_Settings
|
||||
*/
|
||||
protected $account_settings;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Package_Settings
|
||||
*/
|
||||
protected $package_settings;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Continents
|
||||
*/
|
||||
protected $continents;
|
||||
|
||||
/**
|
||||
* @var array Supported countries by USPS, see: https://webpmt.usps.gov/pmt010.cfm
|
||||
*/
|
||||
private $supported_countries = array( 'US', 'AS', 'PR', 'VI', 'GU', 'MP', 'UM', 'FM', 'MH' );
|
||||
|
||||
/**
|
||||
* @var array Supported currencies
|
||||
*/
|
||||
private $supported_currencies = array( 'USD' );
|
||||
|
||||
private $show_metabox = null;
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_API_Client $api_client,
|
||||
WC_Connect_Service_Settings_Store $settings_store,
|
||||
WC_Connect_Service_Schemas_Store $service_schemas_store,
|
||||
WC_Connect_Payment_Methods_Store $payment_methods_store
|
||||
) {
|
||||
$this->api_client = $api_client;
|
||||
$this->settings_store = $settings_store;
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
$this->account_settings = new WC_Connect_Account_Settings(
|
||||
$settings_store,
|
||||
$payment_methods_store
|
||||
);
|
||||
$this->package_settings = new WC_Connect_Package_Settings(
|
||||
$settings_store,
|
||||
$service_schemas_store
|
||||
);
|
||||
$this->continents = new WC_Connect_Continents();
|
||||
}
|
||||
|
||||
public function get_item_data( WC_Order $order, $item ) {
|
||||
$product = WC_Connect_Utils::get_item_product( $order, $item );
|
||||
if ( ! $product || ! $product->needs_shipping() ) {
|
||||
return null;
|
||||
}
|
||||
$height = 0;
|
||||
$length = 0;
|
||||
$weight = $product->get_weight();
|
||||
$width = 0;
|
||||
|
||||
if ( $product->has_dimensions() ) {
|
||||
$height = $product->get_height();
|
||||
$length = $product->get_length();
|
||||
$width = $product->get_width();
|
||||
}
|
||||
$parent_product_id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
|
||||
$product_data = array(
|
||||
'height' => (float) $height,
|
||||
'product_id' => $product->get_id(),
|
||||
'length' => (float) $length,
|
||||
'quantity' => 1,
|
||||
'weight' => (float) $weight,
|
||||
'width' => (float) $width,
|
||||
'name' => $this->get_name( $product ),
|
||||
'url' => get_edit_post_link( $parent_product_id, null ),
|
||||
);
|
||||
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$product_data['attributes'] = wc_get_formatted_variation( $product, true );
|
||||
}
|
||||
|
||||
return $product_data;
|
||||
}
|
||||
|
||||
protected function get_packaging_from_shipping_method( $shipping_method ) {
|
||||
if ( ! $shipping_method || ! isset( $shipping_method['wc_connect_packages'] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$packages_data = $shipping_method['wc_connect_packages'];
|
||||
if ( ! $packages_data ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// WC3 retrieves metadata as non-scalar values.
|
||||
if ( is_array( $packages_data ) ) {
|
||||
return $packages_data;
|
||||
}
|
||||
|
||||
// WC2.6 stores non-scalar values as string, but doesn't deserialize it on retrieval.
|
||||
$packages = maybe_unserialize( $packages_data );
|
||||
if ( is_array( $packages ) ) {
|
||||
return $packages;
|
||||
}
|
||||
|
||||
// legacy WCS stored the labels as JSON.
|
||||
$packages = json_decode( $packages_data, true );
|
||||
if ( $packages ) {
|
||||
return $packages;
|
||||
}
|
||||
|
||||
$packages_data = $this->settings_store->try_recover_invalid_json_string( 'box_id', $packages_data );
|
||||
$packages = json_decode( $packages_data, true );
|
||||
if ( $packages ) {
|
||||
return $packages;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function get_packaging_metadata( WC_Order $order ) {
|
||||
$shipping_methods = $order->get_shipping_methods();
|
||||
$shipping_method = reset( $shipping_methods );
|
||||
$packaging = $this->get_packaging_from_shipping_method( $shipping_method );
|
||||
|
||||
if ( is_array( $packaging ) ) {
|
||||
return array_filter( $packaging );
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function get_name( WC_Product $product ) {
|
||||
if ( $product->get_sku() ) {
|
||||
$identifier = $product->get_sku();
|
||||
} else {
|
||||
$identifier = '#' . $product->get_id();
|
||||
}
|
||||
return sprintf( '%s - %s', $identifier, $product->get_title() );
|
||||
}
|
||||
|
||||
public function get_selected_packages( WC_Order $order ) {
|
||||
$packages = $this->get_packaging_metadata( $order );
|
||||
if ( ! $packages ) {
|
||||
$items = $this->get_all_items( $order );
|
||||
$weight = array_sum( wp_list_pluck( $items, 'weight' ) );
|
||||
|
||||
$packages = array(
|
||||
'default_box' => array(
|
||||
'id' => 'default_box',
|
||||
'box_id' => 'not_selected',
|
||||
'height' => 0,
|
||||
'length' => 0,
|
||||
'weight' => $weight,
|
||||
'width' => 0,
|
||||
'items' => $items,
|
||||
),
|
||||
);
|
||||
}
|
||||
$formatted_packages = array();
|
||||
|
||||
foreach ( $packages as $package_obj ) {
|
||||
$package = (array) $package_obj;
|
||||
$package_id = $package['id'];
|
||||
$formatted_packages[ $package_id ] = $package;
|
||||
|
||||
foreach ( $package['items'] as $item_index => $item ) {
|
||||
$product_data = (array) $item;
|
||||
$product = WC_Connect_Utils::get_item_product( $order, $product_data );
|
||||
|
||||
if ( $product ) {
|
||||
$parent_product_id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
|
||||
$product_data['name'] = $this->get_name( $product );
|
||||
$product_data['url'] = get_edit_post_link( $parent_product_id, null );
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$product_data['attributes'] = wc_get_formatted_variation( $product, true );
|
||||
}
|
||||
$customs_info = $product->get_meta( 'wc_connect_customs_info', true );
|
||||
if ( is_array( $customs_info ) ) {
|
||||
$product_data = array_merge( $product_data, $customs_info );
|
||||
}
|
||||
} else {
|
||||
$product_data['name'] = WC_Connect_Utils::get_product_name_from_order( $product_data['product_id'], $order );
|
||||
}
|
||||
$product_data['value'] = WC_Connect_Utils::get_product_price_from_order( $product_data['product_id'], $order );
|
||||
if ( ! isset( $product_data['value'] ) ) {
|
||||
$product_data['value'] = 0;
|
||||
}
|
||||
|
||||
$formatted_packages[ $package_id ]['items'][ $item_index ] = $product_data;
|
||||
}
|
||||
}
|
||||
|
||||
return $formatted_packages;
|
||||
}
|
||||
|
||||
public function get_all_items( WC_Order $order ) {
|
||||
if ( $this->get_packaging_metadata( $order ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$items = array();
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$item_data = $this->get_item_data( $order, $item );
|
||||
if ( null === $item_data ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$refunded_qty = $order->get_qty_refunded_for_item( $item->get_id() );
|
||||
$remaining_quantity = $item['qty'] - absint( $refunded_qty );
|
||||
/**
|
||||
* The threshold at which we will start batching items together.
|
||||
* As an example, if a single order item has a quantity 60, which is more than the default threshold
|
||||
* value of 20 we will start batching together.
|
||||
*/
|
||||
$threshold = apply_filters( 'wc_connect_shipment_item_quantity_threshold', 20 );
|
||||
/**
|
||||
* Max number of shipments allowed to be created for this item should quantity of this order items
|
||||
* exceeds `wc_connect_shipment_item_quantity_threshold`
|
||||
*/
|
||||
$max_shipments = apply_filters( 'wc_connect_max_shipments_if_quantity_exceeds_threshold', 5 );
|
||||
|
||||
$weight_per_item = $item_data['weight'];
|
||||
$should_cap_shipments = $remaining_quantity > $threshold;
|
||||
|
||||
if ( $should_cap_shipments ) {
|
||||
$quantity_per_shipment = floor( $remaining_quantity / $max_shipments );
|
||||
for ( $i = 0; $i < $max_shipments; $i ++ ) {
|
||||
$remaining_quantity -= $quantity_per_shipment;
|
||||
|
||||
if( $remaining_quantity >= $quantity_per_shipment ) {
|
||||
$item_data['quantity'] = $quantity_per_shipment;
|
||||
} else {
|
||||
$item_data['quantity'] = $quantity_per_shipment + $remaining_quantity;
|
||||
}
|
||||
|
||||
$item_data['weight'] = round( $item_data['quantity'] * $weight_per_item, 2 );
|
||||
$items[] = $item_data;
|
||||
}
|
||||
} else {
|
||||
for ( $i = 0; $i < $remaining_quantity; $i ++ ) {
|
||||
$items[] = $item_data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function get_selected_rates( WC_Order $order ) {
|
||||
$shipping_methods = $order->get_shipping_methods();
|
||||
$shipping_method = reset( $shipping_methods );
|
||||
$packages = $this->get_packaging_from_shipping_method( $shipping_method );
|
||||
$rates = array();
|
||||
|
||||
foreach ( $packages as $idx => $package_obj ) {
|
||||
$package = (array) $package_obj;
|
||||
// Abort if the package data is malformed
|
||||
if ( ! isset( $package['id'] ) || ! isset( $package['service_id'] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$rates[ $package['id'] ] = $package['service_id'];
|
||||
}
|
||||
|
||||
return $rates;
|
||||
}
|
||||
|
||||
protected function format_address_for_api( $address ) {
|
||||
// Combine first and last name.
|
||||
if ( ! isset( $address['name'] ) ) {
|
||||
$first_name = isset( $address['first_name'] ) ? trim( $address['first_name'] ) : '';
|
||||
$last_name = isset( $address['last_name'] ) ? trim( $address['last_name'] ) : '';
|
||||
|
||||
$address['name'] = $first_name . ' ' . $last_name;
|
||||
}
|
||||
|
||||
// Rename address_1 to address.
|
||||
if ( ! isset( $address['address'] ) && isset( $address['address_1'] ) ) {
|
||||
$address['address'] = $address['address_1'];
|
||||
}
|
||||
|
||||
// Remove now defunct keys.
|
||||
unset( $address['first_name'], $address['last_name'], $address['address_1'] );
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
protected function get_origin_address() {
|
||||
$origin = $this->format_address_for_api( $this->settings_store->get_origin_address() );
|
||||
|
||||
return $origin;
|
||||
}
|
||||
|
||||
protected function get_destination_address( WC_Order $order ) {
|
||||
$order_address = $order->get_address( 'shipping' );
|
||||
$destination = $this->format_address_for_api( $order_address );
|
||||
|
||||
return $destination;
|
||||
}
|
||||
|
||||
protected function get_form_data( WC_Order $order ) {
|
||||
$order_id = $order->get_id();
|
||||
$selected_packages = $this->get_selected_packages( $order );
|
||||
$is_packed = ( false !== $this->get_packaging_metadata( $order ) );
|
||||
$origin = $this->get_origin_address();
|
||||
$selected_rates = $this->get_selected_rates( $order );
|
||||
$destination = $this->get_destination_address( $order );
|
||||
|
||||
if ( ! $destination['country'] ) {
|
||||
$destination['country'] = $origin['country'];
|
||||
}
|
||||
|
||||
$origin_normalized = (bool) WC_Connect_Options::get_option( 'origin_address', false );
|
||||
$destination_normalized = (bool) $order->get_meta( '_wc_connect_destination_normalized', true );
|
||||
|
||||
$form_data = compact( 'is_packed', 'selected_packages', 'origin', 'destination', 'origin_normalized', 'destination_normalized' );
|
||||
|
||||
$form_data['rates'] = array(
|
||||
'selected' => (object) $selected_rates,
|
||||
);
|
||||
|
||||
$form_data['order_id'] = $order_id;
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given order is eligible for shipping label creation - the order has at least one product that is:
|
||||
* - Shippable.
|
||||
* - Non-refunded.
|
||||
*
|
||||
* @param WC_Order $order The order to check for shipping label creation eligibility.
|
||||
* @return bool Whether the given order is eligible for shipping label creation.
|
||||
*/
|
||||
public function is_order_eligible_for_shipping_label_creation( WC_Order $order ) {
|
||||
// Set up a dictionary from product ID to quantity in the order, which will be updated by refunds and existing labels later.
|
||||
$quantities_by_product_id = array();
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$product = WC_Connect_Utils::get_item_product( $order, $item );
|
||||
if ( $product && $product->needs_shipping() ) {
|
||||
$product_id = $product->get_id();
|
||||
$current_quantity = array_key_exists( $product_id, $quantities_by_product_id ) ? $quantities_by_product_id[ $product_id ] : 0;
|
||||
$quantities_by_product_id[ $product_id ] = $current_quantity + $item->get_quantity();
|
||||
}
|
||||
}
|
||||
|
||||
// A shipping label cannot be created without a shippable product.
|
||||
if ( empty( $quantities_by_product_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the quantity for each refunded product ID in the order.
|
||||
foreach ( $order->get_refunds() as $refund ) {
|
||||
foreach ( $refund->get_items() as $refunded_item ) {
|
||||
$product = WC_Connect_Utils::get_item_product( $order, $refunded_item );
|
||||
if ( ! is_a( $product, 'WC_Product' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$product_id = $product->get_id();
|
||||
if ( array_key_exists( $product_id, $quantities_by_product_id ) ) {
|
||||
$current_count = $quantities_by_product_id[ $product_id ];
|
||||
$quantities_by_product_id[ $product_id ] = $current_count - abs( $refunded_item->get_quantity() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The order is eligible for shipping label creation when there is at least one product with positive quantity.
|
||||
foreach ( $quantities_by_product_id as $product_id => $quantity ) {
|
||||
if ( $quantity > 0 ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the store is eligible for shipping label creation:
|
||||
* - Store currency is supported.
|
||||
* - Store country is supported.
|
||||
*
|
||||
* @return bool Whether the WC store is eligible for shipping label creation.
|
||||
*/
|
||||
public function is_store_eligible_for_shipping_label_creation() {
|
||||
$base_currency = get_woocommerce_currency();
|
||||
if ( ! $this->is_supported_currency( $base_currency ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$base_location = wc_get_base_location();
|
||||
if ( ! $this->is_supported_country( $base_location['country'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given country code is supported for shipping labels.
|
||||
*
|
||||
* @param string $country_code Country code of the WC store.
|
||||
* @return bool Whether the given country code is supported for shipping labels.
|
||||
*/
|
||||
private function is_supported_country( $country_code ) {
|
||||
return in_array( $country_code, $this->supported_countries, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given currency code is supported for shipping labels.
|
||||
*
|
||||
* @param string $currency_code Currency code of the WC store.
|
||||
* @return bool Whether the given country code is supported for shipping labels.
|
||||
*/
|
||||
private function is_supported_currency( $currency_code ) {
|
||||
return in_array( $currency_code, $this->supported_currencies, true );
|
||||
}
|
||||
|
||||
public function is_dhl_express_available() {
|
||||
$dhl_express = $this->service_schemas_store->get_service_schema_by_id( 'dhlexpress' );
|
||||
|
||||
return ! ! $dhl_express;
|
||||
}
|
||||
|
||||
public function is_order_dhl_express_eligible() {
|
||||
if ( ! $this->is_dhl_express_available() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
global $post;
|
||||
|
||||
$order = WC_Connect_Compatibility::instance()->init_theorder_object( $post );
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$origin = $this->get_origin_address();
|
||||
$destination = $this->get_destination_address( $order );
|
||||
|
||||
return $origin['country'] !== $destination['country'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if meta boxes should be displayed.
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return boolean
|
||||
*/
|
||||
public function should_show_meta_box( $post ) {
|
||||
if ( null === $this->show_metabox ) {
|
||||
$this->show_metabox = $this->calculate_should_show_meta_box( $post );
|
||||
}
|
||||
|
||||
return $this->show_metabox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if meta boxes should be displayed.
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return bool
|
||||
*/
|
||||
private function calculate_should_show_meta_box( $post ) {
|
||||
// not all users have the permission to manage shipping labels.
|
||||
// if a request is made to the JS backend and the user doesn't have permission, an error would be displayed.
|
||||
if ( ! WC_Connect_Functions::user_can_manage_labels() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = WC_Connect_Compatibility::instance()->init_theorder_object( $post );
|
||||
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the shipping label is disabled, will remove the meta box.
|
||||
if ( ! $this->is_shipping_label_enabled() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the order already has purchased labels, show the meta-box no matter what.
|
||||
if ( $order->get_meta( 'wc_connect_labels', true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Restrict showing the metabox to supported store countries and currencies.
|
||||
if ( ! $this->is_store_eligible_for_shipping_label_creation() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the order was created using WCS checkout rates, show the meta-box regardless of the products' state.
|
||||
if ( $this->get_packaging_metadata( $order ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// At this point (no packaging data), only show if there's at least one existing and shippable product.
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$product = WC_Connect_Utils::get_item_product( $order, $item );
|
||||
if ( $product && $product->needs_shipping() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether shipping label feature is enabled from WC Services setting.
|
||||
*
|
||||
* @return bool True if shipping label is enabled from the settings.
|
||||
*/
|
||||
public function is_shipping_label_enabled() {
|
||||
$account_settings = $this->account_settings->get();
|
||||
|
||||
if ( isset( $account_settings['formData']['enabled'] ) && is_bool( $account_settings['formData']['enabled'] ) ) {
|
||||
return $account_settings['formData']['enabled'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_label_payload( $post_order_or_id ) {
|
||||
$order = wc_get_order( $post_order_or_id );
|
||||
if ( ! is_a( $order, 'WC_Order' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order_id = $order->get_id();
|
||||
$payload = array(
|
||||
'orderId' => $order_id,
|
||||
'paperSize' => $this->settings_store->get_preferred_paper_size(),
|
||||
'formData' => $this->get_form_data( $order ),
|
||||
'labelsData' => $this->settings_store->get_label_order_meta_data( $order_id ),
|
||||
'storeOptions' => $this->settings_store->get_store_options(),
|
||||
// for backwards compatibility, still disable the country dropdown for calypso users with older plugin versions.
|
||||
'canChangeCountries' => true,
|
||||
);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter items needing shipping callback.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @param array $item Item to check for shipping.
|
||||
* @return bool
|
||||
*/
|
||||
public function filter_items_needing_shipping( $item ) {
|
||||
$product = $item->get_product();
|
||||
return $product && $product->needs_shipping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce items to sum their quantities.
|
||||
*
|
||||
* @param int $sum Current sum.
|
||||
* @param array $item Item to add to sum.
|
||||
* @return int
|
||||
*/
|
||||
protected function reducer_items_quantity( $sum, $item ) {
|
||||
return $sum + $item->get_quantity();
|
||||
}
|
||||
|
||||
public function meta_box( $post, $args ) {
|
||||
|
||||
$connect_order_presenter = new WC_Connect_Order_Presenter();
|
||||
$order = WC_Connect_Compatibility::instance()->init_theorder_object( $post );
|
||||
$items = array_filter( $order->get_items(), array( $this, 'filter_items_needing_shipping' ) );
|
||||
$items_count = array_reduce( $items, array( $this, 'reducer_items_quantity' ), 0 ) - absint( $order->get_item_count_refunded() );
|
||||
$payload = apply_filters(
|
||||
'wc_connect_meta_box_payload',
|
||||
array(
|
||||
'order' => $connect_order_presenter->get_order_for_api( $order ),
|
||||
'accountSettings' => $this->account_settings->get(),
|
||||
'packagesSettings' => $this->package_settings->get(),
|
||||
'shippingLabelData' => $this->get_label_payload( $order->get_id() ),
|
||||
'continents' => $this->continents->get(),
|
||||
'euCountries' => WC()->countries->get_european_union_countries(),
|
||||
'context' => $args['args']['context'],
|
||||
'items' => $items_count,
|
||||
),
|
||||
$args,
|
||||
$order,
|
||||
$this
|
||||
);
|
||||
|
||||
do_action( 'enqueue_wc_connect_script', 'wc-connect-create-shipping-label', $payload );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,744 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Shipping_Method' ) ) {
|
||||
|
||||
class WC_Connect_Shipping_Method extends WC_Shipping_Method {
|
||||
|
||||
/**
|
||||
* @var object A reference to a the fetched properties of the service
|
||||
*/
|
||||
protected $service_schema = null;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $service_settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* Store validation errors in property for later retrieval.
|
||||
*
|
||||
* @var WP_Error
|
||||
*/
|
||||
protected $package_validation_errors;
|
||||
|
||||
/**
|
||||
* Cache of destinations which have already been validated.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $validated_package_destinations = array();
|
||||
|
||||
public function __construct( $id_or_instance_id = null ) {
|
||||
parent::__construct( $id_or_instance_id );
|
||||
|
||||
// If $arg looks like a number, treat it as an instance_id,
|
||||
// otherwise, treat it as a (method) id (e.g. wc_connect_usps).
|
||||
if ( is_numeric( $id_or_instance_id ) ) {
|
||||
$this->instance_id = absint( $id_or_instance_id );
|
||||
} else {
|
||||
$this->instance_id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a dependency injection point for each shipping method.
|
||||
*
|
||||
* WooCommerce core instantiates shipping method with only a string ID
|
||||
* or a numeric instance ID. We depend on more than that, so we need
|
||||
* to provide a hook for our plugin to inject dependencies into each
|
||||
* shipping method instance.
|
||||
*
|
||||
* @param WC_Connect_Shipping_Method $this
|
||||
* @param int|string $id_or_instance_id
|
||||
*/
|
||||
do_action( 'wc_connect_service_init', $this, $id_or_instance_id );
|
||||
|
||||
if ( ! $this->service_schema ) {
|
||||
$this->log_error(
|
||||
'Error. A WC_Connect_Shipping_Method was constructed without an id or instance_id',
|
||||
__FUNCTION__
|
||||
);
|
||||
$this->id = 'wc_connect_uninitialized_shipping_method';
|
||||
$this->method_title = '';
|
||||
$this->method_description = '';
|
||||
$this->supports = array();
|
||||
$this->title = '';
|
||||
} else {
|
||||
$this->id = $this->service_schema->method_id;
|
||||
$this->method_title = $this->service_schema->method_title;
|
||||
$this->method_description = $this->service_schema->method_description;
|
||||
$this->supports = array(
|
||||
'shipping-zones',
|
||||
'instance-settings',
|
||||
);
|
||||
|
||||
// Set title to default value.
|
||||
$this->title = $this->service_schema->method_title;
|
||||
|
||||
// Load form values from options, updating title if present.
|
||||
$this->init_form_settings();
|
||||
|
||||
// Note - we cannot hook admin_enqueue_scripts here because we need an instance id
|
||||
// and this constructor is not called with an instance id until after
|
||||
// admin_enqueue_scripts has already fired. This is why WC_Connect_Loader
|
||||
// does it instead.
|
||||
}
|
||||
$this->package_validation_errors = new WP_Error();
|
||||
}
|
||||
|
||||
public function get_service_schema() {
|
||||
|
||||
return $this->service_schema;
|
||||
|
||||
}
|
||||
|
||||
public function set_service_schema( $service_schema ) {
|
||||
|
||||
$this->service_schema = $service_schema;
|
||||
|
||||
}
|
||||
|
||||
public function get_service_settings_store() {
|
||||
|
||||
return $this->service_settings_store;
|
||||
|
||||
}
|
||||
|
||||
public function set_service_settings_store( $service_settings_store ) {
|
||||
|
||||
$this->service_settings_store = $service_settings_store;
|
||||
|
||||
}
|
||||
|
||||
public function get_logger() {
|
||||
|
||||
return $this->logger;
|
||||
|
||||
}
|
||||
|
||||
public function set_logger( WC_Connect_Logger $logger ) {
|
||||
|
||||
$this->logger = $logger;
|
||||
|
||||
}
|
||||
|
||||
public function get_api_client() {
|
||||
|
||||
return $this->api_client;
|
||||
|
||||
}
|
||||
|
||||
public function set_api_client( WC_Connect_API_Client $api_client ) {
|
||||
|
||||
$this->api_client = $api_client;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging helper.
|
||||
*
|
||||
* Avoids calling methods on an undefined object if no logger was
|
||||
* injected during the init action in the constructor.
|
||||
*
|
||||
* @see WC_Connect_Logger::debug()
|
||||
* @param string|WP_Error $message
|
||||
* @param string $context
|
||||
*/
|
||||
protected function log( $message, $context = '' ) {
|
||||
|
||||
$logger = $this->get_logger();
|
||||
|
||||
if ( is_a( $logger, 'WC_Connect_Logger' ) ) {
|
||||
|
||||
$logger->log( $message, $context );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function log_error( $message, $context = '' ) {
|
||||
$logger = $this->get_logger();
|
||||
if ( is_a( $logger, 'WC_Connect_Logger' ) ) {
|
||||
$logger->error( $message, $context );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores any values persisted to the DB for this service instance
|
||||
* and sets up title for WC core to work properly
|
||||
*/
|
||||
protected function init_form_settings() {
|
||||
|
||||
$form_settings = $this->get_service_settings();
|
||||
|
||||
// We need to initialize the instance title ($this->title)
|
||||
// from the settings blob.
|
||||
if ( property_exists( $form_settings, 'title' ) ) {
|
||||
$this->title = $form_settings->title;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings for this service (e.g. for use in the form or for
|
||||
* sending to the rate request endpoint
|
||||
*
|
||||
* Used by WC_Connect_Loader to embed the form schema in the page for JS to consume
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function get_service_settings() {
|
||||
$service_settings = $this->service_settings_store->get_service_settings( $this->id, $this->instance_id );
|
||||
if ( ! is_object( $service_settings ) ) {
|
||||
$service_settings = new stdClass();
|
||||
}
|
||||
|
||||
if ( ! property_exists( $service_settings, 'services' ) ) {
|
||||
return $service_settings;
|
||||
}
|
||||
|
||||
return $service_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a package's destination is valid enough for a rate quote.
|
||||
*
|
||||
* @param array $package Current Package.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid_package_destination( $package ) {
|
||||
$country = isset( $package['destination']['country'] ) ? $package['destination']['country'] : '';
|
||||
$postcode = isset( $package['destination']['postcode'] ) ? $package['destination']['postcode'] : '';
|
||||
$state = isset( $package['destination']['state'] ) ? $package['destination']['state'] : '';
|
||||
$countries = WC()->countries->get_countries();
|
||||
|
||||
$destination_key = md5( wp_json_encode( $package['destination'] ) );
|
||||
|
||||
if ( isset( $this->validated_package_destinations[ $destination_key ] ) ) {
|
||||
// We are using a cache because this method could be called multiple times and we don't want to show double errors.
|
||||
return $this->validated_package_destinations[ $destination_key ];
|
||||
}
|
||||
|
||||
// Ensure that Country is specified.
|
||||
if ( empty( $country ) ) {
|
||||
$this->package_validation_errors->add(
|
||||
'country_required',
|
||||
esc_html__( 'A country is required', 'woocommerce-services' ),
|
||||
[ 'id' => 'country' ]
|
||||
);
|
||||
}
|
||||
|
||||
// Validate Postcode.
|
||||
if ( ! WC_Validation::is_postcode( $postcode, $country ) ) {
|
||||
$fields = WC()->countries->get_address_fields( $country, '' );
|
||||
if ( empty( $postcode ) ) {
|
||||
$this->package_validation_errors->add(
|
||||
'postcode_required',
|
||||
sprintf(
|
||||
/* Translators: %1$s: Localized label for Zip/postal code, %2$s: Country name */
|
||||
esc_html__(
|
||||
'A %1$s is required for %2$s.',
|
||||
'woocommerce-services'
|
||||
),
|
||||
'<strong>' . esc_html( $fields['postcode']['label'] ) . '</strong>',
|
||||
'<strong>' . esc_html( $countries[ $country ] ) . '</strong>'
|
||||
),
|
||||
[ 'id' => 'postcode' ]
|
||||
);
|
||||
} else {
|
||||
$this->package_validation_errors->add(
|
||||
'postcode_validation',
|
||||
sprintf(
|
||||
/* Translators: %1$s: Localized label for Zip/postal code, %2$s: submitted zip/postal code, %3$s: Country name */
|
||||
esc_html__(
|
||||
'%1$s %2$s is invalid for %3$s.',
|
||||
'woocommerce-services'
|
||||
),
|
||||
esc_html( $fields['postcode']['label'] ),
|
||||
'<strong>' . esc_html( $postcode ) . '</strong>',
|
||||
'<strong>' . esc_html( $countries[ $country ] ) . '</strong>'
|
||||
),
|
||||
[ 'id' => 'postcode' ]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate State.
|
||||
$valid_states = WC()->countries->get_states( $country );
|
||||
|
||||
if ( $valid_states && ! isset( $valid_states[ $state ] ) ) {
|
||||
if ( empty( $state ) ) {
|
||||
$fields = WC()->countries->get_address_fields( $country, '' );
|
||||
$this->package_validation_errors->add(
|
||||
'state_required',
|
||||
sprintf(
|
||||
/* Translators: %1$s: Localized label for province/region/state, %2$s: Country name */
|
||||
esc_html__(
|
||||
'A %1$s is required for %2$s.',
|
||||
'woocommerce-services'
|
||||
),
|
||||
'<strong>' . esc_html( $fields['state']['label'] ) . '</strong>',
|
||||
'<strong>' . esc_html( $countries[ $country ] ) . '</strong>'
|
||||
),
|
||||
[ 'id' => 'state' ]
|
||||
);
|
||||
} else {
|
||||
$this->package_validation_errors->add(
|
||||
'state_validation',
|
||||
sprintf(
|
||||
/* Translators: %1$s: State name, %2$s: Country name */
|
||||
esc_html__(
|
||||
'State %1$s is invalid for %2$s.',
|
||||
'woocommerce-services'
|
||||
),
|
||||
'<strong>' . esc_html( $state ) . '</strong>',
|
||||
'<strong>' . esc_html( $countries[ $country ] ) . '</strong>'
|
||||
),
|
||||
[ 'id' => 'state' ]
|
||||
);
|
||||
}
|
||||
}
|
||||
$is_valid = ! $this->package_validation_errors->has_errors();
|
||||
$this->validated_package_destinations[ $destination_key ] = $is_valid;
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return WP_Error object which may have validation errors.
|
||||
*
|
||||
* @return WP_Error
|
||||
*/
|
||||
public function get_package_validation_errors() {
|
||||
return $this->package_validation_errors;
|
||||
}
|
||||
|
||||
private function lookup_product( $package, $product_id ) {
|
||||
foreach ( $package['contents'] as $item ) {
|
||||
if ( $item['product_id'] === $product_id || $item['variation_id'] === $product_id ) {
|
||||
return $item['data'];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function filter_preset_boxes( $preset_id ) {
|
||||
return is_string( $preset_id );
|
||||
}
|
||||
|
||||
private function check_and_handle_response_error( $response_body, $service_settings ) {
|
||||
if ( is_wp_error( $response_body ) ) {
|
||||
$this->debug(
|
||||
sprintf(
|
||||
'Request failed: %s',
|
||||
$response_body->get_error_message()
|
||||
),
|
||||
'error'
|
||||
);
|
||||
$this->log_error(
|
||||
sprintf(
|
||||
'Error. Unable to get shipping rate(s) for %s instance id %d.',
|
||||
$this->id,
|
||||
$this->instance_id
|
||||
),
|
||||
__FUNCTION__
|
||||
);
|
||||
|
||||
$this->set_last_request_failed();
|
||||
|
||||
$this->log_error( $response_body, __FUNCTION__ );
|
||||
$this->add_fallback_rate( $service_settings );
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! property_exists( $response_body, 'rates' ) ) {
|
||||
$this->debug( 'Response is missing `rates` property', 'error' );
|
||||
$this->set_last_request_failed();
|
||||
$this->add_fallback_rate( $service_settings );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function add_fallback_rate( $service_settings ) {
|
||||
if ( ! property_exists( $service_settings, 'fallback_rate' ) || 0 >= $service_settings->fallback_rate ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->debug( 'No rates found, adding fallback.', 'error' );
|
||||
|
||||
$rate_to_add = array(
|
||||
'id' => self::format_rate_id( 'fallback', $this->id, 0 ),
|
||||
'label' => self::format_rate_title( $this->service_schema->carrier_name ),
|
||||
'cost' => $service_settings->fallback_rate,
|
||||
);
|
||||
|
||||
$this->add_rate( $rate_to_add );
|
||||
}
|
||||
|
||||
public function calculate_shipping( $package = array() ) {
|
||||
if ( ! WC_Connect_Functions::should_send_cart_api_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->debug(
|
||||
sprintf(
|
||||
'WooCommerce Shipping & Tax debug mode is on - to hide these messages, turn debug mode off in the <a href="%s" style="text-decoration: underline;">settings</a>.',
|
||||
admin_url( 'admin.php?page=wc-status&tab=connect' )
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $this->is_valid_package_destination( $package ) ) {
|
||||
if ( WC_Connect_functions::is_cart() || WC_Connect_functions::is_checkout() || WC_Connect_functions::is_store_api_call() ) {
|
||||
foreach ( $this->package_validation_errors->errors as $code => $messages ) {
|
||||
foreach ( $messages as $message ) {
|
||||
// Using debug instead of regular notice because the error always shows before customer enters any shipping information.
|
||||
$this->debug( $message, 'error' );
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$service_settings = $this->get_service_settings();
|
||||
$settings_keys = get_object_vars( $service_settings );
|
||||
|
||||
if ( empty( $settings_keys ) ) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'Service settings empty. Skipping %s rate request (instance id %d).',
|
||||
$this->id,
|
||||
$this->instance_id
|
||||
),
|
||||
__FUNCTION__
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Request rates for all WooCommerce Shipping & Tax powered methods in
|
||||
// the current shipping zone to avoid each method making an independent request.
|
||||
$services = array(
|
||||
array(
|
||||
'id' => $this->service_schema->id,
|
||||
'instance' => $this->instance_id,
|
||||
'service_settings' => $service_settings,
|
||||
),
|
||||
);
|
||||
|
||||
$custom_boxes = $this->service_settings_store->get_packages();
|
||||
$predefined_boxes = $this->service_settings_store->get_predefined_packages_for_service( $this->service_schema->id );
|
||||
$predefined_boxes = array_values( array_filter( $predefined_boxes, array( $this, 'filter_preset_boxes' ) ) );
|
||||
|
||||
$cache_key = sprintf(
|
||||
'wcs_rates_%s',
|
||||
md5( serialize( array( $services, $package, $custom_boxes, $predefined_boxes ) ) ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
);
|
||||
$is_debug_mode = 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' );
|
||||
$response_body = get_transient( $cache_key );
|
||||
$this->debug( false === $response_body ? 'Cache does not contain rates response' : 'Cache contains rates response' );
|
||||
if ( ! $is_debug_mode && false !== $response_body ) {
|
||||
$this->debug( 'Rates response retrieved from cache' );
|
||||
} else {
|
||||
$response_body = $this->api_client->get_shipping_rates( $services, $package, $custom_boxes, $predefined_boxes );
|
||||
if ( $this->check_and_handle_response_error( $response_body, $service_settings ) ) {
|
||||
return;
|
||||
}
|
||||
set_transient( $cache_key, $response_body, HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
$instances = $response_body->rates;
|
||||
|
||||
foreach ( (array) $instances as $instance ) {
|
||||
if ( property_exists( $instance, 'error' ) ) {
|
||||
$this->log_error( $instance->error, __FUNCTION__ );
|
||||
$this->set_last_request_failed();
|
||||
}
|
||||
|
||||
if ( ! property_exists( $instance, 'rates' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$packaging_lookup = $this->service_settings_store->get_package_lookup();
|
||||
|
||||
foreach ( (array) $instance->rates as $rate_idx => $rate ) {
|
||||
$package_summaries = array();
|
||||
$service_ids = array();
|
||||
|
||||
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
|
||||
$weight_unit = get_option( 'woocommerce_weight_unit' );
|
||||
$measurements_format = '(%s x %s x %s ' . $dimension_unit . ', %s ' . $weight_unit . ')';
|
||||
|
||||
foreach ( $rate->packages as $rate_package ) {
|
||||
$service_ids[] = $rate_package->service_id;
|
||||
|
||||
$item_product_ids = array();
|
||||
$item_by_product = array();
|
||||
foreach ( $rate_package->items as $package_item ) {
|
||||
$item_product_ids[] = $package_item->product_id;
|
||||
$item_by_product[ $package_item->product_id ] = $package_item;
|
||||
}
|
||||
|
||||
$product_summaries = array();
|
||||
$product_counts = array_count_values( $item_product_ids );
|
||||
foreach ( $product_counts as $product_id => $count ) {
|
||||
/**
|
||||
* WC Product.
|
||||
*
|
||||
* @var WC_product $product
|
||||
*/
|
||||
$product = $this->lookup_product( $package, $product_id );
|
||||
if ( is_a( $product, 'WC_Product' ) ) {
|
||||
$item_name = $product->get_name();
|
||||
$item = $item_by_product[ $product_id ];
|
||||
$item_measurements = sprintf( $measurements_format, $item->length, $item->width, $item->height, $item->weight );
|
||||
$product_summaries[] =
|
||||
( $count > 1 ? sprintf( '<em>%d x</em> ', $count ) : '' ) .
|
||||
sprintf( '(ID: %d) <strong>%s</strong> %s', $product_id, esc_html( $item_name ), esc_html( $item_measurements ) );
|
||||
}
|
||||
}
|
||||
|
||||
$package_measurements = '';
|
||||
|
||||
if ( ! property_exists( $rate_package, 'box_id' ) ) {
|
||||
$package_name = __( 'Unknown package', 'woocommerce-services' );
|
||||
} elseif ( 'individual' === $rate_package->box_id ) {
|
||||
$package_name = __( 'Individual packaging', 'woocommerce-services' );
|
||||
} elseif (
|
||||
isset( $packaging_lookup[ $rate_package->box_id ] ) &&
|
||||
isset( $packaging_lookup[ $rate_package->box_id ]['name'] )
|
||||
) {
|
||||
$package_name = $packaging_lookup[ $rate_package->box_id ]['name'];
|
||||
$package_measurements = sprintf(
|
||||
$measurements_format,
|
||||
$rate_package->length,
|
||||
$rate_package->width,
|
||||
$rate_package->height,
|
||||
$rate_package->weight
|
||||
);
|
||||
}
|
||||
|
||||
$package_summaries[] = sprintf( '<strong>%s</strong> %s', $package_name, $package_measurements )
|
||||
. '<ul><li>' . implode( '</li><li>', $product_summaries ) . '</li></ul>';
|
||||
}
|
||||
|
||||
$packaging_info = implode( ', ', $package_summaries );
|
||||
$services_list = implode( '-', array_unique( $service_ids ) );
|
||||
$box_packing_log = empty( $rate->box_packing_log ) ? array() : $rate->box_packing_log;
|
||||
|
||||
$rate_to_add = array(
|
||||
// Make sure the rate ID is identifiable for extensions like Conditional Shipping and Payments.
|
||||
// The new format looks like: `wc_services_usps:1:pri_medium_flat_box_top`.
|
||||
'id' => self::format_rate_id( $this->id, $instance->instance, $services_list ),
|
||||
'label' => self::format_rate_title( $rate->title ),
|
||||
'cost' => $rate->rate,
|
||||
'meta_data' => array(
|
||||
'wc_connect_packages' => $rate->packages,
|
||||
__( 'Packaging', 'woocommerce-services' ) => $packaging_info,
|
||||
'wc_connect_packing_log' => $box_packing_log,
|
||||
),
|
||||
);
|
||||
|
||||
if ( $this->logger->is_debug_enabled() ) {
|
||||
if ( 'fallback' === $services_list ) {
|
||||
// Notify the merchant when the fallback rate is added by the WCS server.
|
||||
$this->debug( 'No rates found, adding fallback.', 'error' );
|
||||
} else {
|
||||
$rate_debug = '<strong>';
|
||||
$rate_debug .= sprintf(
|
||||
/* translators: 1: name of shipping service, 2: shipping rate (price) */
|
||||
__( 'Received rate: %1$s (%2$s)', 'woocommerce-services' ),
|
||||
$rate_to_add['label'],
|
||||
wc_price( $rate->rate )
|
||||
);
|
||||
$rate_debug .= '</strong><ul><li>' . implode( '</li><li>', $package_summaries ) . '</li></ul>';
|
||||
|
||||
if ( ! empty( $box_packing_log ) ) {
|
||||
$rate_debug .= '<strong>' . __( 'Packing log:', 'woocommerce-services' ) . '</strong>';
|
||||
$rate_debug .= '<ul><li>' . implode( '</li><li>', array_map( 'esc_html', $box_packing_log ) ) . '</li></ul>';
|
||||
}
|
||||
|
||||
$this->debug( $rate_debug, 'success' );
|
||||
}
|
||||
}
|
||||
|
||||
$this->add_rate( $rate_to_add );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 0 === count( $this->rates ) ) {
|
||||
$this->add_fallback_rate( $service_settings );
|
||||
} else {
|
||||
$this->set_last_request_failed( 0 );
|
||||
}
|
||||
|
||||
$this->update_last_rate_request_timestamp();
|
||||
}
|
||||
|
||||
public function update_last_rate_request_timestamp() {
|
||||
$previous_timestamp = WC_Connect_Options::get_option( 'last_rate_request' );
|
||||
if ( false === $previous_timestamp ||
|
||||
( time() - HOUR_IN_SECONDS ) > $previous_timestamp ) {
|
||||
WC_Connect_Options::update_option( 'last_rate_request', time() );
|
||||
}
|
||||
}
|
||||
|
||||
public function set_last_request_failed( $timestamp = null ) {
|
||||
if ( is_null( $timestamp ) ) {
|
||||
$timestamp = time();
|
||||
}
|
||||
|
||||
WC_Connect_Options::update_shipping_method_option( 'failure_timestamp', $timestamp, $this->id, $this->instance_id );
|
||||
}
|
||||
|
||||
public function admin_options() {
|
||||
// hide WP native save button on settings page.
|
||||
global $hide_save_button;
|
||||
$hide_save_button = true;
|
||||
|
||||
do_action( 'wc_connect_service_admin_options', $this->id, $this->instance_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method_id
|
||||
* @param int $instance_id
|
||||
* @param string $service_ids
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function format_rate_id( $method_id, $instance_id, $service_ids ) {
|
||||
return sprintf( '%s:%d:%s', $method_id, $instance_id, $service_ids );
|
||||
}
|
||||
|
||||
public static function format_rate_title( $rate_title ) {
|
||||
$formatted_title = wp_kses(
|
||||
html_entity_decode( $rate_title ),
|
||||
array(
|
||||
'sup' => array(),
|
||||
'del' => array(),
|
||||
'small' => array(),
|
||||
'em' => array(),
|
||||
'i' => array(),
|
||||
'strong' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
return $formatted_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log debug by printing it as notice.
|
||||
*
|
||||
* @param string $message Debug message.
|
||||
* @param string $type Notice type.
|
||||
*/
|
||||
public function debug( $message, $type = 'notice' ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing --- No input from $_POST is used as input.
|
||||
if ( WC_Connect_Functions::is_cart() || WC_Connect_Functions::is_checkout() || isset( $_POST['update_cart'] ) || WC_Connect_Functions::is_store_api_call() ) {
|
||||
$debug_message = sprintf( '%s (%s:%d)', $message, esc_html( $this->title ), $this->instance_id );
|
||||
$this->logger->debug( $debug_message, $type );
|
||||
$this->logger->log( $debug_message, __CLASS__ );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this method available?
|
||||
*
|
||||
* @param array $package Package.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_available( $package ) {
|
||||
if ( ! parent::is_available( $package ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->matches_package_shipping_classes( $package ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the shipping classes of all products in a package are
|
||||
* actually supported by the method. If a single product has an un-supported class,
|
||||
* the whole package will not be supported by the method.
|
||||
*
|
||||
* @param array $package The contents of a package.
|
||||
* @return bool
|
||||
*/
|
||||
public function matches_package_shipping_classes( $package ) {
|
||||
$settings = $this->get_service_settings();
|
||||
$method_classes = property_exists( $settings, 'shipping_classes' )
|
||||
? $settings->shipping_classes
|
||||
: array();
|
||||
|
||||
// No checks needed if the method is not limited to certain classes.
|
||||
if ( empty( $method_classes ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Go through the cart contents and check if all products are supported.
|
||||
foreach ( $package['contents'] as $item ) {
|
||||
$shipping_class_id = $item['data']->get_shipping_class_id();
|
||||
|
||||
if ( in_array( $shipping_class_id, $method_classes, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $this->logger->is_debug_enabled() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = 'Skipping the "%1$s" shipping method because %2$s (%3$s) does not match the shipping classes specified in the method settings (%4$s).';
|
||||
|
||||
$product_class_name = 'No shipping class';
|
||||
if ( $shipping_class_id ) {
|
||||
$shipping_class = get_term_by( 'id', $shipping_class_id, 'product_shipping_class' );
|
||||
|
||||
if ( $shipping_class ) {
|
||||
$product_class_name = $shipping_class->name;
|
||||
}
|
||||
}
|
||||
|
||||
$method_classes = get_terms(
|
||||
array(
|
||||
'taxonomy' => 'product_shipping_class',
|
||||
'hide_empty' => false,
|
||||
'include' => $method_classes,
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! is_wp_error( $method_classes ) && ! empty( $method_classes ) ) {
|
||||
$class_names = implode( ', ', wp_list_pluck( $method_classes, 'name' ) );
|
||||
} else {
|
||||
$class_names = 'No shipping classes found';
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
$message,
|
||||
$this->title,
|
||||
$item['data']->get_title(),
|
||||
$product_class_name,
|
||||
$class_names
|
||||
);
|
||||
|
||||
$this->debug( $message );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1496
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Tracks' ) ) {
|
||||
|
||||
class WC_Connect_Tracks {
|
||||
static $product_name = 'woocommerceconnect';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Plugin file path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $plugin_file;
|
||||
|
||||
public function __construct( WC_Connect_Logger $logger, $plugin_file ) {
|
||||
$this->logger = $logger;
|
||||
$this->plugin_file = $plugin_file;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
add_action( 'wc_connect_shipping_zone_method_added', array( $this, 'shipping_zone_method_added' ), 10, 3 );
|
||||
add_action( 'wc_connect_shipping_zone_method_deleted', array( $this, 'shipping_zone_method_deleted' ), 10, 3 );
|
||||
add_action( 'wc_connect_shipping_zone_method_status_toggled', array( $this, 'shipping_zone_method_status_toggled' ), 10, 4 );
|
||||
add_action( 'wc_connect_saved_service_settings', array( $this, 'saved_service_settings' ), 10, 3 );
|
||||
register_deactivation_hook( $this->plugin_file, array( $this, 'opted_out' ) );
|
||||
}
|
||||
|
||||
public function opted_in( $source = null ) {
|
||||
if ( is_null( $source ) ) {
|
||||
$this->record_user_event( 'opted_in' );
|
||||
} else {
|
||||
$this->record_user_event( 'opted_in', compact( 'source' ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function opted_out() {
|
||||
$this->record_user_event( 'opted_out' );
|
||||
}
|
||||
|
||||
public function shipping_zone_method_added( $instance_id, $service_id ) {
|
||||
$this->record_user_event( 'shipping_zone_method_added' );
|
||||
$this->record_user_event( 'shipping_zone_' . $service_id . '_added' );
|
||||
}
|
||||
|
||||
public function shipping_zone_method_deleted( $instance_id, $service_id ) {
|
||||
$this->record_user_event( 'shipping_zone_method_deleted' );
|
||||
$this->record_user_event( 'shipping_zone_' . $service_id . '_deleted' );
|
||||
}
|
||||
|
||||
public function shipping_zone_method_status_toggled( $instance_id, $service_id, $zone_id, $enabled ) {
|
||||
if ( $enabled ) {
|
||||
$this->record_user_event( 'shipping_zone_method_enabled' );
|
||||
$this->record_user_event( 'shipping_zone_' . $service_id . '_enabled' );
|
||||
} else {
|
||||
$this->record_user_event( 'shipping_zone_method_disabled' );
|
||||
$this->record_user_event( 'shipping_zone_' . $service_id . '_disabled' );
|
||||
}
|
||||
}
|
||||
|
||||
public function saved_service_settings( $service_id ) {
|
||||
$this->record_user_event( 'saved_service_settings' );
|
||||
$this->record_user_event( 'saved_' . $service_id . '_settings' );
|
||||
}
|
||||
|
||||
public function record_user_event( $event_type, $data = array() ) {
|
||||
$user = wp_get_current_user();
|
||||
|
||||
// Check for WooCommerce
|
||||
$wc_version = 'unavailable';
|
||||
if ( function_exists( 'WC' ) ) {
|
||||
$wc_version = WC()->version;
|
||||
}
|
||||
|
||||
$jetpack_blog_id = WC_Connect_Jetpack::get_wpcom_site_id();
|
||||
if ( $jetpack_blog_id instanceof WP_Error ) {
|
||||
$jetpack_blog_id = -1;
|
||||
}
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
$data = array();
|
||||
}
|
||||
|
||||
$data['_via_ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
|
||||
$data['_via_ip'] = isset( $_SERVER['REMOTE_ADDR'] ) ? wc_clean( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
|
||||
$data['_lg'] = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : '';
|
||||
$data['blog_url'] = get_option( 'siteurl' );
|
||||
$data['blog_id'] = $jetpack_blog_id;
|
||||
$data['wcs_version'] = WC_Connect_Loader::get_wcs_version();
|
||||
$data['jetpack_version'] = 'embed-' . WC_Connect_Jetpack::get_jetpack_connection_package_version();
|
||||
$data['is_atomic'] = WC_Connect_Jetpack::is_atomic_site();
|
||||
$data['wc_version'] = $wc_version;
|
||||
$data['wp_version'] = get_bloginfo( 'version' );
|
||||
|
||||
$event_type = self::$product_name . '_' . $event_type;
|
||||
|
||||
$this->debug( 'Tracked the following event: ' . $event_type );
|
||||
WC_Connect_Jetpack::tracks_record_event( $user, $event_type, $data );
|
||||
}
|
||||
|
||||
protected function debug( $message ) {
|
||||
if ( ! is_null( $this->logger ) ) {
|
||||
$this->logger->log( $message );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* A class for working around the quirks and different versions of WordPress/WooCommerce
|
||||
* This is for versions higher than 2.6 (3.0 and higher)
|
||||
*/
|
||||
|
||||
// No direct access please.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Utils' ) ) {
|
||||
/**
|
||||
* WC_Connect_Compatibility class.
|
||||
*/
|
||||
class WC_Connect_Utils {
|
||||
|
||||
/**
|
||||
* For a given product ID, it tries to find its name inside an order's line items.
|
||||
* This is useful when an order has a product which was later deleted from the
|
||||
* store.
|
||||
*
|
||||
* @param int $product_id Product ID or variation ID.
|
||||
* @param WC_Order $order WC Order.
|
||||
*
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
public static function get_product_name_from_order( $product_id, $order ) {
|
||||
$line_item = self::get_line_item_from_order( $product_id, $order );
|
||||
|
||||
if ( ! $line_item ) {
|
||||
/* translators: %d: Deleted Product ID */
|
||||
return sprintf( __( '#%d - [Deleted product]', 'woocommerce-services' ), $product_id );
|
||||
}
|
||||
|
||||
/* translators: %1$d: Product ID, %2$s: Product Name */
|
||||
return sprintf( __( '#%1$d - %2$s', 'woocommerce-services' ), $product_id, $line_item->get_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given product ID, it tries to find its price inside an order's line items.
|
||||
*
|
||||
* @param int $product_id Product ID or variation ID.
|
||||
* @param WC_Order $order WC Order.
|
||||
*
|
||||
* @return float The product (or variation) price, or NULL if it wasn't found
|
||||
*/
|
||||
public static function get_product_price_from_order( $product_id, $order ) {
|
||||
$line_item = self::get_line_item_from_order( $product_id, $order );
|
||||
|
||||
if ( ! $line_item ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return round( floatval( $line_item->get_total() ) / $line_item->get_quantity(), 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding Product for the given Order Item.
|
||||
*
|
||||
* @param WC_Order $order WC Order.
|
||||
* @param WC_Order_Item|WC_Order_Item_Product|array $item Order Item.
|
||||
*
|
||||
* @return WC_Product|null|false
|
||||
*/
|
||||
public static function get_item_product( WC_Order $order, $item ) {
|
||||
if ( is_array( $item ) && isset( $item['product_id'] ) ) {
|
||||
return wc_get_product( $item['product_id'] );
|
||||
}
|
||||
if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
|
||||
/**
|
||||
* Order Item Product
|
||||
*
|
||||
* @var WC_Order_Item_Product $item
|
||||
*/
|
||||
return $item->get_product();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if order contains given product.
|
||||
*
|
||||
* @param int $product_id WC Product ID.
|
||||
* @param WC_Order $order WC Order.
|
||||
*
|
||||
* @return WC_Order_Item_Product|false
|
||||
*/
|
||||
public static function get_line_item_from_order( $product_id, $order ) {
|
||||
/**
|
||||
* Order Item Product
|
||||
*
|
||||
* @var WC_Order_Item_Product $line_item
|
||||
*/
|
||||
foreach ( $order->get_items() as $line_item ) {
|
||||
$line_product_id = $line_item->get_product_id();
|
||||
$line_variation_id = $line_item->get_variation_id();
|
||||
|
||||
if ( $line_product_id === $product_id || $line_variation_id === $product_id ) {
|
||||
return $line_item;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Enum for WCS&T to WCShipping migration states.
|
||||
*/
|
||||
class WC_Connect_WCST_To_WCShipping_Migration_State_Enum {
|
||||
// These are used for WCS&T to WCShipping migration.
|
||||
public const NOT_STARTED = 1;
|
||||
public const STARTED = 2;
|
||||
public const ERROR_STARTED = 3;
|
||||
public const INSTALLING = 4;
|
||||
public const ERROR_INSTALLING = 5;
|
||||
public const ACTIVATING = 6;
|
||||
public const ERROR_ACTIVATING = 7;
|
||||
public const DB_MIGRATION = 8;
|
||||
public const ERROR_DB_MIGRATION = 9;
|
||||
public const DEACTIVATING = 10;
|
||||
public const ERROR_DEACTIVATING = 11;
|
||||
public const COMPLETED = 12;
|
||||
|
||||
public static function is_valid_value( $state ) {
|
||||
$valid_states = array(
|
||||
self::NOT_STARTED,
|
||||
self::STARTED,
|
||||
self::ERROR_STARTED,
|
||||
self::INSTALLING,
|
||||
self::ERROR_INSTALLING,
|
||||
self::ACTIVATING,
|
||||
self::ERROR_ACTIVATING,
|
||||
self::DB_MIGRATION,
|
||||
self::ERROR_DB_MIGRATION,
|
||||
self::DEACTIVATING,
|
||||
self::ERROR_DEACTIVATING,
|
||||
self::COMPLETED,
|
||||
);
|
||||
return in_array( $state, $valid_states, true );
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Account_Settings_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Account_Settings_Controller extends WC_REST_Connect_Base_Controller {
|
||||
|
||||
protected $rest_base = 'connect/account/settings';
|
||||
|
||||
/*
|
||||
* @var WC_Connect_Payment_Methods_Store
|
||||
*/
|
||||
protected $payment_methods_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Account_Settings
|
||||
*/
|
||||
protected $account_settings;
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger, WC_Connect_Payment_Methods_Store $payment_methods_store ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->payment_methods_store = $payment_methods_store;
|
||||
|
||||
$this->account_settings = new WC_Connect_Account_Settings(
|
||||
$settings_store,
|
||||
$payment_methods_store
|
||||
);
|
||||
}
|
||||
|
||||
public function get() {
|
||||
return new WP_REST_Response(
|
||||
array_merge(
|
||||
array( 'success' => true ),
|
||||
$this->account_settings->get()
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$settings = $request->get_json_params();
|
||||
|
||||
if ( ! $this->settings_store->can_user_manage_payment_methods() ) {
|
||||
// Ignore the user-provided payment method ID if they don't have permission to change it
|
||||
$old_settings = $this->settings_store->get_account_settings();
|
||||
$settings['selected_payment_method_id'] = $old_settings['selected_payment_method_id'];
|
||||
}
|
||||
|
||||
$result = $this->settings_store->update_account_settings( $settings );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$error = new WP_Error(
|
||||
'save_failed',
|
||||
sprintf(
|
||||
__( 'Unable to update settings. %s', 'woocommerce-services' ),
|
||||
$result->get_error_message()
|
||||
),
|
||||
array_merge(
|
||||
array( 'status' => 400 ),
|
||||
$result->get_error_data()
|
||||
)
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array( 'success' => true ), 200 );
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Address_Normalization_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Address_Normalization_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/normalize-address';
|
||||
|
||||
public function post( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$address = $data['address'];
|
||||
$phone = $address['phone'];
|
||||
|
||||
unset( $address['phone'] );
|
||||
|
||||
$body = array(
|
||||
'destination' => $address,
|
||||
);
|
||||
$response = $this->api_client->send_address_normalization_request( $body );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
if ( isset( $response->field_errors ) ) {
|
||||
$this->logger->log( 'Address validation errors: ' . implode( '; ', array_values( (array) $response->field_errors ) ), __CLASS__ );
|
||||
return array(
|
||||
'success' => true,
|
||||
'field_errors' => $response->field_errors,
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset( $response->normalized ) ) {
|
||||
$response->normalized = new stdClass();
|
||||
}
|
||||
|
||||
$response->normalized->phone = $phone;
|
||||
$is_trivial_normalization = isset( $response->is_trivial_normalization ) ? $response->is_trivial_normalization : false;
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'normalized' => $response->normalized,
|
||||
'is_trivial_normalization' => $is_trivial_normalization,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the requester's permissions
|
||||
*/
|
||||
public function check_permission( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if ( 'origin' === $data['type'] ) {
|
||||
return WC_Connect_Functions::user_can_manage_labels(); // Only an admin can normalize the origin address
|
||||
}
|
||||
|
||||
return true; // non-authenticated service for the 'destination' address
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Assets_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Assets_Controller extends WC_REST_Connect_Base_Controller {
|
||||
|
||||
protected $rest_base = 'connect/assets';
|
||||
|
||||
public function get() {
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'assets' => array(
|
||||
'wc_connect_admin_script' => WC_Connect_Loader::get_wcs_admin_script_url(),
|
||||
'wc_connect_admin_style' => WC_Connect_Loader::get_wcs_admin_style_url(),
|
||||
),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Base_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
abstract class WC_REST_Connect_Base_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v1';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
protected $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Settings_Store
|
||||
*/
|
||||
protected $settings_store;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger ) {
|
||||
$this->api_client = $api_client;
|
||||
$this->settings_store = $settings_store;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
if ( method_exists( $this, 'get' ) ) {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_internal' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( method_exists( $this, 'post' ) ) {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'post_internal' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( method_exists( $this, 'put' ) ) {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => 'PUT',
|
||||
'callback' => array( $this, 'put_internal' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
if ( method_exists( $this, 'delete' ) ) {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => 'DELETE',
|
||||
'callback' => array( $this, 'delete_internal' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidate cache prevention mechanisms.
|
||||
*/
|
||||
public function prevent_route_caching() {
|
||||
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
|
||||
define( 'DONOTCACHEPAGE', true ); // Play nice with WP-Super-Cache
|
||||
}
|
||||
|
||||
// Prevent our REST API endpoint responses from being added to browser cache
|
||||
add_filter( 'rest_post_dispatch', array( $this, 'send_nocache_header' ), PHP_INT_MAX, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a no-cache header for WCS REST API responses. Prompted by cache issues
|
||||
* on the Pantheon hosting platform.
|
||||
*
|
||||
* See: https://pantheon.io/docs/cache-control/
|
||||
*
|
||||
* @param WP_REST_Response $response
|
||||
* @param WP_REST_Server $server
|
||||
*
|
||||
* @return WP_REST_Response passthrough $response parameter
|
||||
*/
|
||||
public function send_nocache_header( $response, $server ) {
|
||||
$server->send_header( 'Cache-Control', 'no-cache, must-revalidate, max-age=0' );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function get_internal( $request ) {
|
||||
$this->prevent_route_caching();
|
||||
|
||||
return $this->get( $request );
|
||||
}
|
||||
|
||||
public function post_internal( $request ) {
|
||||
$this->prevent_route_caching();
|
||||
|
||||
return $this->post( $request );
|
||||
}
|
||||
|
||||
public function put_internal( $request ) {
|
||||
$this->prevent_route_caching();
|
||||
|
||||
return $this->put( $request );
|
||||
}
|
||||
|
||||
public function delete_internal( $request ) {
|
||||
$this->prevent_route_caching();
|
||||
|
||||
return $this->delete( $request );
|
||||
}
|
||||
/**
|
||||
* Validate the requester's permissions
|
||||
*/
|
||||
public function check_permission( $request ) {
|
||||
return WC_Connect_Functions::user_can_manage_labels();
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Migration_Flag_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Migration_Flag_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/migration-flag';
|
||||
|
||||
/**
|
||||
* Tracks instance.
|
||||
*
|
||||
* @var WC_Connect_Tracks
|
||||
*/
|
||||
protected $tracks;
|
||||
|
||||
public function __construct( $api_client, $settings_store, $logger, $tracks ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
|
||||
$this->set_tracks( $tracks );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tracks instance.
|
||||
*
|
||||
* @param WC_Connect_Tracks $tracks Tracks instance.
|
||||
*/
|
||||
public function set_tracks( $tracks ) {
|
||||
$this->tracks = $tracks;
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$params = $request->get_json_params();
|
||||
$migration_state = intval( $params['migration_state'] );
|
||||
|
||||
// If the migration state is greater than "COMPLETED", then we can assume that the next part of the migration
|
||||
// state is being handled by WooCommerce Shipping.
|
||||
// This shouldn't be necessary since our migration shouldn't be displayed to begin with, but we're adding
|
||||
// support for it, just in case.
|
||||
if ( $migration_state > WC_Connect_WCST_To_WCShipping_Migration_State_Enum::COMPLETED ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'result' => __( 'WooCommerce Shipping has taken over the migration process.', 'woocommerce-services' ),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! WC_Connect_WCST_To_WCShipping_Migration_State_Enum::is_valid_value( $migration_state ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_migration_state',
|
||||
__( 'Invalid migration state. Can not update migration state.', 'woocommerce-services' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$existing_migration_state = get_option( 'wcshipping_migration_state' );
|
||||
if ( $existing_migration_state && intval( $existing_migration_state ) === $migration_state ) {
|
||||
return new WP_REST_Response( array( 'result' => __( 'Migration flag is the same, no changes needed.', 'woocommerce-services' ) ), 304 );
|
||||
}
|
||||
|
||||
$result = update_option( 'wcshipping_migration_state', $migration_state );
|
||||
|
||||
if ( $result ) {
|
||||
$this->tracks->record_user_event(
|
||||
'migration_flag_state_update',
|
||||
array(
|
||||
'migration_state' => $migration_state,
|
||||
'updated' => $result,
|
||||
)
|
||||
);
|
||||
|
||||
return new WP_REST_Response( array( 'result' => __( 'Migration flag updated successfully.', 'woocommerce-services' ) ), 200 );
|
||||
}
|
||||
|
||||
$error = new WP_Error(
|
||||
'wcst_to_wcshipping_migration_failed_to_update',
|
||||
__( 'Unable to update migration flag. The flag could not be updated.', 'woocommerce-services' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Packages_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Packages_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/packages';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Package_Settings
|
||||
*/
|
||||
protected $package_settings;
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger, WC_Connect_Service_Schemas_Store $service_schemas_store ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->package_settings = new WC_Connect_Package_Settings(
|
||||
$settings_store,
|
||||
$service_schemas_store
|
||||
);
|
||||
}
|
||||
|
||||
public function get() {
|
||||
return new WP_REST_Response(
|
||||
array_merge(
|
||||
array( 'success' => true ),
|
||||
$this->package_settings->get()
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
$this->settings_store->update_packages( $packages['custom'] );
|
||||
$this->settings_store->update_predefined_packages( $packages['predefined'] );
|
||||
|
||||
return new WP_REST_Response( array( 'success' => true ), 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.' ),
|
||||
'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.' ),
|
||||
'data' => array( 'package_names' => array_values( $duplicate_package_names ) ),
|
||||
);
|
||||
return new WP_REST_Response( $error, 400 );
|
||||
}
|
||||
|
||||
// If no duplicate custom packages, create the given packages.
|
||||
$this->settings_store->create_packages( $custom_packages );
|
||||
}
|
||||
|
||||
// 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' => __( 'The new predefined package names are not unique.' ),
|
||||
'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 existing packages.' ),
|
||||
'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 new WP_REST_Response( array( 'success' => true ), 200 );
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Self_Help_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Self_Help_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/self-help';
|
||||
|
||||
public function post( $request ) {
|
||||
$settings = $request->get_json_params();
|
||||
|
||||
if (
|
||||
empty( $settings )
|
||||
|| ! array_key_exists( 'wcc_debug_on', $settings )
|
||||
|| ! array_key_exists( 'wcc_logging_on', $settings )
|
||||
) {
|
||||
$error = new WP_Error(
|
||||
'bad_form_data',
|
||||
__( 'Unable to update settings. The form data could not be read.', 'woocommerce-services' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
if ( 1 == $settings['wcc_logging_on'] ) {
|
||||
$this->logger->enable_logging();
|
||||
} else {
|
||||
$this->logger->disable_logging();
|
||||
}
|
||||
|
||||
if ( 1 == $settings['wcc_debug_on'] ) {
|
||||
$this->logger->enable_debug();
|
||||
} else {
|
||||
$this->logger->disable_debug();
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array( 'success' => true ), 200 );
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Service_Data_Refresh_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Service_Data_Refresh_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/service-data-refresh';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $services_schemas_store;
|
||||
|
||||
public function set_service_schemas_store( $services_schemas_store ) {
|
||||
$this->services_schemas_store = $services_schemas_store;
|
||||
}
|
||||
|
||||
public function post() {
|
||||
$result = $this->services_schemas_store->fetch_service_schemas_from_connect_server();
|
||||
if ( $result === false ) {
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => false,
|
||||
],
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
$schemas = $this->services_schemas_store->get_service_schemas();
|
||||
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => true,
|
||||
'timestamp' => $this->services_schemas_store->get_last_fetch_timestamp(),
|
||||
'has_service_schemas' => ! is_null( $schemas ),
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Services_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Services_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/services/(?P<id>[a-z_]+)\/(?P<instance>[\d]+)';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Service_Schemas_Store
|
||||
*/
|
||||
protected $service_schemas_store;
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_API_Client $api_client,
|
||||
WC_Connect_Service_Settings_Store $settings_store,
|
||||
WC_Connect_Logger $logger,
|
||||
WC_Connect_Service_Schemas_Store $schemas_store
|
||||
) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->service_schemas_store = $schemas_store;
|
||||
}
|
||||
|
||||
public function get( $request ) {
|
||||
$method_id = $request['id'];
|
||||
$instance_id = isset( $request['instance'] ) ? $request['instance'] : false;
|
||||
|
||||
$service_schema = $this->service_schemas_store->get_service_schema_by_id_or_instance_id(
|
||||
$instance_id
|
||||
? $instance_id
|
||||
: $method_id
|
||||
);
|
||||
|
||||
if ( ! $service_schema ) {
|
||||
return new WP_Error( 'schemas_not_found', __( 'Service schemas were not loaded', 'woocommerce-services' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$payload = apply_filters(
|
||||
'wc_connect_shipping_service_settings',
|
||||
array(
|
||||
'success' => true,
|
||||
),
|
||||
$method_id,
|
||||
$instance_id
|
||||
);
|
||||
|
||||
return new WP_REST_Response( $payload, 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to update the settings on a particular service and instance
|
||||
*/
|
||||
public function post( $request ) {
|
||||
$request_params = $request->get_params();
|
||||
|
||||
$id = array_key_exists( 'id', $request_params ) ? $request_params['id'] : '';
|
||||
$instance = array_key_exists( 'instance', $request_params ) ? absint( $request_params['instance'] ) : false;
|
||||
|
||||
if ( empty( $id ) ) {
|
||||
$error = new WP_Error(
|
||||
'service_id_missing',
|
||||
__( 'Unable to update service settings. Form data is missing service ID.', 'woocommerce-services' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
$settings = (object) $request->get_json_params();
|
||||
|
||||
if ( empty( $settings ) ) {
|
||||
$error = new WP_Error(
|
||||
'bad_form_data',
|
||||
__( 'Unable to update service settings. The form data could not be read.', 'woocommerce-services' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
$validation_result = $this->settings_store->validate_and_possibly_update_settings( $settings, $id, $instance );
|
||||
|
||||
if ( is_wp_error( $validation_result ) ) {
|
||||
$error = new WP_Error(
|
||||
'validation_failed',
|
||||
sprintf(
|
||||
__( 'Unable to update service settings. Validation failed. %s', 'woocommerce-services' ),
|
||||
$validation_result->get_error_message()
|
||||
),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array( 'success' => true ), 200 );
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Carrier_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Carrier_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/shipping/carrier';
|
||||
|
||||
public function post( $request ) {
|
||||
$settings = $request->get_json_params();
|
||||
|
||||
$response = $this->api_client->create_shipping_carrier_account( $settings );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
do_action( 'wc_connect_fetch_service_schemas' );
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Carrier_Delete_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Carrier_Delete_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/shipping/carrier/(?P<carrier_id>.+)';
|
||||
|
||||
public function delete( $request ) {
|
||||
$carrier_id = $request['carrier_id'];
|
||||
|
||||
$response = $this->api_client->disconnect_carrier_account( $carrier_id );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
do_action( 'wc_connect_fetch_service_schemas' );
|
||||
return array( 'success' => true );
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Carrier_Types_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of carrier WooCommerce Shipping & Tax supports, along with the
|
||||
* fields needed for each carrier in order to do carrier account registration.
|
||||
*/
|
||||
class WC_REST_Connect_Shipping_Carrier_Types_Controller extends WC_REST_Connect_Base_Controller {
|
||||
/**
|
||||
* Carrier-types end point
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'connect/shipping/carrier-types';
|
||||
|
||||
/**
|
||||
* GET request
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get() {
|
||||
$response = $this->api_client->get_carrier_types();
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
return $response;
|
||||
}
|
||||
return new WP_REST_Response(
|
||||
[
|
||||
'success' => true,
|
||||
'carriers' => $response->carriers,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Carriers_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Carriers_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/shipping/carriers';
|
||||
|
||||
public function get() {
|
||||
$response = $this->api_client->get_all_shipping_carriers();
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Label_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Label_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/label/(?P<order_id>\d+)';
|
||||
|
||||
/*
|
||||
* @var WC_Connect_Shipping_Label
|
||||
*/
|
||||
protected $shipping_label;
|
||||
|
||||
/*
|
||||
* @var WC_Connect_Payment_Methods_Store
|
||||
*/
|
||||
protected $payment_methods_store;
|
||||
|
||||
public function __construct( WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger, WC_Connect_Shipping_Label $shipping_label, WC_Connect_Payment_Methods_Store $payment_methods_store ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->shipping_label = $shipping_label;
|
||||
$this->payment_methods_store = $payment_methods_store;
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
parent::register_routes();
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/creation_eligibility',
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_creation_eligibility' ),
|
||||
'permission_callback' => array( $this, 'check_permission' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function get( $request ) {
|
||||
$order_id = $request['order_id'];
|
||||
$payload = $this->shipping_label->get_label_payload( $order_id );
|
||||
if ( ! $payload ) {
|
||||
return new WP_Error( 'not_found', __( 'Order not found', 'woocommerce-services' ), array( 'status' => 404 ) );
|
||||
}
|
||||
$payload['success'] = true;
|
||||
return new WP_REST_Response( $payload, 200 );
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$settings = $request->get_json_params();
|
||||
$order_id = $request['order_id'];
|
||||
$settings['order_id'] = $order_id;
|
||||
|
||||
if ( empty( $settings['payment_method_id'] ) || ! $this->settings_store->can_user_manage_payment_methods() ) {
|
||||
$settings['payment_method_id'] = $this->settings_store->get_selected_payment_method_id();
|
||||
} else {
|
||||
$this->settings_store->set_selected_payment_method_id( $settings['payment_method_id'] );
|
||||
}
|
||||
|
||||
$last_box_id = '';
|
||||
$last_service_id = '';
|
||||
$last_carrier_id = '';
|
||||
$service_names = array();
|
||||
foreach ( $settings['packages'] as $index => $package ) {
|
||||
$service_names[] = $package['service_name'];
|
||||
unset( $package['service_name'] );
|
||||
$settings['packages'][ $index ] = $package;
|
||||
|
||||
if ( empty( $last_box_id ) && ! empty( $package['box_id'] ) ) {
|
||||
$last_box_id = $package['box_id'];
|
||||
}
|
||||
|
||||
if ( empty( $last_service_id ) && ! empty( $package['service_id'] ) ) {
|
||||
$last_service_id = $package['service_id'];
|
||||
}
|
||||
|
||||
if ( empty( $last_carrier_id ) && ! empty( $package['carrier_id'] ) ) {
|
||||
$last_carrier_id = $package['carrier_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $last_box_id ) && 'individual' !== $last_box_id ) {
|
||||
update_user_meta( get_current_user_id(), 'wc_connect_last_box_id', $last_box_id );
|
||||
}
|
||||
|
||||
if ( ! empty( $last_service_id ) && '' !== $last_service_id ) {
|
||||
update_user_meta( get_current_user_id(), 'wc_connect_last_service_id', $last_service_id );
|
||||
}
|
||||
|
||||
if ( ! empty( $last_carrier_id ) && '' !== $last_carrier_id ) {
|
||||
update_user_meta( get_current_user_id(), 'wc_connect_last_carrier_id', $last_carrier_id );
|
||||
}
|
||||
|
||||
$response = $this->api_client->send_shipping_label_request( $settings );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
$label_ids = array();
|
||||
$purchased_labels_meta = array();
|
||||
$package_lookup = $this->settings_store->get_package_lookup();
|
||||
foreach ( $response->labels as $index => $label_data ) {
|
||||
if ( isset( $label_data->error ) ) {
|
||||
$error = new WP_Error(
|
||||
$label_data->error->code,
|
||||
$label_data->error->message,
|
||||
array( 'message' => $label_data->error->message )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
$label_ids[] = $label_data->label->label_id;
|
||||
|
||||
$label_meta = array(
|
||||
'label_id' => $label_data->label->label_id,
|
||||
'tracking' => $label_data->label->tracking_id,
|
||||
'refundable_amount' => $label_data->label->refundable_amount,
|
||||
'created' => $label_data->label->created,
|
||||
'carrier_id' => $label_data->label->carrier_id,
|
||||
'service_name' => $service_names[ $index ],
|
||||
'status' => $label_data->label->status,
|
||||
'commercial_invoice_url' => $label_data->label->commercial_invoice_url ?? '',
|
||||
'is_commercial_invoice_submitted_electronically' => $label_data->label->is_commercial_invoice_submitted_electronically ?? '',
|
||||
);
|
||||
|
||||
$package = $settings['packages'][ $index ];
|
||||
$box_id = $package['box_id'];
|
||||
if ( 'individual' === $box_id ) {
|
||||
$label_meta['package_name'] = __( 'Individual packaging', 'woocommerce-services' );
|
||||
} elseif ( isset( $package_lookup[ $box_id ] ) ) {
|
||||
$label_meta['package_name'] = $package_lookup[ $box_id ]['name'];
|
||||
} else {
|
||||
$label_meta['package_name'] = __( 'Unknown package', 'woocommerce-services' );
|
||||
}
|
||||
|
||||
$label_meta['is_letter'] = isset( $package['is_letter'] ) ? $package['is_letter'] : false;
|
||||
|
||||
$product_names = array();
|
||||
$product_ids = array();
|
||||
foreach ( $package['products'] as $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
$product_ids[] = $product_id;
|
||||
|
||||
if ( $product ) {
|
||||
$product_names[] = $product->get_title();
|
||||
} else {
|
||||
$order = wc_get_order( $order_id );
|
||||
$product_names[] = WC_Connect_Utils::get_product_name_from_order( $product_id, $order );
|
||||
}
|
||||
}
|
||||
|
||||
$label_meta['product_names'] = $product_names;
|
||||
$label_meta['product_ids'] = $product_ids;
|
||||
|
||||
array_unshift( $purchased_labels_meta, $label_meta );
|
||||
}
|
||||
|
||||
$this->settings_store->add_labels_to_order( $order_id, $purchased_labels_meta );
|
||||
|
||||
return array(
|
||||
'labels' => $purchased_labels_meta,
|
||||
'success' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Available params for $request:
|
||||
* - `can_create_payment_method: Boolean`: optional with default value `true`. If `false`, at least one existing payment method is
|
||||
* required for label creation.
|
||||
* - `can_create_package: Boolean`: optional with default value `true`. If `false`, at least one pre-existing
|
||||
* package (custom or predefined) is required for label creation.
|
||||
* - `can_create_customs_form: Boolean`: optional with default value `true`. If `false`, the order is eligible for
|
||||
* label creation if a customs form is not required for the origin and destination address in the US.
|
||||
*
|
||||
* @param WP_REST_Request $request API request with optional parameters as above.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function get_creation_eligibility( $request ) {
|
||||
$order_id = $request['order_id'];
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
if ( ! $order ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'order_not_found',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
// Shipping labels should be enabled in account settings.
|
||||
if ( true !== $this->settings_store->get_account_settings()['enabled'] ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'account_settings_disabled',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the store is eligible for shipping label creation.
|
||||
if ( ! $this->shipping_label->is_store_eligible_for_shipping_label_creation() ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'store_not_eligible',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
// If the client cannot create a customs form:
|
||||
// - The store address has to be in the US.
|
||||
// - The origin and destination addresses have to be in the US.
|
||||
$client_can_create_customs_form = isset( $request['can_create_customs_form'] ) ? filter_var( $request['can_create_customs_form'], FILTER_VALIDATE_BOOLEAN ) : true;
|
||||
$store_country = wc_get_base_location()['country'];
|
||||
if ( ! $client_can_create_customs_form ) {
|
||||
// The store address has to be in the US.
|
||||
if ( 'US' !== $store_country ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'store_country_not_supported_when_customs_form_is_not_supported_by_client',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
// The origin and destination addresses have to be in the US.
|
||||
$origin_address = $this->settings_store->get_origin_address();
|
||||
$destination_address = $order->get_address( 'shipping' );
|
||||
if ( 'US' !== $origin_address['country'] || 'US' !== $destination_address['country'] ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'origin_or_destination_country_not_supported_when_customs_form_is_not_supported_by_client',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If the client cannot create a package (`can_create_package` param is set to `false`), a pre-existing package
|
||||
// is required.
|
||||
$client_can_create_package = isset( $request['can_create_package'] ) ? filter_var( $request['can_create_package'], FILTER_VALIDATE_BOOLEAN ) : true;
|
||||
if ( ! $client_can_create_package ) {
|
||||
if ( empty( $this->settings_store->get_packages() ) && empty( $this->settings_store->get_predefined_packages() ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'no_packages_when_client_cannot_create_package',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// There is at least one non-refunded and shippable product.
|
||||
if ( ! $this->shipping_label->is_order_eligible_for_shipping_label_creation( $order ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'order_not_eligible',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
// If the client cannot create a payment method (`can_create_payment_method` param is set to `false`), an existing payment method is required.
|
||||
$client_can_create_payment_method = isset( $request['can_create_payment_method'] ) ? filter_var( $request['can_create_payment_method'], FILTER_VALIDATE_BOOLEAN ) : true;
|
||||
if ( ! $client_can_create_payment_method && empty( $this->payment_methods_store->get_payment_methods() ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'no_payment_methods_and_client_cannot_create_one',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
// There is a pre-selected payment method or the user can manage payment methods.
|
||||
if ( ! ( $this->settings_store->get_selected_payment_method_id() || $this->settings_store->can_user_manage_payment_methods() ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => false,
|
||||
'reason' => 'no_selected_payment_method_and_user_cannot_manage_payment_methods',
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'is_eligible' => true,
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Label_Preview_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Label_Preview_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/label/preview';
|
||||
|
||||
public function get( $request ) {
|
||||
$raw_params = $request->get_params();
|
||||
$params = array();
|
||||
|
||||
$params['paper_size'] = $raw_params['paper_size'];
|
||||
$this->settings_store->set_preferred_paper_size( $params['paper_size'] );
|
||||
$params['carrier'] = 'usps';
|
||||
$params['labels'] = array();
|
||||
$captions = empty( $raw_params['caption_csv'] ) ? array() : explode( ',', $raw_params['caption_csv'] );
|
||||
|
||||
foreach ( $captions as $caption ) {
|
||||
$params['labels'][] = array( 'caption' => urldecode( $caption ) );
|
||||
}
|
||||
|
||||
$raw_response = $this->api_client->get_labels_preview_pdf( $params );
|
||||
|
||||
if ( is_wp_error( $raw_response ) ) {
|
||||
$this->logger->log( $raw_response, __CLASS__ );
|
||||
return $raw_response;
|
||||
}
|
||||
|
||||
header( 'content-type: ' . $raw_response['headers']['content-type'] );
|
||||
echo $raw_response['body']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
die();
|
||||
}
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Label_Print_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Label_Print_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/label/print';
|
||||
|
||||
public function get( $request ) {
|
||||
$raw_params = $request->get_params();
|
||||
$params = array();
|
||||
|
||||
$params['paper_size'] = $raw_params['paper_size'];
|
||||
$this->settings_store->set_preferred_paper_size( $params['paper_size'] );
|
||||
|
||||
$label_ids = ! empty( $raw_params['label_id_csv'] ) ? explode( ',', $raw_params['label_id_csv'] ) : array();
|
||||
$n_label_ids = count( $label_ids );
|
||||
$captions = ! empty( $raw_params['caption_csv'] ) ? explode( ',', $raw_params['caption_csv'] ) : array();
|
||||
$n_captions = count( $captions );
|
||||
// Either there are the same number of captions as labels, or no captions at all
|
||||
if ( ! $n_label_ids || ( $n_captions && $n_captions !== $n_label_ids ) ) {
|
||||
$message = __( 'Invalid PDF request.', 'woocommerce-services' );
|
||||
$error = new WP_Error(
|
||||
'invalid_pdf_request',
|
||||
$message,
|
||||
array(
|
||||
'message' => $message,
|
||||
'status' => 400,
|
||||
)
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
$params['labels'] = array();
|
||||
for ( $i = 0; $i < $n_label_ids; $i++ ) {
|
||||
$params['labels'][ $i ] = array();
|
||||
$params['labels'][ $i ]['label_id'] = (int) $label_ids[ $i ];
|
||||
|
||||
if ( $n_captions ) {
|
||||
$params['labels'][ $i ]['caption'] = urldecode( $captions[ $i ] );
|
||||
}
|
||||
}
|
||||
|
||||
$raw_response = $this->api_client->get_labels_print_pdf( $params );
|
||||
|
||||
if ( is_wp_error( $raw_response ) ) {
|
||||
$this->logger->log( $raw_response, __CLASS__ );
|
||||
return $raw_response;
|
||||
}
|
||||
|
||||
if ( isset( $raw_params['json'] ) && $raw_params['json'] ) {
|
||||
return array(
|
||||
'mimeType' => $raw_response['headers']['content-type'],
|
||||
'b64Content' => base64_encode( $raw_response['body'] ),
|
||||
'success' => true,
|
||||
);
|
||||
} else {
|
||||
header( 'content-type: ' . $raw_response['headers']['content-type'] );
|
||||
echo $raw_response['body']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Label_Refund_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Label_Refund_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/label/(?P<order_id>\d+)/(?P<label_id>\d+)/refund';
|
||||
|
||||
public function post( $request ) {
|
||||
$response = $this->api_client->send_shipping_label_refund_request( $request['label_id'] );
|
||||
|
||||
if ( isset( $response->error ) ) {
|
||||
$response = new WP_Error(
|
||||
property_exists( $response->error, 'code' ) ? $response->error->code : 'refund_error',
|
||||
property_exists( $response->error, 'message' ) ? $response->error->message : ''
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$response->add_data(
|
||||
array(
|
||||
'message' => $response->get_error_message(),
|
||||
),
|
||||
$response->get_error_code()
|
||||
);
|
||||
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
return $response;
|
||||
}
|
||||
|
||||
$label_refund = (object) array(
|
||||
'label_id' => (int) $response->label->id,
|
||||
'refund' => $response->refund,
|
||||
);
|
||||
$this->settings_store->update_label_order_meta_data( $request['order_id'], $label_refund );
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'refund' => $response->refund,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Label_Status_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Label_Status_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/label/(?P<order_id>\d+)/(?P<label_ids>(\d+)(,\d+)*)';
|
||||
|
||||
public function get( $request ) {
|
||||
$label_ids = explode( ',', $request['label_ids'] );
|
||||
$labels = array();
|
||||
foreach ( $label_ids as $label_id ) {
|
||||
$response = $this->api_client->get_label_status( $label_id );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
$label = $this->settings_store->update_label_order_meta_data( $request['order_id'], $response->label );
|
||||
$labels[] = $label;
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'labels' => $labels,
|
||||
);
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Shipping_Rates_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Shipping_Rates_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/label/(?P<order_id>\d+)/rates';
|
||||
|
||||
/**
|
||||
* Prefix to add in package name for making requests with multiple rates.
|
||||
*/
|
||||
public $SPECIAL_RATE_PREFIX = '_wcs_rate_type_';
|
||||
|
||||
/**
|
||||
* Array of extra options to collect rates for.
|
||||
*/
|
||||
protected $extra_rates = array(
|
||||
'signature_required' => array(
|
||||
'signature' => 'yes',
|
||||
),
|
||||
'adult_signature_required' => array(
|
||||
'signature' => 'adult',
|
||||
),
|
||||
);
|
||||
|
||||
private function has_customs_data( $package ) {
|
||||
return isset( $package['contents_type'] );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param WP_REST_Request $request - See WC_Connect_API_Client::get_label_rates()
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function post( $request ) {
|
||||
$payload = $request->get_json_params();
|
||||
$payload['payment_method_id'] = $this->settings_store->get_selected_payment_method_id();
|
||||
$order_id = $request['order_id'];
|
||||
|
||||
// This is the earliest point in the printing label flow where we are sure that
|
||||
// the merchant wants to ship from this exact address (normalized or otherwise)
|
||||
$this->settings_store->update_origin_address( $payload['origin'] );
|
||||
$this->settings_store->update_destination_address( $order_id, $payload['destination'] );
|
||||
|
||||
// Update the customs information on all this order's products
|
||||
$updated_product_ids = array();
|
||||
foreach ( $payload['packages'] as $package_id => $package ) {
|
||||
if ( ! $this->has_customs_data( $package ) ) {
|
||||
break;
|
||||
}
|
||||
foreach ( $package['items'] as $index => $item ) {
|
||||
if ( ! isset( $updated_product_ids[ $item['product_id'] ] ) ) {
|
||||
$updated_product_ids[ $item['product_id'] ] = true;
|
||||
update_post_meta(
|
||||
$item['product_id'],
|
||||
'wc_connect_customs_info',
|
||||
array(
|
||||
'description' => $item['description'],
|
||||
'hs_tariff_number' => $item['hs_tariff_number'],
|
||||
'origin_country' => $item['origin_country'],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->get_all_rates( $payload );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'rates' => $response,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get standard rates along with rates for special options
|
||||
* that are defined in $this->extra_rates
|
||||
*
|
||||
* @param stdClass $payload Request payload.
|
||||
* @return WPError|stdClass
|
||||
*/
|
||||
public function get_all_rates( $payload ) {
|
||||
$signature_packages = [];
|
||||
$original_package_names = [];
|
||||
|
||||
// Add extra package requests with special options set.
|
||||
foreach ( $this->extra_rates as $rate_name => $rate_option ) {
|
||||
foreach ( $rate_option as $option_name => $option_value ) {
|
||||
foreach ( $payload['packages'] as $package_id => $package ) {
|
||||
$original_package_names[] = $package['id'];
|
||||
$new_package = $package;
|
||||
$new_package[ $option_name ] = $option_value;
|
||||
|
||||
$new_package['id'] .= $this->SPECIAL_RATE_PREFIX . $rate_name;
|
||||
$signature_packages[] = $new_package;
|
||||
}
|
||||
}
|
||||
}
|
||||
$payload['packages'] = array_merge( $payload['packages'], $signature_packages );
|
||||
|
||||
$response = $this->request_rates( $payload );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
if ( property_exists( $response, 'rates' ) ) {
|
||||
return $this->merge_all_rates( $response->rates, $original_package_names );
|
||||
}
|
||||
return new stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge default rates together with extra rates.
|
||||
*
|
||||
* get_all_rates requests extra rate options as separate
|
||||
* packages. This function groups these separate packages
|
||||
* under the original the package name for easier parsing
|
||||
* on the frontend.
|
||||
*
|
||||
* @param stdClass $rates Rate response for server.
|
||||
* @param array $original_package_names Package names.
|
||||
*
|
||||
* @return array Rates
|
||||
*/
|
||||
public function merge_all_rates( $rates, $original_package_names ) {
|
||||
$parsed_rates = [];
|
||||
|
||||
foreach ( $original_package_names as $name ) {
|
||||
// Add a 'default' entry for the rate with no special options.
|
||||
$parsed_rates[ $name ] = array(
|
||||
'default' => $rates->{ $name },
|
||||
);
|
||||
|
||||
// Get package for each extra rate to group them under the original package name.
|
||||
foreach ( $this->extra_rates as $extra_rate_name => $option ) {
|
||||
$extra_rate_package_name = $name . $this->SPECIAL_RATE_PREFIX . $extra_rate_name;
|
||||
if ( isset( $rates->{ $extra_rate_package_name } ) ) {
|
||||
$parsed_rates[ $name ][ $extra_rate_name ] = $rates->{ $extra_rate_package_name };
|
||||
}
|
||||
}
|
||||
}
|
||||
return $parsed_rates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make rate request.
|
||||
*
|
||||
* @param stdClass $payload Request payload.
|
||||
* @return WPError|stdClass
|
||||
*/
|
||||
public function request_rates( $payload ) {
|
||||
$response = $this->api_client->get_label_rates( $payload );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array( 'message' => $response->get_error_message() )
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Subscription_Activate_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Subscription_Activate_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/subscription/(?P<subscription_key>.+)/activate';
|
||||
|
||||
public function post( $request ) {
|
||||
$subscription_key = $request['subscription_key'];
|
||||
|
||||
$response = $this->api_client->activate_subscription( $subscription_key );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
return $response;
|
||||
}
|
||||
|
||||
$activated = wp_remote_retrieve_response_code( $activation_response ) === 200;
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
if ( ( ! $activated && ! empty( $body['code'] ) && 'already_connected' === $body['code'] ) ) {
|
||||
return new WP_Error(
|
||||
'already_active',
|
||||
__( 'The subscription is already active.', 'woocommerce-services' )
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Subscriptions_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Subscriptions_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/subscriptions';
|
||||
|
||||
public function post() {
|
||||
$response = $this->api_client->get_wccom_subscriptions();
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
return $response;
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'subscriptions' => $response->subscriptions,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Tos_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Tos_Controller extends WC_REST_Connect_Base_Controller {
|
||||
|
||||
protected $rest_base = 'connect/tos';
|
||||
|
||||
public function get() {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'accepted' => WC_Connect_Options::get_option( 'tos_accepted' ),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$settings = $request->get_json_params();
|
||||
|
||||
if ( ! $settings || ! isset( $settings['accepted'] ) || ! $settings['accepted'] ) {
|
||||
return new WP_Error( 'bad_request', __( 'Bad request', 'woocommerce-services' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
WC_Connect_Options::update_option( 'tos_accepted', true );
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'accepted' => WC_Connect_Options::get_option( 'tos_accepted' ),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the requester's permissions
|
||||
*/
|
||||
public function check_permission( $request ) {
|
||||
return current_user_can( 'manage_woocommerce' ) &&
|
||||
current_user_can( 'install_plugins' ) &&
|
||||
current_user_can( 'activate_plugins' );
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_WCShipping_Compatibility_Packages_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/class-wc-rest-connect-packages-controller.php';
|
||||
require_once __DIR__ . '/class-wc-rest-connect-wcshipping-compatibility-packages-controller.php';
|
||||
|
||||
/**
|
||||
* REST controller using WCS&T's settings store instead of WCShipping's.
|
||||
*/
|
||||
class WC_REST_Connect_WCShipping_Compatibility_Packages_Controller extends WC_REST_Connect_Packages_Controller {
|
||||
protected $rest_base = 'connect/wcservices/packages';
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API Data controller.
|
||||
*
|
||||
* Handles requests to the /data/continents endpoint.
|
||||
*
|
||||
* Directly copied from the wc-api-dev plugin. Delete this when the "v3" REST API is included in all the WC versions we support.
|
||||
*
|
||||
* @author Automattic
|
||||
* @category API
|
||||
* @package WooCommerce/API
|
||||
* @since 3.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API Data controller class.
|
||||
*
|
||||
* @package WooCommerce/API
|
||||
* @extends WC_REST_Controller
|
||||
*/
|
||||
class WC_REST_Dev_Data_Continents_Controller extends WC_REST_Dev_Data_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Continents
|
||||
*/
|
||||
protected $continents;
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'data/continents';
|
||||
|
||||
public function __construct() {
|
||||
$this->continents = new WC_Connect_Continents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<location>[\w-]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => array(
|
||||
'continent' => array(
|
||||
'description' => __( '2 character continent code.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of states for all continents.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$continents = WC()->countries->get_continents();
|
||||
$data = array();
|
||||
|
||||
foreach ( array_keys( $continents ) as $continent_code ) {
|
||||
$continent = $this->continents->get_continent( $continent_code, $request );
|
||||
$response = $this->prepare_item_for_response( $continent, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of locations for a given continent.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$data = $this->continents->get_continent( strtoupper( $request['location'] ), $request );
|
||||
if ( empty( $data ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_data_invalid_location', __( 'There are no locations matching these parameters.', 'woocommerce' ), array( 'status' => 404 ) );
|
||||
}
|
||||
return $this->prepare_item_for_response( $data, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data object for response.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param object $item Data object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response $response Response data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$data = $this->add_additional_fields_to_object( $item, $request );
|
||||
$data = $this->filter_response_by_context( $data, 'view' );
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$response->add_links( $this->prepare_links( $item ) );
|
||||
|
||||
/**
|
||||
* Filter the location list returned from the API.
|
||||
*
|
||||
* Allows modification of the loction data right before it is returned.
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param array $item The original list of continent(s), countries, and states.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'woocommerce_rest_prepare_data_continent', $response, $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param object $item Data object.
|
||||
* @return array Links for the given continent.
|
||||
*/
|
||||
protected function prepare_links( $item ) {
|
||||
$continent_code = strtolower( $item['code'] );
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $continent_code ) ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
);
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'data_continents',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'code' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( '2 character continent code.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Full name of continent.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'countries' => array(
|
||||
'type' => 'array',
|
||||
'description' => __( 'List of countries on this continent.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'code' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'ISO3166 alpha-2 country code.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'currency_code' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Default ISO4127 alpha-3 currency code for the country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'currency_pos' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Currency symbol position for this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'decimal_sep' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Decimal separator for displayed prices for this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'dimension_unit' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The unit lengths are defined in for this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Full name of country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'num_decimals' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Number of decimal points shown in displayed prices for this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'states' => array(
|
||||
'type' => 'array',
|
||||
'description' => __( 'List of states in this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'code' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'State code.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Full name of state.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'thousand_sep' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Thousands separator for displayed prices in this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'weight_unit' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The unit weights are defined in for this country.', 'woocommerce' ),
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API Data controller.
|
||||
*
|
||||
* Handles requests to the /data endpoint.
|
||||
*
|
||||
* Directly copied from the wc-api-dev plugin. Delete this when the "v3" REST API is included in all the WC versions we support.
|
||||
*
|
||||
* @author Automattic
|
||||
* @category API
|
||||
* @package WooCommerce/API
|
||||
* @since 3.1.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API Data controller class.
|
||||
*
|
||||
* @package WooCommerce/API
|
||||
* @extends WC_REST_Controller
|
||||
*/
|
||||
class WC_REST_Dev_Data_Controller extends WC_REST_Controller {
|
||||
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/v3';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'data';
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to read site data.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a given request has permission to read site settings.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|boolean
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
|
||||
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of data resources.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @param WP_REST_Request $request
|
||||
* @return WP_Error|WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$data = array();
|
||||
$resources = array(
|
||||
array(
|
||||
'slug' => 'continents',
|
||||
'description' => __( 'List of supported continents, countries, and states.', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'slug' => 'countries',
|
||||
'description' => __( 'List of supported states in a given country.', 'woocommerce' ),
|
||||
),
|
||||
array(
|
||||
'slug' => 'currencies',
|
||||
'description' => __( 'List of supported currencies.', 'woocommerce' ),
|
||||
),
|
||||
);
|
||||
|
||||
foreach ( $resources as $resource ) {
|
||||
$item = $this->prepare_item_for_response( (object) $resource, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $item );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a data resource object for serialization.
|
||||
*
|
||||
* @param stdClass $report Report data.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response $response Response data.
|
||||
*/
|
||||
public function prepare_item_for_response( $resource, $request ) {
|
||||
$data = array(
|
||||
'slug' => $resource->slug,
|
||||
'description' => $resource->description,
|
||||
);
|
||||
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, 'view' );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
$response->add_links( $this->prepare_links( $resource ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param object $item Data object.
|
||||
* @return array Links for the given country.
|
||||
*/
|
||||
protected function prepare_links( $item ) {
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $item->slug ) ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data index schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'data_index',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'slug' => array(
|
||||
'description' => __( 'Data resource ID.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Data resource description.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[116],{382:function(a,e,d){!function(a){"use strict";a.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Ju\xf1u_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"segundu balun",ss:"segundu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(a){var e=a%10,d=1===~~(a%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return a+d},week:{dow:1,doy:4}})}(d(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[58],{324:function(e,_,t){!function(e){"use strict";var _={1:"\u0ae7",2:"\u0ae8",3:"\u0ae9",4:"\u0aea",5:"\u0aeb",6:"\u0aec",7:"\u0aed",8:"\u0aee",9:"\u0aef",0:"\u0ae6"},t={"\u0ae7":"1","\u0ae8":"2","\u0ae9":"3","\u0aea":"4","\u0aeb":"5","\u0aec":"6","\u0aed":"7","\u0aee":"8","\u0aef":"9","\u0ae6":"0"};e.defineLocale("gu",{months:"\u0a9c\u0abe\u0aa8\u0acd\u0aaf\u0ac1\u0a86\u0ab0\u0ac0_\u0aab\u0ac7\u0aac\u0acd\u0ab0\u0ac1\u0a86\u0ab0\u0ac0_\u0aae\u0abe\u0ab0\u0acd\u0a9a_\u0a8f\u0aaa\u0acd\u0ab0\u0abf\u0ab2_\u0aae\u0ac7_\u0a9c\u0ac2\u0aa8_\u0a9c\u0ac1\u0ab2\u0abe\u0a88_\u0a91\u0a97\u0ab8\u0acd\u0a9f_\u0ab8\u0aaa\u0acd\u0a9f\u0ac7\u0aae\u0acd\u0aac\u0ab0_\u0a91\u0a95\u0acd\u0a9f\u0acd\u0aac\u0ab0_\u0aa8\u0ab5\u0ac7\u0aae\u0acd\u0aac\u0ab0_\u0aa1\u0abf\u0ab8\u0ac7\u0aae\u0acd\u0aac\u0ab0".split("_"),monthsShort:"\u0a9c\u0abe\u0aa8\u0acd\u0aaf\u0ac1._\u0aab\u0ac7\u0aac\u0acd\u0ab0\u0ac1._\u0aae\u0abe\u0ab0\u0acd\u0a9a_\u0a8f\u0aaa\u0acd\u0ab0\u0abf._\u0aae\u0ac7_\u0a9c\u0ac2\u0aa8_\u0a9c\u0ac1\u0ab2\u0abe._\u0a91\u0a97._\u0ab8\u0aaa\u0acd\u0a9f\u0ac7._\u0a91\u0a95\u0acd\u0a9f\u0acd._\u0aa8\u0ab5\u0ac7._\u0aa1\u0abf\u0ab8\u0ac7.".split("_"),monthsParseExact:!0,weekdays:"\u0ab0\u0ab5\u0abf\u0ab5\u0abe\u0ab0_\u0ab8\u0acb\u0aae\u0ab5\u0abe\u0ab0_\u0aae\u0a82\u0a97\u0ab3\u0ab5\u0abe\u0ab0_\u0aac\u0ac1\u0aa7\u0acd\u0ab5\u0abe\u0ab0_\u0a97\u0ac1\u0ab0\u0ac1\u0ab5\u0abe\u0ab0_\u0ab6\u0ac1\u0a95\u0acd\u0ab0\u0ab5\u0abe\u0ab0_\u0ab6\u0aa8\u0abf\u0ab5\u0abe\u0ab0".split("_"),weekdaysShort:"\u0ab0\u0ab5\u0abf_\u0ab8\u0acb\u0aae_\u0aae\u0a82\u0a97\u0ab3_\u0aac\u0ac1\u0aa7\u0acd_\u0a97\u0ac1\u0ab0\u0ac1_\u0ab6\u0ac1\u0a95\u0acd\u0ab0_\u0ab6\u0aa8\u0abf".split("_"),weekdaysMin:"\u0ab0_\u0ab8\u0acb_\u0aae\u0a82_\u0aac\u0ac1_\u0a97\u0ac1_\u0ab6\u0ac1_\u0ab6".split("_"),longDateFormat:{LT:"A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",LTS:"A h:mm:ss \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7",LLLL:"dddd, D MMMM YYYY, A h:mm \u0ab5\u0abe\u0a97\u0acd\u0aaf\u0ac7"},calendar:{sameDay:"[\u0a86\u0a9c] LT",nextDay:"[\u0a95\u0abe\u0ab2\u0ac7] LT",nextWeek:"dddd, LT",lastDay:"[\u0a97\u0a87\u0a95\u0abe\u0ab2\u0ac7] LT",lastWeek:"[\u0aaa\u0abe\u0a9b\u0ab2\u0abe] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0aae\u0abe",past:"%s \u0aaa\u0ab9\u0ac7\u0ab2\u0abe",s:"\u0a85\u0aae\u0ac1\u0a95 \u0aaa\u0ab3\u0acb",ss:"%d \u0ab8\u0ac7\u0a95\u0a82\u0aa1",m:"\u0a8f\u0a95 \u0aae\u0abf\u0aa8\u0abf\u0a9f",mm:"%d \u0aae\u0abf\u0aa8\u0abf\u0a9f",h:"\u0a8f\u0a95 \u0a95\u0ab2\u0abe\u0a95",hh:"%d \u0a95\u0ab2\u0abe\u0a95",d:"\u0a8f\u0a95 \u0aa6\u0abf\u0ab5\u0ab8",dd:"%d \u0aa6\u0abf\u0ab5\u0ab8",M:"\u0a8f\u0a95 \u0aae\u0ab9\u0abf\u0aa8\u0acb",MM:"%d \u0aae\u0ab9\u0abf\u0aa8\u0acb",y:"\u0a8f\u0a95 \u0ab5\u0ab0\u0acd\u0ab7",yy:"%d \u0ab5\u0ab0\u0acd\u0ab7"},preparse:function(e){return e.replace(/[\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0ae6]/g,function(e){return t[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return _[e]})},meridiemParse:/\u0ab0\u0abe\u0aa4|\u0aac\u0aaa\u0acb\u0ab0|\u0ab8\u0ab5\u0abe\u0ab0|\u0ab8\u0abe\u0a82\u0a9c/,meridiemHour:function(e,_){return 12===e&&(e=0),"\u0ab0\u0abe\u0aa4"===_?e<4?e:e+12:"\u0ab8\u0ab5\u0abe\u0ab0"===_?e:"\u0aac\u0aaa\u0acb\u0ab0"===_?e>=10?e:e+12:"\u0ab8\u0abe\u0a82\u0a9c"===_?e+12:void 0},meridiem:function(e,_,t){return e<4?"\u0ab0\u0abe\u0aa4":e<10?"\u0ab8\u0ab5\u0abe\u0ab0":e<17?"\u0aac\u0aaa\u0acb\u0ab0":e<20?"\u0ab8\u0abe\u0a82\u0a9c":"\u0ab0\u0abe\u0aa4"},week:{dow:0,doy:6}})}(t(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[126],{392:function(e,_,d){!function(e){"use strict";e.defineLocale("ug-cn",{months:"\u064a\u0627\u0646\u06cb\u0627\u0631_\u0641\u06d0\u06cb\u0631\u0627\u0644_\u0645\u0627\u0631\u062a_\u0626\u0627\u067e\u0631\u06d0\u0644_\u0645\u0627\u064a_\u0626\u0649\u064a\u06c7\u0646_\u0626\u0649\u064a\u06c7\u0644_\u0626\u0627\u06cb\u063a\u06c7\u0633\u062a_\u0633\u06d0\u0646\u062a\u06d5\u0628\u0649\u0631_\u0626\u06c6\u0643\u062a\u06d5\u0628\u0649\u0631_\u0646\u0648\u064a\u0627\u0628\u0649\u0631_\u062f\u06d0\u0643\u0627\u0628\u0649\u0631".split("_"),monthsShort:"\u064a\u0627\u0646\u06cb\u0627\u0631_\u0641\u06d0\u06cb\u0631\u0627\u0644_\u0645\u0627\u0631\u062a_\u0626\u0627\u067e\u0631\u06d0\u0644_\u0645\u0627\u064a_\u0626\u0649\u064a\u06c7\u0646_\u0626\u0649\u064a\u06c7\u0644_\u0626\u0627\u06cb\u063a\u06c7\u0633\u062a_\u0633\u06d0\u0646\u062a\u06d5\u0628\u0649\u0631_\u0626\u06c6\u0643\u062a\u06d5\u0628\u0649\u0631_\u0646\u0648\u064a\u0627\u0628\u0649\u0631_\u062f\u06d0\u0643\u0627\u0628\u0649\u0631".split("_"),weekdays:"\u064a\u06d5\u0643\u0634\u06d5\u0646\u0628\u06d5_\u062f\u06c8\u0634\u06d5\u0646\u0628\u06d5_\u0633\u06d5\u064a\u0634\u06d5\u0646\u0628\u06d5_\u0686\u0627\u0631\u0634\u06d5\u0646\u0628\u06d5_\u067e\u06d5\u064a\u0634\u06d5\u0646\u0628\u06d5_\u062c\u06c8\u0645\u06d5_\u0634\u06d5\u0646\u0628\u06d5".split("_"),weekdaysShort:"\u064a\u06d5_\u062f\u06c8_\u0633\u06d5_\u0686\u0627_\u067e\u06d5_\u062c\u06c8_\u0634\u06d5".split("_"),weekdaysMin:"\u064a\u06d5_\u062f\u06c8_\u0633\u06d5_\u0686\u0627_\u067e\u06d5_\u062c\u06c8_\u0634\u06d5".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY-\u064a\u0649\u0644\u0649M-\u0626\u0627\u064a\u0646\u0649\u06adD-\u0643\u06c8\u0646\u0649",LLL:"YYYY-\u064a\u0649\u0644\u0649M-\u0626\u0627\u064a\u0646\u0649\u06adD-\u0643\u06c8\u0646\u0649\u060c HH:mm",LLLL:"dddd\u060c YYYY-\u064a\u0649\u0644\u0649M-\u0626\u0627\u064a\u0646\u0649\u06adD-\u0643\u06c8\u0646\u0649\u060c HH:mm"},meridiemParse:/\u064a\u06d0\u0631\u0649\u0645 \u0643\u06d0\u0686\u06d5|\u0633\u06d5\u06be\u06d5\u0631|\u0686\u06c8\u0634\u062a\u0649\u0646 \u0628\u06c7\u0631\u06c7\u0646|\u0686\u06c8\u0634|\u0686\u06c8\u0634\u062a\u0649\u0646 \u0643\u06d0\u064a\u0649\u0646|\u0643\u06d5\u0686/,meridiemHour:function(e,_){return 12===e&&(e=0),"\u064a\u06d0\u0631\u0649\u0645 \u0643\u06d0\u0686\u06d5"===_||"\u0633\u06d5\u06be\u06d5\u0631"===_||"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0628\u06c7\u0631\u06c7\u0646"===_?e:"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0643\u06d0\u064a\u0649\u0646"===_||"\u0643\u06d5\u0686"===_?e+12:e>=11?e:e+12},meridiem:function(e,_,d){var t=100*e+_;return t<600?"\u064a\u06d0\u0631\u0649\u0645 \u0643\u06d0\u0686\u06d5":t<900?"\u0633\u06d5\u06be\u06d5\u0631":t<1130?"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0628\u06c7\u0631\u06c7\u0646":t<1230?"\u0686\u06c8\u0634":t<1800?"\u0686\u06c8\u0634\u062a\u0649\u0646 \u0643\u06d0\u064a\u0649\u0646":"\u0643\u06d5\u0686"},calendar:{sameDay:"[\u0628\u06c8\u06af\u06c8\u0646 \u0633\u0627\u0626\u06d5\u062a] LT",nextDay:"[\u0626\u06d5\u062a\u06d5 \u0633\u0627\u0626\u06d5\u062a] LT",nextWeek:"[\u0643\u06d0\u0644\u06d5\u0631\u0643\u0649] dddd [\u0633\u0627\u0626\u06d5\u062a] LT",lastDay:"[\u062a\u06c6\u0646\u06c8\u06af\u06c8\u0646] LT",lastWeek:"[\u0626\u0627\u0644\u062f\u0649\u0646\u0642\u0649] dddd [\u0633\u0627\u0626\u06d5\u062a] LT",sameElse:"L"},relativeTime:{future:"%s \u0643\u06d0\u064a\u0649\u0646",past:"%s \u0628\u06c7\u0631\u06c7\u0646",s:"\u0646\u06d5\u0686\u0686\u06d5 \u0633\u06d0\u0643\u0648\u0646\u062a",ss:"%d \u0633\u06d0\u0643\u0648\u0646\u062a",m:"\u0628\u0649\u0631 \u0645\u0649\u0646\u06c7\u062a",mm:"%d \u0645\u0649\u0646\u06c7\u062a",h:"\u0628\u0649\u0631 \u0633\u0627\u0626\u06d5\u062a",hh:"%d \u0633\u0627\u0626\u06d5\u062a",d:"\u0628\u0649\u0631 \u0643\u06c8\u0646",dd:"%d \u0643\u06c8\u0646",M:"\u0628\u0649\u0631 \u0626\u0627\u064a",MM:"%d \u0626\u0627\u064a",y:"\u0628\u0649\u0631 \u064a\u0649\u0644",yy:"%d \u064a\u0649\u0644"},dayOfMonthOrdinalParse:/\d{1,2}(-\u0643\u06c8\u0646\u0649|-\u0626\u0627\u064a|-\u06be\u06d5\u067e\u062a\u06d5)/,ordinal:function(e,_){switch(_){case"d":case"D":case"DDD":return e+"-\u0643\u06c8\u0646\u0649";case"w":case"W":return e+"-\u06be\u06d5\u067e\u062a\u06d5";default:return e}},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:7}})}(d(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[77],{343:function(e,n,t){!function(e){"use strict";function n(e,n,t,r){var u={m:["eng Minutt","enger Minutt"],h:["eng Stonn","enger Stonn"],d:["een Dag","engem Dag"],M:["ee Mount","engem Mount"],y:["ee Joer","engem Joer"]};return n?u[t][0]:u[t][1]}function t(e){if(e=parseInt(e,10),isNaN(e))return!1;if(e<0)return!0;if(e<10)return 4<=e&&e<=7;if(e<100){var n=e%10,r=e/10;return t(0===n?r:n)}if(e<1e4){for(;e>=10;)e/=10;return t(e)}return t(e/=1e3)}e.defineLocale("lb",{months:"Januar_Februar_M\xe4erz_Abr\xebll_Mee_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),monthsParseExact:!0,weekdays:"Sonndeg_M\xe9indeg_D\xebnschdeg_M\xebttwoch_Donneschdeg_Freideg_Samschdeg".split("_"),weekdaysShort:"So._M\xe9._D\xeb._M\xeb._Do._Fr._Sa.".split("_"),weekdaysMin:"So_M\xe9_D\xeb_M\xeb_Do_Fr_Sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm [Auer]",LTS:"H:mm:ss [Auer]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm [Auer]",LLLL:"dddd, D. MMMM YYYY H:mm [Auer]"},calendar:{sameDay:"[Haut um] LT",sameElse:"L",nextDay:"[Muer um] LT",nextWeek:"dddd [um] LT",lastDay:"[G\xebschter um] LT",lastWeek:function(){switch(this.day()){case 2:case 4:return"[Leschten] dddd [um] LT";default:return"[Leschte] dddd [um] LT"}}},relativeTime:{future:function(e){return t(e.substr(0,e.indexOf(" ")))?"a "+e:"an "+e},past:function(e){return t(e.substr(0,e.indexOf(" ")))?"viru "+e:"virun "+e},s:"e puer Sekonnen",ss:"%d Sekonnen",m:n,mm:"%d Minutten",h:n,hh:"%d Stonnen",d:n,dd:"%d Deeg",M:n,MM:"%d M\xe9int",y:n,yy:"%d Joer"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(t(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[43],{309:function(e,a,s){!function(e){"use strict";function a(e,a,s,t){var n={s:["m\xf5ne sekundi","m\xf5ni sekund","paar sekundit"],ss:[e+"sekundi",e+"sekundit"],m:["\xfche minuti","\xfcks minut"],mm:[e+" minuti",e+" minutit"],h:["\xfche tunni","tund aega","\xfcks tund"],hh:[e+" tunni",e+" tundi"],d:["\xfche p\xe4eva","\xfcks p\xe4ev"],M:["kuu aja","kuu aega","\xfcks kuu"],MM:[e+" kuu",e+" kuud"],y:["\xfche aasta","aasta","\xfcks aasta"],yy:[e+" aasta",e+" aastat"]};return a?n[s][2]?n[s][2]:n[s][1]:t?n[s][0]:n[s][1]}e.defineLocale("et",{months:"jaanuar_veebruar_m\xe4rts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"),monthsShort:"jaan_veebr_m\xe4rts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"),weekdays:"p\xfchap\xe4ev_esmasp\xe4ev_teisip\xe4ev_kolmap\xe4ev_neljap\xe4ev_reede_laup\xe4ev".split("_"),weekdaysShort:"P_E_T_K_N_R_L".split("_"),weekdaysMin:"P_E_T_K_N_R_L".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[T\xe4na,] LT",nextDay:"[Homme,] LT",nextWeek:"[J\xe4rgmine] dddd LT",lastDay:"[Eile,] LT",lastWeek:"[Eelmine] dddd LT",sameElse:"L"},relativeTime:{future:"%s p\xe4rast",past:"%s tagasi",s:a,ss:a,m:a,mm:a,h:a,hh:a,d:a,dd:"%d p\xe4eva",M:a,MM:a,y:a,yy:a},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(s(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[98],{364:function(e,i,t){!function(e){"use strict";var i="stycze\u0144_luty_marzec_kwiecie\u0144_maj_czerwiec_lipiec_sierpie\u0144_wrzesie\u0144_pa\u017adziernik_listopad_grudzie\u0144".split("_"),t="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_wrze\u015bnia_pa\u017adziernika_listopada_grudnia".split("_"),s=[/^sty/i,/^lut/i,/^mar/i,/^kwi/i,/^maj/i,/^cze/i,/^lip/i,/^sie/i,/^wrz/i,/^pa\u017a/i,/^lis/i,/^gru/i];function a(e){return e%10<5&&e%10>1&&~~(e/10)%10!==1}function r(e,i,t){var s=e+" ";switch(t){case"ss":return s+(a(e)?"sekundy":"sekund");case"m":return i?"minuta":"minut\u0119";case"mm":return s+(a(e)?"minuty":"minut");case"h":return i?"godzina":"godzin\u0119";case"hh":return s+(a(e)?"godziny":"godzin");case"ww":return s+(a(e)?"tygodnie":"tygodni");case"MM":return s+(a(e)?"miesi\u0105ce":"miesi\u0119cy");case"yy":return s+(a(e)?"lata":"lat")}}e.defineLocale("pl",{months:function(e,s){return e?/D MMMM/.test(s)?t[e.month()]:i[e.month()]:i},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_pa\u017a_lis_gru".split("_"),monthsParse:s,longMonthsParse:s,shortMonthsParse:s,weekdays:"niedziela_poniedzia\u0142ek_wtorek_\u015broda_czwartek_pi\u0105tek_sobota".split("_"),weekdaysShort:"ndz_pon_wt_\u015br_czw_pt_sob".split("_"),weekdaysMin:"Nd_Pn_Wt_\u015ar_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Dzi\u015b o] LT",nextDay:"[Jutro o] LT",nextWeek:function(){switch(this.day()){case 0:return"[W niedziel\u0119 o] LT";case 2:return"[We wtorek o] LT";case 3:return"[W \u015brod\u0119 o] LT";case 6:return"[W sobot\u0119 o] LT";default:return"[W] dddd [o] LT"}},lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zesz\u0142\u0105 niedziel\u0119 o] LT";case 3:return"[W zesz\u0142\u0105 \u015brod\u0119 o] LT";case 6:return"[W zesz\u0142\u0105 sobot\u0119 o] LT";default:return"[W zesz\u0142y] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",ss:r,m:r,mm:r,h:r,hh:r,d:"1 dzie\u0144",dd:"%d dni",w:"tydzie\u0144",ww:r,M:"miesi\u0105c",MM:r,y:"rok",yy:r},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(t(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[48],{314:function(a,e,d){!function(a){"use strict";a.defineLocale("fo",{months:"januar_februar_mars_apr\xedl_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sunnudagur_m\xe1nadagur_t\xfdsdagur_mikudagur_h\xf3sdagur_fr\xedggjadagur_leygardagur".split("_"),weekdaysShort:"sun_m\xe1n_t\xfds_mik_h\xf3s_fr\xed_ley".split("_"),weekdaysMin:"su_m\xe1_t\xfd_mi_h\xf3_fr_le".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D. MMMM, YYYY HH:mm"},calendar:{sameDay:"[\xcd dag kl.] LT",nextDay:"[\xcd morgin kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[\xcd gj\xe1r kl.] LT",lastWeek:"[s\xed\xf0stu] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"um %s",past:"%s s\xed\xf0ani",s:"f\xe1 sekund",ss:"%d sekundir",m:"ein minuttur",mm:"%d minuttir",h:"ein t\xedmi",hh:"%d t\xedmar",d:"ein dagur",dd:"%d dagar",M:"ein m\xe1na\xf0ur",MM:"%d m\xe1na\xf0ir",y:"eitt \xe1r",yy:"%d \xe1r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(d(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[62],{328:function(e,r,s){!function(e){"use strict";var r="vas\xe1rnap h\xe9tf\u0151n kedden szerd\xe1n cs\xfct\xf6rt\xf6k\xf6n p\xe9nteken szombaton".split(" ");function s(e,r,s,n){var a=e;switch(s){case"s":return n||r?"n\xe9h\xe1ny m\xe1sodperc":"n\xe9h\xe1ny m\xe1sodperce";case"ss":return a+(n||r)?" m\xe1sodperc":" m\xe1sodperce";case"m":return"egy"+(n||r?" perc":" perce");case"mm":return a+(n||r?" perc":" perce");case"h":return"egy"+(n||r?" \xf3ra":" \xf3r\xe1ja");case"hh":return a+(n||r?" \xf3ra":" \xf3r\xe1ja");case"d":return"egy"+(n||r?" nap":" napja");case"dd":return a+(n||r?" nap":" napja");case"M":return"egy"+(n||r?" h\xf3nap":" h\xf3napja");case"MM":return a+(n||r?" h\xf3nap":" h\xf3napja");case"y":return"egy"+(n||r?" \xe9v":" \xe9ve");case"yy":return a+(n||r?" \xe9v":" \xe9ve")}return""}function n(e){return(e?"":"[m\xfalt] ")+"["+r[this.day()]+"] LT[-kor]"}e.defineLocale("hu",{months:"janu\xe1r_febru\xe1r_m\xe1rcius_\xe1prilis_m\xe1jus_j\xfanius_j\xfalius_augusztus_szeptember_okt\xf3ber_november_december".split("_"),monthsShort:"jan._feb._m\xe1rc._\xe1pr._m\xe1j._j\xfan._j\xfal._aug._szept._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"vas\xe1rnap_h\xe9tf\u0151_kedd_szerda_cs\xfct\xf6rt\xf6k_p\xe9ntek_szombat".split("_"),weekdaysShort:"vas_h\xe9t_kedd_sze_cs\xfct_p\xe9n_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D. H:mm",LLLL:"YYYY. MMMM D., dddd H:mm"},meridiemParse:/de|du/i,isPM:function(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,r,s){return e<12?!0===s?"de":"DE":!0===s?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return n.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return n.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s m\xfalva",past:"%s",s:s,ss:s,m:s,mm:s,h:s,hh:s,d:s,dd:s,M:s,MM:s,y:s,yy:s},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(s(68))}}]);
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[46],{312:function(a,e,u){!function(a){"use strict";var e="nolla yksi kaksi kolme nelj\xe4 viisi kuusi seitsem\xe4n kahdeksan yhdeks\xe4n".split(" "),u=["nolla","yhden","kahden","kolmen","nelj\xe4n","viiden","kuuden",e[7],e[8],e[9]];function n(a,n,t,i){var s="";switch(t){case"s":return i?"muutaman sekunnin":"muutama sekunti";case"ss":s=i?"sekunnin":"sekuntia";break;case"m":return i?"minuutin":"minuutti";case"mm":s=i?"minuutin":"minuuttia";break;case"h":return i?"tunnin":"tunti";case"hh":s=i?"tunnin":"tuntia";break;case"d":return i?"p\xe4iv\xe4n":"p\xe4iv\xe4";case"dd":s=i?"p\xe4iv\xe4n":"p\xe4iv\xe4\xe4";break;case"M":return i?"kuukauden":"kuukausi";case"MM":s=i?"kuukauden":"kuukautta";break;case"y":return i?"vuoden":"vuosi";case"yy":s=i?"vuoden":"vuotta"}return s=function(a,n){return a<10?n?u[a]:e[a]:a}(a,i)+" "+s}a.defineLocale("fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kes\xe4kuu_hein\xe4kuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kes\xe4_hein\xe4_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] HH.mm",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] HH.mm",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] HH.mm",llll:"ddd, Do MMM YYYY, [klo] HH.mm"},calendar:{sameDay:"[t\xe4n\xe4\xe4n] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s p\xe4\xe4st\xe4",past:"%s sitten",s:n,ss:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(u(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[85],{351:function(e,s,a){!function(e){"use strict";function s(e,s,a,t){switch(a){case"s":return s?"\u0445\u044d\u0434\u0445\u044d\u043d \u0441\u0435\u043a\u0443\u043d\u0434":"\u0445\u044d\u0434\u0445\u044d\u043d \u0441\u0435\u043a\u0443\u043d\u0434\u044b\u043d";case"ss":return e+(s?" \u0441\u0435\u043a\u0443\u043d\u0434":" \u0441\u0435\u043a\u0443\u043d\u0434\u044b\u043d");case"m":case"mm":return e+(s?" \u043c\u0438\u043d\u0443\u0442":" \u043c\u0438\u043d\u0443\u0442\u044b\u043d");case"h":case"hh":return e+(s?" \u0446\u0430\u0433":" \u0446\u0430\u0433\u0438\u0439\u043d");case"d":case"dd":return e+(s?" \u04e9\u0434\u04e9\u0440":" \u04e9\u0434\u0440\u0438\u0439\u043d");case"M":case"MM":return e+(s?" \u0441\u0430\u0440":" \u0441\u0430\u0440\u044b\u043d");case"y":case"yy":return e+(s?" \u0436\u0438\u043b":" \u0436\u0438\u043b\u0438\u0439\u043d");default:return e}}e.defineLocale("mn",{months:"\u041d\u044d\u0433\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0425\u043e\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0413\u0443\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0414\u04e9\u0440\u04e9\u0432\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0422\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0417\u0443\u0440\u0433\u0430\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0414\u043e\u043b\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u041d\u0430\u0439\u043c\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0415\u0441\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0410\u0440\u0430\u0432\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440_\u0410\u0440\u0432\u0430\u043d \u043d\u044d\u0433\u0434\u04af\u0433\u044d\u044d\u0440 \u0441\u0430\u0440_\u0410\u0440\u0432\u0430\u043d \u0445\u043e\u0451\u0440\u0434\u0443\u0433\u0430\u0430\u0440 \u0441\u0430\u0440".split("_"),monthsShort:"1 \u0441\u0430\u0440_2 \u0441\u0430\u0440_3 \u0441\u0430\u0440_4 \u0441\u0430\u0440_5 \u0441\u0430\u0440_6 \u0441\u0430\u0440_7 \u0441\u0430\u0440_8 \u0441\u0430\u0440_9 \u0441\u0430\u0440_10 \u0441\u0430\u0440_11 \u0441\u0430\u0440_12 \u0441\u0430\u0440".split("_"),monthsParseExact:!0,weekdays:"\u041d\u044f\u043c_\u0414\u0430\u0432\u0430\u0430_\u041c\u044f\u0433\u043c\u0430\u0440_\u041b\u0445\u0430\u0433\u0432\u0430_\u041f\u04af\u0440\u044d\u0432_\u0411\u0430\u0430\u0441\u0430\u043d_\u0411\u044f\u043c\u0431\u0430".split("_"),weekdaysShort:"\u041d\u044f\u043c_\u0414\u0430\u0432_\u041c\u044f\u0433_\u041b\u0445\u0430_\u041f\u04af\u0440_\u0411\u0430\u0430_\u0411\u044f\u043c".split("_"),weekdaysMin:"\u041d\u044f_\u0414\u0430_\u041c\u044f_\u041b\u0445_\u041f\u04af_\u0411\u0430_\u0411\u044f".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"YYYY \u043e\u043d\u044b MMMM\u044b\u043d D",LLL:"YYYY \u043e\u043d\u044b MMMM\u044b\u043d D HH:mm",LLLL:"dddd, YYYY \u043e\u043d\u044b MMMM\u044b\u043d D HH:mm"},meridiemParse:/\u04ae\u04e8|\u04ae\u0425/i,isPM:function(e){return"\u04ae\u0425"===e},meridiem:function(e,s,a){return e<12?"\u04ae\u04e8":"\u04ae\u0425"},calendar:{sameDay:"[\u04e8\u043d\u04e9\u04e9\u0434\u04e9\u0440] LT",nextDay:"[\u041c\u0430\u0440\u0433\u0430\u0430\u0448] LT",nextWeek:"[\u0418\u0440\u044d\u0445] dddd LT",lastDay:"[\u04e8\u0447\u0438\u0433\u0434\u04e9\u0440] LT",lastWeek:"[\u04e8\u043d\u0433\u04e9\u0440\u0441\u04e9\u043d] dddd LT",sameElse:"L"},relativeTime:{future:"%s \u0434\u0430\u0440\u0430\u0430",past:"%s \u04e9\u043c\u043d\u04e9",s:s,ss:s,m:s,mm:s,h:s,hh:s,d:s,dd:s,M:s,MM:s,y:s,yy:s},dayOfMonthOrdinalParse:/\d{1,2} \u04e9\u0434\u04e9\u0440/,ordinal:function(e,s){switch(s){case"d":case"D":case"DDD":return e+" \u04e9\u0434\u04e9\u0440";default:return e}}})}(a(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[76],{342:function(_,d,e){!function(_){"use strict";var d={0:"-\u0447\u04af",1:"-\u0447\u0438",2:"-\u0447\u0438",3:"-\u0447\u04af",4:"-\u0447\u04af",5:"-\u0447\u0438",6:"-\u0447\u044b",7:"-\u0447\u0438",8:"-\u0447\u0438",9:"-\u0447\u0443",10:"-\u0447\u0443",20:"-\u0447\u044b",30:"-\u0447\u0443",40:"-\u0447\u044b",50:"-\u0447\u04af",60:"-\u0447\u044b",70:"-\u0447\u0438",80:"-\u0447\u0438",90:"-\u0447\u0443",100:"-\u0447\u04af"};_.defineLocale("ky",{months:"\u044f\u043d\u0432\u0430\u0440\u044c_\u0444\u0435\u0432\u0440\u0430\u043b\u044c_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0435\u043b\u044c_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043d\u0442\u044f\u0431\u0440\u044c_\u043e\u043a\u0442\u044f\u0431\u0440\u044c_\u043d\u043e\u044f\u0431\u0440\u044c_\u0434\u0435\u043a\u0430\u0431\u0440\u044c".split("_"),monthsShort:"\u044f\u043d\u0432_\u0444\u0435\u0432_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440_\u043c\u0430\u0439_\u0438\u044e\u043d\u044c_\u0438\u044e\u043b\u044c_\u0430\u0432\u0433_\u0441\u0435\u043d_\u043e\u043a\u0442_\u043d\u043e\u044f_\u0434\u0435\u043a".split("_"),weekdays:"\u0416\u0435\u043a\u0448\u0435\u043c\u0431\u0438_\u0414\u04af\u0439\u0448\u04e9\u043c\u0431\u04af_\u0428\u0435\u0439\u0448\u0435\u043c\u0431\u0438_\u0428\u0430\u0440\u0448\u0435\u043c\u0431\u0438_\u0411\u0435\u0439\u0448\u0435\u043c\u0431\u0438_\u0416\u0443\u043c\u0430_\u0418\u0448\u0435\u043c\u0431\u0438".split("_"),weekdaysShort:"\u0416\u0435\u043a_\u0414\u04af\u0439_\u0428\u0435\u0439_\u0428\u0430\u0440_\u0411\u0435\u0439_\u0416\u0443\u043c_\u0418\u0448\u0435".split("_"),weekdaysMin:"\u0416\u043a_\u0414\u0439_\u0428\u0439_\u0428\u0440_\u0411\u0439_\u0416\u043c_\u0418\u0448".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0411\u04af\u0433\u04af\u043d \u0441\u0430\u0430\u0442] LT",nextDay:"[\u042d\u0440\u0442\u0435\u04a3 \u0441\u0430\u0430\u0442] LT",nextWeek:"dddd [\u0441\u0430\u0430\u0442] LT",lastDay:"[\u041a\u0435\u0447\u044d\u044d \u0441\u0430\u0430\u0442] LT",lastWeek:"[\u04e8\u0442\u043a\u04e9\u043d \u0430\u043f\u0442\u0430\u043d\u044b\u043d] dddd [\u043a\u04af\u043d\u04af] [\u0441\u0430\u0430\u0442] LT",sameElse:"L"},relativeTime:{future:"%s \u0438\u0447\u0438\u043d\u0434\u0435",past:"%s \u043c\u0443\u0440\u0443\u043d",s:"\u0431\u0438\u0440\u043d\u0435\u0447\u0435 \u0441\u0435\u043a\u0443\u043d\u0434",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434",m:"\u0431\u0438\u0440 \u043c\u04af\u043d\u04e9\u0442",mm:"%d \u043c\u04af\u043d\u04e9\u0442",h:"\u0431\u0438\u0440 \u0441\u0430\u0430\u0442",hh:"%d \u0441\u0430\u0430\u0442",d:"\u0431\u0438\u0440 \u043a\u04af\u043d",dd:"%d \u043a\u04af\u043d",M:"\u0431\u0438\u0440 \u0430\u0439",MM:"%d \u0430\u0439",y:"\u0431\u0438\u0440 \u0436\u044b\u043b",yy:"%d \u0436\u044b\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0447\u0438|\u0447\u044b|\u0447\u04af|\u0447\u0443)/,ordinal:function(_){var e=_%10,s=_>=100?100:null;return _+(d[_]||d[e]||d[s])},week:{dow:1,doy:7}})}(e(68))}}]);
|
||||
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[91],{357:function(e,d,a){!function(e){"use strict";e.defineLocale("nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan._feb._mars_apr._mai_juni_juli_aug._sep._okt._nov._des.".split("_"),monthsParseExact:!0,weekdays:"s\xf8ndag_mandag_tirsdag_onsdag_torsdag_fredag_l\xf8rdag".split("_"),weekdaysShort:"s\xf8._ma._ti._on._to._fr._l\xf8.".split("_"),weekdaysMin:"s\xf8_ma_ti_on_to_fr_l\xf8".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] HH:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i g\xe5r kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"noen sekunder",ss:"%d sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",w:"en uke",ww:"%d uker",M:"en m\xe5ned",MM:"%d m\xe5neder",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(a(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[114],{380:function(e,_,n){!function(e){"use strict";var _={1:"\u0be7",2:"\u0be8",3:"\u0be9",4:"\u0bea",5:"\u0beb",6:"\u0bec",7:"\u0bed",8:"\u0bee",9:"\u0bef",0:"\u0be6"},n={"\u0be7":"1","\u0be8":"2","\u0be9":"3","\u0bea":"4","\u0beb":"5","\u0bec":"6","\u0bed":"7","\u0bee":"8","\u0bef":"9","\u0be6":"0"};e.defineLocale("ta",{months:"\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf_\u0baa\u0bbf\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf_\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd_\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd_\u0bae\u0bc7_\u0b9c\u0bc2\u0ba9\u0bcd_\u0b9c\u0bc2\u0bb2\u0bc8_\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd_\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bc6\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b85\u0b95\u0bcd\u0b9f\u0bc7\u0bbe\u0baa\u0bb0\u0bcd_\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcd".split("_"),monthsShort:"\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf_\u0baa\u0bbf\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf_\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd_\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd_\u0bae\u0bc7_\u0b9c\u0bc2\u0ba9\u0bcd_\u0b9c\u0bc2\u0bb2\u0bc8_\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd_\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bc6\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b85\u0b95\u0bcd\u0b9f\u0bc7\u0bbe\u0baa\u0bb0\u0bcd_\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd_\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcd".split("_"),weekdays:"\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bcd\u0bb1\u0bc1\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0b9f\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0baa\u0bc1\u0ba4\u0ba9\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8_\u0b9a\u0ba9\u0bbf\u0b95\u0bcd\u0b95\u0bbf\u0bb4\u0bae\u0bc8".split("_"),weekdaysShort:"\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bc1_\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0bb3\u0bcd_\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd_\u0baa\u0bc1\u0ba4\u0ba9\u0bcd_\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0ba9\u0bcd_\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf_\u0b9a\u0ba9\u0bbf".split("_"),weekdaysMin:"\u0b9e\u0bbe_\u0ba4\u0bbf_\u0b9a\u0bc6_\u0baa\u0bc1_\u0bb5\u0bbf_\u0bb5\u0bc6_\u0b9a".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, HH:mm",LLLL:"dddd, D MMMM YYYY, HH:mm"},calendar:{sameDay:"[\u0b87\u0ba9\u0bcd\u0bb1\u0bc1] LT",nextDay:"[\u0ba8\u0bbe\u0bb3\u0bc8] LT",nextWeek:"dddd, LT",lastDay:"[\u0ba8\u0bc7\u0bb1\u0bcd\u0bb1\u0bc1] LT",lastWeek:"[\u0b95\u0b9f\u0ba8\u0bcd\u0ba4 \u0bb5\u0bbe\u0bb0\u0bae\u0bcd] dddd, LT",sameElse:"L"},relativeTime:{future:"%s \u0b87\u0bb2\u0bcd",past:"%s \u0bae\u0bc1\u0ba9\u0bcd",s:"\u0b92\u0bb0\u0bc1 \u0b9a\u0bbf\u0bb2 \u0bb5\u0bbf\u0ba8\u0bbe\u0b9f\u0bbf\u0b95\u0bb3\u0bcd",ss:"%d \u0bb5\u0bbf\u0ba8\u0bbe\u0b9f\u0bbf\u0b95\u0bb3\u0bcd",m:"\u0b92\u0bb0\u0bc1 \u0ba8\u0bbf\u0bae\u0bbf\u0b9f\u0bae\u0bcd",mm:"%d \u0ba8\u0bbf\u0bae\u0bbf\u0b9f\u0b99\u0bcd\u0b95\u0bb3\u0bcd",h:"\u0b92\u0bb0\u0bc1 \u0bae\u0ba3\u0bbf \u0ba8\u0bc7\u0bb0\u0bae\u0bcd",hh:"%d \u0bae\u0ba3\u0bbf \u0ba8\u0bc7\u0bb0\u0bae\u0bcd",d:"\u0b92\u0bb0\u0bc1 \u0ba8\u0bbe\u0bb3\u0bcd",dd:"%d \u0ba8\u0bbe\u0b9f\u0bcd\u0b95\u0bb3\u0bcd",M:"\u0b92\u0bb0\u0bc1 \u0bae\u0bbe\u0ba4\u0bae\u0bcd",MM:"%d \u0bae\u0bbe\u0ba4\u0b99\u0bcd\u0b95\u0bb3\u0bcd",y:"\u0b92\u0bb0\u0bc1 \u0bb5\u0bb0\u0bc1\u0b9f\u0bae\u0bcd",yy:"%d \u0b86\u0ba3\u0bcd\u0b9f\u0bc1\u0b95\u0bb3\u0bcd"},dayOfMonthOrdinalParse:/\d{1,2}\u0bb5\u0ba4\u0bc1/,ordinal:function(e){return e+"\u0bb5\u0ba4\u0bc1"},preparse:function(e){return e.replace(/[\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0be6]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return _[e]})},meridiemParse:/\u0baf\u0bbe\u0bae\u0bae\u0bcd|\u0bb5\u0bc8\u0b95\u0bb1\u0bc8|\u0b95\u0bbe\u0bb2\u0bc8|\u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd|\u0b8e\u0bb1\u0bcd\u0baa\u0bbe\u0b9f\u0bc1|\u0bae\u0bbe\u0bb2\u0bc8/,meridiem:function(e,_,n){return e<2?" \u0baf\u0bbe\u0bae\u0bae\u0bcd":e<6?" \u0bb5\u0bc8\u0b95\u0bb1\u0bc8":e<10?" \u0b95\u0bbe\u0bb2\u0bc8":e<14?" \u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd":e<18?" \u0b8e\u0bb1\u0bcd\u0baa\u0bbe\u0b9f\u0bc1":e<22?" \u0bae\u0bbe\u0bb2\u0bc8":" \u0baf\u0bbe\u0bae\u0bae\u0bcd"},meridiemHour:function(e,_){return 12===e&&(e=0),"\u0baf\u0bbe\u0bae\u0bae\u0bcd"===_?e<2?e:e+12:"\u0bb5\u0bc8\u0b95\u0bb1\u0bc8"===_||"\u0b95\u0bbe\u0bb2\u0bc8"===_?e:"\u0ba8\u0ba3\u0bcd\u0baa\u0b95\u0bb2\u0bcd"===_&&e>=10?e:e+12},week:{dow:0,doy:6}})}(n(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[118],{384:function(_,e,s){!function(_){"use strict";_.defineLocale("th",{months:"\u0e21\u0e01\u0e23\u0e32\u0e04\u0e21_\u0e01\u0e38\u0e21\u0e20\u0e32\u0e1e\u0e31\u0e19\u0e18\u0e4c_\u0e21\u0e35\u0e19\u0e32\u0e04\u0e21_\u0e40\u0e21\u0e29\u0e32\u0e22\u0e19_\u0e1e\u0e24\u0e29\u0e20\u0e32\u0e04\u0e21_\u0e21\u0e34\u0e16\u0e38\u0e19\u0e32\u0e22\u0e19_\u0e01\u0e23\u0e01\u0e0e\u0e32\u0e04\u0e21_\u0e2a\u0e34\u0e07\u0e2b\u0e32\u0e04\u0e21_\u0e01\u0e31\u0e19\u0e22\u0e32\u0e22\u0e19_\u0e15\u0e38\u0e25\u0e32\u0e04\u0e21_\u0e1e\u0e24\u0e28\u0e08\u0e34\u0e01\u0e32\u0e22\u0e19_\u0e18\u0e31\u0e19\u0e27\u0e32\u0e04\u0e21".split("_"),monthsShort:"\u0e21.\u0e04._\u0e01.\u0e1e._\u0e21\u0e35.\u0e04._\u0e40\u0e21.\u0e22._\u0e1e.\u0e04._\u0e21\u0e34.\u0e22._\u0e01.\u0e04._\u0e2a.\u0e04._\u0e01.\u0e22._\u0e15.\u0e04._\u0e1e.\u0e22._\u0e18.\u0e04.".split("_"),monthsParseExact:!0,weekdays:"\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c_\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c_\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23_\u0e1e\u0e38\u0e18_\u0e1e\u0e24\u0e2b\u0e31\u0e2a\u0e1a\u0e14\u0e35_\u0e28\u0e38\u0e01\u0e23\u0e4c_\u0e40\u0e2a\u0e32\u0e23\u0e4c".split("_"),weekdaysShort:"\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c_\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c_\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23_\u0e1e\u0e38\u0e18_\u0e1e\u0e24\u0e2b\u0e31\u0e2a_\u0e28\u0e38\u0e01\u0e23\u0e4c_\u0e40\u0e2a\u0e32\u0e23\u0e4c".split("_"),weekdaysMin:"\u0e2d\u0e32._\u0e08._\u0e2d._\u0e1e._\u0e1e\u0e24._\u0e28._\u0e2a.".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY \u0e40\u0e27\u0e25\u0e32 H:mm",LLLL:"\u0e27\u0e31\u0e19dddd\u0e17\u0e35\u0e48 D MMMM YYYY \u0e40\u0e27\u0e25\u0e32 H:mm"},meridiemParse:/\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07|\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07/,isPM:function(_){return"\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"===_},meridiem:function(_,e,s){return _<12?"\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07":"\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"},calendar:{sameDay:"[\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",nextDay:"[\u0e1e\u0e23\u0e38\u0e48\u0e07\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",nextWeek:"dddd[\u0e2b\u0e19\u0e49\u0e32 \u0e40\u0e27\u0e25\u0e32] LT",lastDay:"[\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e27\u0e25\u0e32] LT",lastWeek:"[\u0e27\u0e31\u0e19]dddd[\u0e17\u0e35\u0e48\u0e41\u0e25\u0e49\u0e27 \u0e40\u0e27\u0e25\u0e32] LT",sameElse:"L"},relativeTime:{future:"\u0e2d\u0e35\u0e01 %s",past:"%s\u0e17\u0e35\u0e48\u0e41\u0e25\u0e49\u0e27",s:"\u0e44\u0e21\u0e48\u0e01\u0e35\u0e48\u0e27\u0e34\u0e19\u0e32\u0e17\u0e35",ss:"%d \u0e27\u0e34\u0e19\u0e32\u0e17\u0e35",m:"1 \u0e19\u0e32\u0e17\u0e35",mm:"%d \u0e19\u0e32\u0e17\u0e35",h:"1 \u0e0a\u0e31\u0e48\u0e27\u0e42\u0e21\u0e07",hh:"%d \u0e0a\u0e31\u0e48\u0e27\u0e42\u0e21\u0e07",d:"1 \u0e27\u0e31\u0e19",dd:"%d \u0e27\u0e31\u0e19",w:"1 \u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c",ww:"%d \u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c",M:"1 \u0e40\u0e14\u0e37\u0e2d\u0e19",MM:"%d \u0e40\u0e14\u0e37\u0e2d\u0e19",y:"1 \u0e1b\u0e35",yy:"%d \u0e1b\u0e35"}})}(s(68))}}]);
|
||||
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[157],{835:function(e,a,t){},840:function(e,a,t){"use strict";t.r(a);var r=t(2),n=t.n(r),c=t(19),i=t.n(c),o=t(61),l=t(36),s=t(8),d=t(261),m=t.n(d),u=t(680),k=t(677),g=t(127),b=t(717),w=t(11),_=t(756),p=(t(835),function(e){var a=e.loaded,t=e.translate;if(!a)return null;var r=function(){return e.exitTrackingFlow(e.orderId,e.siteId,!1)};return n.a.createElement(u.a,{additionalClassNames:"woocommerce tracking-modal wcc-root",isVisible:e.showTrackingDialog,onClose:r},n.a.createElement("div",{className:"tracking-modal__content"},n.a.createElement("div",{className:"tracking-modal__header"},n.a.createElement(k.a,null,t("Which package would you like to track?")),n.a.createElement(b.a,{className:"tracking-modal__close-button",onClick:r},n.a.createElement(m.a,{icon:"cross"}))),n.a.createElement("div",{className:"tracking-modal__body"},n.a.createElement("div",{className:"tracking-modal__main-section"},n.a.createElement("div",{className:"shipment-tracking__dummy-class order-activity-log"},n.a.createElement(_.a,{orderId:e.orderId,siteId:e.siteId,isModal:!0}))))))});p.propTypes={siteId:i.a.number.isRequired,orderId:i.a.number.isRequired};a.default=Object(o.b)(function(e,a){var t=a.orderId,r=a.siteId,n=Object(w.x)(e,t,r),c=Object(w.k)(e,t,r);return{loaded:n,form:n&&c.form,showTrackingDialog:c.showTrackingDialog,isCustomsFormRequired:Object(w.s)(e,t,r)}},function(e){return Object(l.b)({exitTrackingFlow:g.p},e)})(Object(s.localize)(p))}}]);
|
||||
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[30],{296:function(a,e,d){!function(a){"use strict";a.defineLocale("en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(a){var e=a%10,d=1===~~(a%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return a+d},week:{dow:0,doy:4}})}(d(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[107],{373:function(e,n,s){!function(e){"use strict";function n(e,n,s,a){var t=e+" ";switch(s){case"s":return n||a?"nekaj sekund":"nekaj sekundami";case"ss":return t+=1===e?n?"sekundo":"sekundi":2===e?n||a?"sekundi":"sekundah":e<5?n||a?"sekunde":"sekundah":"sekund";case"m":return n?"ena minuta":"eno minuto";case"mm":return t+=1===e?n?"minuta":"minuto":2===e?n||a?"minuti":"minutama":e<5?n||a?"minute":"minutami":n||a?"minut":"minutami";case"h":return n?"ena ura":"eno uro";case"hh":return t+=1===e?n?"ura":"uro":2===e?n||a?"uri":"urama":e<5?n||a?"ure":"urami":n||a?"ur":"urami";case"d":return n||a?"en dan":"enim dnem";case"dd":return t+=1===e?n||a?"dan":"dnem":2===e?n||a?"dni":"dnevoma":n||a?"dni":"dnevi";case"M":return n||a?"en mesec":"enim mesecem";case"MM":return t+=1===e?n||a?"mesec":"mesecem":2===e?n||a?"meseca":"mesecema":e<5?n||a?"mesece":"meseci":n||a?"mesecev":"meseci";case"y":return n||a?"eno leto":"enim letom";case"yy":return t+=1===e?n||a?"leto":"letom":2===e?n||a?"leti":"letoma":e<5?n||a?"leta":"leti":n||a?"let":"leti"}}e.defineLocale("sl",{months:"januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljek_torek_sreda_\u010detrtek_petek_sobota".split("_"),weekdaysShort:"ned._pon._tor._sre._\u010det._pet._sob.".split("_"),weekdaysMin:"ne_po_to_sr_\u010de_pe_so".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danes ob] LT",nextDay:"[jutri ob] LT",nextWeek:function(){switch(this.day()){case 0:return"[v] [nedeljo] [ob] LT";case 3:return"[v] [sredo] [ob] LT";case 6:return"[v] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[v] dddd [ob] LT"}},lastDay:"[v\u010deraj ob] LT",lastWeek:function(){switch(this.day()){case 0:return"[prej\u0161njo] [nedeljo] [ob] LT";case 3:return"[prej\u0161njo] [sredo] [ob] LT";case 6:return"[prej\u0161njo] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[prej\u0161nji] dddd [ob] LT"}},sameElse:"L"},relativeTime:{future:"\u010dez %s",past:"pred %s",s:n,ss:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}(s(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[100],{365:function(e,a,s){!function(e){"use strict";e.defineLocale("pt-br",{months:"janeiro_fevereiro_mar\xe7o_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_ter\xe7a-feira_quarta-feira_quinta-feira_sexta-feira_s\xe1bado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_s\xe1b".split("_"),weekdaysMin:"do_2\xaa_3\xaa_4\xaa_5\xaa_6\xaa_s\xe1".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [\xe0s] HH:mm",LLLL:"dddd, D [de] MMMM [de] YYYY [\xe0s] HH:mm"},calendar:{sameDay:"[Hoje \xe0s] LT",nextDay:"[Amanh\xe3 \xe0s] LT",nextWeek:"dddd [\xe0s] LT",lastDay:"[Ontem \xe0s] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[\xdaltimo] dddd [\xe0s] LT":"[\xdaltima] dddd [\xe0s] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"h\xe1 %s",s:"poucos segundos",ss:"%d segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um m\xeas",MM:"%d meses",y:"um ano",yy:"%d anos"},dayOfMonthOrdinalParse:/\d{1,2}\xba/,ordinal:"%d\xba",invalidDate:"Data inv\xe1lida"})}(s(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[8],{273:function(_,e,d){!function(_){"use strict";_.defineLocale("ar-ma",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648\u0632_\u063a\u0634\u062a_\u0634\u062a\u0646\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0646\u0628\u0631_\u062f\u062c\u0646\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0627\u062d\u062f_\u0627\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},week:{dow:1,doy:4}})}(d(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[13],{279:function(_,d,e){!function(_){"use strict";_.defineLocale("bg",{months:"\u044f\u043d\u0443\u0430\u0440\u0438_\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438_\u043c\u0430\u0440\u0442_\u0430\u043f\u0440\u0438\u043b_\u043c\u0430\u0439_\u044e\u043d\u0438_\u044e\u043b\u0438_\u0430\u0432\u0433\u0443\u0441\u0442_\u0441\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438_\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438_\u043d\u043e\u0435\u043c\u0432\u0440\u0438_\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438".split("_"),monthsShort:"\u044f\u043d\u0443_\u0444\u0435\u0432_\u043c\u0430\u0440_\u0430\u043f\u0440_\u043c\u0430\u0439_\u044e\u043d\u0438_\u044e\u043b\u0438_\u0430\u0432\u0433_\u0441\u0435\u043f_\u043e\u043a\u0442_\u043d\u043e\u0435_\u0434\u0435\u043a".split("_"),weekdays:"\u043d\u0435\u0434\u0435\u043b\u044f_\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a_\u0432\u0442\u043e\u0440\u043d\u0438\u043a_\u0441\u0440\u044f\u0434\u0430_\u0447\u0435\u0442\u0432\u044a\u0440\u0442\u044a\u043a_\u043f\u0435\u0442\u044a\u043a_\u0441\u044a\u0431\u043e\u0442\u0430".split("_"),weekdaysShort:"\u043d\u0435\u0434_\u043f\u043e\u043d_\u0432\u0442\u043e_\u0441\u0440\u044f_\u0447\u0435\u0442_\u043f\u0435\u0442_\u0441\u044a\u0431".split("_"),weekdaysMin:"\u043d\u0434_\u043f\u043d_\u0432\u0442_\u0441\u0440_\u0447\u0442_\u043f\u0442_\u0441\u0431".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[\u0414\u043d\u0435\u0441 \u0432] LT",nextDay:"[\u0423\u0442\u0440\u0435 \u0432] LT",nextWeek:"dddd [\u0432] LT",lastDay:"[\u0412\u0447\u0435\u0440\u0430 \u0432] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[\u041c\u0438\u043d\u0430\u043b\u0430\u0442\u0430] dddd [\u0432] LT";case 1:case 2:case 4:case 5:return"[\u041c\u0438\u043d\u0430\u043b\u0438\u044f] dddd [\u0432] LT"}},sameElse:"L"},relativeTime:{future:"\u0441\u043b\u0435\u0434 %s",past:"\u043f\u0440\u0435\u0434\u0438 %s",s:"\u043d\u044f\u043a\u043e\u043b\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434\u0438",ss:"%d \u0441\u0435\u043a\u0443\u043d\u0434\u0438",m:"\u043c\u0438\u043d\u0443\u0442\u0430",mm:"%d \u043c\u0438\u043d\u0443\u0442\u0438",h:"\u0447\u0430\u0441",hh:"%d \u0447\u0430\u0441\u0430",d:"\u0434\u0435\u043d",dd:"%d \u0434\u0435\u043d\u0430",w:"\u0441\u0435\u0434\u043c\u0438\u0446\u0430",ww:"%d \u0441\u0435\u0434\u043c\u0438\u0446\u0438",M:"\u043c\u0435\u0441\u0435\u0446",MM:"%d \u043c\u0435\u0441\u0435\u0446\u0430",y:"\u0433\u043e\u0434\u0438\u043d\u0430",yy:"%d \u0433\u043e\u0434\u0438\u043d\u0438"},dayOfMonthOrdinalParse:/\d{1,2}-(\u0435\u0432|\u0435\u043d|\u0442\u0438|\u0432\u0438|\u0440\u0438|\u043c\u0438)/,ordinal:function(_){var d=_%10,e=_%100;return 0===_?_+"-\u0435\u0432":0===e?_+"-\u0435\u043d":e>10&&e<20?_+"-\u0442\u0438":1===d?_+"-\u0432\u0438":2===d?_+"-\u0440\u0438":7===d||8===d?_+"-\u043c\u0438":_+"-\u0442\u0438"},week:{dow:1,doy:7}})}(e(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[103],{369:function(e,d,n){!function(e){"use strict";var d=["\u062c\u0646\u0648\u0631\u064a","\u0641\u064a\u0628\u0631\u0648\u0631\u064a","\u0645\u0627\u0631\u0686","\u0627\u067e\u0631\u064a\u0644","\u0645\u0626\u064a","\u062c\u0648\u0646","\u062c\u0648\u0644\u0627\u0621\u0650","\u0622\u06af\u0633\u067d","\u0633\u064a\u067e\u067d\u0645\u0628\u0631","\u0622\u06aa\u067d\u0648\u0628\u0631","\u0646\u0648\u0645\u0628\u0631","\u068a\u0633\u0645\u0628\u0631"],n=["\u0622\u0686\u0631","\u0633\u0648\u0645\u0631","\u0627\u06b1\u0627\u0631\u0648","\u0627\u0631\u0628\u0639","\u062e\u0645\u064a\u0633","\u062c\u0645\u0639","\u0687\u0646\u0687\u0631"];e.defineLocale("sd",{months:d,monthsShort:d,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd\u060c D MMMM YYYY HH:mm"},meridiemParse:/\u0635\u0628\u062d|\u0634\u0627\u0645/,isPM:function(e){return"\u0634\u0627\u0645"===e},meridiem:function(e,d,n){return e<12?"\u0635\u0628\u062d":"\u0634\u0627\u0645"},calendar:{sameDay:"[\u0627\u0684] LT",nextDay:"[\u0633\u0680\u0627\u06bb\u064a] LT",nextWeek:"dddd [\u0627\u06b3\u064a\u0646 \u0647\u0641\u062a\u064a \u062a\u064a] LT",lastDay:"[\u06aa\u0627\u0644\u0647\u0647] LT",lastWeek:"[\u06af\u0632\u0631\u064a\u0644 \u0647\u0641\u062a\u064a] dddd [\u062a\u064a] LT",sameElse:"L"},relativeTime:{future:"%s \u067e\u0648\u0621",past:"%s \u0627\u06b3",s:"\u0686\u0646\u062f \u0633\u064a\u06aa\u0646\u068a",ss:"%d \u0633\u064a\u06aa\u0646\u068a",m:"\u0647\u06aa \u0645\u0646\u067d",mm:"%d \u0645\u0646\u067d",h:"\u0647\u06aa \u06aa\u0644\u0627\u06aa",hh:"%d \u06aa\u0644\u0627\u06aa",d:"\u0647\u06aa \u068f\u064a\u0646\u0647\u0646",dd:"%d \u068f\u064a\u0646\u0647\u0646",M:"\u0647\u06aa \u0645\u0647\u064a\u0646\u0648",MM:"%d \u0645\u0647\u064a\u0646\u0627",y:"\u0647\u06aa \u0633\u0627\u0644",yy:"%d \u0633\u0627\u0644"},preparse:function(e){return e.replace(/\u060c/g,",")},postformat:function(e){return e.replace(/,/g,"\u060c")},week:{dow:1,doy:4}})}(n(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[69],{335:function(e,n,i){!function(e){"use strict";e.defineLocale("jv",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des".split("_"),weekdays:"Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu".split("_"),weekdaysShort:"Min_Sen_Sel_Reb_Kem_Jem_Sep".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sp".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] HH.mm",LLLL:"dddd, D MMMM YYYY [pukul] HH.mm"},meridiemParse:/enjing|siyang|sonten|ndalu/,meridiemHour:function(e,n){return 12===e&&(e=0),"enjing"===n?e:"siyang"===n?e>=11?e:e+12:"sonten"===n||"ndalu"===n?e+12:void 0},meridiem:function(e,n,i){return e<11?"enjing":e<15?"siyang":e<19?"sonten":"ndalu"},calendar:{sameDay:"[Dinten puniko pukul] LT",nextDay:"[Mbenjang pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kala wingi pukul] LT",lastWeek:"dddd [kepengker pukul] LT",sameElse:"L"},relativeTime:{future:"wonten ing %s",past:"%s ingkang kepengker",s:"sawetawis detik",ss:"%d detik",m:"setunggal menit",mm:"%d menit",h:"setunggal jam",hh:"%d jam",d:"sedinten",dd:"%d dinten",M:"sewulan",MM:"%d wulan",y:"setaun",yy:"%d taun"},week:{dow:1,doy:7}})}(i(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[9],{274:function(e,_,t){!function(e){"use strict";var _={1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"},t={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"};e.defineLocale("ar-sa",{months:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a\u0648_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648_\u0623\u063a\u0633\u0637\u0633_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),monthsShort:"\u064a\u0646\u0627\u064a\u0631_\u0641\u0628\u0631\u0627\u064a\u0631_\u0645\u0627\u0631\u0633_\u0623\u0628\u0631\u064a\u0644_\u0645\u0627\u064a\u0648_\u064a\u0648\u0646\u064a\u0648_\u064a\u0648\u0644\u064a\u0648_\u0623\u063a\u0633\u0637\u0633_\u0633\u0628\u062a\u0645\u0628\u0631_\u0623\u0643\u062a\u0648\u0628\u0631_\u0646\u0648\u0641\u0645\u0628\u0631_\u062f\u064a\u0633\u0645\u0628\u0631".split("_"),weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,_,t){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u0627 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0644\u0649 \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0641\u064a %s",past:"\u0645\u0646\u0630 %s",s:"\u062b\u0648\u0627\u0646",ss:"%d \u062b\u0627\u0646\u064a\u0629",m:"\u062f\u0642\u064a\u0642\u0629",mm:"%d \u062f\u0642\u0627\u0626\u0642",h:"\u0633\u0627\u0639\u0629",hh:"%d \u0633\u0627\u0639\u0627\u062a",d:"\u064a\u0648\u0645",dd:"%d \u0623\u064a\u0627\u0645",M:"\u0634\u0647\u0631",MM:"%d \u0623\u0634\u0647\u0631",y:"\u0633\u0646\u0629",yy:"%d \u0633\u0646\u0648\u0627\u062a"},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return t[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return _[e]}).replace(/,/g,"\u060c")},week:{dow:0,doy:6}})}(t(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{288:function(_,d,e){!function(_){"use strict";_.defineLocale("cv",{months:"\u043a\u04d1\u0440\u043b\u0430\u0447_\u043d\u0430\u0440\u04d1\u0441_\u043f\u0443\u0448_\u0430\u043a\u0430_\u043c\u0430\u0439_\u04ab\u04d7\u0440\u0442\u043c\u0435_\u0443\u0442\u04d1_\u04ab\u0443\u0440\u043b\u0430_\u0430\u0432\u04d1\u043d_\u044e\u043f\u0430_\u0447\u04f3\u043a_\u0440\u0430\u0448\u0442\u0430\u0432".split("_"),monthsShort:"\u043a\u04d1\u0440_\u043d\u0430\u0440_\u043f\u0443\u0448_\u0430\u043a\u0430_\u043c\u0430\u0439_\u04ab\u04d7\u0440_\u0443\u0442\u04d1_\u04ab\u0443\u0440_\u0430\u0432\u043d_\u044e\u043f\u0430_\u0447\u04f3\u043a_\u0440\u0430\u0448".split("_"),weekdays:"\u0432\u044b\u0440\u0441\u0430\u0440\u043d\u0438\u043a\u0443\u043d_\u0442\u0443\u043d\u0442\u0438\u043a\u0443\u043d_\u044b\u0442\u043b\u0430\u0440\u0438\u043a\u0443\u043d_\u044e\u043d\u043a\u0443\u043d_\u043a\u04d7\u04ab\u043d\u0435\u0440\u043d\u0438\u043a\u0443\u043d_\u044d\u0440\u043d\u0435\u043a\u0443\u043d_\u0448\u04d1\u043c\u0430\u0442\u043a\u0443\u043d".split("_"),weekdaysShort:"\u0432\u044b\u0440_\u0442\u0443\u043d_\u044b\u0442\u043b_\u044e\u043d_\u043a\u04d7\u04ab_\u044d\u0440\u043d_\u0448\u04d1\u043c".split("_"),weekdaysMin:"\u0432\u0440_\u0442\u043d_\u044b\u0442_\u044e\u043d_\u043a\u04ab_\u044d\u0440_\u0448\u043c".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7]",LLL:"YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7], HH:mm",LLLL:"dddd, YYYY [\u04ab\u0443\u043b\u0445\u0438] MMMM [\u0443\u0439\u04d1\u0445\u04d7\u043d] D[-\u043c\u04d7\u0448\u04d7], HH:mm"},calendar:{sameDay:"[\u041f\u0430\u044f\u043d] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",nextDay:"[\u042b\u0440\u0430\u043d] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",lastDay:"[\u04d6\u043d\u0435\u0440] LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",nextWeek:"[\u04aa\u0438\u0442\u0435\u0441] dddd LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",lastWeek:"[\u0418\u0440\u0442\u043d\u04d7] dddd LT [\u0441\u0435\u0445\u0435\u0442\u0440\u0435]",sameElse:"L"},relativeTime:{future:function(_){var d=/\u0441\u0435\u0445\u0435\u0442$/i.exec(_)?"\u0440\u0435\u043d":/\u04ab\u0443\u043b$/i.exec(_)?"\u0442\u0430\u043d":"\u0440\u0430\u043d";return _+d},past:"%s \u043a\u0430\u044f\u043b\u043b\u0430",s:"\u043f\u04d7\u0440-\u0438\u043a \u04ab\u0435\u043a\u043a\u0443\u043d\u0442",ss:"%d \u04ab\u0435\u043a\u043a\u0443\u043d\u0442",m:"\u043f\u04d7\u0440 \u043c\u0438\u043d\u0443\u0442",mm:"%d \u043c\u0438\u043d\u0443\u0442",h:"\u043f\u04d7\u0440 \u0441\u0435\u0445\u0435\u0442",hh:"%d \u0441\u0435\u0445\u0435\u0442",d:"\u043f\u04d7\u0440 \u043a\u0443\u043d",dd:"%d \u043a\u0443\u043d",M:"\u043f\u04d7\u0440 \u0443\u0439\u04d1\u0445",MM:"%d \u0443\u0439\u04d1\u0445",y:"\u043f\u04d7\u0440 \u04ab\u0443\u043b",yy:"%d \u04ab\u0443\u043b"},dayOfMonthOrdinalParse:/\d{1,2}-\u043c\u04d7\u0448/,ordinal:"%d-\u043c\u04d7\u0448",week:{dow:1,doy:7}})}(e(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{276:function(e,d,n){!function(e){"use strict";var d={1:"\u0661",2:"\u0662",3:"\u0663",4:"\u0664",5:"\u0665",6:"\u0666",7:"\u0667",8:"\u0668",9:"\u0669",0:"\u0660"},n={"\u0661":"1","\u0662":"2","\u0663":"3","\u0664":"4","\u0665":"5","\u0666":"6","\u0667":"7","\u0668":"8","\u0669":"9","\u0660":"0"},t=function(e){return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5},r={s:["\u0623\u0642\u0644 \u0645\u0646 \u062b\u0627\u0646\u064a\u0629","\u062b\u0627\u0646\u064a\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062b\u0627\u0646\u064a\u062a\u0627\u0646","\u062b\u0627\u0646\u064a\u062a\u064a\u0646"],"%d \u062b\u0648\u0627\u0646","%d \u062b\u0627\u0646\u064a\u0629","%d \u062b\u0627\u0646\u064a\u0629"],m:["\u0623\u0642\u0644 \u0645\u0646 \u062f\u0642\u064a\u0642\u0629","\u062f\u0642\u064a\u0642\u0629 \u0648\u0627\u062d\u062f\u0629",["\u062f\u0642\u064a\u0642\u062a\u0627\u0646","\u062f\u0642\u064a\u0642\u062a\u064a\u0646"],"%d \u062f\u0642\u0627\u0626\u0642","%d \u062f\u0642\u064a\u0642\u0629","%d \u062f\u0642\u064a\u0642\u0629"],h:["\u0623\u0642\u0644 \u0645\u0646 \u0633\u0627\u0639\u0629","\u0633\u0627\u0639\u0629 \u0648\u0627\u062d\u062f\u0629",["\u0633\u0627\u0639\u062a\u0627\u0646","\u0633\u0627\u0639\u062a\u064a\u0646"],"%d \u0633\u0627\u0639\u0627\u062a","%d \u0633\u0627\u0639\u0629","%d \u0633\u0627\u0639\u0629"],d:["\u0623\u0642\u0644 \u0645\u0646 \u064a\u0648\u0645","\u064a\u0648\u0645 \u0648\u0627\u062d\u062f",["\u064a\u0648\u0645\u0627\u0646","\u064a\u0648\u0645\u064a\u0646"],"%d \u0623\u064a\u0627\u0645","%d \u064a\u0648\u0645\u064b\u0627","%d \u064a\u0648\u0645"],M:["\u0623\u0642\u0644 \u0645\u0646 \u0634\u0647\u0631","\u0634\u0647\u0631 \u0648\u0627\u062d\u062f",["\u0634\u0647\u0631\u0627\u0646","\u0634\u0647\u0631\u064a\u0646"],"%d \u0623\u0634\u0647\u0631","%d \u0634\u0647\u0631\u0627","%d \u0634\u0647\u0631"],y:["\u0623\u0642\u0644 \u0645\u0646 \u0639\u0627\u0645","\u0639\u0627\u0645 \u0648\u0627\u062d\u062f",["\u0639\u0627\u0645\u0627\u0646","\u0639\u0627\u0645\u064a\u0646"],"%d \u0623\u0639\u0648\u0627\u0645","%d \u0639\u0627\u0645\u064b\u0627","%d \u0639\u0627\u0645"]},s=function(e){return function(d,n,s,a){var i=t(d),o=r[e][t(d)];return 2===i&&(o=o[n?0:1]),o.replace(/%d/i,d)}},a=["\u064a\u0646\u0627\u064a\u0631","\u0641\u0628\u0631\u0627\u064a\u0631","\u0645\u0627\u0631\u0633","\u0623\u0628\u0631\u064a\u0644","\u0645\u0627\u064a\u0648","\u064a\u0648\u0646\u064a\u0648","\u064a\u0648\u0644\u064a\u0648","\u0623\u063a\u0633\u0637\u0633","\u0633\u0628\u062a\u0645\u0628\u0631","\u0623\u0643\u062a\u0648\u0628\u0631","\u0646\u0648\u0641\u0645\u0628\u0631","\u062f\u064a\u0633\u0645\u0628\u0631"];e.defineLocale("ar",{months:a,monthsShort:a,weekdays:"\u0627\u0644\u0623\u062d\u062f_\u0627\u0644\u0625\u062b\u0646\u064a\u0646_\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621_\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621_\u0627\u0644\u062e\u0645\u064a\u0633_\u0627\u0644\u062c\u0645\u0639\u0629_\u0627\u0644\u0633\u0628\u062a".split("_"),weekdaysShort:"\u0623\u062d\u062f_\u0625\u062b\u0646\u064a\u0646_\u062b\u0644\u0627\u062b\u0627\u0621_\u0623\u0631\u0628\u0639\u0627\u0621_\u062e\u0645\u064a\u0633_\u062c\u0645\u0639\u0629_\u0633\u0628\u062a".split("_"),weekdaysMin:"\u062d_\u0646_\u062b_\u0631_\u062e_\u062c_\u0633".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"D/\u200fM/\u200fYYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},meridiemParse:/\u0635|\u0645/,isPM:function(e){return"\u0645"===e},meridiem:function(e,d,n){return e<12?"\u0635":"\u0645"},calendar:{sameDay:"[\u0627\u0644\u064a\u0648\u0645 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextDay:"[\u063a\u062f\u064b\u0627 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",nextWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastDay:"[\u0623\u0645\u0633 \u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",lastWeek:"dddd [\u0639\u0646\u062f \u0627\u0644\u0633\u0627\u0639\u0629] LT",sameElse:"L"},relativeTime:{future:"\u0628\u0639\u062f %s",past:"\u0645\u0646\u0630 %s",s:s("s"),ss:s("s"),m:s("m"),mm:s("m"),h:s("h"),hh:s("h"),d:s("d"),dd:s("d"),M:s("M"),MM:s("M"),y:s("y"),yy:s("y")},preparse:function(e){return e.replace(/[\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u0660]/g,function(e){return n[e]}).replace(/\u060c/g,",")},postformat:function(e){return e.replace(/\d/g,function(e){return d[e]}).replace(/,/g,"\u060c")},week:{dow:6,doy:12}})}(n(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[21],{287:function(e,n,s){!function(e){"use strict";var n="leden_\xfanor_b\u0159ezen_duben_kv\u011bten_\u010derven_\u010dervenec_srpen_z\xe1\u0159\xed_\u0159\xedjen_listopad_prosinec".split("_"),s="led_\xfano_b\u0159e_dub_kv\u011b_\u010dvn_\u010dvc_srp_z\xe1\u0159_\u0159\xedj_lis_pro".split("_"),r=[/^led/i,/^\xfano/i,/^b\u0159e/i,/^dub/i,/^kv\u011b/i,/^(\u010dvn|\u010derven$|\u010dervna)/i,/^(\u010dvc|\u010dervenec|\u010dervence)/i,/^srp/i,/^z\xe1\u0159/i,/^\u0159\xedj/i,/^lis/i,/^pro/i],t=/^(leden|\xfanor|b\u0159ezen|duben|kv\u011bten|\u010dervenec|\u010dervence|\u010derven|\u010dervna|srpen|z\xe1\u0159\xed|\u0159\xedjen|listopad|prosinec|led|\xfano|b\u0159e|dub|kv\u011b|\u010dvn|\u010dvc|srp|z\xe1\u0159|\u0159\xedj|lis|pro)/i;function d(e){return e>1&&e<5&&1!==~~(e/10)}function a(e,n,s,r){var t=e+" ";switch(s){case"s":return n||r?"p\xe1r sekund":"p\xe1r sekundami";case"ss":return n||r?t+(d(e)?"sekundy":"sekund"):t+"sekundami";case"m":return n?"minuta":r?"minutu":"minutou";case"mm":return n||r?t+(d(e)?"minuty":"minut"):t+"minutami";case"h":return n?"hodina":r?"hodinu":"hodinou";case"hh":return n||r?t+(d(e)?"hodiny":"hodin"):t+"hodinami";case"d":return n||r?"den":"dnem";case"dd":return n||r?t+(d(e)?"dny":"dn\xed"):t+"dny";case"M":return n||r?"m\u011bs\xedc":"m\u011bs\xedcem";case"MM":return n||r?t+(d(e)?"m\u011bs\xedce":"m\u011bs\xedc\u016f"):t+"m\u011bs\xedci";case"y":return n||r?"rok":"rokem";case"yy":return n||r?t+(d(e)?"roky":"let"):t+"lety"}}e.defineLocale("cs",{months:n,monthsShort:s,monthsRegex:t,monthsShortRegex:t,monthsStrictRegex:/^(leden|ledna|\xfanora|\xfanor|b\u0159ezen|b\u0159ezna|duben|dubna|kv\u011bten|kv\u011btna|\u010dervenec|\u010dervence|\u010derven|\u010dervna|srpen|srpna|z\xe1\u0159\xed|\u0159\xedjen|\u0159\xedjna|listopadu|listopad|prosinec|prosince)/i,monthsShortStrictRegex:/^(led|\xfano|b\u0159e|dub|kv\u011b|\u010dvn|\u010dvc|srp|z\xe1\u0159|\u0159\xedj|lis|pro)/i,monthsParse:r,longMonthsParse:r,shortMonthsParse:r,weekdays:"ned\u011ble_pond\u011bl\xed_\xfater\xfd_st\u0159eda_\u010dtvrtek_p\xe1tek_sobota".split("_"),weekdaysShort:"ne_po_\xfat_st_\u010dt_p\xe1_so".split("_"),weekdaysMin:"ne_po_\xfat_st_\u010dt_p\xe1_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd D. MMMM YYYY H:mm",l:"D. M. YYYY"},calendar:{sameDay:"[dnes v] LT",nextDay:"[z\xedtra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v ned\u011bli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve st\u0159edu v] LT";case 4:return"[ve \u010dtvrtek v] LT";case 5:return"[v p\xe1tek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[v\u010dera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou ned\u011bli v] LT";case 1:case 2:return"[minul\xe9] dddd [v] LT";case 3:return"[minulou st\u0159edu v] LT";case 4:case 5:return"[minul\xfd] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"p\u0159ed %s",s:a,ss:a,m:a,mm:a,h:a,hh:a,d:a,dd:a,M:a,MM:a,y:a,yy:a},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}(s(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[112],{378:function(e,a,d){!function(e){"use strict";e.defineLocale("sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"s\xf6ndag_m\xe5ndag_tisdag_onsdag_torsdag_fredag_l\xf6rdag".split("_"),weekdaysShort:"s\xf6n_m\xe5n_tis_ons_tor_fre_l\xf6r".split("_"),weekdaysMin:"s\xf6_m\xe5_ti_on_to_fr_l\xf6".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [kl.] HH:mm",LLLL:"dddd D MMMM YYYY [kl.] HH:mm",lll:"D MMM YYYY HH:mm",llll:"ddd D MMM YYYY HH:mm"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Ig\xe5r] LT",nextWeek:"[P\xe5] dddd LT",lastWeek:"[I] dddd[s] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"f\xf6r %s sedan",s:"n\xe5gra sekunder",ss:"%d sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en m\xe5nad",MM:"%d m\xe5nader",y:"ett \xe5r",yy:"%d \xe5r"},dayOfMonthOrdinalParse:/\d{1,2}(\:e|\:a)/,ordinal:function(e){var a=e%10,d=1===~~(e%100/10)?":e":1===a?":a":2===a?":a":":e";return e+d},week:{dow:1,doy:4}})}(d(68))}}]);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[32],{298:function(a,e,d){!function(a){"use strict";a.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(a){var e=a%10,d=1===~~(a%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return a+d},week:{dow:1,doy:4}})}(d(68))}}]);
|
||||
+1
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user