1365 lines
47 KiB
PHP
1365 lines
47 KiB
PHP
<?php
|
|
|
|
defined('ABSPATH') or exit;
|
|
|
|
/**
|
|
* The main controller for Extended Coupon Features for WooCommerce.
|
|
*/
|
|
class WJECF_Controller
|
|
{
|
|
// Coupon message codes
|
|
// NOTE: I use prefix 79 for this plugin; there's no guarantee that other plugins don't use the same values!
|
|
public const E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET = 79100;
|
|
public const E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET = 79101;
|
|
public const E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET = 79102;
|
|
public const E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET = 79103;
|
|
public const E_WC_COUPON_SHIPPING_METHOD_NOT_MET = 79104;
|
|
public const E_WC_COUPON_PAYMENT_METHOD_NOT_MET = 79105;
|
|
public const E_WC_COUPON_NOT_FOR_THIS_USER = 79106;
|
|
public const E_WC_COUPON_FIRST_PURCHASE_ONLY = 79107;
|
|
public const E_WC_COUPON_SHIPPING_ZONE_NOT_MET = 79108;
|
|
protected static $_instance;
|
|
|
|
protected $plugins = [];
|
|
|
|
private $options;
|
|
private $_user_emails;
|
|
private $flexible_shipping_rates;
|
|
|
|
// Temporary storage
|
|
private $coupon_multiplier_values = [];
|
|
|
|
private $overwrite_coupon_message = []; // [ 'coupon_code' => [ msg_code => 'new_message' ] ]
|
|
|
|
private $inhibit_overwrite = 0;
|
|
|
|
// ============
|
|
|
|
private $_session_data;
|
|
|
|
/**
|
|
* The debugger.
|
|
*
|
|
* @var null|bool|WJECF_Debug False if disabled, null if not yet loaded otherwise the WJECF_Debug instance
|
|
*/
|
|
private $debugger;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->options = new WJECF_Options(
|
|
'wjecf_options',
|
|
[
|
|
'db_version' => 0, // integer
|
|
'debug_mode' => false, // true or false
|
|
'disabled_plugins' => [], // e.g. [ 'WJECF_AutoCoupon' ]
|
|
'autocoupon_allow_remove' => false,
|
|
],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Singleton Instance.
|
|
*
|
|
* @static
|
|
*
|
|
* @return Singleton Instance
|
|
*/
|
|
public static function instance()
|
|
{
|
|
if (is_null(self::$_instance)) {
|
|
self::$_instance = class_exists('WJECF_Pro_Controller') ? new WJECF_Pro_Controller() : new WJECF_Controller();
|
|
}
|
|
|
|
return self::$_instance;
|
|
}
|
|
|
|
public function start()
|
|
{
|
|
$this->init_plugins();
|
|
$this->init_hooks();
|
|
}
|
|
|
|
public function init_hooks()
|
|
{
|
|
// Frontend hooks
|
|
|
|
// assert_coupon_is_valid (which raises exception on invalid coupon) can only be used on WC 2.3.0 and up
|
|
add_filter('woocommerce_coupon_is_valid', [$this, 'assert_coupon_is_valid'], 10, 3); // Since WC3.2 WC_Discounts is passed as a 3rd argument
|
|
|
|
// Last check for coupons with restricted_emails (moved from WJECF_AutoCoupon since 2.5.6)
|
|
add_action('woocommerce_checkout_update_order_review', [$this, 'fetch_billing_email'], 10); // AJAX One page checkout
|
|
add_action('woocommerce_after_checkout_validation', [$this, 'fetch_billing_email'], 10); // Checkout posted
|
|
|
|
// Overwrite coupon info message
|
|
add_filter('woocommerce_coupon_message', [$this, 'filter_woocommerce_coupon_message'], 10, 3);
|
|
}
|
|
|
|
/**
|
|
* Load a WJECF Plugin (class name).
|
|
*
|
|
* @param mixed $instance
|
|
*
|
|
* @return bool True if succeeded, otherwise false
|
|
*/
|
|
public function add_plugin($instance)
|
|
{
|
|
if (is_string($instance)) {
|
|
if (!class_exists($instance)) {
|
|
$this->log('warning', 'Unknown plugin: '.$instance);
|
|
|
|
return false; // Not found
|
|
}
|
|
$instance = new $instance();
|
|
}
|
|
|
|
$name = $instance->get_plugin_name();
|
|
|
|
if (isset($this->plugins[$name])) {
|
|
$this->log('warning', 'Plugin already loaded: '.$name);
|
|
|
|
return false; // Already loaded
|
|
}
|
|
|
|
if (!$instance instanceof Abstract_WJECF_Plugin) {
|
|
$this->log('warning', 'Plugin must be an instance of Abstract_WJECF_Plugin: '.$name);
|
|
|
|
return false; // Invalid
|
|
}
|
|
|
|
$this->plugins[$name] = $instance;
|
|
$this->log('debug', 'Loaded plugin: '.$name);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get an array of all the plugins.
|
|
*
|
|
* @return array [ $key => $plugin ]
|
|
*/
|
|
public function get_plugins()
|
|
{
|
|
return $this->plugins;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the WJECF Plugin.
|
|
*
|
|
* @param string $name Name of the plugin as yielded by $plugin->get_plugin_name() e.g. 'admin-settings'
|
|
*
|
|
* @return bool|object The plugin if found, otherwise returns false
|
|
*/
|
|
public function get_plugin($name)
|
|
{
|
|
if (isset($this->plugins[$name])) {
|
|
return $this->plugins[$name];
|
|
}
|
|
|
|
// Legacy support: E.g. allow 'WJECF_Autocoupon' instead of 'autocoupon'
|
|
$adj_name = Abstract_WJECF_Plugin::sanitize_plugin_name($name);
|
|
if (isset($this->plugins[$adj_name])) {
|
|
$this->log('warning', sprintf('Plugin name %s has been changed to: %s', $name, $adj_name));
|
|
|
|
return $this->plugins[$adj_name];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// OPTIONS
|
|
|
|
public function get_options()
|
|
{
|
|
return $this->options->get();
|
|
}
|
|
|
|
public function get_option($key, $default = null)
|
|
{
|
|
return $this->options->get($key, $default);
|
|
}
|
|
|
|
public function set_option($key, $value)
|
|
{
|
|
$this->options->set($key, $value);
|
|
}
|
|
|
|
public function save_options()
|
|
{
|
|
if (!is_admin()) {
|
|
$this->log('error', 'WJECF Options must only be saved from admin.');
|
|
|
|
return;
|
|
}
|
|
$this->options->save();
|
|
}
|
|
|
|
public function sanitizer()
|
|
{
|
|
return WJECF_Sanitizer::instance();
|
|
}
|
|
|
|
/**
|
|
* Same as WordPress add_action(), but prevents the callback to be recursively called.
|
|
*
|
|
* @param string $tag
|
|
* @param callable $function_to_add
|
|
* @param int $priority
|
|
* @param int $accepted_args
|
|
*/
|
|
public function safe_add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1)
|
|
{
|
|
$_recursion_limit = 5;
|
|
WJECF_Action_Or_Filter::action($tag, $function_to_add, $priority, $accepted_args, $_recursion_limit);
|
|
}
|
|
|
|
// FRONTEND HOOKS
|
|
|
|
/**
|
|
* Extra validation rules for coupons.
|
|
*
|
|
* @param bool $valid
|
|
* @param WC_Coupon $coupon
|
|
* @param null|mixed $wc_discounts
|
|
*
|
|
* @return bool true if valid; False if not valid
|
|
*/
|
|
public function coupon_is_valid($valid, $coupon, $wc_discounts = null)
|
|
{
|
|
try {
|
|
return $this->assert_coupon_is_valid($valid, $coupon, $wc_discounts);
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extra validation rules for coupons. Throw an exception when not valid.
|
|
*
|
|
* @param bool $valid
|
|
* @param WC_Coupon $coupon
|
|
* @param null|mixed $wc_discounts
|
|
*
|
|
* @return bool True if valid; False if already invalid on function call. In any other case an Exception will be thrown.
|
|
*/
|
|
public function assert_coupon_is_valid($valid, $coupon, $wc_discounts = null)
|
|
{
|
|
// Not valid? Then it will never validate, so get out of here
|
|
if (!$valid) {
|
|
return false;
|
|
}
|
|
|
|
if (is_null($wc_discounts)) {
|
|
$wc_discounts = new WJECF_WC_Discounts(WC()->cart);
|
|
}
|
|
|
|
$coupon_code = $coupon->get_code();
|
|
|
|
// Reset multiplier to initial value of null. The validate_* routines will call limit_multiplier() to set the multiplier value
|
|
$this->coupon_multiplier_values[$coupon_code] = null;
|
|
|
|
try {
|
|
$this->validate_products_and($coupon, $wc_discounts);
|
|
$this->validate_categories_and($coupon, $wc_discounts);
|
|
$this->validate_min_max_quantity($coupon, $wc_discounts);
|
|
$this->validate_min_max_subtotal($coupon, $wc_discounts);
|
|
$this->validate_shipping_method($coupon);
|
|
$this->validate_excluded_shipping_method($coupon);
|
|
$this->validate_payment_method($coupon);
|
|
$this->validate_customer($coupon, $wc_discounts);
|
|
|
|
if ($this->is_pro()) {
|
|
$this->validate_pro($coupon, $wc_discounts);
|
|
}
|
|
|
|
// We use our own filter (instead of woocommerce_coupon_is_valid) for easier compatibility management
|
|
// e.g. WC prior to 2.3.0 can't handle Exceptions; while 2.3.0 and above require exceptions
|
|
do_action('wjecf_assert_coupon_is_valid', $coupon, $wc_discounts);
|
|
} catch (Exception $exception) {
|
|
// Invalid coupon? Multiplier 0
|
|
$this->coupon_multiplier_values[$coupon_code] = 0;
|
|
|
|
throw $exception;
|
|
}
|
|
|
|
if ((float) $coupon->get_minimum_amount()) {
|
|
$this->limit_multiplier($coupon, floor($this->get_subtotal($wc_discounts) / $coupon->get_minimum_amount()));
|
|
}
|
|
|
|
/*
|
|
* Filters the (product-)multiplier value of the coupon.
|
|
*
|
|
* @since 2.6.3
|
|
*
|
|
* @param float|null $multiplier Current multiplier value (or null if no multiplier yet known)
|
|
* @param WC_Coupon $coupon The coupon
|
|
* @param WC_Discounts $wc_discounts Discounts class containing the cart items (NOTE: Will be a WJECF_WC_Discounts for WC < 3.2.0)
|
|
*/
|
|
$this->coupon_multiplier_values[$coupon_code] = apply_filters('wjecf_coupon_multiplier_value', $this->coupon_multiplier_values[$coupon_code], $coupon, $wc_discounts);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The amount of times the minimum spend / quantity / subtotal values are reached.
|
|
*
|
|
* @param mixed $coupon
|
|
*
|
|
* @return int 1 or more if coupon is valid, otherwise 0
|
|
*/
|
|
public function get_coupon_multiplier_value($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$coupon_code = $coupon->get_code();
|
|
|
|
// If coupon validation was not executed, the value is unknown
|
|
if (!array_key_exists($coupon_code, $this->coupon_multiplier_values) && !$this->coupon_is_valid(true, $coupon)) {
|
|
return 0;
|
|
// Calling coupon_is_valid enforces $this->coupon_multiplier_values to be set; if the coupon is valid.
|
|
}
|
|
|
|
// null defaults to 1
|
|
return is_null($this->coupon_multiplier_values[$coupon_code]) ? 1 : $this->coupon_multiplier_values[$coupon_code];
|
|
}
|
|
|
|
/**
|
|
* (API FUNCTION)
|
|
* The total amount of the products in the cart that match the coupon restrictions
|
|
* since 2.2.2-b3.
|
|
*
|
|
* @param mixed $coupon
|
|
* @param null|mixed $wc_discounts
|
|
*/
|
|
public function get_quantity_of_matching_products($coupon, $wc_discounts = null)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$items = WJECF_WC()->get_discount_items($wc_discounts);
|
|
|
|
$qty = 0;
|
|
foreach ($items as $item_key => $item) {
|
|
if ($item->product && $this->coupon_is_valid_for_product($coupon, $item->product, $item->object)) {
|
|
$item_quantity = $item->quantity;
|
|
|
|
/**
|
|
* Filter to override the quantity of a single cart item or order item.
|
|
*
|
|
* @since 3.3.3
|
|
*
|
|
* @param int $item_quantity The quantity
|
|
* @param WC_Coupon $coupon The coupon for which the quantity of matching products is currently being calculated
|
|
* @param object $item Contains these propeties: { object, product, quantity, price, key }
|
|
* @param WC_Discounts $wc_discounts The WC_Discounts object that contains the full cart or order
|
|
*/
|
|
$item_quantity = apply_filters('wjecf_coupon_item_quantity', $item_quantity, $coupon, $item, $wc_discounts);
|
|
|
|
$qty += $item_quantity;
|
|
}
|
|
}
|
|
|
|
return $qty;
|
|
}
|
|
|
|
/**
|
|
* (API FUNCTION)
|
|
* The total value of the products in the cart that match the coupon restrictions
|
|
* since 2.2.2-b3.
|
|
*
|
|
* @param mixed $coupon
|
|
* @param null|mixed $wc_discounts
|
|
*/
|
|
public function get_subtotal_of_matching_products($coupon, $wc_discounts = null)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$items = WJECF_WC()->get_discount_items($wc_discounts);
|
|
|
|
$subtotal_precise = 0;
|
|
foreach ($items as $item_key => $item) {
|
|
if ($item->product && $this->coupon_is_valid_for_product($coupon, $item->product, $item->object)) {
|
|
$subtotal_precise += $item->price;
|
|
}
|
|
}
|
|
|
|
return WJECF_WC()->wc_remove_number_precision($subtotal_precise);
|
|
}
|
|
|
|
/**
|
|
* The total value of the products in the cart
|
|
* since 2.2.2-b3.
|
|
*
|
|
* @param null|mixed $wc_discounts
|
|
*/
|
|
public function get_subtotal($wc_discounts = null)
|
|
{
|
|
$items = WJECF_WC()->get_discount_items($wc_discounts);
|
|
|
|
$subtotal_precise = 0;
|
|
foreach ($items as $item_key => $item) {
|
|
if ($item->product) {
|
|
$subtotal_precise += $item->price;
|
|
}
|
|
}
|
|
|
|
return WJECF_WC()->wc_remove_number_precision($subtotal_precise);
|
|
}
|
|
|
|
/**
|
|
* (API FUNCTION)
|
|
* Test if coupon is valid for the product
|
|
* (this function is used to count the quantity of matching products).
|
|
*
|
|
* @param mixed $coupon
|
|
* @param mixed $product
|
|
* @param mixed $values
|
|
*/
|
|
public function coupon_is_valid_for_product($coupon, $product, $values = [])
|
|
{
|
|
// Do not count the free products
|
|
if (isset($values['_wjecf_free_product_coupon'])) {
|
|
return false;
|
|
}
|
|
|
|
// Get the original coupon, without values overwritten by WJECF
|
|
$duplicate_coupon = $this->get_original_coupon($coupon);
|
|
|
|
// $coupon->is_valid_for_product() only works for fixed_product or percent_product discounts
|
|
if (!$duplicate_coupon->is_type(WJECF_WC()->wc_get_product_coupon_types())) {
|
|
$duplicate_coupon->set_discount_type('fixed_product');
|
|
}
|
|
|
|
return $duplicate_coupon->is_valid_for_product($product, $values);
|
|
}
|
|
|
|
// =====================
|
|
|
|
/**
|
|
* Get array of the selected shipping methods ids.
|
|
*
|
|
* @deprecated 3.2.0
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array id's of the shipping methods or an empty array
|
|
*/
|
|
public function get_coupon_shipping_method_ids($coupon)
|
|
{
|
|
// Get all the coupon_shipping_ids that start with 'method:' and truncate the 'method:'-part
|
|
$restrictions = $this->get_coupon_shipping_restrictions();
|
|
$grouped = $this->group_shipping_restrictions($restrictions);
|
|
|
|
return $grouped['method'] ?? '';
|
|
}
|
|
|
|
/**
|
|
* Get array of the selected shipping zones, methods or instance ids.
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array Id's or an empty array. Id's are prefixed by 'zone:', 'instance:' or 'method:'
|
|
*/
|
|
public function get_coupon_shipping_restrictions($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$value = WJECF()->sanitizer()->sanitize($coupon->get_meta('_wjecf_shipping_restrictions'), 'clean');
|
|
|
|
return is_array($value) ? $value : [];
|
|
}
|
|
|
|
/**
|
|
* Get array of the excluded shipping zones, methods or instance ids.
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array Id's or an empty array. Id's are prefixed by 'zone:', 'instance:' or 'method:'
|
|
*/
|
|
public function get_coupon_excluded_shipping_restrictions($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$value = WJECF()->sanitizer()->sanitize($coupon->get_meta('_wjecf_excluded_shipping_restrictions'), 'clean');
|
|
|
|
return is_array($value) ? $value : [];
|
|
}
|
|
|
|
/**
|
|
* Get array of the selected payment method ids.
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array id's of the payment methods or an empty array
|
|
*/
|
|
public function get_coupon_payment_method_ids($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$v = $coupon->get_meta('_wjecf_payment_methods');
|
|
|
|
return is_array($v) ? $v : [];
|
|
}
|
|
|
|
/**
|
|
* Get array of the selected customer ids.
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array id's of the customers (users) or an empty array
|
|
*/
|
|
public function get_coupon_customer_ids($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$v = $coupon->get_meta('_wjecf_customer_ids');
|
|
|
|
return WJECF()->sanitizer()->sanitize($v, 'int[]');
|
|
}
|
|
|
|
/**
|
|
* Get array of the selected customer role ids.
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array id's (string) of the customer roles or an empty array
|
|
*/
|
|
public function get_coupon_customer_roles($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$v = $coupon->get_meta('_wjecf_customer_roles');
|
|
|
|
return is_array($v) ? $v : [];
|
|
}
|
|
|
|
/**
|
|
* Get array of the excluded customer role ids.
|
|
*
|
|
* @param string|WC_Coupon $coupon The coupon code or a WC_Coupon object
|
|
*
|
|
* @return array id's (string) of the excluded customer roles or an empty array
|
|
*/
|
|
public function get_coupon_excluded_customer_roles($coupon)
|
|
{
|
|
$coupon = WJECF_WC()->get_coupon($coupon);
|
|
$v = $coupon->get_meta('_wjecf_excluded_customer_roles');
|
|
|
|
return is_array($v) ? $v : [];
|
|
}
|
|
|
|
// ===========================================================================
|
|
// User identification
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* Get a list of the users' known email addresses.
|
|
*
|
|
* NOTE: Also called in AutoCoupon
|
|
*
|
|
* @since 2.5.6 (Moved from WJECF_AutoCoupon)
|
|
*
|
|
* @return array The user's known email addresses
|
|
*/
|
|
public function get_user_emails()
|
|
{
|
|
if (!is_array($this->_user_emails)) {
|
|
$this->_user_emails = [];
|
|
// Email of the logged in user
|
|
if (is_user_logged_in()) {
|
|
$current_user = wp_get_current_user();
|
|
$this->_user_emails[] = $current_user->user_email;
|
|
}
|
|
}
|
|
$user_emails = $this->_user_emails;
|
|
|
|
$billing_email = $this->get_session('billing_email', '');
|
|
if (is_email($billing_email)) {
|
|
$user_emails[] = $billing_email;
|
|
}
|
|
|
|
$user_emails = array_map('strtolower', $user_emails);
|
|
$user_emails = array_map('sanitize_email', $user_emails);
|
|
$user_emails = array_filter($user_emails, 'is_email');
|
|
|
|
return array_unique($user_emails);
|
|
}
|
|
|
|
/**
|
|
* Called on action: woocommerce_checkout_update_order_review.
|
|
*
|
|
* Collects billing email address from the checkout-form
|
|
*
|
|
* @param mixed $post_data
|
|
*/
|
|
public function fetch_billing_email($post_data)
|
|
{
|
|
// post_data can be an array, or a query=string&like=this
|
|
if (!is_array($post_data)) {
|
|
parse_str($post_data, $posted);
|
|
} else {
|
|
$posted = $post_data;
|
|
}
|
|
|
|
if (isset($posted['billing_email'])) {
|
|
// $this->log('debug', 'billing:' . $posted['billing_email']);
|
|
WJECF()->set_session('billing_email', $posted['billing_email']);
|
|
}
|
|
}
|
|
|
|
public function is_pro()
|
|
{
|
|
return $this instanceof WJECF_Pro_Controller;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// START - OVERWRITE COUPON SUCCESS MESSAGE
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* 2.3.4
|
|
* If a 'Coupon applied' message is displayed by WooCommerce, replace it by another message (or no message).
|
|
*
|
|
* @param WC_Coupon $coupon The coupon to replace the message for
|
|
* @param string $new_message The new message. Set to empty string if no message must be displayed
|
|
*/
|
|
public function start_overwrite_success_message($coupon, $new_message = '')
|
|
{
|
|
$this->overwrite_coupon_message[$coupon->get_code()] = [WC_Coupon::WC_COUPON_SUCCESS => $new_message];
|
|
}
|
|
|
|
/**
|
|
* 2.3.4
|
|
* Stop overwriting messages.
|
|
*/
|
|
public function stop_overwrite_success_message()
|
|
{
|
|
$this->overwrite_coupon_message = [];
|
|
}
|
|
|
|
public function filter_woocommerce_coupon_message($msg, $msg_code, $coupon)
|
|
{
|
|
if (isset($this->overwrite_coupon_message[$coupon->get_code()][$msg_code])) {
|
|
$msg = $this->overwrite_coupon_message[$coupon->get_code()][$msg_code];
|
|
}
|
|
|
|
return $msg;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// END - OVERWRITE COUPON SUCCESS MESSAGE
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* Return an array of WC_Coupons with coupons that shouldn't cause individual use conflicts.
|
|
*
|
|
* @param WC_Coupon[] $coupons The coupons
|
|
* @param string[] $applied_coupon_codes Coupon codes that are considered to be in the cart. If null WC()->cart->get_applied_coupons() will be used.
|
|
*
|
|
* @return WC_Coupon[]
|
|
*/
|
|
public function coupon_combination_filter($coupons, $applied_coupon_codes = null)
|
|
{
|
|
$filtered_coupons = [];
|
|
|
|
// Contains coupon codes that are already in cart or pending in the filtered-array
|
|
if (null === $applied_coupon_codes) {
|
|
$applied_coupon_codes = WC()->cart->get_applied_coupons();
|
|
}
|
|
|
|
foreach ($coupons as $the_coupon) {
|
|
if ($the_coupon->get_individual_use() && !in_array($the_coupon->get_code(), $applied_coupon_codes)) {
|
|
// Only allow a new automatic individual use coupon if it doesn't remove coupons from the cart.
|
|
$coupons_to_keep = apply_filters('woocommerce_apply_individual_use_coupon', [], $the_coupon, $applied_coupon_codes);
|
|
if (count($applied_coupon_codes) != count(array_intersect($applied_coupon_codes, $coupons_to_keep))) {
|
|
continue; // skip coupon.
|
|
}
|
|
}
|
|
|
|
// Check to see if an individual use coupon is already in the cart.
|
|
foreach ($applied_coupon_codes as $code) {
|
|
if ($code === $the_coupon->get_code()) {
|
|
// Dont compare the coupon with itself
|
|
continue;
|
|
}
|
|
$coupon = new WC_Coupon($code);
|
|
if ($coupon->get_individual_use() && false === apply_filters('woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $applied_coupon_codes)) {
|
|
continue 2; // skip coupon.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Filter to disallow certain coupon combinations to be auto-applied together.
|
|
*
|
|
* @since 3.0.0
|
|
*
|
|
* @param WC_Coupon $the_coupon Coupon to apply
|
|
* @param string[] $applied_coupon_codes Codes of the coupons already in the cart
|
|
*/
|
|
if (!apply_filters('wjecf_apply_with_other_coupons', true, $the_coupon, $applied_coupon_codes)) {
|
|
continue; // skip coupon.
|
|
}
|
|
|
|
$applied_coupon_codes[] = $the_coupon->get_code();
|
|
$filtered_coupons[] = $the_coupon;
|
|
}
|
|
|
|
return $filtered_coupons;
|
|
}
|
|
|
|
/**
|
|
* @since 2.4.4
|
|
*
|
|
* Get a coupon, but inhibit the woocommerce_coupon_loaded to overwrite values.
|
|
*
|
|
* @param string|WC_Coupon $coupon_code The coupon code or a WC_Coupon object
|
|
*
|
|
* @return WC_Coupon The coupon object
|
|
*/
|
|
public function get_original_coupon($coupon_code)
|
|
{
|
|
// Prevent returning the same instance
|
|
if ($coupon_code instanceof WC_Coupon) {
|
|
$coupon_code = $coupon_code->get_code();
|
|
}
|
|
++$this->inhibit_overwrite;
|
|
$coupon = WJECF_WC()->get_coupon($coupon_code);
|
|
--$this->inhibit_overwrite;
|
|
|
|
return $coupon;
|
|
}
|
|
|
|
/**
|
|
* @since 2.4.4
|
|
*
|
|
* May coupon values be overwritten by this plugin upon load?
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function allow_overwrite_coupon_values()
|
|
{
|
|
return (0 == $this->inhibit_overwrite) && $this->is_request('frontend');
|
|
}
|
|
|
|
/**
|
|
* Read something from the session.
|
|
*
|
|
* If key is omitted; all the session data will be returned as an array
|
|
*
|
|
* @param string $key The key for identification
|
|
* @param any $default The default value (Default: false)
|
|
*
|
|
* @return The saved value if found, otherwise the default value
|
|
*/
|
|
public function get_session($key = null, $default = false)
|
|
{
|
|
if (!isset($this->_session_data)) {
|
|
if (!isset(WC()->session)) {
|
|
$this->log('error', 'Trying to access WC()->session while it was not yet initialized.');
|
|
|
|
return null;
|
|
}
|
|
$this->_session_data = WC()->session->get('_wjecf_session_data', []);
|
|
}
|
|
|
|
if (!isset($key)) {
|
|
return $this->_session_data;
|
|
}
|
|
if (!isset($this->_session_data[$key])) {
|
|
return $default;
|
|
}
|
|
|
|
return $this->_session_data[$key];
|
|
}
|
|
|
|
/**
|
|
* Save something in the session.
|
|
*
|
|
* @param string $key The key for identification
|
|
* @param anything $value The value to store. Use 'null' to remove the value
|
|
*/
|
|
public function set_session($key, $value)
|
|
{
|
|
if (!isset($this->_session_data)) {
|
|
if (!isset(WC()->session)) {
|
|
$this->log('error', 'Trying to access WC()->session while it was not yet initialized.');
|
|
|
|
return null;
|
|
}
|
|
$this->_session_data = WC()->session->get('_wjecf_session_data', []);
|
|
}
|
|
if (is_null($value)) {
|
|
unset($this->_session_data[$key]);
|
|
} else {
|
|
$this->_session_data[$key] = $value;
|
|
}
|
|
|
|
WC()->session->set('_wjecf_session_data', $this->_session_data);
|
|
}
|
|
|
|
/**
|
|
* (Copied from class-woocommerce.php) What type of request is this?
|
|
*
|
|
* @since 2.6.2
|
|
*
|
|
* @param string $type admin, ajax, cron or frontend
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function is_request($type)
|
|
{
|
|
switch ($type) {
|
|
case 'admin':
|
|
return is_admin();
|
|
|
|
case 'ajax':
|
|
return defined('DOING_AJAX');
|
|
|
|
case 'cron':
|
|
return defined('DOING_CRON');
|
|
|
|
case 'frontend':
|
|
return (!is_admin() || defined('DOING_AJAX')) && !defined('DOING_CRON');
|
|
}
|
|
}
|
|
|
|
// ========================
|
|
// INFO ABOUT WJECF PLUGIN
|
|
// ========================
|
|
|
|
/**
|
|
* Filename of this plugin including the containing directory.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function plugin_file()
|
|
{
|
|
$filename = $this->is_pro() ? 'woocommerce-jos-autocoupon-pro.php' : 'woocommerce-jos-autocoupon.php';
|
|
|
|
return trailingslashit(basename(dirname(dirname(__FILE__)))).$filename;
|
|
}
|
|
|
|
public function plugin_basename()
|
|
{
|
|
return plugin_basename($this->plugin_file());
|
|
}
|
|
|
|
/**
|
|
* url to the base directory of this plugin (wp-content/woocommerce-jos-autocoupon/) with trailing slash.
|
|
*
|
|
* @param mixed $suffix
|
|
*
|
|
* @return string
|
|
*/
|
|
public function plugin_url($suffix = '')
|
|
{
|
|
return plugins_url('/', dirname(__FILE__)).$suffix;
|
|
}
|
|
|
|
public function plugin_version()
|
|
{
|
|
return WJECF_VERSION;
|
|
}
|
|
|
|
// ========================
|
|
// LOGGING
|
|
// ========================
|
|
|
|
/**
|
|
* Log a message for debugging.
|
|
*
|
|
* If debug_mode is false; messages with level 'debug' will be ignored.
|
|
*
|
|
* @param string $level The level of the message. e.g. 'debug' or 'warning'
|
|
* @param int $skip_backtrace Defaults to 0, amount of items to skip in backtrace to fetch class and method name
|
|
* @param null|mixed $message
|
|
*/
|
|
public function log($level, $message = null, $skip_backtrace = 0)
|
|
{
|
|
if (false === $this->debugger) {
|
|
return; // Debugger is disabled.
|
|
}
|
|
|
|
if (!isset($this->debugger)) {
|
|
if (in_array('debug', $this->get_option('disabled_plugins'))) {
|
|
$this->debugger = false;
|
|
|
|
return;
|
|
}
|
|
|
|
$debugger = $this->get_plugin('debug');
|
|
if (!$debugger) {
|
|
// Fallback to error_log if the logger is not yet loaded.
|
|
error_log(sprintf('WJECF: %s: %s', $level, $message));
|
|
|
|
return;
|
|
}
|
|
|
|
$this->debugger = $debugger;
|
|
}
|
|
|
|
$this->debugger->log($level, $message, $skip_backtrace + 1);
|
|
}
|
|
|
|
protected function init_plugins()
|
|
{
|
|
/*
|
|
* Fires before the WJECF plugins are initialised.
|
|
*
|
|
* Perfect hook for themes or plugins to load custom WJECF plugins.
|
|
*
|
|
* @since 2.3.7
|
|
*/
|
|
do_action('wjecf_init_plugins');
|
|
|
|
// Start the plugins
|
|
foreach (WJECF()->get_plugins() as $name => $plugin) {
|
|
if ($plugin->plugin_is_enabled()) {
|
|
try {
|
|
$plugin->assert_dependencies();
|
|
} catch (Exception $ex) {
|
|
if (is_admin() && WJECF_Admin()) {
|
|
// translators: 1: plugin-name 2: exception message
|
|
$msg = sprintf(__('Failed loading plugin %1$s: %2$s.', 'woocommerce-jos-autocoupon'), $name, $ex->getMessage());
|
|
WJECF_Admin()->enqueue_notice($msg);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
$plugin->init_hook();
|
|
if (is_admin()) {
|
|
$plugin->init_admin_hook();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate 'products AND'. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
* @param WC_Discounts $wc_discounts
|
|
*/
|
|
private function validate_products_and($coupon, $wc_discounts)
|
|
{
|
|
// Test if ALL products are in the cart (if AND-operator selected instead of the default OR)
|
|
$products_and = 'yes' == $coupon->get_meta('_wjecf_products_and');
|
|
if (!$products_and || sizeof($coupon->get_product_ids()) <= 1) { // We use > 1, because if size == 1, 'AND' makes no difference
|
|
return;
|
|
}
|
|
|
|
// Get array of all cart product and variation ids
|
|
$item_ids = [];
|
|
foreach ($wc_discounts->get_items() as $item_key => $item) {
|
|
if (!empty($item->product)) {
|
|
$item_ids[] = $item->product->get_id();
|
|
if ($item->product->is_type('variation')) {
|
|
$item_ids[] = $item->product->get_parent_id();
|
|
}
|
|
}
|
|
}
|
|
// Filter used by WJECF_WPML hook
|
|
$item_ids = apply_filters('wjecf_get_product_ids', array_unique($item_ids));
|
|
|
|
// check if every single product is in the cart
|
|
foreach (apply_filters('wjecf_get_product_ids', $coupon->get_product_ids()) as $product_id) {
|
|
if (!in_array($product_id, $item_ids)) {
|
|
throw new Exception(WC_Coupon::E_WC_COUPON_NOT_APPLICABLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate 'categories AND'. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
* @param WC_Discounts $wc_discounts
|
|
*/
|
|
private function validate_categories_and($coupon, $wc_discounts)
|
|
{
|
|
// Test if products form ALL categories are in the cart (if AND-operator selected instead of the default OR)
|
|
$categories_and = 'yes' == $coupon->get_meta('_wjecf_categories_and');
|
|
if (!$categories_and || sizeof($coupon->get_product_categories()) <= 1) { // We use > 1, because if size == 1, 'AND' makes no difference
|
|
return;
|
|
}
|
|
|
|
// Get array of all cart product and variation ids
|
|
$product_cats = [];
|
|
|
|
foreach ($wc_discounts->get_items() as $item_key => $item) {
|
|
if (!$item->product) {
|
|
continue;
|
|
}
|
|
|
|
$product_id = $item->product->get_id();
|
|
if ('product_variation' == get_post_type($product_id)) {
|
|
$product_id = $item->product->get_parent_id();
|
|
}
|
|
$product_cats = array_merge($product_cats, wp_get_post_terms($product_id, 'product_cat', ['fields' => 'ids']));
|
|
}
|
|
|
|
// Filter used by WJECF_WPML hook
|
|
$product_cats = apply_filters('wjecf_get_product_cat_ids', $product_cats);
|
|
// check if every single category is in the cart
|
|
foreach (apply_filters('wjecf_get_product_cat_ids', $coupon->get_product_categories()) as $cat_id) {
|
|
if (!in_array($cat_id, $product_cats)) {
|
|
$this->log('debug', $cat_id.' is not in '.implode(',', $product_cats));
|
|
|
|
throw new Exception(WC_Coupon::E_WC_COUPON_NOT_APPLICABLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate min/max quantity of matching products. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
* @param WC_Discounts $wc_discounts
|
|
*/
|
|
private function validate_min_max_quantity($coupon, $wc_discounts)
|
|
{
|
|
// Test min/max quantity of matching products
|
|
//
|
|
// For all items in the cart:
|
|
// If coupon contains both a product AND category inclusion filter: the item is counted if it matches either one of them
|
|
// If coupon contains either a product OR category exclusion filter: the item will NOT be counted if it matches either one of them
|
|
// If sale items are excluded by the coupon: the item will NOT be counted if it is a sale item
|
|
// If no filter exist, all items will be counted
|
|
|
|
// Validate quantity
|
|
$min_matching_product_qty = intval($coupon->get_meta('_wjecf_min_matching_product_qty'));
|
|
$max_matching_product_qty = intval($coupon->get_meta('_wjecf_max_matching_product_qty'));
|
|
if ($min_matching_product_qty <= 0 && 0 == $max_matching_product_qty) {
|
|
return;
|
|
}
|
|
|
|
// Count the products
|
|
$qty = $this->get_quantity_of_matching_products($coupon, $wc_discounts);
|
|
// $this->log( 'debug', 'Quantity of matching products: ' . $qty );
|
|
if ($min_matching_product_qty > 0 && $qty < $min_matching_product_qty) {
|
|
throw new Exception(
|
|
// translators: 1: minimum quantity
|
|
sprintf(__('The minimum quantity of matching products for this coupon is %s.', 'woocommerce-jos-autocoupon'), $min_matching_product_qty),
|
|
self::E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET,
|
|
);
|
|
}
|
|
if ($max_matching_product_qty > 0 && $qty > $max_matching_product_qty) {
|
|
throw new Exception(
|
|
// translators: 1: maximum quantity
|
|
sprintf(__('The maximum quantity of matching products for this coupon is %s.', 'woocommerce-jos-autocoupon'), $max_matching_product_qty),
|
|
self::E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET,
|
|
);
|
|
}
|
|
|
|
if ($min_matching_product_qty > 0) {
|
|
$this->limit_multiplier($coupon, floor($qty / $min_matching_product_qty));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate min/max subtotal of matching products. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
* @param WC_Discounts $wc_discounts
|
|
*/
|
|
private function validate_min_max_subtotal($coupon, $wc_discounts)
|
|
{
|
|
// Validate subtotal (2.2.2)
|
|
$min_matching_product_subtotal = floatval($coupon->get_meta('_wjecf_min_matching_product_subtotal'));
|
|
$max_matching_product_subtotal = floatval($coupon->get_meta('_wjecf_max_matching_product_subtotal'));
|
|
|
|
if ($min_matching_product_subtotal <= 0.0 && 0.0 === $max_matching_product_subtotal) {
|
|
return;
|
|
}
|
|
|
|
$subtotal = $this->get_subtotal_of_matching_products($coupon, $wc_discounts);
|
|
|
|
if ($min_matching_product_subtotal > 0.0) {
|
|
if ($subtotal < $min_matching_product_subtotal) {
|
|
throw new Exception(
|
|
// translators: 1: minimum subtotal
|
|
sprintf(__('The minimum subtotal of the matching products for this coupon is %s.', 'woocommerce-jos-autocoupon'), wc_price($min_matching_product_subtotal)),
|
|
self::E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET,
|
|
);
|
|
}
|
|
$this->limit_multiplier($coupon, floor($subtotal / $min_matching_product_subtotal));
|
|
}
|
|
if ($max_matching_product_subtotal > 0.0 && $subtotal > $max_matching_product_subtotal) {
|
|
throw new Exception(
|
|
// translators: 1: maximum subtotal
|
|
sprintf(__('The maximum subtotal of the matching products for this coupon is %s.', 'woocommerce-jos-autocoupon'), wc_price($max_matching_product_subtotal)),
|
|
self::E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate shipping method. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
*/
|
|
private function validate_shipping_method($coupon)
|
|
{
|
|
$restrictions = $this->get_coupon_shipping_restrictions($coupon);
|
|
if (empty($restrictions)) {
|
|
return;
|
|
}
|
|
|
|
$grouped = $this->group_shipping_restrictions($restrictions);
|
|
|
|
foreach ($grouped as $group => $ids) {
|
|
switch ($group) {
|
|
case 'zone':
|
|
if ($this->matches_shipping_zone($ids)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'method':
|
|
if ($this->matches_shipping_method($ids)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'instance':
|
|
if ($this->matches_shipping_instance($ids)) {
|
|
return;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
$this->log('warning', "Unknown shipping restriction type '{$group}'");
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('zone', $grouped) && !array_intersect(array_keys($grouped), ['method', 'instance'])) {
|
|
throw new Exception(
|
|
__('The coupon is not valid for your region.', 'woocommerce-jos-autocoupon'),
|
|
self::E_WC_COUPON_SHIPPING_ZONE_NOT_MET,
|
|
);
|
|
}
|
|
|
|
throw new Exception(
|
|
__('The coupon is not valid for the currently selected shipping method.', 'woocommerce-jos-autocoupon'),
|
|
self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Validate shipping method. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
*/
|
|
private function validate_excluded_shipping_method($coupon)
|
|
{
|
|
$restrictions = $this->get_coupon_excluded_shipping_restrictions($coupon);
|
|
if (empty($restrictions)) {
|
|
return;
|
|
}
|
|
|
|
$grouped = $this->group_shipping_restrictions($restrictions);
|
|
|
|
foreach ($grouped as $group => $ids) {
|
|
switch ($group) {
|
|
case 'zone':
|
|
if ($this->matches_shipping_zone($ids)) {
|
|
throw new Exception(
|
|
__('The coupon is not valid for your region.', 'woocommerce-jos-autocoupon'),
|
|
self::E_WC_COUPON_SHIPPING_ZONE_NOT_MET,
|
|
);
|
|
}
|
|
|
|
break;
|
|
|
|
case 'method':
|
|
if ($this->matches_shipping_method($ids)) {
|
|
throw new Exception(
|
|
__('The coupon is not valid for the currently selected shipping method.', 'woocommerce-jos-autocoupon'),
|
|
self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET,
|
|
);
|
|
}
|
|
|
|
break;
|
|
|
|
case 'instance':
|
|
if ($this->matches_shipping_instance($ids)) {
|
|
throw new Exception(
|
|
__('The coupon is not valid for the currently selected shipping method.', 'woocommerce-jos-autocoupon'),
|
|
self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET,
|
|
);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
$this->log('warning', "Unknown shipping restriction type '{$group}'");
|
|
}
|
|
}
|
|
}
|
|
|
|
private function matches_shipping_method($coupon_shipping_methods)
|
|
{
|
|
return !empty(array_intersect(wc_get_chosen_shipping_method_ids(), $coupon_shipping_methods));
|
|
}
|
|
|
|
private function matches_shipping_zone($coupon_shipping_zones)
|
|
{
|
|
$packages = WC()->cart->get_shipping_packages();
|
|
foreach ($packages as $package) {
|
|
$zone_id = WC_Shipping_Zones::get_zone_matching_package($package)->get_id();
|
|
|
|
if (in_array($zone_id, $coupon_shipping_zones)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function matches_shipping_instance($coupon_shipping_instance_ids)
|
|
{
|
|
$instances = WC()->session->get('chosen_shipping_methods', []); // e.g. [ 'local_pickup:1', 'flat_rate:2' ]
|
|
if (!is_array($instances)) {
|
|
return false;
|
|
}
|
|
foreach ($instances as $instance) {
|
|
// "Flexible Shipping" by WP Desk compatibility
|
|
if (0 === strpos($instance, 'flexible_shipping_')) {
|
|
$instance = $this->get_flexible_shipping_instance($instance);
|
|
}
|
|
|
|
// Examples where 11 is the instance id:
|
|
// Core shipping methods use format 'shiping_method:11'
|
|
// "Table Rate Shipping for WooCommerce" by Border Elements uses format 'betrs_shipping:11-1'
|
|
// "Table Rate Shipping" by WooCommerce uses format 'table_rate:11:4'.
|
|
if (!preg_match('/:(\d+)/', $instance, $matches)) {
|
|
continue;
|
|
}
|
|
$instance_id = $matches[1];
|
|
if (in_array($instance_id, $coupon_shipping_instance_ids)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Flexible shipping plugin stores the shipping method as 'flexible_shipping_?_?'.
|
|
* By looking up the rates from ['flexible_shipping'] we can get the instance id.
|
|
*
|
|
* Returns 'flexible_shipping_?_?:?' if found (where the latter question mark is the instance id)
|
|
* Otherwise returns the original $instance_name
|
|
*
|
|
* @param string $instance_name
|
|
*
|
|
* @return string $instance_name with the instance id added
|
|
*/
|
|
private function get_flexible_shipping_instance($instance_name)
|
|
{
|
|
if (null === $this->flexible_shipping_rates) {
|
|
$all_shipping_methods = WC()->shipping()->load_shipping_methods();
|
|
if (isset($all_shipping_methods['flexible_shipping'])) {
|
|
$this->flexible_shipping_rates = $all_shipping_methods['flexible_shipping']->get_all_rates();
|
|
} else {
|
|
$this->flexible_shipping_rates = false;
|
|
}
|
|
}
|
|
|
|
if (isset($this->flexible_shipping_rates[$instance_name]['woocommerce_method_instance_id'])) {
|
|
return $instance_name.':'.$this->flexible_shipping_rates[$instance_name]['woocommerce_method_instance_id'];
|
|
}
|
|
|
|
return $instance_name;
|
|
}
|
|
|
|
/**
|
|
* Validate payment method. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
*/
|
|
private function validate_payment_method($coupon)
|
|
{
|
|
$payment_method_ids = $this->get_coupon_payment_method_ids($coupon);
|
|
if (empty($payment_method_ids)) {
|
|
return;
|
|
}
|
|
|
|
$chosen_payment_method = WC()->session->get('chosen_payment_method');
|
|
if (!in_array($chosen_payment_method, $payment_method_ids)) {
|
|
throw new Exception(
|
|
__('The coupon is not valid for the currently selected payment method.', 'woocommerce-jos-autocoupon'),
|
|
self::E_WC_COUPON_PAYMENT_METHOD_NOT_MET,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate if the coupon applies to the customer. An Exception will be thrown if the coupon does not apply.
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
* @param mixed $wc_discounts
|
|
*/
|
|
private function validate_customer($coupon, $wc_discounts)
|
|
{
|
|
// NOTE: If both customer id and role restrictions are provided, the coupon matches if either the id or the role matches
|
|
$coupon_customer_ids = $this->get_coupon_customer_ids($coupon);
|
|
$coupon_customer_roles = $this->get_coupon_customer_roles($coupon);
|
|
if (!empty($coupon_customer_ids) || !empty($coupon_customer_roles)) {
|
|
$user = WJECF_WC()->get_user($wc_discounts);
|
|
// If both fail we invalidate. Otherwise it's ok
|
|
if (!$user || (!in_array($user->ID, $coupon_customer_ids) && !array_intersect($user->roles, $coupon_customer_roles))) {
|
|
throw new Exception(
|
|
// translators: 1: coupon code
|
|
sprintf(__('Sorry, it seems the coupon "%s" is not yours.', 'woocommerce-jos-autocoupon'), $coupon->get_code()),
|
|
self::E_WC_COUPON_NOT_FOR_THIS_USER,
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================
|
|
// Test excluded user roles
|
|
$coupon_excluded_customer_roles = $this->get_coupon_excluded_customer_roles($coupon);
|
|
if (!empty($coupon_excluded_customer_roles)) {
|
|
$user = WJECF_WC()->get_user($wc_discounts);
|
|
if ($user && array_intersect($user->roles, $coupon_excluded_customer_roles)) {
|
|
throw new Exception(
|
|
// translators: 1: coupon code
|
|
sprintf(__('Sorry, it seems the coupon "%s" is not yours.', 'woocommerce-jos-autocoupon'), $coupon->get_code()),
|
|
self::E_WC_COUPON_NOT_FOR_THIS_USER,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limit the multiplier value for the coupon (i.e. if $multiplier_value < current multiplier value; overwrite current multiplier value).
|
|
*
|
|
* @param WC_Coupon $coupon
|
|
* @param mixed $multiplier_value
|
|
*/
|
|
private function limit_multiplier($coupon, $multiplier_value)
|
|
{
|
|
$coupon_code = $coupon->get_code();
|
|
if (isset($this->coupon_multiplier_values[$coupon_code])) {
|
|
$this->coupon_multiplier_values[$coupon_code] = min($this->coupon_multiplier_values[$coupon_code], $multiplier_value);
|
|
} else {
|
|
$this->coupon_multiplier_values[$coupon_code] = $multiplier_value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Groups the restrictions by the part before the ':' of every restriction.
|
|
*
|
|
* @param string[] $restrictions E.g. [ 'zone:1', 'method:flat_rate', 'method:local_pickup' ]
|
|
*
|
|
* @return array Grouped array, e.g. [ 'zone' : ['1'], 'method' : ['flat_rate', 'local_pickup'] ]
|
|
*/
|
|
private function group_shipping_restrictions($restrictions)
|
|
{
|
|
$grouped = [];
|
|
foreach ($restrictions as $restriction) {
|
|
$split = explode(':', $restriction, 2);
|
|
if (2 === count($split)) {
|
|
$grouped[$split[0]][] = $split[1];
|
|
}
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
}
|