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' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user