Skip to content

Instantly share code, notes, and snippets.

@raduconst
Last active November 30, 2020 14:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raduconst/4a155ac0c9ebc62318e75a56e9da5e1d to your computer and use it in GitHub Desktop.
Save raduconst/4a155ac0c9ebc62318e75a56e9da5e1d to your computer and use it in GitHub Desktop.
Printful gist
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
class Printful_Shipping extends WC_Shipping_Method
{
public $show_warnings = false;
public $calculate_tax = false;
public $override_defaults = true;
private $last_error = false;
const PRINTFUL_SHIPPING = 'printful_shipping';
//Store whether currently processed package contains Printful products (for WC<2.6)
private $printful_package = true;
public static function init() {
new self;
}
public function __construct() {
$this->id = 'printful_shipping';
$this->method_title = $this->title = 'Printful Shipping';
$this->method_description = 'Calculate live shipping rates based on actual Printful shipping costs.';
$this->init_form_fields();
$this->init_settings();
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( &$this, 'process_admin_options' ) );
$this->enabled = $this->get_option( 'enabled' );
$this->show_warnings = $this->get_option( 'show_warnings' ) == 'yes';
$this->override_defaults = $this->get_option( 'override_defaults' ) == 'yes';
//Initialize shipping methods for specific package (or no package)
add_filter( 'woocommerce_load_shipping_methods', array( $this, 'woocommerce_load_shipping_methods' ), 10000 );
//Remove other shipping methods for Printful package on WC < 2.6
add_filter( 'woocommerce_shipping_methods', array( $this, 'woocommerce_shipping_methods' ), 10000 );
add_filter( 'woocommerce_cart_shipping_packages', array( $this, 'woocommerce_cart_shipping_packages' ), 10000 );
}
/**
* Init fields for Printful Shipping form
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable this shipping method', 'woocommerce' ),
'default' => 'no',
),
'override_defaults' => array(
'title' => __( 'Disable Woocommerce rates', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Disable standard Woocommerce rates for products fulfilled by Printful', 'woocommerce' ),
'default' => 'yes',
),
'show_warnings' => array(
'title' => __( 'Show Printful warnings', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Display Printful status messages if rate API request fails', 'woocommerce' ),
'default' => 'yes',
),
);
if ( !Printful_Integration::instance()->get_option('printful_key') ) {
$this->form_fields['info'] = array(
'type' => 'title',
'description' => 'Please add Printful API key to the
<a href="' . admin_url( 'admin.php?page=printful-dashboard&tab=settings' ) . '">Printful Integration settings section</a>
to enable rate calculation.',
);
}
}
/**
* Enable only Printful shipping method for Printful packages
* @param array $package
*/
public function woocommerce_load_shipping_methods( $package = array() ) {
$this->printful_package = false;
if ( $package && ! empty( $package['printful'] ) ) {
if ( $this->enabled == 'yes' ) {
$this->printful_package = true;
if ( $this->override_defaults ) {
//Remove default methods if we process Printful package
WC()->shipping()->unregister_shipping_methods();
}
WC()->shipping()->register_shipping_method( $this );
}
} else if ( ! $package ) {
//Show Printful tab on Shipping rate settings
WC()->shipping()->register_shipping_method( $this );
}
}
/**
* Remove non-Printful methods for Printful packages on WC < 2.6
* @param $methods
*
* @return array
*/
public function woocommerce_shipping_methods( $methods ) {
if ( $this->override_defaults && $this->printful_package && version_compare( WC()->version, '2.6', '<' ) ) {
//For WC < 2.6 woocommerce_shipping_methods is executed after woocommerce_load_shipping_methods
//So we need to clean up unnecessary methods from there
return array();
}
return $methods;
}
/**
* Split Printul products to a separate package if there are any
* @param array $packages
*
* @return array
*/
public function woocommerce_cart_shipping_packages( $packages = array() ) {
//Printful rates are turned off, do not split products
if ( $this->enabled !== 'yes' ) {
return $packages;
}
$return_packages = array();
foreach ( $packages as $package ) {
$ids = array();
foreach ( $package['contents'] as $key => $item ) {
$ids[ $key ] = $item['variation_id'] ? $item['variation_id'] : $item['product_id'];
// WPML compatibility.
$lang = apply_filters( 'wpml_default_language', false );
if ( ! empty( $lang ) ) {
$post_type = $item['variation_id'] ? 'product_variation' : 'product';
$ids[ $key ] = apply_filters( 'wpml_object_id', $ids[ $key ], $post_type, true, $lang );
}
}
$printful_ids = array();
if ( $ids ) {
asort( $ids );
$values = implode( ',', array_unique( $ids ) );
$key = 'printful_productids_' . md5( $values );
$printful_ids = get_transient( $key );
if ( ! is_array( $printful_ids ) ) {
$printful_ids = array();
try {
$client = Printful_Integration::instance()->get_client();
$status = $client->get( 'sync/variants', array(
'external_ids' => $values,
) );
if ( ! empty( $status['sync_variants'] ) ) {
foreach ( $status['sync_variants'] as $variant ) {
if ( $variant['synced'] ) {
$printful_ids[] = $variant['external_id'];
}
}
}
set_transient( $key, $printful_ids, 1800 );
} catch ( PrintfulException $e ) {
$this->set_error( $e );
//Failed to get Printful status, return default packages
return $packages;
}
}
}
$new_contents = array(
'printful' => array(),
'virtual' => array(),
'woocommerce' => array(),
);
foreach ( $ids as $key => $external_id ) {
$item = $package['contents'][ $key ];
if ( in_array( $external_id, $printful_ids ) ) {
$new_contents['printful'][ $key ] = $item;
} else if ( $item['data']->is_virtual() || $item['data']->is_downloadable() ) {
$new_contents['virtual'][ $key ] = $item;
} else {
$new_contents['woocommerce'][ $key ] = $item;
}
}
//Put virtual products together with any other package
if ( $new_contents['virtual'] ) {
if ( $new_contents['printful'] && ! $new_contents['woocommerce'] ) {
$new_contents['printful'] += $new_contents['virtual'];
} else {
$new_contents['woocommerce'] += $new_contents['virtual'];
}
unset ( $new_contents['virtual'] );
}
foreach ( $new_contents as $key => $contents ) {
if ( $contents ) {
$new_package = $package;
$new_package['contents_cost'] = 0;
$new_package['contents'] = $contents;
foreach ( $contents as $item ) {
if ( $item['data']->needs_shipping() ) {
if ( isset( $item['line_total'] ) ) {
$new_package['contents_cost'] += $item['line_total'];
}
}
}
if ( $key == 'printful' ) {
$new_package['printful'] = true;
}
$return_packages[] = $new_package;
}
}
}
return $return_packages;
}
/**
* @param array $package
*
* @return bool
*/
public function calculate_shipping( $package = array() ) {
$request = array(
'recipient' => array(
'address1' => $package['destination']['address'],
'address2' => $package['destination']['address_2'],
'city' => $package['destination']['city'],
'state_code' => $package['destination']['state'],
'country_code' => $package['destination']['country'],
'zip' => $package['destination']['postcode'],
),
'items' => array(),
'currency' => get_option( 'woocommerce_currency' ),
);
if ( $request['recipient']['country_code'] == 'US' &&
( empty( $request['recipient']['zip'] ) ||
empty( $request['recipient']['state_code'] ) )
) {
return false;
}
foreach ( $package['contents'] as $item ) {
if ( ! empty( $item['data'] ) && ( $item['data']->is_virtual() || $item['data']->is_downloadable() ) ) {
continue;
}
// WPML compatibility.
$external_id = $item['variation_id'] ? $item['variation_id'] : $item['product_id'];
$lang = apply_filters( 'wpml_default_language', false );
if ( ! empty( $lang ) ) {
$post_type = $item['variation_id'] ? 'product_variation' : 'product';
$external_id = apply_filters( 'wpml_object_id', $external_id, $post_type, true, $lang );
}
$request['items'] [] = array(
'external_variant_id' => $external_id,
'quantity' => $item['quantity'],
'value' => $item['line_total'] / $item['quantity'],
);
}
if ( ! $request['items'] ) {
return false;
}
try {
$client = Printful_Integration::instance()->get_client();
} catch ( PrintfulException $e ) {
$this->set_error( $e );
return false;
}
try {
$key = 'printful_rates_' . md5( json_encode( $request ) );
$response = get_transient( $key );
if ( $response === false ) {
$response = $client->post( 'shipping/rates', $request, array(
'expedited' => true,
) );
//Cache locally, since WC < 2.6 had problems with caching rates form multiple packages internally
set_transient( $key, $response, 1800 );
}
foreach ( $response as $rate ) {
$rateData = array(
'id' => $this->id . '_' . $rate['id'],
'label' => $rate['name'],
'cost' => $rate['rate'],
'calc_tax' => 'per_order',
);
// Before 3.4.0 rate could be passed as ID, after it's set as method_id which refers to class ID
if ( version_compare( WC()->version, '3.4.0', '>=' ) ) {
$this->id = self::PRINTFUL_SHIPPING . '_' . $rate['id'];
}
$this->add_rate( $rateData );
// Reset class ID after adding rate so ID name does not stack as huge string in foreach
$this->id = self::PRINTFUL_SHIPPING;
}
} catch ( PrintfulException $e ) {
$this->set_error( $e );
return false;
}
return false;
}
/**
* @param $error
*/
private function set_error( $error ) {
if ( $this->show_warnings ) {
$this->last_error = $error;
add_filter( 'woocommerce_cart_no_shipping_available_html', array( $this, 'show_error' ) );
add_filter( 'woocommerce_no_shipping_available_html', array( $this, 'show_error' ) );
}
}
/**
* @param $data
*
* @return string
*/
public function show_error( $data ) {
$error = $this->last_error;
$message = $error->getMessage();
if ( $error instanceof PrintfulApiException && $error->getCode() == 401 ) {
$message = 'Invalid API key';
}
return '<p>ERROR: ' . htmlspecialchars( $message ) . '</p>';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment