Added login request
This commit is contained in:
@@ -0,0 +1,600 @@
|
||||
<?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' ) ) {
|
||||
|
||||
class WC_Connect_API_Client {
|
||||
|
||||
/**
|
||||
* @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 Services Server
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_service_schemas() {
|
||||
$response_body = $this->request( 'POST', '/services' );
|
||||
|
||||
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 Services 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 underscores or letters
|
||||
if ( 1 === preg_match( '/[^a-z_]/i', $service_slug ) ) {
|
||||
return new WP_Error( 'invalid_service_slug', 'Invalid WooCommerce Services service slug provided' );
|
||||
}
|
||||
|
||||
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.", $product->get_id() ),
|
||||
array( 'product_id' => $product->get_id() )
|
||||
);
|
||||
}
|
||||
|
||||
$weight = $product->get_weight();
|
||||
$height = 0;
|
||||
$length = 0;
|
||||
$width = 0;
|
||||
|
||||
if ( $product->has_dimensions() ) {
|
||||
$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 Services 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.' );
|
||||
}
|
||||
|
||||
// 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 );
|
||||
}
|
||||
|
||||
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 Services server for an array of payment methods
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function get_payment_methods() {
|
||||
return $this->request( 'POST', '/payment/methods' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets shipping rates (for labels) from the WooCommerce Services 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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the connection to the WooCommerce Services Server
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function auth_test() {
|
||||
return $this->request( 'GET', '/connection/test' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deferred Stripe Standard Account
|
||||
* @param $email string The user's email address
|
||||
* @param $country string The user's country
|
||||
* @return object|WP_Error
|
||||
*/
|
||||
public function create_stripe_account( $email, $country ) {
|
||||
$request = array(
|
||||
'email' => $email,
|
||||
'country' => $country,
|
||||
);
|
||||
return $this->request( 'POST', '/stripe/account', $request );
|
||||
}
|
||||
|
||||
public function get_stripe_account_details() {
|
||||
return $this->request( 'GET', '/stripe/account' );
|
||||
}
|
||||
|
||||
public function get_stripe_oauth_init( $return_url ) {
|
||||
$request = array(
|
||||
'returnUrl' => $return_url,
|
||||
);
|
||||
return $this->request( 'POST', '/stripe/oauth-init', $request );
|
||||
}
|
||||
|
||||
public function get_stripe_oauth_keys( $code ) {
|
||||
$request = array(
|
||||
'code' => $code,
|
||||
);
|
||||
return $this->request( 'POST', '/stripe/oauth-keys', $request );
|
||||
}
|
||||
|
||||
public function deauthorize_stripe_account() {
|
||||
return $this->request( 'POST', '/stripe/account/deauthorize' );
|
||||
}
|
||||
/**
|
||||
* Sends a request to the WooCommerce Services Server
|
||||
*
|
||||
* @param $method
|
||||
* @param $path
|
||||
* @param $body
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
protected function request( $method, $path, $body = array() ) {
|
||||
|
||||
// TODO - incorporate caching for repeated identical requests
|
||||
if ( ! class_exists( 'Jetpack_Data' ) ) {
|
||||
return new WP_Error( 'jetpack_data_class_not_found', 'Unable to send request to WooCommerce Services server. Jetpack_Data was not found.' );
|
||||
}
|
||||
|
||||
if ( ! method_exists( 'Jetpack_Data', 'get_access_token' ) ) {
|
||||
return new WP_Error( 'jetpack_data_get_access_token_not_found', 'Unable to send request to WooCommerce Services server. Jetpack_Data does not implement get_access_token.' );
|
||||
}
|
||||
|
||||
if ( ! is_array( $body ) ) {
|
||||
return new WP_Error(
|
||||
'request_body_should_be_array',
|
||||
'Unable to send request to WooCommerce Services server. Body must be an array.'
|
||||
);
|
||||
}
|
||||
|
||||
$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 Services server.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$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 Services server returned HTTP code: %d', $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 Services server returned ( %d ) and an empty response body.',
|
||||
$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' ) ? $response_body->data : '';
|
||||
|
||||
return new WP_Error(
|
||||
'wcc_server_error_response',
|
||||
sprintf(
|
||||
'Error: The WooCommerce Services server returned: %s %s ( %d )',
|
||||
$error,
|
||||
$message,
|
||||
$response_code
|
||||
),
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
return $response_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, '/' );
|
||||
|
||||
$args['headers']['Authorization'] = $this->authorization_header();
|
||||
|
||||
$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' => JETPACK__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(),
|
||||
) );
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates headers for our request to the WooCommerce Services 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.v1';
|
||||
$headers[ 'Authorization' ] = $authorization;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
protected function authorization_header() {
|
||||
$token = Jetpack_Data::get_access_token( 0 );
|
||||
$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 Services server. Jetpack Token is missing' );
|
||||
}
|
||||
|
||||
if ( false === strpos( $token->secret, '.' ) ) {
|
||||
return new WP_Error( 'invalid_token', 'Unable to send request to WooCommerce Services server. Jetpack Token is malformed.' );
|
||||
}
|
||||
|
||||
list( $token_key, $token_secret ) = explode( '.', $token->secret );
|
||||
$token_key = sprintf( '%s:%d:%d', $token_key, 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;
|
||||
}
|
||||
|
||||
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 Services server. The timestamp generated for the signature is too old.' );
|
||||
}
|
||||
|
||||
$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,130 @@
|
||||
<?php
|
||||
/**
|
||||
* A class for working around the quirks and different versions of WordPress/WooCommerce
|
||||
* This is for versions 2.6 and lower
|
||||
*/
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Compatibility_WC26' ) ) {
|
||||
|
||||
class WC_Connect_Compatibility_WC26 extends WC_Connect_Compatibility {
|
||||
/**
|
||||
* Get the ID for a given Order.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_order_id( WC_Order $order ) {
|
||||
return $order->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get admin url for a given order
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_edit_order_url( WC_Order $order ) {
|
||||
return get_admin_url( null, 'post.php?post=' . $this->get_order_id( $order ) . '&action=edit' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payment method for a given Order.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_payment_method( WC_Order $order ) {
|
||||
return $order->payment_method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding Product for the given Order Item.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @param WC_Order_Item|WC_Order_Item_Product|array $item
|
||||
*
|
||||
* @return WC_Product
|
||||
*/
|
||||
public function get_item_product( WC_Order $order, $item ) {
|
||||
return $order->get_product_from_item( $item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted list of Product Variations, if applicable.
|
||||
*
|
||||
* @param WC_Product_Variation $product
|
||||
* @param bool $flat
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_formatted_variation( WC_Product_Variation $product, $flat = false ) {
|
||||
return $product->get_formatted_variation_attributes( $flat );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most specific ID for a given Product.
|
||||
*
|
||||
* Note: Returns the Variation ID for Variable Products.
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_product_id( WC_Product $product ) {
|
||||
return ( $product->is_type( 'variation' ) ) ? $product->variation_id : $product->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top-level ID for a given Product.
|
||||
*
|
||||
* Note: Returns the Parent ID for Variable Products.
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_parent_product_id( WC_Product $product ) {
|
||||
return ( $product->is_type( 'variation' ) ) ? $product->parent->get_id() : $product->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
public function get_product_name_from_order( $product_id, $order ) {
|
||||
foreach ( $order->get_items() as $line_item ) {
|
||||
if ( (int) $line_item[ 'product_id' ] === $product_id || (int) $line_item[ 'variation_id' ] === $product_id ) {
|
||||
/* translators: %1$d: Product ID, %2$s: Product Name */
|
||||
return sprintf( __( '#%1$d - %2$s', 'woocommerce-services' ), $product_id, $line_item[ 'name' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/* translators: %d: Deleted Product ID */
|
||||
return sprintf( __( '#%d - [Deleted product]', 'woocommerce-services' ), $product_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given product, return it's name. In supported versions, variable
|
||||
* products will include their attributes.
|
||||
*
|
||||
* @param WC_Product $product Product (variable, simple, etc)
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
public function get_product_name( WC_Product $product ) {
|
||||
return $product->get_title();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?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' ) ) {
|
||||
|
||||
class WC_Connect_Compatibility_WC30 extends WC_Connect_Compatibility {
|
||||
/**
|
||||
* Get the ID for a given Order.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_order_id( WC_Order $order ) {
|
||||
return $order->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get admin url for a given order
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_edit_order_url( WC_Order $order ) {
|
||||
return $order->get_edit_order_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the payment method for a given Order.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_payment_method( WC_Order $order ) {
|
||||
return $order->get_payment_method();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding Product for the given Order Item.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @param WC_Order_Item|WC_Order_Item_Product|array $item
|
||||
*
|
||||
* @return WC_Product
|
||||
*/
|
||||
public function get_item_product( WC_Order $order, $item ) {
|
||||
if ( is_array( $item ) ) {
|
||||
return wc_get_product( $item[ 'product_id' ] );
|
||||
}
|
||||
return $item->get_product();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted list of Product Variations, if applicable.
|
||||
*
|
||||
* @param WC_Product_Variation $product
|
||||
* @param bool $flat
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_formatted_variation( WC_Product_Variation $product, $flat = false ) {
|
||||
return wc_get_formatted_variation( $product, $flat );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most specific ID for a given Product.
|
||||
*
|
||||
* Note: Returns the Variation ID for Variable Products.
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_product_id( WC_Product $product ) {
|
||||
return $product->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top-level ID for a given Product.
|
||||
*
|
||||
* Note: Returns the Parent ID for Variable Products.
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_parent_product_id( WC_Product $product ) {
|
||||
return ( $product->is_type( 'variation' ) ) ? $product->get_parent_id() : $product->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
public function get_product_name_from_order( $product_id, $order ) {
|
||||
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 ) {
|
||||
$line_product_id = (int) get_metadata( 'order_item', $line_item->get_id(), '_product_id', true );
|
||||
}
|
||||
|
||||
if ( ! $line_variation_id ) {
|
||||
$line_variation_id = (int) get_metadata( 'order_item', $line_item->get_id(), '_variation_id', true );
|
||||
}
|
||||
|
||||
if ( $line_product_id === $product_id || $line_variation_id === $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() );
|
||||
}
|
||||
}
|
||||
|
||||
/* translators: %d: Deleted Product ID */
|
||||
return sprintf( __( '#%d - [Deleted product]', 'woocommerce-services' ), $product_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given product, return it's name. In supported versions, variable
|
||||
* products will include their attributes.
|
||||
*
|
||||
* @param WC_Product $product Product (variable, simple, etc)
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
public function get_product_name( WC_Product $product ) {
|
||||
return $product->get_name();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?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' ) ) {
|
||||
|
||||
abstract class WC_Connect_Compatibility {
|
||||
private static $singleton;
|
||||
private static $version = WC_VERSION;
|
||||
|
||||
/**
|
||||
* @return WC_Connect_Compatibility
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( is_null( self::$singleton ) ) {
|
||||
self::$singleton = self::select_compatibility();
|
||||
}
|
||||
|
||||
return self::$singleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WC_Connect_Compatibility subclass for active version of WooCommerce
|
||||
*/
|
||||
private static function select_compatibility() {
|
||||
if ( version_compare( self::$version, '3.0.0', '<' ) ) {
|
||||
require_once 'class-wc-connect-compatibility-wc26.php';
|
||||
return new WC_Connect_Compatibility_WC26();
|
||||
} else {
|
||||
require_once 'class-wc-connect-compatibility-wc30.php';
|
||||
return new WC_Connect_Compatibility_WC30();
|
||||
}
|
||||
}
|
||||
|
||||
public static function set_version( $value ) {
|
||||
self::$singleton = null;
|
||||
self::$version = $value;
|
||||
}
|
||||
|
||||
public static function reset_version() {
|
||||
self::$singleton = null;
|
||||
self::$version = WC_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID for a given Order.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function get_order_id( WC_Order $order );
|
||||
|
||||
/**
|
||||
* Get admin url for a given order
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_edit_order_url( WC_Order $order );
|
||||
|
||||
/**
|
||||
* Get the payment method for a given Order.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_payment_method( WC_Order $order );
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding Product for the given Order Item.
|
||||
*
|
||||
* @param WC_Order $order
|
||||
* @param WC_Order_Item|WC_Order_Item_Product|array $item
|
||||
*
|
||||
* @return WC_Product
|
||||
*/
|
||||
abstract public function get_item_product( WC_Order $order, $item );
|
||||
|
||||
/**
|
||||
* Get formatted list of Product Variations, if applicable.
|
||||
*
|
||||
* @param WC_Product_Variation $product
|
||||
* @param bool $flat
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_formatted_variation( WC_Product_Variation $product, $flat = false );
|
||||
|
||||
/**
|
||||
* Get the most specific ID for a given Product.
|
||||
*
|
||||
* Note: Returns the Variation ID for Variable Products.
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function get_product_id( WC_Product $product );
|
||||
|
||||
/**
|
||||
* Get the top-level ID for a given Product.
|
||||
*
|
||||
* Note: Returns the Parent ID for Variable Products.
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function get_parent_product_id( WC_Product $product );
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
abstract public function get_product_name_from_order( $product_id, $order );
|
||||
|
||||
/**
|
||||
* For a given product, return it's name. In supported versions, variable
|
||||
* products will include their attributes.
|
||||
*
|
||||
* @param WC_Product $product Product (variable, simple, etc)
|
||||
* @return string The product (or variation) name, ready to print
|
||||
*/
|
||||
abstract public function get_product_name( WC_Product $product );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
// No direct access please
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Debug_Tools' ) ) {
|
||||
|
||||
class WC_Connect_Debug_Tools {
|
||||
|
||||
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 Services connection', 'woocommerce-services' ),
|
||||
'button' => __( 'Test Connection', 'woocommerce-services' ),
|
||||
'desc' => __( 'This will test your WooCommerce Services connection to ensure everything is working correctly', 'woocommerce-services' ),
|
||||
'callback' => array( $this, 'test_connection' ),
|
||||
);
|
||||
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>' . __( 'Your site is succesfully communicating to the WooCommerce Services API.', 'woocommerce-services' ) . '</p></div>';
|
||||
} else {
|
||||
echo '<div class="error inline"><p>' . __( 'ERROR: Your site has a problem connecting to the WooCommerce Services API. Please make sure your Jetpack connection is working.', 'woocommerce-services' ) . '</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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' );
|
||||
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 ) && 'product_missing_weight' === $error->get_error_code() ) {
|
||||
$error_data = $error->get_error_data();
|
||||
$product_id = $error_data['product_id'];
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( ! $product || $product->has_weight() ) {
|
||||
$this->disable_notice();
|
||||
return;
|
||||
}
|
||||
|
||||
$product_name = WC_Connect_Compatibility::instance()->get_product_name( $product );
|
||||
$message = sprintf(
|
||||
__( '<strong>%2$s does not have a weight defined.</strong><br />Shipping rates cannot be calculated. <a href="%1$s">Add a weight for %2$s</a> so your customers can purchase this item.', 'woocommerce-services' ),
|
||||
get_edit_post_link( $product_id ), $product_name
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
__( 'An error occurred in WooCommerce Services. Details are logged <a href="%s">here</a>.', 'woocommerce-services' ),
|
||||
$link_status
|
||||
);
|
||||
}
|
||||
|
||||
$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 "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 Services 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,23 @@
|
||||
<?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() {
|
||||
return ! (
|
||||
// Skip for carts loaded from session in the dashboard
|
||||
( is_admin() && did_action( 'woocommerce_cart_loaded_from_session' ) ) ||
|
||||
// Skip during Jetpack API requests
|
||||
( false !== strpos( $_SERVER['REQUEST_URI'], 'jetpack/v4/' ) ) ||
|
||||
// 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'] ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @array
|
||||
*/
|
||||
protected $fieldsets;
|
||||
|
||||
public function __construct( WC_Connect_Service_Schemas_Store $service_schemas_store,
|
||||
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;
|
||||
|
||||
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 %s or higher is required (You are running %s)', 'woocommerce-services' ),
|
||||
WOOCOMMERCE_CONNECT_MINIMUM_WOOCOMMERCE_VERSION,
|
||||
WC()->version
|
||||
),
|
||||
);
|
||||
} else if ( 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;
|
||||
|
||||
// Jetpack
|
||||
// Only one of the following should present
|
||||
// Check that Jetpack is active
|
||||
// Check that Jetpack is connected
|
||||
include_once ( ABSPATH . 'wp-admin/includes/plugin.php' ); // required for is_plugin_active
|
||||
$is_connected = WC_Connect_Jetpack::is_active() || WC_Connect_Jetpack::is_development_mode();
|
||||
if ( ! is_plugin_active( 'jetpack/jetpack.php' ) ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => sprintf(
|
||||
__( 'Please install and activate the Jetpack plugin, version %s or higher', 'woocommerce-services' ),
|
||||
WOOCOMMERCE_CONNECT_MINIMUM_JETPACK_VERSION
|
||||
),
|
||||
);
|
||||
} else if ( version_compare( JETPACK__VERSION, WOOCOMMERCE_CONNECT_MINIMUM_JETPACK_VERSION, "<" ) ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => sprintf(
|
||||
__( 'Jetpack %s or higher is required (You are running %s)', 'woocommerce-services' ),
|
||||
WOOCOMMERCE_CONNECT_MINIMUM_JETPACK_VERSION,
|
||||
JETPACK__VERSION
|
||||
),
|
||||
);
|
||||
} else if ( ! $is_connected ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => __( 'Jetpack is not connected to WordPress.com. Make sure the Jetpack plugin is installed, activated, and connected.', 'woocommerce-services' ),
|
||||
);
|
||||
} else if ( WC_Connect_Jetpack::is_staging_site() ) {
|
||||
$health_item = array(
|
||||
'state' => 'warning',
|
||||
'message' => __( 'This is a Jetpack staging site', 'woocommerce-services' ),
|
||||
);
|
||||
} else {
|
||||
$health_item = array(
|
||||
'state' => 'success',
|
||||
'message' => sprintf(
|
||||
__( 'Jetpack %s is connected and working correctly', 'woocommerce-services' ),
|
||||
JETPACK__VERSION
|
||||
),
|
||||
);
|
||||
}
|
||||
$health_items['jetpack'] = $health_item;
|
||||
|
||||
// Lastly, do the WooCommerce Services health check
|
||||
// Check that we have schema
|
||||
// Check that we are able to talk to the WooCommerce Services server
|
||||
$schemas = $this->service_schemas_store->get_service_schemas();
|
||||
$last_fetch_timestamp = $this->service_schemas_store->get_last_fetch_timestamp();
|
||||
if ( isset( $_GET['refresh'] ) && 'failed' === $_GET['refresh'] ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => __( 'An error occurred while refreshing service data.', 'woocommerce-services' ),
|
||||
'timestamp' => $last_fetch_timestamp,
|
||||
);
|
||||
} else if ( is_null( $schemas ) ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => __( 'No service data available', 'woocommerce-services' ),
|
||||
);
|
||||
} else if ( is_null( $last_fetch_timestamp ) ) {
|
||||
$health_item = array(
|
||||
'state' => 'warning',
|
||||
'message' => __( 'Service data was found, but may be out of date', 'woocommerce-services' ),
|
||||
'timestamp' => $last_fetch_timestamp
|
||||
);
|
||||
} else if ( $last_fetch_timestamp < time() - WOOCOMMERCE_CONNECT_SCHEMA_AGE_ERROR_THRESHOLD ) {
|
||||
$health_item = array(
|
||||
'state' => 'error',
|
||||
'message' => __( 'Service data was found, but is more than three days old', 'woocommerce-services' ),
|
||||
'timestamp' => $last_fetch_timestamp
|
||||
);
|
||||
} else if ( $last_fetch_timestamp < time() - WOOCOMMERCE_CONNECT_SCHEMA_AGE_WARNING_THRESHOLD ) {
|
||||
$health_item = array(
|
||||
'state' => 'warning',
|
||||
'message' => __( 'Service data was found, but is more than one day old', 'woocommerce-services' ),
|
||||
'timestamp' => $last_fetch_timestamp
|
||||
);
|
||||
} else {
|
||||
$health_item = array(
|
||||
'state' => 'success',
|
||||
'message' => __( 'Service data is up-to-date', 'woocommerce-services' ),
|
||||
'timestamp' => $last_fetch_timestamp
|
||||
);
|
||||
}
|
||||
|
||||
$health_items['woocommerce_services'] = $health_item;
|
||||
|
||||
return $health_items;
|
||||
}
|
||||
|
||||
protected function get_services_items() {
|
||||
$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' );
|
||||
} else if ( -1 === $last_failed_request_timestamp ) {
|
||||
$state = 'warning';
|
||||
$message = __( 'No rate requests have yet been made for this service', 'woocommerce-services' );
|
||||
} else if ( 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 Services 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 Services', 'The WooCommerce Services brandname', 'woocommerce-services' );
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data bootstrap for the help page
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_form_data() {
|
||||
$form_data = 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(),
|
||||
)
|
||||
);
|
||||
|
||||
return $form_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Localizes the bootstrap, enqueues the script and styles for the help page
|
||||
*/
|
||||
public function page() {
|
||||
if ( isset( $_GET['refresh'] ) && 'true' === $_GET['refresh'] ) {
|
||||
$fetched = $this->service_schemas_store->fetch_service_schemas_from_connect_server();
|
||||
$url = add_query_arg( 'refresh', $fetched ? false : 'failed' );
|
||||
wp_safe_redirect( $url );
|
||||
}
|
||||
|
||||
?>
|
||||
<h2>
|
||||
<?php _e( 'WooCommerce Services 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(
|
||||
'storeOptions' => $this->service_settings_store->get_store_options(),
|
||||
'paperSize' => $this->service_settings_store->get_preferred_paper_size(),
|
||||
) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Jetpack' ) ) {
|
||||
class WC_Connect_Jetpack {
|
||||
/**
|
||||
* Helper method to get if Jetpack is in development mode
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_development_mode() {
|
||||
if ( method_exists( 'Jetpack', 'is_development_mode' ) ) {
|
||||
return Jetpack::is_development_mode();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get if Jetpack is connected (aka active)
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_active() {
|
||||
if ( method_exists( 'Jetpack', 'is_active' ) ) {
|
||||
return Jetpack::is_active();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get if the current Jetpack website is marked as staging
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_staging_site() {
|
||||
if ( method_exists( 'Jetpack', 'is_staging_site' ) ) {
|
||||
return Jetpack::is_staging_site();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get whether the current site is an Atomic site
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_atomic_site() {
|
||||
if ( function_exists( 'jetpack_is_atomic_site' ) ) {
|
||||
return jetpack_is_atomic_site();
|
||||
} elseif ( function_exists( 'jetpack_is_automated_transfer_site' ) ) {
|
||||
return jetpack_is_automated_transfer_site();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function get_connected_user_data( $user_id ) {
|
||||
if ( method_exists( 'Jetpack', 'get_connected_user_data' ) ) {
|
||||
return Jetpack::get_connected_user_data( $user_id );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the Jetpack master user, IF we are connected
|
||||
* @return WP_User | false
|
||||
*/
|
||||
public static function get_master_user() {
|
||||
include_once ( ABSPATH . 'wp-admin/includes/plugin.php' );
|
||||
if ( self::is_active() && method_exists( 'Jetpack_Options', 'get_option' ) ) {
|
||||
$master_user_id = Jetpack_Options::get_option( 'master_user' );
|
||||
return get_userdata( $master_user_id );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a connect url
|
||||
* @param $redirect_url
|
||||
* @return string
|
||||
*/
|
||||
public static function build_connect_url( $redirect_url ) {
|
||||
return Jetpack::init()->build_connect_url(
|
||||
true,
|
||||
$redirect_url,
|
||||
'woocommerce-services-auto-authorize'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
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 date_i18n( 'Y-m-d', current_time( 'timestamp' ) ); ?>.csv"
|
||||
class="export_csv"
|
||||
data-export="table"
|
||||
>
|
||||
<?php _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;
|
||||
$query = "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = 'wc_connect_labels'";
|
||||
$db_results = $wpdb->get_results( $query );
|
||||
$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->post_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
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 timestapms
|
||||
$start_date = $this->start_date * 1000;
|
||||
$end_date = $this->end_date * 1000;
|
||||
|
||||
$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_order_url( $post_id ) {
|
||||
$order = wc_get_order( $post_id );
|
||||
return '<a href="' . WC_Connect_Compatibility::instance()->get_edit_order_url( $order ) . '">' . $order->get_order_number( $order ) . '</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 get_date_from_gmt( date( 'Y-m-d H:i:s', $label['created'] / 1000 ) ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo $this->get_order_url( $label['order_id'] ); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo wc_price( $label['rate'] ); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $label['service_name']; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $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 _e( 'Total', 'woocommerce-services' ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php echo count( $labels ); ?>
|
||||
</th>
|
||||
<th>
|
||||
<?php echo 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,135 @@
|
||||
<?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_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 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 );
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,730 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Nux' ) ) {
|
||||
|
||||
class WC_Connect_Nux {
|
||||
/**
|
||||
* Jetpack status constants.
|
||||
*/
|
||||
const JETPACK_NOT_INSTALLED = 'uninstalled';
|
||||
const JETPACK_INSTALLED_NOT_ACTIVATED = 'installed';
|
||||
const JETPACK_ACTIVATED_NOT_CONNECTED = 'activated';
|
||||
const JETPACK_DEV = 'dev';
|
||||
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_woocommerce_page_wc-settings', array( $this, 'register_add_service_to_zone_pointer' ) );
|
||||
add_filter( 'wc_services_pointer_post.php', array( $this, 'register_order_page_labels_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();
|
||||
}
|
||||
|
||||
public function register_add_service_to_zone_pointer( $pointers ) {
|
||||
$pointers[] = array(
|
||||
'id' => 'wc_services_add_service_to_zone',
|
||||
'target' => 'th.wc-shipping-zone-methods',
|
||||
'options' => array(
|
||||
'content' => sprintf( '<h3>%s</h3><p>%s</p>',
|
||||
__( 'Add a WooCommerce shipping service to a Zone' ,'woocommerce-services' ),
|
||||
__( "To ship products to customers using USPS or Canada Post, you will need to add them as a shipping method to an applicable zone. If you don't have any zones, add one first.", 'woocommerce-services' )
|
||||
),
|
||||
'position' => array( 'edge' => 'right', 'align' => 'left' ),
|
||||
),
|
||||
);
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
public function is_new_labels_user() {
|
||||
$is_new_user = get_transient( self::IS_NEW_LABEL_USER );
|
||||
if ( false === $is_new_user ) {
|
||||
global $wpdb;
|
||||
$query = "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key = 'wc_connect_labels' LIMIT 1";
|
||||
$results = $wpdb->get_results( $query );
|
||||
$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 ) {
|
||||
$dismissed_pointers = $this->get_dismissed_pointers();
|
||||
if ( in_array( 'wc_services_labels_metabox', $dismissed_pointers, true ) ) {
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
// If the user is not new to labels, we should just dismiss this pointer
|
||||
if ( ! $this->is_new_labels_user() ) {
|
||||
$dismissed_pointers[] = 'wc_services_labels_metabox';
|
||||
$dismissed_data = implode( ',', $dismissed_pointers );
|
||||
update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed_data );
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
if ( $this->shipping_label->should_show_meta_box() ) {
|
||||
$pointers[] = array(
|
||||
'id' => 'wc_services_labels_metabox',
|
||||
'target' => '#woocommerce-order-label',
|
||||
'options' => array(
|
||||
'content' => sprintf( '<h3>%s</h3><p>%s</p>',
|
||||
__( 'Discounted Shipping Labels' ,'woocommerce-services' ),
|
||||
__( "When you're ready, purchase and print discounted labels from USPS right here.", 'woocommerce-services' )
|
||||
),
|
||||
'position' => array( 'edge' => 'right', 'align' => 'left' ),
|
||||
),
|
||||
'dim' => true,
|
||||
);
|
||||
}
|
||||
|
||||
return $pointers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the current user is the owner of the Jetpack connection
|
||||
* - Only that person can accept the TOS
|
||||
*
|
||||
* @uses self::get_jetpack_install_status()
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_accept_tos() {
|
||||
$jetpack_status = $this->get_jetpack_install_status();
|
||||
|
||||
if (
|
||||
( self::JETPACK_NOT_INSTALLED === $jetpack_status ) ||
|
||||
( self::JETPACK_INSTALLED_NOT_ACTIVATED === $jetpack_status )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Developer case
|
||||
if ( self::JETPACK_DEV === $jetpack_status ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
|
||||
$can_accept = (
|
||||
isset( $user_token->external_user_id ) &&
|
||||
get_current_user_id() === $user_token->external_user_id
|
||||
);
|
||||
|
||||
return $can_accept;
|
||||
}
|
||||
|
||||
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_INSTALLED:
|
||||
case self::JETPACK_INSTALLED_NOT_ACTIVATED:
|
||||
case self::JETPACK_ACTIVATED_NOT_CONNECTED:
|
||||
return 'before_jetpack_connection';
|
||||
case self::JETPACK_CONNECTED:
|
||||
case self::JETPACK_DEV:
|
||||
// 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() {
|
||||
// we need to use validate_plugin to check that Jetpack is installed
|
||||
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
|
||||
|
||||
// check if jetpack is installed
|
||||
if ( 0 !== validate_plugin( 'jetpack/jetpack.php' ) ) {
|
||||
return self::JETPACK_NOT_INSTALLED;
|
||||
}
|
||||
|
||||
// check if Jetpack is activated
|
||||
if ( ! class_exists( 'Jetpack_Data' ) ) {
|
||||
return self::JETPACK_INSTALLED_NOT_ACTIVATED;
|
||||
}
|
||||
|
||||
if ( defined( 'JETPACK_DEV_DEBUG' ) && true === JETPACK_DEV_DEBUG ) {
|
||||
// installed, activated, and dev mode on
|
||||
return self::JETPACK_DEV;
|
||||
}
|
||||
|
||||
// installed, activated, dev mode off
|
||||
// check if connected
|
||||
$user_token = Jetpack_Data::get_access_token( JETPACK_MASTER_USER );
|
||||
if ( ! isset( $user_token->external_user_id ) ) { // always an int
|
||||
return self::JETPACK_ACTIVATED_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.
|
||||
'shop_order' === $screen->post_type
|
||||
&& 'edit' === $screen->base
|
||||
)
|
||||
|| ( // Edit order page.
|
||||
'shop_order' === $screen->post_type
|
||||
&& 'post' === $screen->base
|
||||
)
|
||||
|| ( // 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://stripe.com/global
|
||||
*/
|
||||
public function is_stripe_supported_country( $country_code ) {
|
||||
$stripe_supported_countries = array(
|
||||
'AU',
|
||||
'AT',
|
||||
'BE',
|
||||
'CA',
|
||||
'DK',
|
||||
'FI',
|
||||
'FR',
|
||||
'DE',
|
||||
'HK',
|
||||
'IE',
|
||||
'JP',
|
||||
'LU',
|
||||
'NL',
|
||||
'NZ',
|
||||
'NO',
|
||||
'SG',
|
||||
'ES',
|
||||
'SE',
|
||||
'CH',
|
||||
'GB',
|
||||
'US',
|
||||
);
|
||||
|
||||
return in_array( $country_code, $stripe_supported_countries );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_stripe = $this->is_stripe_supported_country( $store_country );
|
||||
$supports_taxes = $this->is_taxjar_supported_country( $store_country );
|
||||
$supports_shipping = in_array( $store_country, array( 'US', 'CA' ) );
|
||||
|
||||
return $supports_shipping || $supports_stripe || $supports_taxes;
|
||||
}
|
||||
|
||||
public function get_feature_list_for_country( $country ) {
|
||||
$feature_list = false;
|
||||
$supports_stripe = $this->is_stripe_supported_country( $country );
|
||||
$supports_taxes = $this->is_taxjar_supported_country( $country );
|
||||
$supports_rates = in_array( $country, array( 'US', 'CA' ) );
|
||||
$supports_labels = ( 'US' === $country );
|
||||
|
||||
$is_stripe_active = is_plugin_active( 'woocommerce-gateway-stripe/woocommerce-gateway-stripe.php' );
|
||||
$stripe_settings = get_option( 'woocommerce_stripe_settings', array() );
|
||||
$is_stripe_ready = $is_stripe_active && isset( $stripe_settings['enabled'] ) && 'yes' === $stripe_settings['enabled'];
|
||||
|
||||
$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() );
|
||||
$is_ppec_ready = $is_ppec_active && ( ! isset( $ppec_settings['enabled'] ) || 'yes' === $ppec_settings['enabled'] );
|
||||
|
||||
$supports_payments = ( $supports_stripe && $is_stripe_ready ) || $is_ppec_ready;
|
||||
|
||||
if ( $supports_payments && $supports_taxes && $supports_rates && $supports_labels ) {
|
||||
$feature_list = __( 'automated tax calculation, live shipping rates, shipping label printing, and smoother payment setup', 'woocommerce-services' );
|
||||
} elseif ( $supports_payments && $supports_taxes && $supports_rates ) {
|
||||
$feature_list = __( 'automated tax calculation, live shipping rates, and smoother payment setup', 'woocommerce-services' );
|
||||
} else if ( $supports_payments && $supports_taxes ) {
|
||||
$feature_list = __( 'automated tax calculation and smoother payment setup', 'woocommerce-services' );
|
||||
} else if ( $supports_payments && $supports_rates && $supports_labels ) {
|
||||
$feature_list = __( 'live shipping rates, shipping label printing, and smoother payment setup', 'woocommerce-services' );
|
||||
} else if ( $supports_payments && $supports_rates ) {
|
||||
$feature_list = __( 'live shipping rates and smoother payment setup', 'woocommerce-services' );
|
||||
} else if ( $supports_payments ) {
|
||||
$feature_list = __( 'smoother payment setup', 'woocommerce-services' );
|
||||
} else if ( $supports_taxes && $supports_rates && $supports_labels ) {
|
||||
$feature_list = __( 'automated tax calculation, live shipping rates, and shipping label printing', 'woocommerce-services' );
|
||||
} else if ( $supports_taxes && $supports_rates ) {
|
||||
$feature_list = __( 'automated tax calculation and live shipping rates', 'woocommerce-services' );
|
||||
} else if ( $supports_taxes ) {
|
||||
$feature_list = __( 'automated tax calculation', 'woocommerce-services' );
|
||||
} else if ( $supports_rates && $supports_labels ) {
|
||||
$feature_list = __( 'live shipping rates and shipping label printing', 'woocommerce-services' );
|
||||
} else if ( $supports_rates ) {
|
||||
$feature_list = __( 'live shipping rates', '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 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();
|
||||
if ( ( self::JETPACK_NOT_INSTALLED === $jetpack_install_status && ! current_user_can( 'install_plugins' ) )
|
||||
|| ( self::JETPACK_INSTALLED_NOT_ACTIVATED === $jetpack_install_status && ! current_user_can( 'activate_plugins' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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' => $this->can_accept_tos(),
|
||||
'should_display_after_cxn_banner' => WC_Connect_Options::get_option( self::SHOULD_SHOW_AFTER_CXN_BANNER ),
|
||||
) );
|
||||
|
||||
switch ( $banner_to_display ) {
|
||||
case 'before_jetpack_connection':
|
||||
$ajax_data = array(
|
||||
'nonce' => wp_create_nonce( 'wcs_nux_notice' ),
|
||||
'initial_install_status' => $jetpack_install_status,
|
||||
'redirect_url' => $this->get_jetpack_redirect_url(),
|
||||
'translations' => array(
|
||||
'activating' => __( 'Activating...', 'woocommerce-services' ),
|
||||
'connecting' => __( 'Connecting...', 'woocommerce-services' ),
|
||||
'installError' => __( 'There was an error installing Jetpack. Please try installing it manually.', 'woocommerce-services' ),
|
||||
'defaultError' => __( 'Something went wrong. Please try connecting to Jetpack manually, or contact support on the WordPress.org forums.', 'woocommerce-services' ),
|
||||
),
|
||||
);
|
||||
wp_enqueue_script( 'wc_connect_banner' );
|
||||
wp_localize_script( 'wc_connect_banner', 'wcs_nux_notice', $ajax_data );
|
||||
add_action( 'wp_ajax_woocommerce_services_activate_jetpack',
|
||||
array( $this, 'ajax_activate_jetpack' )
|
||||
);
|
||||
add_action( 'wp_ajax_woocommerce_services_get_jetpack_connect_url',
|
||||
array( $this, 'ajax_get_jetpack_connect_url' )
|
||||
);
|
||||
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 );
|
||||
|
||||
$jetpack_status = $this->get_jetpack_install_status();
|
||||
$button_text = __( 'Connect', 'woocommerce-services' );
|
||||
$banner_title = __( 'Connect Jetpack to activate WooCommerce Services', 'woocommerce-services' );
|
||||
$image_url = plugins_url( 'images/wcs-notice.png', dirname( __FILE__ ) );
|
||||
|
||||
switch ( $jetpack_status ) {
|
||||
case self::JETPACK_NOT_INSTALLED:
|
||||
$button_text = __( 'Install Jetpack and connect', 'woocommerce-services' );
|
||||
break;
|
||||
case self::JETPACK_INSTALLED_NOT_ACTIVATED:
|
||||
$button_text = __( 'Activate Jetpack and connect', 'woocommerce-services' );
|
||||
break;
|
||||
}
|
||||
|
||||
$country = WC()->countries->get_base_country();
|
||||
/* translators: %s: list of features, potentially comma separated */
|
||||
$description_base = __( "WooCommerce Services is almost ready to go! Once you connect Jetpack you'll have access to %s.", 'woocommerce-services' );
|
||||
$feature_list = $this->get_feature_list_for_country( $country );
|
||||
$banner_content = array(
|
||||
'title' => $banner_title,
|
||||
'description' => sprintf( $description_base, $feature_list ),
|
||||
'button_text' => $button_text,
|
||||
'image_url' => $image_url,
|
||||
'should_show_jp' => true,
|
||||
'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_jp' => false,
|
||||
'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 Services is almost ready to go! Once you connect your store 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 store to activate WooCommerce Services', '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_jp' => false,
|
||||
'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 if ( $content['should_show_jp'] ) : ?>
|
||||
<img
|
||||
class="wcs-nux__notice-logo-jetpack"
|
||||
src="<?php echo esc_url( plugins_url( 'images/jetpack-logo.png', dirname( __FILE__ ) ) ); ?>"
|
||||
>
|
||||
<?php endif; ?>
|
||||
<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 $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 the <a href="%2$s">Terms of Service</a> and to <a href="%3$s">share certain data and settings</a> with WordPress.com and/or third parties.', 'woocommerce-services' ),
|
||||
array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
),
|
||||
) ),
|
||||
esc_html( $content['button_text'] ),
|
||||
'https://wordpress.com/tos/',
|
||||
'https://jetpack.com/support/what-data-does-jetpack-sync/'
|
||||
); ?></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 : ?>
|
||||
<button
|
||||
class="woocommerce-services__connect-jetpack wcs-nux__notice-content-button button button-primary"
|
||||
>
|
||||
<?php echo esc_html( $content['button_text'] ); ?>
|
||||
</button>
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates Jetpack after an ajax request
|
||||
*/
|
||||
public function ajax_activate_jetpack() {
|
||||
check_ajax_referer( 'wcs_nux_notice' );
|
||||
|
||||
$result = activate_plugin( 'jetpack/jetpack.php' );
|
||||
|
||||
if ( is_null( $result ) ) {
|
||||
// The function activate_plugin() returns NULL on success.
|
||||
echo 'success';
|
||||
} else {
|
||||
if ( is_wp_error( $result ) ) {
|
||||
echo esc_html( $result->get_error_message() );
|
||||
} else {
|
||||
echo 'error';
|
||||
}
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Jetpack connection URL.
|
||||
*
|
||||
*/
|
||||
public function ajax_get_jetpack_connect_url() {
|
||||
check_ajax_referer( 'wcs_nux_notice' );
|
||||
|
||||
$redirect_url = '';
|
||||
if ( isset( $_POST['redirect_url'] ) ) {
|
||||
$redirect_url = esc_url_raw( wp_unslash( $_POST['redirect_url'] ) );
|
||||
}
|
||||
|
||||
$connect_url = WC_Connect_Jetpack::build_connect_url( $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 );
|
||||
|
||||
echo esc_url_raw( $connect_url );
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?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',
|
||||
);
|
||||
case 'shipping_method':
|
||||
return array(
|
||||
'form_settings',
|
||||
'failure_timestamp',
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'tos_accepted',
|
||||
'store_guid',
|
||||
'debug_logging_enabled',
|
||||
'debug_display_enabled',
|
||||
'payment_methods',
|
||||
'account_settings',
|
||||
'paper_size',
|
||||
'packages',
|
||||
'predefined_packages',
|
||||
'shipping_methods_migrated',
|
||||
'should_display_nux_after_jp_cxn_banner',
|
||||
'needs_tax_environment_setup',
|
||||
'stripe_state',
|
||||
'banner_ppec',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all options created by WooCommerce Services, 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( sprintf( 'Invalid WooCommerce Services 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( sprintf( 'Invalid WooCommerce Services 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( sprintf( 'Invalid WooCommerce Services 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( sprintf( 'Invalid WooCommerce Services 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( sprintf( 'Invalid WooCommerce Services 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( sprintf( 'Invalid WooCommerce Services 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,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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
$payment_methods = $this->get_payment_methods_from_response_body( $response_body );
|
||||
if ( is_wp_error( $payment_methods ) ) {
|
||||
$this->logger->log( $payment_methods, __FUNCTION__ );
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we made it this far, it is safe to store the object
|
||||
$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? Select the first available one
|
||||
$selected_payment_method_id = $this->service_settings_store->get_selected_payment_method_id();
|
||||
if (
|
||||
$selected_payment_method_id &&
|
||||
! 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 );
|
||||
}
|
||||
|
||||
protected function get_payment_methods_from_response_body( $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.' );
|
||||
}
|
||||
|
||||
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.' );
|
||||
}
|
||||
|
||||
$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 ( (array) $payment_methods as $payment_method ) {
|
||||
$required_keys = array( 'payment_method_id', 'name', 'card_type', 'card_digits', 'expiry' );
|
||||
foreach ( (array) $required_keys as $required_key ) {
|
||||
if ( ! array_key_exists( $required_key, $payment_method ) ) {
|
||||
return new WP_Error( 'payment_methods_key_missing', 'Payment method array element is missing a required key' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $payment_methods;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_PayPal_EC' ) ) {
|
||||
|
||||
/**
|
||||
* Integrates with WooCommerce PayPal Express 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 Express 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 = WC_Connect_Compatibility::instance()->get_payment_method( $order );
|
||||
|
||||
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();
|
||||
|
||||
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' === WC_Connect_Compatibility::instance()->get_payment_method( wc_get_order() )
|
||||
)
|
||||
|| ( // WooCommerce settings.
|
||||
'woocommerce_page_wc-settings' === $screen->base
|
||||
&& isset( $_GET['tab'] ) && 'checkout' === $_GET['tab']
|
||||
)
|
||||
|| ( // WooCommerce payment gateway extension page
|
||||
'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 Services 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, 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, 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 Services, <a href="%s">click here</a>.', 'woocommerce-services' );
|
||||
if ( empty( $settings->api_username ) ) {
|
||||
$api_creds_text = sprintf( $api_creds_template, 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, 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' )
|
||||
) {
|
||||
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,118 @@
|
||||
<?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 Services', '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 Services 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 object $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 );
|
||||
|
||||
update_post_meta( $order_id, 'wc_connect_labels', $labels );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?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 ) ) {
|
||||
$this->logger->log( $response_body, __FUNCTION__ );
|
||||
return false;
|
||||
}
|
||||
|
||||
$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() );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
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 get_origin_address() {
|
||||
$wc_address_fields = array();
|
||||
$wc_address_fields['company'] = get_bloginfo( 'name' );
|
||||
$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() );
|
||||
return array_merge( $wc_address_fields, $stored_address_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 ) {
|
||||
$label_data = get_post_meta( ( int ) $order_id, '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;
|
||||
$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'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
update_post_meta( $order_id, 'wc_connect_labels', $labels_data );
|
||||
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 );
|
||||
update_post_meta( $order_id, 'wc_connect_labels', $labels_data );
|
||||
}
|
||||
|
||||
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'] );
|
||||
|
||||
$order->set_address( $new_address, 'shipping' );
|
||||
update_post_meta( $order_id, '_wc_connect_destination_normalized', true );
|
||||
}
|
||||
|
||||
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 Services 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;
|
||||
$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;"
|
||||
);
|
||||
|
||||
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;
|
||||
} else if ( 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
|
||||
|| ! $this->service_schemas_store->fetch_service_schemas_from_connect_server() ) { //ensure the latest schemas are fetched
|
||||
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 ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'error' => 'bad_instance_id',
|
||||
'message' => __( '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 ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'error' => 'bad_service_id',
|
||||
'message' => __( '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
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'error' => 'validation_failure',
|
||||
'message' => $response_body->get_error_message(),
|
||||
'data' => $response_body->get_error_data(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 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() );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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,79 @@
|
||||
<?php
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Settings_Pages' ) ) {
|
||||
|
||||
class WC_Connect_Settings_Pages {
|
||||
/**
|
||||
* @array
|
||||
*/
|
||||
protected $fieldsets;
|
||||
|
||||
public function __construct() {
|
||||
$this->id = 'connect';
|
||||
$this->label = _x( 'WooCommerce Services', 'The WooCommerce Services brandname', 'woocommerce-services' );
|
||||
|
||||
add_filter( 'woocommerce_get_sections_shipping', array( $this, 'get_sections' ), 30 );
|
||||
add_action( 'woocommerce_settings_shipping', array( $this, 'output_settings_screen' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sections.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sections( $shipping_tabs ) {
|
||||
if ( ! is_array( $shipping_tabs ) ) {
|
||||
$shipping_tabs = array();
|
||||
}
|
||||
|
||||
$shipping_tabs[ 'woocommerce-services-settings' ] = __( 'WooCommerce Services', 'woocommerce-services' );
|
||||
return $shipping_tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the settings.
|
||||
*/
|
||||
public function output_settings_screen() {
|
||||
global $current_section;
|
||||
|
||||
if ( 'woocommerce-services-settings' !== $current_section ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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_development_mode() ) {
|
||||
if ( WC_Connect_Jetpack::is_active() ) {
|
||||
$message = __( 'Note: Jetpack is connected, but development mode is also enabled on this site. Please disable development mode.', 'woocommerce-services' );
|
||||
} else {
|
||||
$message = __( 'Note: Jetpack development mode is enabled on this site. This site will not be able to obtain payment methods from WooCommerce Services production servers.', 'woocommerce-services' );
|
||||
}
|
||||
?>
|
||||
<div class="wc-connect-admin-dev-notice">
|
||||
<p>
|
||||
<?php echo esc_html( $message ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
$extra_args = array();
|
||||
if ( isset( $_GET['from_order'] ) ) {
|
||||
$extra_args['order_id'] = $_GET['from_order'];
|
||||
$extra_args['order_href'] = get_edit_post_link( $_GET['from_order'] );
|
||||
}
|
||||
|
||||
do_action( 'enqueue_wc_connect_script', 'wc-connect-shipping-settings', $extra_args );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
<?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_Payment_Methods_Store
|
||||
*/
|
||||
protected $payment_methods_store;
|
||||
|
||||
/**
|
||||
* @var array Supported countries
|
||||
*/
|
||||
private $supported_countries = array( 'US', 'PR' );
|
||||
|
||||
/**
|
||||
* @var array Supported currencies
|
||||
*/
|
||||
private $supported_currencies = array( 'USD' );
|
||||
|
||||
/**
|
||||
* @var array Unsupported states, by country
|
||||
*/
|
||||
private $unsupported_states = array(
|
||||
'US' => array( 'AA', 'AE', 'AP' ),
|
||||
);
|
||||
|
||||
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
|
||||
) {
|
||||
$this->api_client = $api_client;
|
||||
$this->settings_store = $settings_store;
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
}
|
||||
|
||||
public function get_item_data( WC_Order $order, $item ) {
|
||||
$product = WC_Connect_Compatibility::instance()->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();
|
||||
}
|
||||
|
||||
$product_data = array(
|
||||
'height' => (float) $height,
|
||||
'product_id' => $item['product_id'],
|
||||
'length' => (float) $length,
|
||||
'quantity' => 1,
|
||||
'weight' => (float) $weight,
|
||||
'width' => (float) $width,
|
||||
'name' => $this->get_name( $product ),
|
||||
'url' => get_edit_post_link( WC_Connect_Compatibility::instance()->get_parent_product_id( $product ), null ),
|
||||
);
|
||||
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$product_data['attributes'] = WC_Connect_Compatibility::instance()->get_formatted_variation( $product, true );
|
||||
}
|
||||
|
||||
return $product_data;
|
||||
}
|
||||
|
||||
public function get_items_as_individual_packages( WC_Order $order ) {
|
||||
$packages = array();
|
||||
$item_count = 0;
|
||||
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$item_data = $this->get_item_data( $order, $item );
|
||||
if ( null === $item_data ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < $item['qty']; $i++ ) {
|
||||
$id = 'weight_' . $item_count++ . '_individual';
|
||||
$packages[ $id ] = array(
|
||||
'id' => $id,
|
||||
'box_id' => 'individual',
|
||||
'height' => $item_data['height'],
|
||||
'length' => $item_data['length'],
|
||||
'weight' => $item_data['weight'],
|
||||
'width' => $item_data['width'],
|
||||
'items' => array( $item_data ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
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 = '#' . WC_Connect_Compatibility::instance()->get_product_id( $product );
|
||||
|
||||
}
|
||||
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' ) );
|
||||
|
||||
return 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_Compatibility::instance()->get_item_product( $order, $product_data );
|
||||
|
||||
if ( $product ) {
|
||||
$product_data['name'] = $this->get_name( $product );
|
||||
$product_data['url'] = get_edit_post_link( WC_Connect_Compatibility::instance()->get_parent_product_id( $product ), null );
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$formatted = WC_Connect_Compatibility::instance()->get_formatted_variation( $product, true );
|
||||
$product_data['attributes'] = $formatted;
|
||||
}
|
||||
} else {
|
||||
$product_data['name'] = WC_Connect_Compatibility::instance()->get_product_name_from_order( $product_data['product_id'], $order );
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < $item['qty']; $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 = WC_Connect_Compatibility::instance()->get_order_id( $order );
|
||||
$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 ) get_post_meta( $order_id, '_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;
|
||||
}
|
||||
|
||||
private function is_supported_state( $country_code, $state_code ) {
|
||||
if ( ! $country_code || ! $state_code ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $country_code, $this->unsupported_states ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! in_array( $state_code, $this->unsupported_states[ $country_code ] );
|
||||
}
|
||||
|
||||
private function is_supported_country( $country_code ) {
|
||||
return in_array( $country_code, $this->supported_countries );
|
||||
}
|
||||
|
||||
private function is_supported_currency( $currency_code ) {
|
||||
return in_array( $currency_code, $this->supported_currencies );
|
||||
}
|
||||
|
||||
private function is_supported_address( $address ) {
|
||||
$country_code = $address['country'];
|
||||
if ( ! $country_code ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! $this->is_supported_country( $country_code ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$state_code = $address['state'];
|
||||
return $this->is_supported_state( $country_code, $state_code );
|
||||
}
|
||||
|
||||
protected function get_states_map() {
|
||||
$result = array();
|
||||
$all_countries = WC()->countries->get_countries();
|
||||
|
||||
foreach ( $this->supported_countries as $country_code ) {
|
||||
$country_data = array( 'name' => html_entity_decode( $all_countries[ $country_code ] ) );
|
||||
$states = WC()->countries->get_states( $country_code );
|
||||
|
||||
if ( $states ) {
|
||||
$country_data['states'] = array();
|
||||
foreach ( $states as $state_code => $name ) {
|
||||
if ( ! $this->is_supported_state( $country_code, $state_code ) ) {
|
||||
continue;
|
||||
}
|
||||
$country_data['states'][ $state_code ] = html_entity_decode( $name );
|
||||
}
|
||||
}
|
||||
|
||||
$result[ $country_code ] = $country_data;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function should_show_meta_box() {
|
||||
if ( null === $this->show_metabox ) {
|
||||
$this->show_metabox = $this->calculate_should_show_meta_box();
|
||||
}
|
||||
|
||||
return $this->show_metabox;
|
||||
}
|
||||
|
||||
private function calculate_should_show_meta_box() {
|
||||
$order = wc_get_order();
|
||||
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the order already has purchased labels, show the meta-box no matter what
|
||||
if ( get_post_meta( WC_Connect_Compatibility::instance()->get_order_id( $order ), 'wc_connect_labels', true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Restrict showing the metabox to supported store currencies.
|
||||
$base_currency = get_woocommerce_currency();
|
||||
if ( ! $this->is_supported_currency( $base_currency ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restrict showing the meta-box to supported origin and destinations: US domestic, for now
|
||||
$base_location = wc_get_base_location();
|
||||
if ( ! $this->is_supported_country( $base_location['country'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dest_address = $order->get_address( 'shipping' );
|
||||
if ( ! $this->is_supported_address( $dest_address ) ) {
|
||||
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_Compatibility::instance()->get_item_product( $order, $item );
|
||||
if ( $product && $product->needs_shipping() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get_label_payload( $post_order_or_id ) {
|
||||
$order = wc_get_order( $post_order_or_id );
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$account_settings = $this->settings_store->get_account_settings();
|
||||
|
||||
$order_id = WC_Connect_Compatibility::instance()->get_order_id( $order );
|
||||
$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 ),
|
||||
//for backwards compatibility, still disable the country dropdown for calypso users with older plugin versions
|
||||
'canChangeCountries' => true,
|
||||
);
|
||||
|
||||
$store_options = $this->settings_store->get_store_options();
|
||||
$store_options['countriesData'] = $this->get_states_map();
|
||||
$payload['storeOptions'] = $store_options;
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function meta_box( $post ) {
|
||||
$order = wc_get_order( $post );
|
||||
$order_id = WC_Connect_Compatibility::instance()->get_order_id( $order );
|
||||
$payload = array(
|
||||
'orderId' => $order_id,
|
||||
);
|
||||
|
||||
do_action( 'enqueue_wc_connect_script', 'wc-connect-create-shipping-label', $payload );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,542 @@
|
||||
<?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;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @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'] : '';
|
||||
|
||||
// Ensure that Country is specified
|
||||
if ( empty( $country ) ) {
|
||||
$this->debug( 'Skipping rate calculation - missing country', 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate Postcode
|
||||
if ( ! WC_Validation::is_postcode( $postcode, $country ) ) {
|
||||
$this->debug( 'Skipping rate calculation - invalid postcode', 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate State
|
||||
$valid_states = WC()->countries->get_states( $country );
|
||||
|
||||
if ( $valid_states && ! array_key_exists( $state, $valid_states ) ) {
|
||||
$this->debug( 'Skipping rate calculation - invalid/unsupported state', 'error' );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
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 Services 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 ) ) {
|
||||
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 Services 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 ) ) )
|
||||
);
|
||||
$debug_mode = 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' );
|
||||
$response_body = wp_cache_get( $cache_key );
|
||||
if ( ! $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;
|
||||
}
|
||||
wp_cache_set( $cache_key, $response_body, '', 3600 );
|
||||
}
|
||||
|
||||
$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 ) {
|
||||
/** @var WC_Product $product */
|
||||
$product = $this->lookup_product( $package, $product_id );
|
||||
if ( $product ) {
|
||||
$item_name = WC_Connect_Compatibility::instance()->get_product_name( $product );
|
||||
$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>%s x</em> ', $count ) : '' ) .
|
||||
sprintf( '<strong>%s</strong> %s', $item_name, $item_measurements );
|
||||
}
|
||||
}
|
||||
|
||||
$package_measurements = '';
|
||||
|
||||
if ( ! property_exists( $rate_package, 'box_id' ) ) {
|
||||
$package_name = __( 'Unknown package 📦', 'woocommerce-services' );
|
||||
} else if ( 'individual' === $rate_package->box_id ) {
|
||||
$package_name = __( 'Individual packaging 📦', 'woocommerce-services' );
|
||||
} else if (
|
||||
isset( $packaging_lookup[ $rate_package->box_id ] ) &&
|
||||
isset( $packaging_lookup[ $rate_package->box_id ]['name'] )
|
||||
) {
|
||||
$is_letter = isset( $packaging_lookup[ $rate_package->box_id ]['is_letter'] ) && $packaging_lookup[ $rate_package->box_id ]['is_letter'];
|
||||
$icon = $is_letter ? '✉️' : '📦';
|
||||
$package_name = $packaging_lookup[ $rate_package->box_id ]['name'] . ' ' . $icon;
|
||||
$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 ) );
|
||||
|
||||
$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
|
||||
),
|
||||
);
|
||||
|
||||
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 {
|
||||
$this->debug(
|
||||
sprintf(
|
||||
'Received rate: <strong>%s</strong> (%s)<br/><ul><li>%s</li></ul>',
|
||||
$rate_to_add['label'],
|
||||
wc_price( $rate->rate ),
|
||||
implode( '</li><li>', $package_summaries )
|
||||
),
|
||||
'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' ) {
|
||||
if ( is_cart() || is_checkout() || isset( $_POST['update_cart'] ) ) {
|
||||
$debug_message = sprintf( '%s (%s:%d)', $message, esc_html( $this->title ), $this->instance_id );
|
||||
|
||||
$this->logger->debug( $debug_message, $type );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Connect_Stripe' ) ) {
|
||||
|
||||
class WC_Connect_Stripe {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Options
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
const STATE_VAR_NAME = 'stripe_state';
|
||||
const SETTINGS_OPTION = 'woocommerce_stripe_settings';
|
||||
|
||||
public function __construct( WC_Connect_API_Client $client, WC_Connect_Options $options, WC_Connect_Logger $logger ) {
|
||||
$this->api = $client;
|
||||
$this->options = $options;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function is_stripe_plugin_enabled() {
|
||||
return class_exists( 'WC_Stripe' );
|
||||
}
|
||||
|
||||
public function get_oauth_url( $return_url ) {
|
||||
$result = $this->api->get_stripe_oauth_init( $return_url );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$this->options->update_option( self::STATE_VAR_NAME, $result->state );
|
||||
|
||||
return $result->oauthUrl;
|
||||
}
|
||||
|
||||
public function create_account( $email, $country ) {
|
||||
$response = $this->api->create_stripe_account( $email, $country );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
return $this->save_stripe_keys( $response );
|
||||
}
|
||||
|
||||
public function get_account_details() {
|
||||
return $this->api->get_stripe_account_details();
|
||||
}
|
||||
|
||||
public function deauthorize_account() {
|
||||
$response = $this->api->deauthorize_stripe_account();
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->clear_stripe_keys();
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function connect_oauth( $state, $code ) {
|
||||
if ( $state !== $this->options->get_option( self::STATE_VAR_NAME, false ) ) {
|
||||
return new WP_Error( 'Invalid stripe state' );
|
||||
}
|
||||
|
||||
$response = $this->api->get_stripe_oauth_keys( $code );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->save_stripe_keys( $response );
|
||||
}
|
||||
|
||||
private function save_stripe_keys( $result ) {
|
||||
if ( ! isset( $result->publishableKey, $result->secretKey ) ) {
|
||||
return new WP_Error( 'Invalid credentials received from server' );
|
||||
}
|
||||
|
||||
$is_test = false !== strpos( $result->publishableKey, '_test_' );
|
||||
$prefix = $is_test ? 'test_' : '';
|
||||
|
||||
$default_options = $this->get_default_stripe_config();
|
||||
|
||||
$options = array_merge( $default_options, get_option( self::SETTINGS_OPTION, array() ) );
|
||||
$options['enabled'] = 'yes';
|
||||
$options['testmode'] = $is_test ? 'yes' : 'no';
|
||||
$options[ $prefix . 'publishable_key' ] = $result->publishableKey;
|
||||
$options[ $prefix . 'secret_key' ] = $result->secretKey;
|
||||
|
||||
// While we are at it, let's also clear the account_id and
|
||||
// test_account_id if present
|
||||
|
||||
// Those used to be stored by save_stripe_keys but should not have
|
||||
// been since they were not used by anyone
|
||||
|
||||
unset( $options[ 'account_id' ] );
|
||||
unset( $options[ 'test_account_id' ] );
|
||||
|
||||
update_option( self::SETTINGS_OPTION, $options );
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears keys for test or production (whichever is presently enabled).
|
||||
* Especially useful after Stripe Connect account deauthorization.
|
||||
*/
|
||||
private function clear_stripe_keys() {
|
||||
$default_options = $this->get_default_stripe_config();
|
||||
$options = array_merge( $default_options, get_option( self::SETTINGS_OPTION, array() ) );
|
||||
|
||||
if ( 'yes' === $options['testmode'] ) {
|
||||
$options[ 'test_publishable_key' ] = '';
|
||||
$options[ 'test_secret_key' ] = '';
|
||||
} else {
|
||||
$options[ 'publishable_key' ] = '';
|
||||
$options[ 'secret_key' ] = '';
|
||||
}
|
||||
|
||||
// While we are at it, let's also clear the account_id and
|
||||
// test_account_id if present
|
||||
|
||||
// Those used to be stored by save_stripe_keys but should not have
|
||||
// been since they were not used by anyone
|
||||
|
||||
unset( $options[ 'account_id' ] );
|
||||
unset( $options[ 'test_account_id' ] );
|
||||
|
||||
update_option( self::SETTINGS_OPTION, $options );
|
||||
}
|
||||
|
||||
private function get_default_stripe_config() {
|
||||
if ( ! class_exists( 'WC_Gateway_Stripe' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$gateway = new WC_Gateway_Stripe();
|
||||
foreach ( $gateway->form_fields as $key => $value ) {
|
||||
if ( isset( $value['default'] ) ) {
|
||||
$result[ $key ] = $value['default'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,904 @@
|
||||
<?php
|
||||
|
||||
class WC_Connect_TaxJar_Integration {
|
||||
|
||||
/**
|
||||
* @var WC_Connect_API_Client
|
||||
*/
|
||||
public $api_client;
|
||||
|
||||
/**
|
||||
* @var WC_Connect_Logger
|
||||
*/
|
||||
public $logger;
|
||||
|
||||
private $expected_options = array(
|
||||
// If automated taxes are enabled and user disables taxes we re-enable them
|
||||
'woocommerce_calc_taxes' => 'yes',
|
||||
// Users can set either billing or shipping address for tax rates but not shop
|
||||
'woocommerce_tax_based_on' => 'shipping',
|
||||
// Rate calculations assume tax not included
|
||||
'woocommerce_prices_include_tax' => 'no',
|
||||
// Use no special handling on shipping taxes, our API handles that
|
||||
'woocommerce_shipping_tax_class' => '',
|
||||
// API handles rounding precision
|
||||
'woocommerce_tax_round_at_subtotal' => 'no',
|
||||
// Rates are calculated in the cart assuming tax not included
|
||||
'woocommerce_tax_display_shop' => 'excl',
|
||||
// TaxJar returns one total amount, not line item amounts
|
||||
'woocommerce_tax_display_cart' => 'excl',
|
||||
// TaxJar returns one total amount, not line item amounts
|
||||
'woocommerce_tax_total_display' => 'single',
|
||||
);
|
||||
|
||||
const PROXY_PATH = 'taxjar/v2';
|
||||
const OPTION_NAME = 'wc_connect_taxes_enabled';
|
||||
const SETUP_WIZARD_OPTION_NAME = 'woocommerce_setup_automated_taxes';
|
||||
|
||||
public function __construct(
|
||||
WC_Connect_API_Client $api_client,
|
||||
WC_Connect_Logger $logger
|
||||
) {
|
||||
$this->api_client = $api_client;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function init() {
|
||||
// Only enable WCS TaxJar integration if the official TaxJar plugin isn't active.
|
||||
if ( class_exists( 'WC_Taxjar' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$store_settings = $this->get_store_settings();
|
||||
$store_country = $store_settings['store_country_setting'];
|
||||
|
||||
// TaxJar supports USA, Canada, Australia, and the European Union
|
||||
if ( ! $this->is_supported_country( $store_country ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add toggle for automated taxes to the core settings page
|
||||
add_filter( 'woocommerce_tax_settings', array( $this, 'add_tax_settings' ) );
|
||||
|
||||
// Settings values filter to handle the hardcoded settings
|
||||
add_filter( 'woocommerce_admin_settings_sanitize_option', array( $this, 'sanitize_tax_option' ), 10, 2 );
|
||||
|
||||
// Settings Page
|
||||
add_action( 'woocommerce_sections_tax', array( $this, 'output_sections_before' ), 9 );
|
||||
add_action( 'woocommerce_sections_tax', array( $this, 'output_sections_after' ), 11 );
|
||||
|
||||
// Bow out if we're not wanted
|
||||
if ( ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->configure_tax_settings();
|
||||
|
||||
// Calculate Taxes at Cart / Checkout
|
||||
if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
|
||||
add_action( 'woocommerce_after_calculate_totals', array( $this, 'maybe_calculate_totals' ), 20 );
|
||||
} else {
|
||||
add_action( 'woocommerce_calculate_totals', array( $this, 'maybe_calculate_totals' ), 20 );
|
||||
}
|
||||
|
||||
// Calculate Taxes for Backend Orders (Woo 2.6+)
|
||||
add_action( 'woocommerce_before_save_order_items', array( $this, 'calculate_backend_totals' ), 20 );
|
||||
|
||||
// Set customer taxable location for local pickup
|
||||
add_filter( 'woocommerce_customer_taxable_address', array( $this, 'append_base_address_to_customer_taxable_address' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Are automated taxes enabled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled() {
|
||||
// Migrate automated taxes selection from the setup wizard
|
||||
if ( get_option( self::SETUP_WIZARD_OPTION_NAME ) ) {
|
||||
update_option( self::OPTION_NAME, 'yes' );
|
||||
delete_option( self::SETUP_WIZARD_OPTION_NAME );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return ( wc_tax_enabled() && 'yes' === get_option( self::OPTION_NAME ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add our "automated taxes" setting to the core group.
|
||||
*
|
||||
* @param array $tax_settings WooCommerce Tax Settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_tax_settings( $tax_settings ) {
|
||||
$enabled = $this->is_enabled();
|
||||
|
||||
$automated_taxes = array(
|
||||
'title' => __( 'Automated taxes', 'woocommerce-services' ),
|
||||
'id' => self::OPTION_NAME, // TODO: save in `wc_connect_options`?
|
||||
'desc_tip' => __( 'Automate your sales tax calculations with WooCommerce Services, powered by Jetpack.', 'woocommerce-services' ),
|
||||
'desc' => $enabled ? '<p>' . __( 'Powered by WooCommerce Services ― Your tax rates and settings are automatically configured.', 'woocommerce-services' ) . '</p>' : '',
|
||||
'default' => 'no',
|
||||
'type' => 'select',
|
||||
'class' => 'wc-enhanced-select',
|
||||
'options' => array(
|
||||
'no' => __( 'Disable automated taxes', 'woocommerce-services' ),
|
||||
'yes' => __( 'Enable automated taxes', 'woocommerce-services' ),
|
||||
),
|
||||
);
|
||||
|
||||
// Insert the "automated taxes" setting at the top (under the section title)
|
||||
array_splice( $tax_settings, 1, 0, array( $automated_taxes ) );
|
||||
|
||||
if ( $enabled ) {
|
||||
// If the automated taxes are enabled, disable the settings that would be reverted in the original plugin
|
||||
foreach ( $tax_settings as $index => $tax_setting ) {
|
||||
if ( ! array_key_exists( $tax_setting['id'], $this->expected_options ) ) {
|
||||
continue;
|
||||
}
|
||||
$tax_settings[$index]['custom_attributes'] = array( 'disabled' => true );
|
||||
}
|
||||
}
|
||||
|
||||
return $tax_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* When automated taxes are enabled, overwrite core tax settings that might break the API integration
|
||||
* This is similar to the original plugin functionality where these options were reverted on page load
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L66-L91
|
||||
*
|
||||
* @param mixed $value - option value
|
||||
* @param array $option - option metadata
|
||||
* @return string new option value, based on the automated taxes state or $value
|
||||
*/
|
||||
public function sanitize_tax_option( $value, $option ) {
|
||||
if (
|
||||
//skip unrecognized option format
|
||||
! is_array( $option )
|
||||
//skip if unexpected option format
|
||||
|| ! isset( $option['id'] )
|
||||
//skip if not enabled or not being enabled in the current request
|
||||
|| ! $this->is_enabled() && ( ! isset( $_POST[self::OPTION_NAME] ) || 'yes' != $_POST[self::OPTION_NAME] ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
//the option is currently being enabled - backup the rates and flush the rates table
|
||||
if ( ! $this->is_enabled() && self::OPTION_NAME === $option['id'] && 'yes' === $value ) {
|
||||
$this->backup_existing_tax_rates();
|
||||
return $value;
|
||||
}
|
||||
|
||||
//skip if unexpected option
|
||||
if ( ! array_key_exists( $option['id'], $this->expected_options ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $this->expected_options[ $option['id'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite WooCommerce core tax settings if they are different than expected
|
||||
*
|
||||
* Ported from TaxJar's plugin and modified to support $this->expected_options
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L66-L91
|
||||
*/
|
||||
public function configure_tax_settings() {
|
||||
foreach( $this->expected_options as $option => $value ) {
|
||||
//first check the option value - with default memory caching this should help to avoid unnecessary DB operations
|
||||
if ( get_option( $option ) !== $value ) {
|
||||
update_option( $option, $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to hide the tax sections for additional tax class rate tables.
|
||||
*/
|
||||
public function output_sections_before() {
|
||||
if ( ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div style="display: none">
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to hide the tax sections for additional tax class rate tables.
|
||||
*/
|
||||
public function output_sections_after() {
|
||||
if ( ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
?></div><?php
|
||||
}
|
||||
|
||||
/**
|
||||
* TaxJar supports USA, Canada, Australia, and the European Union
|
||||
* See: https://developers.taxjar.com/api/reference/#countries
|
||||
*
|
||||
* @return array Countries supported by TaxJar.
|
||||
*/
|
||||
public function get_supported_countries() {
|
||||
$supported_countries = array_merge(
|
||||
array(
|
||||
'US',
|
||||
'CA',
|
||||
'AU',
|
||||
),
|
||||
WC()->countries->get_european_union_countries()
|
||||
);
|
||||
|
||||
return $supported_countries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given country is supported by TaxJar.
|
||||
*
|
||||
* @param $country Two character country code.
|
||||
*
|
||||
* @return bool Whether or not the country is supported by TaxJar.
|
||||
*/
|
||||
public function is_supported_country( $country ) {
|
||||
return in_array( $country, $this->get_supported_countries() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the store's location settings.
|
||||
*
|
||||
* Modified version of TaxJar's plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L796
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_store_settings() {
|
||||
$store_settings = array(
|
||||
'taxjar_zip_code_setting' => WC()->countries->get_base_postcode(),
|
||||
'store_state_setting' => WC()->countries->get_base_state(),
|
||||
'store_country_setting' => WC()->countries->get_base_country(),
|
||||
'taxjar_city_setting' => WC()->countries->get_base_city(),
|
||||
);
|
||||
|
||||
return $store_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $message
|
||||
*/
|
||||
public function _log( $message ) {
|
||||
$formatted_message = is_scalar( $message ) ? $message : json_encode( $message );
|
||||
|
||||
$this->logger->log( $formatted_message, 'WCS Tax' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $message
|
||||
*/
|
||||
public function _error( $message ) {
|
||||
$formatted_message = is_scalar( $message ) ? $message : json_encode( $message );
|
||||
|
||||
$this->logger->error( $formatted_message, 'WCS Tax' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to avoid calling calculate_totals() for admin carts.
|
||||
*
|
||||
* @param $wc_cart_object
|
||||
*/
|
||||
public function maybe_calculate_totals( $wc_cart_object ) {
|
||||
if ( ! WC_Connect_Functions::should_send_cart_api_request() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->calculate_totals( $wc_cart_object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate tax / totals using TaxJar at checkout
|
||||
*
|
||||
* Unchanged from the TaxJar plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L475
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function calculate_totals( $wc_cart_object ) {
|
||||
global $woocommerce;
|
||||
|
||||
// Skip calculations for WC Subscription recurring totals, tax rate already available
|
||||
if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
|
||||
if ( 'recurring_total' == WC_Subscriptions_Cart::get_calculation_type() ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all of the required customer params
|
||||
$taxable_address = $woocommerce->customer->get_taxable_address(); // returns unassociated array
|
||||
$taxable_address = is_array( $taxable_address ) ? $taxable_address : array();
|
||||
|
||||
$to_country = isset( $taxable_address[0] ) && ! empty( $taxable_address[0] ) ? $taxable_address[0] : false;
|
||||
$to_state = isset( $taxable_address[1] ) && ! empty( $taxable_address[1] ) ? $taxable_address[1] : false;
|
||||
$to_zip = isset( $taxable_address[2] ) && ! empty( $taxable_address[2] ) ? $taxable_address[2] : false;
|
||||
$to_city = isset( $taxable_address[3] ) && ! empty( $taxable_address[3] ) ? $taxable_address[3] : false;
|
||||
$line_items = array();
|
||||
$cart_taxes = array();
|
||||
|
||||
foreach ( $wc_cart_object->coupons as $coupon ) {
|
||||
if ( method_exists( $coupon, 'get_id' ) ) { // Woo 3.0+
|
||||
$limit_usage_qty = get_post_meta( $coupon->get_id(), 'limit_usage_to_x_items', true );
|
||||
|
||||
if ( $limit_usage_qty ) {
|
||||
$coupon->set_limit_usage_to_x_items( $limit_usage_qty );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
|
||||
$product = $cart_item['data'];
|
||||
$id = $product->get_id();
|
||||
$quantity = $cart_item['quantity'];
|
||||
$unit_price = wc_format_decimal( $product->get_price() );
|
||||
$line_subtotal = wc_format_decimal( $cart_item['line_subtotal'] );
|
||||
$discount = wc_format_decimal( $cart_item['line_subtotal'] - $cart_item['line_total'] );
|
||||
$tax_class = explode( '-', $product->get_tax_class() );
|
||||
$tax_code = '';
|
||||
|
||||
if ( ! $product->is_taxable() ) {
|
||||
$tax_code = '99999';
|
||||
}
|
||||
|
||||
if ( isset( $tax_class ) && is_numeric( end( $tax_class ) ) ) {
|
||||
$tax_code = end( $tax_class );
|
||||
}
|
||||
|
||||
// Get WC Subscription sign-up fees for calculations
|
||||
if ( class_exists( 'WC_Subscriptions_Cart' ) ) {
|
||||
if ( 'none' == WC_Subscriptions_Cart::get_calculation_type() ) {
|
||||
if ( class_exists( 'WC_Subscriptions_Synchroniser' ) ) {
|
||||
WC_Subscriptions_Synchroniser::maybe_set_free_trial();
|
||||
}
|
||||
$unit_price = WC_Subscriptions_Cart::set_subscription_prices_for_calculation( $unit_price, $product );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $unit_price && $line_subtotal ) {
|
||||
array_push($line_items, array(
|
||||
'id' => $id . '-' . $cart_item_key,
|
||||
'quantity' => $quantity,
|
||||
'product_tax_code' => $tax_code,
|
||||
'unit_price' => $unit_price,
|
||||
'discount' => $discount,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$this->calculate_tax( array(
|
||||
'to_city' => $to_city,
|
||||
'to_state' => $to_state,
|
||||
'to_country' => $to_country,
|
||||
'to_zip' => $to_zip,
|
||||
'shipping_amount' => $woocommerce->shipping->shipping_total,
|
||||
'line_items' => $line_items,
|
||||
) );
|
||||
|
||||
if ( class_exists( 'WC_Cart_Totals' ) ) { // Woo 3.2+
|
||||
do_action( 'woocommerce_cart_reset', $wc_cart_object, false );
|
||||
do_action( 'woocommerce_before_calculate_totals', $wc_cart_object );
|
||||
new WC_Cart_Totals( $wc_cart_object );
|
||||
}
|
||||
|
||||
foreach ( $this->line_items as $line_item_key => $line_item ) {
|
||||
if ( isset( $cart_taxes[ $this->rate_ids[ $line_item_key ] ] ) ) {
|
||||
$cart_taxes[ $this->rate_ids[ $line_item_key ] ] += $line_item->tax_collectable;
|
||||
} else {
|
||||
$cart_taxes[ $this->rate_ids[ $line_item_key ] ] = $line_item->tax_collectable;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the rate ID and the amount on the cart's totals
|
||||
$wc_cart_object->tax_total = $this->item_collectable;
|
||||
$wc_cart_object->shipping_tax_total = $this->shipping_collectable;
|
||||
$wc_cart_object->taxes = $cart_taxes;
|
||||
|
||||
if ( isset( $this->rate_ids['shipping'] ) ) {
|
||||
$wc_cart_object->shipping_taxes = array(
|
||||
$this->rate_ids['shipping'] => $this->shipping_collectable,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $wc_cart_object->get_cart() as $cart_item_key => $cart_item ) {
|
||||
$product = $cart_item['data'];
|
||||
$line_item_key = $product->get_id() . '-' . $cart_item_key;
|
||||
|
||||
if ( isset( $this->line_items[ $line_item_key ] ) ) {
|
||||
$wc_cart_object->cart_contents[ $cart_item_key ]['line_tax'] = $this->line_items[ $line_item_key ]->tax_collectable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate tax / totals using TaxJar for backend orders
|
||||
*
|
||||
* Unchanged from the TaxJar plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L569
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function calculate_backend_totals( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
$to_country = isset( $_POST['country'] ) ? strtoupper( wc_clean( $_POST['country'] ) ) : false;
|
||||
$to_state = isset( $_POST['state'] ) ? strtoupper( wc_clean( $_POST['state'] ) ) : false;
|
||||
$to_zip = isset( $_POST['postcode'] ) ? strtoupper( wc_clean( $_POST['postcode'] ) ) : false;
|
||||
$to_city = isset( $_POST['city'] ) ? strtoupper( wc_clean( $_POST['city'] ) ) : false;
|
||||
$line_items = array();
|
||||
|
||||
if ( method_exists( $order, 'get_shipping_total' ) ) {
|
||||
$shipping = $order->get_shipping_total(); // Woo 3.0+
|
||||
} else {
|
||||
$shipping = $order->get_total_shipping(); // Woo 2.6
|
||||
}
|
||||
|
||||
foreach ( $order->get_items() as $item_key => $item ) {
|
||||
if ( is_object( $item ) ) { // Woo 3.0+
|
||||
$id = $item->get_product_id();
|
||||
$quantity = $item->get_quantity();
|
||||
$discount = wc_format_decimal( $item->get_subtotal() - $item->get_total() );
|
||||
$tax_class = explode( '-', $item->get_tax_class() );
|
||||
} else { // Woo 2.6
|
||||
$id = $item['product_id'];
|
||||
$quantity = $item['qty'];
|
||||
$discount = wc_format_decimal( $item['line_subtotal'] - $item['line_total'] );
|
||||
$tax_class = explode( '-', $item['tax_class'] );
|
||||
}
|
||||
|
||||
$product = wc_get_product( $id );
|
||||
$unit_price = $product->get_price();
|
||||
$tax_code = '';
|
||||
|
||||
if ( ! $product->is_taxable() ) {
|
||||
$tax_code = '99999';
|
||||
}
|
||||
|
||||
if ( isset( $tax_class[1] ) && is_numeric( $tax_class[1] ) ) {
|
||||
$tax_code = $tax_class[1];
|
||||
}
|
||||
|
||||
if ( $unit_price ) {
|
||||
array_push($line_items, array(
|
||||
'id' => $id . '-' . $item_key,
|
||||
'quantity' => $quantity,
|
||||
'product_tax_code' => $tax_code,
|
||||
'unit_price' => $unit_price,
|
||||
'discount' => $discount,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$this->calculate_tax( array(
|
||||
'to_city' => $to_city,
|
||||
'to_state' => $to_state,
|
||||
'to_country' => $to_country,
|
||||
'to_zip' => $to_zip,
|
||||
'shipping_amount' => $shipping,
|
||||
'line_items' => $line_items,
|
||||
) );
|
||||
|
||||
// Add tax rates manually for Woo 3.0+
|
||||
// Woo 2.6 adds the rates automatically
|
||||
foreach ( $order->get_items() as $item_key => $item ) {
|
||||
if ( is_object( $item ) ) { // Woo 3.0+
|
||||
$product_id = $item->get_product_id();
|
||||
}
|
||||
|
||||
$line_item_key = $product_id . '-' . $item_key;
|
||||
|
||||
if ( isset( $this->rate_ids[ $line_item_key ] ) ) {
|
||||
$rate_id = $this->rate_ids[ $line_item_key ];
|
||||
|
||||
if ( class_exists( 'WC_Order_Item_Tax' ) ) { // Woo 3.0+
|
||||
$item_tax = new WC_Order_Item_Tax();
|
||||
$item_tax->set_rate( $rate_id );
|
||||
$item_tax->set_order_id( $order_id );
|
||||
$item_tax->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set customer zip code and state to store if local shipping option set
|
||||
*
|
||||
* Unchanged from the TaxJar plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c587/includes/class-wc-taxjar-integration.php#L653
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function append_base_address_to_customer_taxable_address( $address ) {
|
||||
$store_settings = $this->get_store_settings();
|
||||
$tax_based_on = '';
|
||||
|
||||
list( $country, $state, $postcode, $city ) = $address;
|
||||
|
||||
// See WC_Customer get_taxable_address()
|
||||
// wc_get_chosen_shipping_method_ids() available since Woo 2.6.2+
|
||||
if ( function_exists( 'wc_get_chosen_shipping_method_ids' ) ) {
|
||||
if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( wc_get_chosen_shipping_method_ids(), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
|
||||
$tax_based_on = 'base';
|
||||
}
|
||||
} else {
|
||||
if ( true === apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( WC()->session->get( 'chosen_shipping_methods', array() ), apply_filters( 'woocommerce_local_pickup_methods', array( 'legacy_local_pickup', 'local_pickup' ) ) ) ) > 0 ) {
|
||||
$tax_based_on = 'base';
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'base' == $tax_based_on ) {
|
||||
$postcode = $store_settings['taxjar_zip_code_setting'];
|
||||
$city = strtoupper( $store_settings['taxjar_city_setting'] );
|
||||
}
|
||||
|
||||
return array( $country, $state, $postcode, $city );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate sales tax using SmartCalcs
|
||||
*
|
||||
* Direct from the TaxJar plugin, without Nexus check.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L256
|
||||
*
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function calculate_tax( $options = array() ) {
|
||||
global $woocommerce;
|
||||
|
||||
$this->_log( ':::: TaxJar Plugin requested ::::' );
|
||||
|
||||
// Process $options array and turn them into variables
|
||||
$options = is_array( $options ) ? $options : array();
|
||||
|
||||
extract( array_replace_recursive(array(
|
||||
'to_country' => null,
|
||||
'to_state' => null,
|
||||
'to_zip' => null,
|
||||
'to_city' => null,
|
||||
'shipping_amount' => null, // $woocommerce->shipping->shipping_total
|
||||
'line_items' => null
|
||||
), $options) );
|
||||
|
||||
// Initalize some variables & properties
|
||||
$store_settings = $this->get_store_settings();
|
||||
$customer = $woocommerce->customer;
|
||||
|
||||
$this->tax_rate = 0;
|
||||
$this->amount_to_collect = 0;
|
||||
$this->item_collectable = 0;
|
||||
$this->shipping_collectable = 0;
|
||||
$this->freight_taxable = 1;
|
||||
$this->line_items = array();
|
||||
$this->has_nexus = 0;
|
||||
$this->rate_ids = array();
|
||||
|
||||
// Strict conditions to be met before API call can be conducted
|
||||
if ( empty( $to_country ) || empty( $to_zip ) || $customer->is_vat_exempt() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup Vars for API call
|
||||
$to_zip = explode( ',' , $to_zip );
|
||||
$to_zip = array_shift( $to_zip );
|
||||
|
||||
$from_country = $store_settings['store_country_setting'];
|
||||
$from_state = $store_settings['store_state_setting'];
|
||||
$from_zip = $store_settings['taxjar_zip_code_setting'];
|
||||
$from_city = $store_settings['taxjar_city_setting'];
|
||||
$shipping_amount = is_null( $shipping_amount ) ? 0.0 : $shipping_amount;
|
||||
|
||||
$this->_log( ':::: TaxJar API called ::::' );
|
||||
|
||||
$body = array(
|
||||
'from_country' => $from_country,
|
||||
'from_state' => $from_state,
|
||||
'from_city' => $from_city,
|
||||
'from_zip' => $from_zip,
|
||||
'to_country' => $to_country,
|
||||
'to_state' => $to_state,
|
||||
'to_city' => $to_city,
|
||||
'to_zip' => $to_zip,
|
||||
'shipping' => $shipping_amount,
|
||||
'line_items' => $line_items,
|
||||
'plugin' => 'woo',
|
||||
);
|
||||
|
||||
$response = $this->smartcalcs_cache_request( wp_json_encode( $body ) );
|
||||
|
||||
if ( isset( $response ) ) {
|
||||
// Log the response
|
||||
$this->_log( 'Received: ' . $response['body'] );
|
||||
|
||||
// Decode Response
|
||||
$taxjar_response = json_decode( $response['body'] );
|
||||
$taxjar_response = $taxjar_response->tax;
|
||||
|
||||
// Update Properties based on Response
|
||||
$this->has_nexus = (int) $taxjar_response->has_nexus;
|
||||
$this->amount_to_collect = $taxjar_response->amount_to_collect;
|
||||
$this->tax_rate = $taxjar_response->rate;
|
||||
$this->freight_taxable = (int) $taxjar_response->freight_taxable;
|
||||
|
||||
if ( ! empty( $taxjar_response->breakdown ) ) {
|
||||
if ( ! empty( $taxjar_response->breakdown->shipping ) ) {
|
||||
$this->shipping_collectable = $taxjar_response->breakdown->shipping->tax_collectable;
|
||||
}
|
||||
|
||||
if ( ! empty( $taxjar_response->breakdown->line_items ) ) {
|
||||
$line_items = array();
|
||||
foreach ( $taxjar_response->breakdown->line_items as $line_item ) {
|
||||
$line_items[ $line_item->id ] = $line_item;
|
||||
}
|
||||
$this->line_items = $line_items;
|
||||
}
|
||||
}
|
||||
|
||||
$this->item_collectable = $this->amount_to_collect - $this->shipping_collectable;
|
||||
}
|
||||
|
||||
// Remove taxes if they are set somehow and customer is exempt
|
||||
if ( $customer->is_vat_exempt() ) {
|
||||
$wc_cart_object->remove_taxes();
|
||||
} elseif ( $this->has_nexus ) {
|
||||
// Use Woo core to find matching rates for taxable address
|
||||
$location = array(
|
||||
'to_country' => $to_country,
|
||||
'to_state' => $to_state,
|
||||
'to_zip' => $to_zip,
|
||||
'to_city' => $to_city,
|
||||
);
|
||||
|
||||
// Add line item tax rates
|
||||
foreach ( $this->line_items as $line_item_key => $line_item ) {
|
||||
$line_item_key_split = explode( '-', $line_item_key );
|
||||
$product_id = $line_item_key_split[0];
|
||||
$product = wc_get_product( $product_id );
|
||||
$tax_class = $product->get_tax_class();
|
||||
$this->create_or_update_tax_rate( $line_item_key, $location, $line_item->combined_tax_rate * 100, $tax_class );
|
||||
}
|
||||
|
||||
// Add shipping tax rate
|
||||
$this->create_or_update_tax_rate( 'shipping', $location, $this->tax_rate * 100 );
|
||||
} // End if().
|
||||
} // End calculate_tax().
|
||||
|
||||
/**
|
||||
* Add or update a native WooCommerce tax rate
|
||||
*
|
||||
* Unchanged from the TaxJar plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/9d8e725/includes/class-wc-taxjar-integration.php#L396
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function create_or_update_tax_rate( $line_item_key, $location, $rate, $tax_class = '' ) {
|
||||
$tax_rate = array(
|
||||
'tax_rate_country' => $location['to_country'],
|
||||
'tax_rate_state' => $location['to_state'],
|
||||
'tax_rate_name' => sprintf( "%s Tax", $location['to_state'] ),
|
||||
'tax_rate_priority' => 1,
|
||||
'tax_rate_compound' => false,
|
||||
'tax_rate_shipping' => $this->freight_taxable,
|
||||
'tax_rate' => $rate,
|
||||
'tax_rate_class' => $tax_class,
|
||||
);
|
||||
|
||||
$wc_rate = WC_Tax::find_rates( array(
|
||||
'country' => $location['to_country'],
|
||||
'state' => $location['to_state'],
|
||||
'postcode' => $location['to_zip'],
|
||||
'city' => $location['to_city'],
|
||||
'tax_class' => $tax_class,
|
||||
) );
|
||||
|
||||
if ( ! empty( $wc_rate ) ) {
|
||||
$this->_log( ':: Tax Rate Found ::' );
|
||||
$this->_log( $wc_rate );
|
||||
|
||||
// Get the existing ID
|
||||
$rate_id = key( $wc_rate );
|
||||
|
||||
// Update Tax Rates with TaxJar rates ( rates might be coming from a cached taxjar rate )
|
||||
$this->_log( ':: Updating Tax Rate To ::' );
|
||||
$this->_log( $tax_rate );
|
||||
|
||||
WC_Tax::_update_tax_rate( $rate_id, $tax_rate );
|
||||
} else {
|
||||
// Insert a rate if we did not find one
|
||||
$this->_log( ':: Adding New Tax Rate ::' );
|
||||
$rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
|
||||
WC_Tax::_update_tax_rate_postcodes( $rate_id, wc_clean( $location['to_zip'] ) );
|
||||
WC_Tax::_update_tax_rate_cities( $rate_id, wc_clean( $location['to_city'] ) );
|
||||
}
|
||||
|
||||
$this->_log( 'Tax Rate ID Set for ' . $line_item_key . ': ' . $rate_id );
|
||||
$this->rate_ids[ $line_item_key ] = $rate_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap SmartCalcs API requests in a transient-based caching layer.
|
||||
*
|
||||
* Modified from TaxJar's plugin (removed use of TLC Transients)
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L463
|
||||
*
|
||||
* @param $json
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function smartcalcs_cache_request( $json ) {
|
||||
$cache_key = 'wcs_tax_' . hash( 'md5', $json );
|
||||
$response = get_transient( $cache_key );
|
||||
|
||||
if ( false === $response ) {
|
||||
$response = $this->smartcalcs_request( $json );
|
||||
|
||||
if ( 200 == wp_remote_retrieve_response_code( $response ) ) {
|
||||
set_transient( $cache_key, $response, HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a TaxJar SmartCalcs API request through the WCS proxy.
|
||||
*
|
||||
* Modified from TaxJar's plugin.
|
||||
* See: https://github.com/taxjar/taxjar-woocommerce-plugin/blob/82bf7c58/includes/class-wc-taxjar-integration.php#L440
|
||||
*
|
||||
* @param $json
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function smartcalcs_request( $json ) {
|
||||
$path = trailingslashit( self::PROXY_PATH ) . 'taxes';
|
||||
|
||||
$this->_log( 'Requesting: ' . $path . ' - ' . $json );
|
||||
|
||||
$response = $this->api_client->proxy_request( $path, array(
|
||||
'method' => 'POST',
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
'body' => $json,
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->_error( 'Error retrieving the tax rates. Received (' . $response->get_error_code() . '): ' . $response->get_error_message() );
|
||||
} elseif ( 200 == $response['response']['code'] ) {
|
||||
return $response;
|
||||
} else {
|
||||
$this->_error( 'Error retrieving the tax rates. Received (' . $response['response']['code'] . '): ' . $response['body'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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;
|
||||
|
||||
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_contents();
|
||||
ob_end_clean();
|
||||
$upload_dir = wp_upload_dir();
|
||||
file_put_contents( $upload_dir['basedir'] . '/taxjar-wc_tax_rates-' . date( 'm-d-Y' ) . '-' . time() . '.csv', $csv );
|
||||
|
||||
// Delete all tax rates
|
||||
$wpdb->query( 'TRUNCATE ' . $wpdb->prefix . 'woocommerce_tax_rates' );
|
||||
$wpdb->query( 'TRUNCATE ' . $wpdb->prefix . 'woocommerce_tax_rate_locations' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?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;
|
||||
|
||||
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() ) {
|
||||
if ( ! function_exists( 'jetpack_tracks_record_event' ) ) {
|
||||
$this->debug( 'Error. jetpack_tracks_record_event is not defined.' );
|
||||
return;
|
||||
}
|
||||
|
||||
$user = wp_get_current_user();
|
||||
$site_url = get_option( 'siteurl' );
|
||||
|
||||
$wcs_version = WC_Connect_Loader::get_wcs_version();
|
||||
|
||||
// Check for WooCommerce
|
||||
$wc_version = 'unavailable';
|
||||
if ( function_exists( 'WC' ) ) {
|
||||
$wc_version = WC()->version;
|
||||
}
|
||||
|
||||
// Check for Jetpack
|
||||
$jp_version = 'unavailable';
|
||||
if ( defined( 'JETPACK__VERSION' ) ) {
|
||||
$jp_version = JETPACK__VERSION;
|
||||
}
|
||||
$is_atomic = WC_Connect_Jetpack::is_atomic_site();
|
||||
|
||||
$jetpack_blog_id = -1;
|
||||
if ( class_exists( 'Jetpack_Options' ) && method_exists( 'Jetpack_Options', 'get_option' ) ) {
|
||||
$jetpack_blog_id = Jetpack_Options::get_option( 'id' );
|
||||
}
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
$data = array();
|
||||
}
|
||||
|
||||
$data['_via_ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
|
||||
$data['_via_ip'] = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
|
||||
$data['_lg'] = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
|
||||
$data['blog_url'] = $site_url;
|
||||
$data['blog_id'] = $jetpack_blog_id;
|
||||
$data['wcs_version'] = $wcs_version;
|
||||
$data['jetpack_version'] = $jp_version;
|
||||
$data['is_atomic'] = $is_atomic;
|
||||
$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 );
|
||||
jetpack_tracks_record_event( $user, $event_type, $data );
|
||||
}
|
||||
|
||||
protected function debug( $message ) {
|
||||
if ( ! is_null( $this->logger ) ) {
|
||||
$this->logger->log( $message );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
// Always get a fresh list of payment methods when hitting this endpoint
|
||||
$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' );
|
||||
}
|
||||
|
||||
$master_user = WC_Connect_Jetpack::get_master_user();
|
||||
if ( is_a( $master_user, 'WP_User' ) ) {
|
||||
$connected_data = WC_Connect_Jetpack::get_connected_user_data( $master_user->ID );
|
||||
$email = $connected_data['email'];
|
||||
} else {
|
||||
$email = '';
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array(
|
||||
'success' => true,
|
||||
'storeOptions' => $this->settings_store->get_store_options(),
|
||||
'formData' => $this->settings_store->get_account_settings(),
|
||||
'formMeta' => array(
|
||||
'can_manage_payments' => $this->can_user_manage_payment_methods(),
|
||||
'can_edit_settings' => true,
|
||||
'master_user_name' => is_a( $master_user, 'WP_User' ) ? $master_user->display_name : '',
|
||||
'master_user_login' => is_a( $master_user, 'WP_User' ) ? $master_user->user_login : '',
|
||||
'master_user_email' => $email,
|
||||
'payment_methods' => $this->payment_methods_store->get_payment_methods(),
|
||||
'warnings' => array( 'payment_methods' => $payment_methods_warning ),
|
||||
),
|
||||
), 200 );
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$settings = $request->get_json_params();
|
||||
|
||||
if ( ! $this->can_user_manage_payment_methods() ) {
|
||||
// Ignore the user-provided payment method ID if he doesn'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 );
|
||||
}
|
||||
|
||||
private function can_user_manage_payment_methods() {
|
||||
global $current_user;
|
||||
$master_user = WC_Connect_Jetpack::get_master_user();
|
||||
return WC_Connect_Jetpack::is_development_mode() ||
|
||||
( is_a( $master_user, 'WP_User' ) && $current_user->ID === $master_user->ID );
|
||||
}
|
||||
}
|
||||
@@ -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' ];
|
||||
$name = $address[ 'name' ];
|
||||
$company = $address[ 'company' ];
|
||||
$phone = $address[ 'phone' ];
|
||||
|
||||
unset( $address[ 'name' ], $address[ 'company' ], $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,
|
||||
);
|
||||
}
|
||||
|
||||
$response->normalized->name = $name;
|
||||
$response->normalized->company = $company;
|
||||
$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 current_user_can( 'manage_woocommerce' ); // Only an admin can normalize the origin address
|
||||
}
|
||||
|
||||
return true; // non-authenticated service for the 'destination' address
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the requester's permissions
|
||||
*/
|
||||
public function check_permission( $request ) {
|
||||
return current_user_can( 'manage_woocommerce' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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_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 $service_schemas_store ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->service_schemas_store = $service_schemas_store;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
return new WP_REST_Response( array(
|
||||
'success' => true,
|
||||
'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()
|
||||
)
|
||||
), 200 );
|
||||
}
|
||||
|
||||
public function post( $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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?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_merge(
|
||||
array( 'status' => 400 ),
|
||||
$validation_result->get_error_data()
|
||||
)
|
||||
);
|
||||
$this->logger->log( $error, __CLASS__ );
|
||||
return $error;
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array( 'success' => true ), 200 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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;
|
||||
|
||||
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 ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->shipping_label = $shipping_label;
|
||||
}
|
||||
|
||||
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[ 'payment_method_id' ] = $this->settings_store->get_selected_payment_method_id();
|
||||
$settings[ 'order_id' ] = $order_id;
|
||||
|
||||
$service_names = array();
|
||||
foreach ( $settings[ 'packages' ] as $index => $package ) {
|
||||
$service_names[] = $package[ 'service_name' ];
|
||||
unset( $package[ 'service_name' ] );
|
||||
$settings[ 'packages' ][ $index ] = $package;
|
||||
}
|
||||
|
||||
$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,
|
||||
);
|
||||
|
||||
$package = $settings[ 'packages' ][ $index ];
|
||||
$box_id = $package[ 'box_id' ];
|
||||
if ( 'individual' === $box_id ) {
|
||||
$label_meta[ 'package_name' ] = __( 'Individual packaging', 'woocommerce-services' );
|
||||
} else if ( isset( $package_lookup[ $box_id ] ) ) {
|
||||
$label_meta[ 'package_name' ] = $package_lookup[ $box_id ][ 'name' ];
|
||||
} else {
|
||||
$label_meta[ 'package_name' ] = __( 'Unknown package', 'woocommerce-services' );
|
||||
}
|
||||
|
||||
$product_names = array();
|
||||
foreach ( $package[ 'products' ] as $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
if ( $product ) {
|
||||
$product_names[] = $product->get_title();
|
||||
} else {
|
||||
$order = wc_get_order( $order_id );
|
||||
$product_names[] = WC_Connect_Compatibility::instance()->get_product_name_from_order( $product_id, $order );
|
||||
}
|
||||
}
|
||||
|
||||
$label_meta[ 'product_names' ] = $product_names;
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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' ];
|
||||
die();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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' ];
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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_id>\d+)';
|
||||
|
||||
public function get( $request ) {
|
||||
$response = $this->api_client->get_label_status( $request[ '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 );
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'label' => $label,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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';
|
||||
|
||||
/**
|
||||
*
|
||||
* @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();
|
||||
$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' ] );
|
||||
|
||||
$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 array(
|
||||
'success' => true,
|
||||
'rates' => property_exists( $response, 'rates' ) ? $response->rates : new stdClass(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Stripe_Account_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Stripe_Account_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/stripe/account';
|
||||
private $stripe;
|
||||
|
||||
public function __construct( WC_Connect_Stripe $stripe, WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function get( $request ) {
|
||||
$response = $this->stripe->get_account_details();
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
|
||||
return new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array(
|
||||
'status' => 400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'account_id' => $response->accountId,
|
||||
'display_name' => $response->displayName,
|
||||
'email' => $response->email,
|
||||
'business_logo' => $response->businessLogo,
|
||||
'legal_entity' => array(
|
||||
'first_name' => $response->legalEntity->firstName,
|
||||
'last_name' => $response->legalEntity->lastName
|
||||
),
|
||||
'payouts_enabled' => $response->payoutsEnabled
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
$response = $this->stripe->create_account( $data['email'], $data['country'] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
|
||||
return new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array(
|
||||
'status' => 400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'account_id' => $response->accountId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Stripe_Deauthorize_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Stripe_Deauthorize_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/stripe/account/deauthorize';
|
||||
private $stripe;
|
||||
|
||||
public function __construct( WC_Connect_Stripe $stripe, WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$response = $this->stripe->deauthorize_account();
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
|
||||
return new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array(
|
||||
'status' => 400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'account_id' => $response->accountId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Stripe_Oauth_Connect_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Stripe_Oauth_Connect_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/stripe/oauth/connect';
|
||||
private $stripe;
|
||||
|
||||
public function __construct( WC_Connect_Stripe $stripe, WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
$response = $this->stripe->connect_oauth( $data['state'], $data['code'] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
|
||||
return new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array(
|
||||
'status' => 400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'account_id' => $response->accountId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( class_exists( 'WC_REST_Connect_Stripe_Oauth_Init_Controller' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
class WC_REST_Connect_Stripe_Oauth_Init_Controller extends WC_REST_Connect_Base_Controller {
|
||||
protected $rest_base = 'connect/stripe/oauth/init';
|
||||
private $stripe;
|
||||
|
||||
public function __construct( WC_Connect_Stripe $stripe, WC_Connect_API_Client $api_client, WC_Connect_Service_Settings_Store $settings_store, WC_Connect_Logger $logger ) {
|
||||
parent::__construct( $api_client, $settings_store, $logger );
|
||||
$this->stripe = $stripe;
|
||||
}
|
||||
|
||||
public function post( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$response = $this->stripe->get_oauth_url( $data['returnUrl'] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->logger->log( $response, __CLASS__ );
|
||||
|
||||
return new WP_Error(
|
||||
$response->get_error_code(),
|
||||
$response->get_error_message(),
|
||||
array(
|
||||
'status' => 400
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'oauthUrl' => $response,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
!function(e){function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}var t={};n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=1679)}({1679:function(e,n,t){e.exports=t(761)},207:function(e,n){e.exports=jQuery},761:function(e,n,t){"use strict";var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=t(207);(0,function(e){return e&&e.__esModule?e:{default:e}}(i).default)(document).ready(function(e){function n(t,i){function r(){u.pointer(p).pointer("open"),c.dim&&(u.css("z-index",9999),e("body").append('<div id="wcs-pointer-page-dimmer" class="wcs-pointer-page-dimmer"></div>'),e("#wcs-pointer-page-dimmer").fadeIn(500))}if(Array.isArray(t)&&t[i]){var c=t[i];if("string"!=typeof c.target||"string"!=typeof c.id||"object"!==o(c.options)||"string"!=typeof c.options.content)return void n(t,i+1);var u=e(c.target),p=e.extend(c.options,{close:function(){e.post(ajaxurl,{pointer:c.id,action:"dismiss-wp-pointer"}),c.dim&&e("#wcs-pointer-page-dimmer").fadeOut(500),n(t,i+1)}});if(c.delayed_opening){var a=e(c.delayed_opening.delegation_container||document);a.one("click",c.delayed_opening.show_button,function(){c.delayed_opening.animating_container?setTimeout(function(){e(c.delayed_opening.animating_container).promise().then(r)},0):r()}),c.delayed_opening.hide_button&&a.one("click",c.delayed_opening.hide_button,function(){u.pointer("close")})}else r()}}n(wcServicesAdminPointers,0)})}});
|
||||
@@ -0,0 +1 @@
|
||||
.notice.wcs-nux__notice{display:flex;padding:0;border:0}.notice.wcs-nux__notice .wcs-nux__notice-logo{display:flex;flex-direction:column;justify-content:center;margin-left:1em;padding:1.5em 2em}.notice.wcs-nux__notice .wcs-nux__notice-logo .wcs-nux__notice-logo-jetpack{width:135px;margin:-10px 0 .75em 21px}.notice.wcs-nux__notice .wcs-nux__notice-logo .wcs-nux__notice-logo-graphic{width:220px;height:121px}.notice.wcs-nux__notice .wcs-nux__notice-content{display:flex;flex-direction:column;justify-content:center;padding:1em 1em 2.5em;margin-left:1em;color:#52768f}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-title{color:#4f748e;font-size:1.3em;font-weight:600}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-tos{font-size:.8em;color:#7591a5;margin:0 0 1.5em;padding:0}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-tos a{color:#49a8ce}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-button{align-self:flex-start;background-color:#00aadc;border-color:#0087be;box-shadow:0 1px 0 #0087be;color:#fff;font-size:1.1em;height:auto;padding:.45em 1.5em;text-shadow:none}.notice.wcs-nux__notice .wcs-nux__notice-content-text{max-width:90%;margin:0 0 1em;line-height:1.6;padding:0}@media (max-width:1000px){.notice.wcs-nux__notice{flex-direction:column}.notice.wcs-nux__notice .wcs-nux__notice-content{margin:0 1em}.notice.wcs-nux__notice .wcs-nux__notice-logo{display:none}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-tos{margin:1.5em 0 .5em;max-width:90%}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-text{margin:1em 0 0;max-width:100%}.notice.wcs-nux__notice .wcs-nux__notice-content .wcs-nux__notice-content-button{-ms-grid-row-align:auto;align-self:auto;margin:1.5em 0 0;text-align:center}.notice.wcs-nux__notice .wcs-nux__notice-jetpack{flex-direction:row;padding:.5em}.notice.wcs-nux__notice .wcs-nux__notice-jetpack img{height:2em;width:2em;margin:0 1em 0 0}.notice.wcs-nux__notice .wcs-nux__notice-jetpack .wcs-nux__notice-jetpack-text{margin:0}}
|
||||
@@ -0,0 +1 @@
|
||||
!function(n){function t(r){if(e[r])return e[r].exports;var c=e[r]={i:r,l:!1,exports:{}};return n[r].call(c.exports,c,c.exports,t),c.l=!0,c.exports}var e={};t.m=n,t.c=e,t.i=function(n){return n},t.d=function(n,e,r){t.o(n,e)||Object.defineProperty(n,e,{configurable:!1,enumerable:!0,get:r})},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=1680)}({1342:function(n,t){},1680:function(n,t,e){n.exports=e(762)},207:function(n,t){n.exports=jQuery},762:function(n,t,e){"use strict";var r=e(207),c=function(n){return n&&n.__esModule?n:{default:n}}(r);e(1342),(0,c.default)(document).ready(function(n){n(".woocommerce-services__connect-jetpack").one("click",function(t){function e(){return"installed"===wcs_nux_notice.initial_install_status||"uninstalled"===wcs_nux_notice.initial_install_status?n.when().then(function(){return c.html(wcs_nux_notice.translations.activating),n.post(ajaxurl,{action:"woocommerce_services_activate_jetpack",_ajax_nonce:wcs_nux_notice.nonce})}).then(function(t){if("success"!==t)return n.Deferred().reject(t)}):n.Deferred().resolve()}function r(){return n.when().then(function(){return c.html(wcs_nux_notice.translations.connecting),n.post(ajaxurl,{action:"woocommerce_services_get_jetpack_connect_url",_ajax_nonce:wcs_nux_notice.nonce,redirect_url:wcs_nux_notice.redirect_url})}).then(function(n){window.location.href=n})}t.preventDefault();var c=n(this);c.addClass("disabled"),function(){return"uninstalled"===wcs_nux_notice.initial_install_status?n.when().then(function(){return c.html(wp.updates.l10n.installing),wp.updates.installPlugin({slug:"jetpack"})}):n.Deferred().resolve()}().then(e).then(r).fail(function(t){var e=t;t||(e=wcs_nux_notice.translations.defaultError),t&&t.install&&"plugin"===t.install&&(e=wcs_nux_notice.translations.installError),n("<p/>",{class:"woocommerce-services__jetpack-install-error-message",text:e}).insertAfter(c),c.remove()})})})}});
|
||||
1
backend/wordpress/wp-content/plugins/woocommerce-services/dist/woocommerce-services.css
vendored
Normal file
1
backend/wordpress/wp-content/plugins/woocommerce-services/dist/woocommerce-services.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/wordpress/wp-content/plugins/woocommerce-services/dist/woocommerce-services.js
vendored
Normal file
1
backend/wordpress/wp-content/plugins/woocommerce-services/dist/woocommerce-services.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/* THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. */
|
||||
$i18nStrings = array(
|
||||
__( "less than a minute ago", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "a minute ago", "%(interval)d minutes ago", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "an hour ago", "%(interval)d hours ago", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "a day ago", "%(interval)d days ago", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "a month ago", "%(interval)d months ago", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "a year ago", "%(interval)d years ago", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unexpected server error.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unexpected server error.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Select one...", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Okay", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Create new label", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add a credit card", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "To purchase shipping labels, you will first need to add a credit card.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Select a credit card", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "To purchase shipping labels, you will first need to select a credit card.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Manage cards", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Labels will be purchased using card ending: {{strong}}%(cardDigits)s.{{/strong}}", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "N/A", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Cancel", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Print", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Paper size", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "NOTE: If you already used the label in a package, printing and using it again is a violation of our terms of service and may result in criminal charges.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "If there was a printing error when you purchased the label, you can print it again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Reprint shipping label", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Cancel", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Refund label (-%(amount)s)", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Amount eligible for refund", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Purchase date", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "You can request a refund for a shipping label that has not been used to ship a package. It will take at least 14 days to process.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Request a refund", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Paper size", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Select one...", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Choose rate: %(pckg)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Choose rate", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "The service and rate chosen by the customer at checkout is not available. Please choose another.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Rates", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Total rate: %(currencySymbol)s%(total)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "%(serviceName)s: %(currencySymbol)s%(rate).2f", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unsaved changes made to packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Total Weight", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "There are no items in this package.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Items to Ship", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Please select a package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Shipping Package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Custom Packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Item Dimensions", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Individually Shipped Item", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add items", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Cancel", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Move", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Ship in original packaging", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add to a New Package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Where would you like to move it?", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Move item", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "{{itemLink/}} is currently in {{pckg/}}.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "{{itemLink/}} is currently shipped in its original packaging.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "{{itemLink/}} is currently saved for a later shipment.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Packages to be Shipped", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Move", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Use these packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "There are no packages or items associated with this order", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "There are no packages configured. The items have been packed individually. You can add or enable packages using the {{a}}Packaging Manager{{/a}}.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "%(itemsCount)d items in %(packageCount)d packages: %(weight)f %(unit)s total", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "%(itemsCount)d items in 1 package: %(weight)f %(unit)s total", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "1 item in 1 package: %(weight)f %(unit)s total", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Weight not entered", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Please select a package type", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "No packages selected", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Close", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Which items would you like to add to {{pckg/}}?", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add item", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "%(item)s from {{pckg/}}", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Destination address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Origin address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Create shipping labels", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Create shipping label", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Cancel", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Buy & Print", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Buy", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Buy & Print (%(currencySymbol)s%(ratesTotal)s)", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Buy (%(currencySymbol)s%(ratesTotal)s)", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Purchasing...", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Print", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Use selected address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Suggested address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Edit address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Address entered", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "We have slightly modified the address entered. If correct, please use the suggested address to ensure accurate delivery.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Invalid address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Validating address...", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Use this address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Country", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Postal code", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "State", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "City", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Address", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Phone", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Company", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Name", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Tracking #: {{trackingLink/}}", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "{{labelDetails/}} purchased {{purchasedAt/}}", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Label #%(labelNum)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Reprint", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Refund rejected", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Refunded on %(date)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Checking refund status", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Refund pending", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Request refund", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Increase the rates calculated by the carrier to account for packaging and handling costs. You can also add a negative amount to save your customers money.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Price adjustment", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Service", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Expand Services", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "%(numSelected)d service selected", "%(numSelected)d services selected", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "All services selected", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add and edit saved packages using the {{a}}Packaging Manager{{/a}}.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Saved Packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Now add your zip code and choose which services you want to offer your customers.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Print", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Paper size", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "You can run a test print for shipping labels by selecting the paper size, then print.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Having trouble configuring your printer?", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Print", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unable to get your settings. Please refresh the page to try again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Predefined packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add a package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Custom packages", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Save changes", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unable to save your packages. Please try again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Your packages have been saved.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Expand Services", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "%(selectedCount)d package selected", "%(selectedCount)d packages selected", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "All packages selected", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Untitled", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "(L x W x H)", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Dimensions", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Name", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Type", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Your packages will display here once they are added.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Type of package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Defines both the weight of the empty box and the max weight it can hold", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Max weight", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Max weight", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Weight of box", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Package weight", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Outer Dimensions (L x W x H) %(dimensionUnit)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Inner Dimensions (L x W x H) %(dimensionUnit)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "This field must be unique", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "The customer will see this during checkout", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Package name", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add a package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Edit package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "This field is required", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Invalid value", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "View exterior dimensions", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Cancel", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Apply changes", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unable to get your settings. Please refresh the page to try again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Save changes", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Unable to save your shipping label settings. Please try again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Your shipping label settings have been saved.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_x( "Expires %(date)s", "date is of the form MM/YY", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "****%(digits)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "PayPal", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "VISA", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "MasterCard", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Discover", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "American Express", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Add another credit card", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "To purchase shipping labels, use your credit card on file or add a new one.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Credit card", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Paper size", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "You have unsaved changes.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "TEST LABEL 2", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "TEST LABEL 1", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "There was a problem saving your settings. Please try again after the page is reloaded.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "More", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Expand", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "There was a problem with one or more entries. Please fix the errors below and try saving again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "There was a problem with one or more entries. Please fix the errors below and try saving again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Your changes have been saved.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Dismiss", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Package %d", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Package %d: %s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Envelope %d", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Envelope %d: %s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Error while loading the settings. Please refresh the page to try again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Save Settings", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Saving…", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Letter", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Legal", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Label (4\"x6\")", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "A4", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "This field is required", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Please choose a rate", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "We couldn't get a rate for this package, please try again.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Invalid weight", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Please select a package", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "This field is required", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Invalid ZIP code format", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Invalid phone number for %(country)s", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "This field is required", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "This address is not recognized. Please try another.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "Required", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "The refund request has been sent successfully.", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
_n( "Your shipping label was purchased successfully", "Your %(count)d shipping labels were purchased successfully", 1, "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
__( "PACKAGE %(num)d (OF %(total)d)", "woocommerce-services" ), // dist/woocommerce-services.js:1
|
||||
);
|
||||
/* THIS IS THE END OF THE GENERATED FILE */
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="cashier" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1436 777.5" style="enable-background:new 0 0 1436 777.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#C8D7E2;}
|
||||
.st2{fill:#E9EFF4;}
|
||||
.st3{fill:#74DCFC;}
|
||||
.st4{fill:#0084C0;}
|
||||
.st5{fill:#004F84;}
|
||||
</style>
|
||||
<title>cashier</title>
|
||||
<path class="st0" d="M892.3,91.9c4.4,0,8.4-2.1,11-5.6c1.2-1.7,2-3.6,2.3-5.7c0.3-2-0.1-4-0.2-6.5l-2.4-28.3
|
||||
c-0.3-4.7-0.5-9.4-0.6-14.2c-0.3-4.4,0.3-10.4-0.7-12.4c-1-2.9-3.3-5.1-6.2-6.1c-0.7-0.2-1.4-0.4-2.2-0.5c-0.4-0.1-0.8,0-1.1-0.1
|
||||
h-1.7h-7.1L770,13.2c-2.8,0-5.5,1.2-7.2,3.4c-0.9,1.1-1.5,2.3-1.8,3.7c-0.4,1.2-0.2,2.8-0.3,5.4l-0.1,28.4l-0.1,14.2
|
||||
c0.1,4.4-0.4,10.5,0.4,12.3c0.9,2.8,3.1,5.1,5.9,6.1c0.7,0.3,1.4,0.4,2.1,0.5c0.4,0.1,0.7,0,1.1,0.1h1.7h7.1
|
||||
c6.8,0.1,13.7,0.1,20.5,0.1c0-0.1,0.1-0.2,0.1-0.3c1.4,0,2.7,0,4.1,0l0,0.3c10.7,0,21.3,0.2,32,0.8
|
||||
C854.5,89.6,873.4,91.9,892.3,91.9L892.3,91.9z"/>
|
||||
<path class="st0" d="M853.7,96.8c-6,0.5-12.1,1-18.1,1.4c-10.7,0.6-21.3,0.7-32,0.8l-0.1,27.5h52.2
|
||||
C854.8,116.3,854.2,106.5,853.7,96.8z"/>
|
||||
<path class="st1" d="M913.5,20.1c-0.1-1.6-0.5-3.2-1-4.8c-2.1-6.2-6.9-11.1-13.1-13.3c-1.5-0.6-3.1-0.9-4.7-1.1
|
||||
c-0.8-0.2-1.6-0.2-2.4-0.2h-1.8h-7.1L769.9-0.1c-6.8,0.1-13.2,3.2-17.5,8.5c-2.1,2.6-3.6,5.7-4.3,9c-0.8,3.4-0.5,6.7-0.5,8.7
|
||||
l0.3,28.3l0.1,14.2l0.1,7.1c0,1.3,0,2.6,0.1,3.9c0.2,1.7,0.5,3.3,1.1,4.9c2.2,6.3,7.1,11.2,13.4,13.4c1.5,0.5,3.1,0.9,4.8,1.1
|
||||
c0.8,0.2,1.6,0.2,2.4,0.2h1.8h7.1c4.9-0.1,9.8-0.1,14.7-0.1l0.1,27.5h4.9h4.9l0.1-27.5c10.7,0,21.3-0.2,32-0.8
|
||||
c6-0.4,12.1-0.9,18.1-1.4c0.5,9.7,1.1,19.5,2,29.8h2.1h2.3c0.7-8.7,1.3-17.6,1.7-26.8c0.1-1.2,0.1-2.4,0.1-3.7
|
||||
c10.1-0.8,20.3-1.5,30.4-1.4c5.3-0.1,10.2-2.4,13.6-6.4c1.7-2,3-4.4,3.8-6.9c0.8-2.6,0.8-5.4,1-7.6l2.2-28.4
|
||||
c0.3-4.7,0.5-9.5,0.5-14.2l0.1-7.1C913.6,22.7,913.6,21.4,913.5,20.1z M835.7,88.3c-10.7-0.6-21.3-0.7-32-0.8l0-0.3
|
||||
c-1.4,0-2.7,0-4.1,0c0,0.1-0.1,0.2-0.1,0.3c-6.8,0-13.7,0-20.5-0.1h-7.1h-1.7c-0.4-0.1-0.8,0-1.1-0.1c-0.7-0.1-1.4-0.3-2.1-0.5
|
||||
c-2.8-1-5-3.3-5.9-6.1c-0.9-1.8-0.3-7.9-0.4-12.3l0.1-14.2l0.1-28.4c0-2.7-0.1-4.2,0.3-5.4c0.3-1.4,0.9-2.6,1.8-3.7
|
||||
c1.8-2.2,4.4-3.4,7.2-3.4l113.4-0.8h7.1h1.7c0.4,0.1,0.8,0,1.1,0.1c0.7,0.1,1.5,0.2,2.2,0.5c2.9,1,5.2,3.3,6.2,6.1
|
||||
c1,2,0.4,8,0.7,12.4c0.1,4.7,0.3,9.4,0.6,14.2l2.4,28.3c0.2,2.5,0.5,4.5,0.2,6.5c-0.3,2.1-1.1,4-2.3,5.7c-2.6,3.5-6.6,5.6-11,5.6v0
|
||||
C873.4,91.9,854.5,89.6,835.7,88.3z"/>
|
||||
<path class="st2" d="M1433,766c-111.9-2.4-223.8-3.5-335.7-4.2v4.7l-766.2,0v-4.8c-109.9,0.9-219.7,2-329.6,5.8l0,0.1
|
||||
c-0.8,0-1.5,0.7-1.5,1.5s0.7,1.5,1.5,1.5c119.3,4.1,238.6,5,357.9,6l357.9,0.9c238.6-0.9,477.2-0.4,715.8-5.5c1.7,0,3-1.3,3-3
|
||||
S1434.7,766,1433,766z"/>
|
||||
<path class="st3" d="M331.1,636.9C331.1,637,331.1,637,331.1,636.9L331.1,636.9L331.1,636.9z"/>
|
||||
<path class="st3" d="M1021.1,331.5l-6.4-126.4c-1.3-24.8-12.2-48.7-31.8-63.9c-11.8-9.2-25.2-14.6-39.9-14.6h-82.9h-2.3h-2.1h-52.2
|
||||
h-4.9h-4.9h-309c-19.9,0-35.2,8.2-48.4,23.5c-14.4,16.8-22.1,38.4-23.3,60.5l-5.9,126.2l-61.4,242.6h738.6L1021.1,331.5z
|
||||
M944.6,173.2c3.8,0,7,2.6,7.2,6l4.8,117.2c0.1,2.3-0.9,4.6-2.6,6.1c-1.9,1.7-4.3,2.6-6.9,2.6H629.3l0.1-12.7l-2.3-119.2H944.6z
|
||||
M478,292.3l5.3-119.1h92.5c3.8,0,7,2.6,7.2,6l-2.7,117.2c0.1,2.3-0.9,4.6-2.6,6.1c-1.9,1.7-4.3,2.6-6.9,2.6h-92.8L478,292.3z
|
||||
M545.8,533.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V533.9z M545.8,473.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V473.9z M545.8,413.9
|
||||
h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V413.9z M620.9,533.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V533.9z M620.9,473.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1h34.9V473.9z M620.9,413.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V413.9z M696,533.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1H696V533.9z M696,473.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1H696V473.9z M696,413.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1H696V413.9z M771.1,533.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V533.9z M771.1,473.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1h34.9V473.9z M771.1,413.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V413.9z M846.3,533.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1h34.9V533.9z M846.3,473.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V473.9z M846.3,413.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1h34.9V413.9z M921.4,533.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V533.9z M921.4,473.9h-38v-35.6
|
||||
c0-1.1,1.4-2.1,3.1-2.1h34.9V473.9z M921.4,413.9h-38v-35.6c0-1.1,1.4-2.1,3.1-2.1h34.9V413.9z M956.6,339.1
|
||||
c-39.9,6.1-79.8,5.6-119.7,6.1s-79.8,0.7-119.7,0.9c-39.9-0.2-79.8-0.3-119.7-0.9s-79.8,0-119.7-6.1v-3.8
|
||||
c39.9-6.1,79.8-5.6,119.7-6.1s79.8-0.7,119.7-0.9c39.9,0.2,79.8,0.3,119.7,0.9s79.8,0,119.7,6.1V339.1z"/>
|
||||
<path class="st4" d="M507.8,378.3v35.6h38v-37.7h-34.9C509.1,376.2,507.8,377.1,507.8,378.3z"/>
|
||||
<path class="st4" d="M582.9,378.3v35.6h38v-37.7H586C584.3,376.2,582.9,377.1,582.9,378.3z"/>
|
||||
<path class="st4" d="M658,378.3v35.6h38v-37.7h-34.9C659.4,376.2,658,377.1,658,378.3z"/>
|
||||
<path class="st4" d="M733.1,378.3v35.6h38v-37.7h-34.9C734.5,376.2,733.1,377.1,733.1,378.3z"/>
|
||||
<path class="st4" d="M808.3,378.3v35.6h38v-37.7h-34.9C809.7,376.2,808.3,377.1,808.3,378.3z"/>
|
||||
<path class="st4" d="M883.4,378.3v35.6h38v-37.7h-34.9C884.8,376.2,883.4,377.1,883.4,378.3z"/>
|
||||
<path class="st4" d="M507.8,438.3v35.6h38v-37.7h-34.9C509.1,436.2,507.8,437.1,507.8,438.3z"/>
|
||||
<path class="st4" d="M582.9,438.3v35.6h38v-37.7H586C584.3,436.2,582.9,437.1,582.9,438.3z"/>
|
||||
<path class="st4" d="M658,438.3v35.6h38v-37.7h-34.9C659.4,436.2,658,437.1,658,438.3z"/>
|
||||
<path class="st4" d="M733.1,438.3v35.6h38v-37.7h-34.9C734.5,436.2,733.1,437.1,733.1,438.3z"/>
|
||||
<path class="st4" d="M808.3,438.3v35.6h38v-37.7h-34.9C809.7,436.2,808.3,437.1,808.3,438.3z"/>
|
||||
<path class="st4" d="M883.4,438.3v35.6h38v-37.7h-34.9C884.8,436.2,883.4,437.1,883.4,438.3z"/>
|
||||
<path class="st4" d="M507.8,498.3v35.6h38v-37.7h-34.9C509.1,496.2,507.8,497.1,507.8,498.3z"/>
|
||||
<path class="st4" d="M582.9,498.3v35.6h38v-37.7H586C584.3,496.2,582.9,497.1,582.9,498.3z"/>
|
||||
<path class="st4" d="M658,498.3v35.6h38v-37.7h-34.9C659.4,496.2,658,497.1,658,498.3z"/>
|
||||
<path class="st4" d="M733.1,498.3v35.6h38v-37.7h-34.9C734.5,496.2,733.1,497.1,733.1,498.3z"/>
|
||||
<path class="st4" d="M808.3,498.3v35.6h38v-37.7h-34.9C809.7,496.2,808.3,497.1,808.3,498.3z"/>
|
||||
<path class="st4" d="M883.4,498.3v35.6h38v-37.7h-34.9C884.8,496.2,883.4,497.1,883.4,498.3z"/>
|
||||
<polygon class="st4" points="331.1,579.4 331.1,636.9 345.6,579.4 "/>
|
||||
<path class="st4" d="M1097.3,766.5v-4.7c-126.7-0.7-253.4-0.8-380.1-1.3l-357.9,0.9c-9.4,0.1-18.9,0.2-28.3,0.2v4.8L1097.3,766.5z"
|
||||
/>
|
||||
<path class="st4" d="M1084.2,579.4H345.6l-14.6,57.5v0.1v124.7c9.4-0.1,18.9-0.2,28.3-0.2l357.9-0.9c126.7,0.5,253.4,0.6,380.1,1.3
|
||||
V579.4H1084.2z M849.9,665.1c-0.2,8-0.5,16.1-0.8,24.1c-0.3,8-0.7,16-1.1,24c-0.1,1-1,1.8-2,1.7c-0.9-0.1-1.7-0.8-1.7-1.7
|
||||
c-0.4-8-0.8-16-1.1-24c-0.3-6.8-0.5-13.5-0.7-20.3c-20.8,0.5-41.6,0.9-62.3,0.9l-66,0.5l-66-0.5c-20.9-0.1-41.8-0.6-62.7-1.3
|
||||
c-0.3,6.9-0.6,13.7-1.1,20.6c-0.4,5.8-0.8,11.7-1.3,17.5c-0.1,0.5-0.5,0.9-1,0.8c-0.4,0-0.8-0.4-0.8-0.8c0,0,0,0,0,0l0-0.1
|
||||
c0,0,0,0,0,0c-0.5-5.8-0.9-11.7-1.3-17.5c-0.5-8-0.9-16-1.2-24.1c-0.1-1.8,1.3-3.3,3.1-3.4c0,0,0,0,0,0h0.3c22-0.8,44-1.4,66-1.4
|
||||
l66-0.5l66,0.5c22,0.1,44,0.5,66,1h0.2C848.3,661.4,849.9,663,849.9,665.1C849.9,665.1,849.9,665.1,849.9,665.1z"/>
|
||||
<path class="st4" d="M629.3,305h317.7c2.5,0,5-0.9,6.9-2.6c1.8-1.5,2.7-3.8,2.6-6.1l-4.8-117.2c-0.2-3.4-3.3-6-7.2-6H627.1
|
||||
l2.3,119.2L629.3,305z"/>
|
||||
<path class="st4" d="M577.5,302.4c1.8-1.5,2.7-3.8,2.6-6.1l2.7-117.2c-0.2-3.4-3.3-6-7.2-6h-92.5L478,292.3l-0.1,12.7h92.8
|
||||
C573.2,305,575.6,304.1,577.5,302.4z"/>
|
||||
<path class="st5" d="M717.2,328.4c-39.9,0.2-79.8,0.3-119.7,0.9s-79.8,0-119.7,6.1v3.8c39.9,6.1,79.8,5.5,119.7,6.1
|
||||
s79.8,0.7,119.7,0.9c39.9-0.2,79.8-0.3,119.7-0.9s79.8,0,119.7-6.1v-3.8c-39.9-6.1-79.8-5.5-119.7-6.1S757.1,328.6,717.2,328.4z"/>
|
||||
<path class="st1" d="M581.2,706.8C581.2,706.8,581.2,706.7,581.2,706.8C581.2,706.7,581.2,706.7,581.2,706.8L581.2,706.8z"/>
|
||||
<path class="st1" d="M581.2,706.7c0.1,0.4,0.4,0.8,0.8,0.8c0.5,0.1,1-0.3,1-0.8c0.5-5.8,0.9-11.7,1.3-17.5h-4.4
|
||||
C580.3,695,580.7,700.9,581.2,706.7z"/>
|
||||
<path class="st1" d="M844.2,713.2c0.1,0.9,0.8,1.7,1.7,1.7c1,0.1,1.9-0.7,2-1.7c0.4-8,0.8-16,1.1-24h-6
|
||||
C843.4,697.2,843.8,705.2,844.2,713.2z"/>
|
||||
<path class="st1" d="M849.9,665.1c0-2-1.6-3.7-3.6-3.7h-0.2c-22-0.5-44-0.9-66-1l-66-0.5l-66,0.5c-22,0.1-44,0.6-66,1.4h-0.3
|
||||
c0,0,0,0,0,0c-1.8,0.1-3.2,1.6-3.1,3.4c0.3,8,0.7,16,1.2,24.1h4.4c0.4-6.9,0.8-13.7,1.1-20.6c-1.1,0-2.2-0.1-3.3-0.1l3.4-3.4
|
||||
c0,1.2-0.1,2.3-0.2,3.5c20.9,0.7,41.8,1.2,62.7,1.3l66,0.5l66-0.5c20.8-0.1,41.6-0.5,62.3-0.9c0-1.3-0.1-2.6-0.1-3.8l3.8,3.8
|
||||
c-1.2,0-2.4,0.1-3.7,0.1c0.2,6.8,0.4,13.5,0.7,20.3h6C849.4,681.1,849.7,673.1,849.9,665.1C849.9,665.1,849.9,665.1,849.9,665.1z"/>
|
||||
<path class="st1" d="M842.3,665.1c0,1.3,0.1,2.6,0.1,3.8c1.2,0,2.4-0.1,3.7-0.1L842.3,665.1z"/>
|
||||
<path class="st1" d="M582.1,668.5c1.1,0,2.2,0.1,3.3,0.1c0.1-1.2,0.1-2.3,0.2-3.5L582.1,668.5z"/>
|
||||
<g>
|
||||
<path class="st1" d="M1058.5,57.8l-2.8-2.4c-8.4,0.5-11.8,5.2-16,9.3s-8.2,8.3-12,12.7s-7.3,9-10.7,13.8s-7.6,8.8-6.7,17.2l1.4,1.2
|
||||
l1.4,1.2c8.4-0.5,11.8-5.2,16-9.3s8.2-8.3,12-12.7s7.3-9,10.7-13.8S1059.3,66.1,1058.5,57.8z"/>
|
||||
<path class="st1" d="M1079.8,139c-4.4-5.1-7.1-3.8-10-3.6c-5.9,0.4-11.6,2-16.8,4.7c-2.7,1.3-5.6,1.6-6.8,8.2l0.5,1.8l0.5,1.8
|
||||
c4.4,5.1,7.1,3.8,10,3.6c5.9-0.4,11.6-2,16.8-4.7c2.7-1.3,5.6-1.6,6.8-8.2L1079.8,139z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M367.5,144.7c-4.9-1.3-9.8-2.5-14.8-3.4s-10-1.5-15-1.9s-9.9-1.7-15.8,2.5l-0.6,3.2c4.1,6,9.1,6.5,14,7.8
|
||||
s9.8,2.5,14.8,3.4s10,1.5,15,1.9s9.9,1.8,15.8-2.5l0.3-1.6l0.3-1.6C377.5,146.6,372.4,146.1,367.5,144.7z"/>
|
||||
<path class="st1" d="M403.3,93.8c-6.9-5.1-14.4-9.4-22.3-12.8c-3.9-1.7-7.4-4.2-13.8-1.9l-1.6,2.8c1.1,6.6,5.1,8.4,8.5,10.9
|
||||
c6.9,5.1,14.4,9.4,22.3,12.8c3.9,1.7,7.4,4.2,13.8,1.9l0.8-1.4l0.8-1.4C410.7,98.1,406.8,96.3,403.3,93.8z"/>
|
||||
<path class="st1" d="M445,87.4c-0.2-4.5-1.4-8.9-3.5-12.8c-1-2-1.2-4.2-6.8-5l-1.5,0.4l-1.6,0.4c-4.4,3.5-3.4,5.5-3.3,7.8
|
||||
c0.2,4.5,1.4,8.9,3.5,12.8c1,2,1.2,4.2,6.8,5l1.5-0.4l1.6-0.4C446.2,91.7,445.2,89.7,445,87.4z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,356 @@
|
||||
=== WooCommerce Services ===
|
||||
Contributors: automattic, woothemes, allendav, kellychoffman, jkudish, jeffstieler, nabsul, robobot3000, danreylop, mikeyarce, shaunkuschel, orangesareorange, pauldechov, dappermountain
|
||||
Tags: canada-post, shipping, stamps, usps, woocommerce, taxes, payment, stripe
|
||||
Requires at least: 4.6
|
||||
Tested up to: 4.9.5
|
||||
Stable tag: 1.14.1
|
||||
License: GPLv2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Hosted services for WooCommerce including automated tax calculation, live shipping rates, shipping label printing, and smoother payment setup.
|
||||
|
||||
== Description ==
|
||||
|
||||
WooCommerce Services makes basic eCommerce features like shipping more reliable by taking the burden off of your site’s infrastructure.
|
||||
|
||||
With WooCommerce Services, critical services are hosted on Automattic’s best-in-class infrastructure, rather than relying on your store’s hosting. That means your store will be more stable and faster.
|
||||
|
||||
To use the features, simply install this plugin and activate the ones you want directly in your dashboard. As we add more services, you’ll see more features available directly in WooCommerce - making setup simpler.
|
||||
|
||||
= Show live rates for USPS or Canada Post =
|
||||
Show your customers accurate shipping rates automatically for USPS, the largest delivery network in the US, or Canada Post.
|
||||
|
||||
|
||||
= Print shipping labels for USPS at a discounted rate =
|
||||
Give customers lower rates on their shipping. Create ready-to-print shipping labels for USPS directly in WooCommerce and take advantage of our special discount rate.
|
||||
|
||||
= Collect accurate taxes at checkout =
|
||||
We've got taxes for you - no need to enter tax rates manually.
|
||||
|
||||
= Be ready to accept payments instantly =
|
||||
Have a Stripe account created on your behalf or accept PayPal Express Checkout payments without having to setup an account.
|
||||
|
||||
== Installation ==
|
||||
|
||||
This section describes how to install the plugin and get it working.
|
||||
|
||||
1. Upload the plugin files to the `/wp-content/plugins/plugin-name` directory, or install the plugin through the WordPress plugins screen directly.
|
||||
1. Activate the plugin through the 'Plugins' screen in WordPress
|
||||
1. Install and activate WooCommerce if you haven't already done so
|
||||
1. Install, activate and connect Jetpack if you haven't already done so
|
||||
1. Add a USPS or Canada Post shipping method instance to any shipping zone
|
||||
1. Want to buy shipping labels? First, add your credit card to https://wordpress.com/me/purchases/billing and then print labels for orders right from the Edit Order page
|
||||
1. Enable automated taxes from WooCommerce > Settings > Tax (make sure "enable taxes" is checked in General settings first)
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= What services are included? =
|
||||
|
||||
* Real-time shipping rates from USPS or Canada Post
|
||||
* USPS label purchase/printing (domestic USA only)
|
||||
* Automated tax calculation
|
||||
* Stripe account provisioning (through WooCommerce setup wizard)
|
||||
* PayPal Express Checkout payment authorization
|
||||
|
||||
= Are Real-Time Rates in Checkout Free? =
|
||||
|
||||
Yes, real-time rates in checkout are totally free.
|
||||
|
||||
= Can I buy and print shipping labels for US domestic and international packages? =
|
||||
|
||||
You can buy and print USPS shipping labels for domestic packages only. International shipping is not yet supported.
|
||||
|
||||
= This works with WooCommerce, right? =
|
||||
|
||||
Yep! WooCommerce version 2.6 or newer, please.
|
||||
|
||||
= Why is a Jetpack Connection and WordPress.com account required? =
|
||||
|
||||
We use the Jetpack connection to authenticate each request and, if you use the shipping label service, to charge your credit card on file.
|
||||
|
||||
= Are there Terms of Service and data usage policies? =
|
||||
|
||||
Absolutely! You can read our Terms of Service [here](https://en.wordpress.com/tos) and our data policy [here](https://jetpack.com/support/what-data-does-jetpack-sync/).
|
||||
|
||||
= Where can I see the source code for this plugin? =
|
||||
|
||||
The source code is freely available [in GitHub](https://github.com/Automattic/woocommerce-services).
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Buying a USPS shipping label for an order
|
||||
2. Adding a shipping method
|
||||
3. Setting up real-time USPS rates
|
||||
4. Real-time rate display in checkout
|
||||
5. Setting up custom packages
|
||||
6. Selecting your preferred payment method
|
||||
7. Enabling automated taxes
|
||||
8. Creating a Stripe account from the setup wizard
|
||||
9. Checking on the health of WooCommerce Services
|
||||
10. Checking and exporting the label purchase reports
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.14.1 =
|
||||
* Fix error when adding shipping method price adjustments
|
||||
|
||||
= 1.14.0 =
|
||||
* GDPR - Added WCS section to the privacy policy guide
|
||||
* GDPR - Support for personal data export functionality
|
||||
* GDPR - Support for personal data erasure functionality
|
||||
* Minor changes to the settings page UI
|
||||
* Fix the PHP warning on the Status page when logs are empty
|
||||
* Fix log file retrieval on Status page to work with new WC 3.4 format that includes the date
|
||||
* Add error message on manual service data refresh failure
|
||||
|
||||
= 1.13.3 =
|
||||
* Fix broken admin after product featured in 'missing weight' notice is deleted
|
||||
|
||||
= 1.13.2 =
|
||||
* Fix PHP Warning when the server cannot be reached for shipping rates or products are missing dimensions
|
||||
|
||||
= 1.13.1 =
|
||||
* Fix PHP Warning for individually packed shipping rates
|
||||
|
||||
= 1.13.0 =
|
||||
* Show customer selected shipping rate when purchasing a shipping label
|
||||
* Add shipping labels to Reports
|
||||
* Add USPS signature requirement support to label purchase
|
||||
* Add link to view receipt for Shipping Labels
|
||||
* Fix bug showing incorrect shipping label rates when changing packages
|
||||
* Fix styling for purchasing shipping labels on mobile devices
|
||||
* Prevent incompatible settings for Automated Taxes
|
||||
* Fix duplicate "packages" section in Shipping Settings (Advanced Shipping Packages extension compatibility)
|
||||
* Add "copy to clipboard" button to debug logs on Status page
|
||||
* Improved error messaging
|
||||
* Fix unnecessary shipping rates requests made in the Dashboard
|
||||
* Fix PHP Warning when saving Tax settings
|
||||
* Add caching to Shipping Rate requests
|
||||
|
||||
= 1.12.3 =
|
||||
* Fixed PHP Fatal when PayPal Express Checkout has not fully initialized
|
||||
|
||||
= 1.12.2 =
|
||||
* Fix some REST API calls being erroneously cached by certain hosting providers
|
||||
|
||||
= 1.12.1 =
|
||||
* Fix missing file in 1.12.0 plugin release
|
||||
|
||||
= 1.12.0 =
|
||||
* Add email receipts for purchased shipping labels
|
||||
* Clean up Stripe account keys when deauthorized
|
||||
* Fix bug in database migration script for older plugin versions
|
||||
* Add "back to order" link when adding a credit card from order details
|
||||
* Add frontend debugging messages for shipping rates
|
||||
* Separate troubleshooting logs by feature (taxes, shipping, etc)
|
||||
* Avoid making unnecessary automated tax requests
|
||||
* Fix PHP Fatal bug in tax request error handling
|
||||
* Integrate with WooCommerce Shipment Tracking extension
|
||||
* Add Conditional Shipping and Payments compatibility
|
||||
|
||||
= 1.11.0 =
|
||||
* Fix bug with TOS acceptance on WordPress Multisite
|
||||
* Add PayPal Express Checkout payment authorization
|
||||
* No longer require phone number for label purchases
|
||||
* Fix bug with label print button on Firefox
|
||||
|
||||
= 1.10.1 =
|
||||
* Fix bug with product variation names in Packaging description
|
||||
|
||||
= 1.10.0 =
|
||||
* Add WooCommerce compatibility to plugin header
|
||||
* Add ability to refresh server schemas from status page
|
||||
* Fix tax calculations for subscription products
|
||||
* Fix "limit usage to X items" coupon tax calculation
|
||||
* Fix tax calculation for product bundles and add-ons
|
||||
* Make phone number optional for shipping labels
|
||||
* Only allow label printing for stores using USD
|
||||
* Add label printing for stores in Puerto Rico
|
||||
|
||||
= 1.9.1 =
|
||||
* Fix PHP Warning when Jetpack is disabled or missing
|
||||
* Fix plain permalinks support
|
||||
|
||||
= 1.9.0 =
|
||||
* Add tracking numbers to completed order emails
|
||||
* Add USPS support for Puerto Rico
|
||||
* Fix some tax bugs related to discount calculation and taxable address
|
||||
* Updated Calypso-based UI
|
||||
|
||||
= 1.8.3 =
|
||||
* Fix tax calculation in order total bug (WooCommerce 3.2+)
|
||||
|
||||
= 1.8.2 =
|
||||
* Fix crash in the WooCommerce setup wizard when picking a supported country but an unsupported currency
|
||||
|
||||
= 1.8.1 =
|
||||
* Fix label printing on iOS
|
||||
* Fix NUX banner images and connect button
|
||||
|
||||
= 1.8.0 =
|
||||
* Automatically configure live rates based on setup wizard choices
|
||||
* Add automated tax calculation
|
||||
* Add deferred Stripe account setup (if chosen in wizard)
|
||||
|
||||
= 1.7.1 =
|
||||
* Support plain permalinks setting
|
||||
* Fix PHP Fatal error when order contains a deleted product
|
||||
* Fix use of non-SSL URLs in plugin header
|
||||
* Fix some React console warnings
|
||||
* Add asynchronous label purchase flow (will be used for future performance gains)
|
||||
|
||||
= 1.7.0 =
|
||||
* Fix bug that allows accidental double click of label purchase button
|
||||
* More immediate display of purchased labels on order detail screen
|
||||
* Fix bug that showed settings screen links before Terms of Service are accepted
|
||||
* No longer override package weight when changing type if specified by user
|
||||
* Several small appearance tweaks (alignment, spacing)
|
||||
|
||||
= 1.6.2 =
|
||||
* Fix spacing bug in purchased label display
|
||||
* Fix bug with "create label" flow on WooCommerce 3.1.0
|
||||
|
||||
= 1.6.1 =
|
||||
* Better UX when selecting shipping label payment method
|
||||
|
||||
= 1.6.0 =
|
||||
* New streamlined onboarding process for plugin dependencies
|
||||
* Better packaging workflow for orders not using live rates at checkout
|
||||
* Improved discovery for label printing
|
||||
* Fix bug with test label printing on status page
|
||||
|
||||
= 1.5.0 =
|
||||
* Handle decoding errors retrieving older package data from orders
|
||||
* Code cleanup - React 16 prep, better i18n support
|
||||
* Fix PHP error when order has no packaging info
|
||||
* Updated shipping label settings to include paper size
|
||||
* Fix missing item bug with individually packaged products
|
||||
* Fix display bug in "saved for later" item list
|
||||
* Fix PHP error when using Jetpack in development mode
|
||||
|
||||
= 1.4.1 =
|
||||
* Fix deleted product bug in labels UI
|
||||
* Tweak Accept-Language header
|
||||
|
||||
= 1.4.0 =
|
||||
* WooCommerce 3.0 compatibility
|
||||
* Add test label print to system status page
|
||||
* Remove shipping label preview
|
||||
* Better handling of logged out users
|
||||
* Add contextual plugin link to WooCommerce.com support form
|
||||
* Fix bug when purchasing additional shipping labels without a page refresh
|
||||
* Better NUX flow when Jetpack missing or inactive
|
||||
* Temporarily remove label support for military addresses (requires customs form)
|
||||
* Fix bug where predefined USPS packages weren't being sent in requests
|
||||
|
||||
= 1.3.2 =
|
||||
* Hide destination address normalized order meta
|
||||
* Log rate retrieval failures as errors with admin notice
|
||||
|
||||
= 1.3.1 =
|
||||
* Fix compatibility bug with `mod_security`
|
||||
* Update shipping address on order when corrected by API
|
||||
|
||||
= 1.3.0 =
|
||||
* Fix label PDF viewing in IE, Microsoft Edge, and Firefox
|
||||
* Fix label modal width issue in Internet Explorer 11
|
||||
* Don't initially show red error highlights for missing label destination address
|
||||
* Labels: Fix escaped HTML showing in product variation names
|
||||
* Remove unused icon font, reducing CSS size
|
||||
* Fix tax rate calculation for shipping
|
||||
* Fix WP Super Cache label printing bug
|
||||
|
||||
= 1.2.0 =
|
||||
* Improve label printing flow when origin or destination addresses need modification
|
||||
* More descriptive text in pointer notice for setting up a WCS shipping method
|
||||
* Remove plugin options during uninstall
|
||||
* Prevent a label rates request when prerequisite data needs modification
|
||||
* Include more information when JSON parsing fails
|
||||
* Fix compatibility issues with Internet Explorer 11 and Microsoft Edge browsers
|
||||
* Add version to plugin script and style includes
|
||||
* Reduce size of CSS and JS files
|
||||
* Fix incompatibility with existing WooCommerce USPS extension
|
||||
|
||||
= 1.1.1 =
|
||||
* Update plugin support documentation link
|
||||
* Fix bug related to tracking Terms of Service acceptance
|
||||
|
||||
= 1.1.0 =
|
||||
* Fetch service schemas earlier - fix bug where no shipping methods displayed
|
||||
* Fix bug related to tracking Terms of Service acceptance too early
|
||||
* Display label number relative to order instead of ID
|
||||
* Clear shipping rates cache when shipping settings change
|
||||
* Ensure only the Jetpack connection owner can accept the Terms of Service
|
||||
* Better visiblity into which shipping services are selected
|
||||
* Add hint for first shipping method instance configuration
|
||||
|
||||
= 1.0.0 =
|
||||
* Improve logging for rates request errors
|
||||
* Fix a rates bug with individually packaged items
|
||||
* Add an easier UI to select grouped shipping services
|
||||
* Fix a database table prefix bug
|
||||
* Fix a bug allowing an errant double-purchase of labels
|
||||
* Always retrieve a refunded label's status
|
||||
* Save the shipping label origin address for reuse earlier
|
||||
* Add a fallback rate for USPS
|
||||
* Recommend checking logs when errors occur
|
||||
* Fix bug showing the wrong label purchase amount when requesting refunds
|
||||
* Add tooltip explaining what shipping service price adjustments are
|
||||
* Notify admins when products are missing dimensions when retrieving rates
|
||||
* Improve terms of service message
|
||||
* Improve user experience with form validation
|
||||
* Move the settings under WooCommerce > Shipping
|
||||
* Automatically enable flat rate packages when their corresponding service is enabled
|
||||
* Improve the on-boarding experience for new users
|
||||
* Show the card information being used to purchase labels
|
||||
* Don't allow the labels flow to be entered without a selected payment method
|
||||
|
||||
= 0.9.5 =
|
||||
* Add non-flat-rate predefined USPS packaging
|
||||
* Add enhanced labels metabox with tracking info, reprint button, and refunds
|
||||
* Allow payment methods to be managed in Jetpack "Developer Mode"
|
||||
* Add messaging in labels dialog when no packages are configured
|
||||
* Fix label preview display bug
|
||||
* Fix a bug with variable product dimensions
|
||||
|
||||
= 0.9.4 =
|
||||
* Fix a bug that caused a packages error in the labels UI when checkout used free shipping
|
||||
* Tweak to the refund request success message
|
||||
* Fix to avoid a WP_User error notice when editing payment methods
|
||||
* Fix a React version error that was happening when editing shipping settings
|
||||
* Only allow the primary Jetpack user to select payment method
|
||||
|
||||
= 0.9.3 =
|
||||
* Improvements for phone validation error messages
|
||||
* Increase the remote request timeout
|
||||
* Adds USPS flat rate packages to the packaging manager
|
||||
* Store the origin address when the label is purchased
|
||||
* Clean-up a few React errors in the console
|
||||
* Tweak the Jetpack status message
|
||||
* Auto-accept address normalizations in labels UI if they are trivial
|
||||
* Make sure notices appear on top of (not beneath) the labels UI
|
||||
* CSS clean-up
|
||||
* Fix an error where clicking outside the labels UI modal did not always close it
|
||||
* Fix incorrect service counts in shipping method instance group headers
|
||||
* Remove inappropriate target blank in certain anchors
|
||||
* Fix timestamp presentation for label refunds
|
||||
* Fix empty dimension handling and dimension casting
|
||||
|
||||
= 0.9.2 =
|
||||
* Fix a fatal error that could happen on activation if WooCommerce or Jetpack were not present
|
||||
* Send order shipping information with label rate requests to allow for pre-selection
|
||||
|
||||
= 0.9.1 =
|
||||
* Update contributors to include a few we missed
|
||||
* Add USPS flat-rate packaging support to the packaging manager
|
||||
* Improvements to label preview
|
||||
* Ensure self-help works even if connect is unable to download the service schemas
|
||||
* Switch to PDF based multi-page labels
|
||||
|
||||
= 0.9.0 =
|
||||
* Beta release
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 0.9.0 =
|
||||
Beta release
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user