context = adp_context();
$processor = $contextOrProcessor instanceof IWcProductProcessor ? $contextOrProcessor : $processorOrEngine;
$this->engine = $processorOrEngine instanceof Engine ? $processorOrEngine : $deprecated;
$this->with($processor);
$this->persistentRuleRepository = new PersistentRuleRepository();
}
public function withContext(Context $context)
{
$this->context = $context;
}
public function with(IWcProductProcessor $processor)
{
$this->processor = $processor;
}
public function initHooks()
{
$context = $this->context;
$priority = PHP_INT_MAX - 1;
add_filter('woocommerce_get_price_html', array($this, 'hookPriceHtml'), $priority, 2);
/**
* Should wait until 'wp' action!
* Because existence of onSale hooks depends to where we processing now
* e.g. the option 'do_not_modify_price_at_product_page' is active, so we should prevent price html changes, but
* if onSale hooks have been installed, product becomes 'is on sale' and we will see corrupted price html
*/
if ($context->is($context::REST_API)) {
add_action('parse_request', array($this, 'installOnSaleHooks'), 1);
} elseif ($context->is($context::WP_CRON)) {
$this->installOnSaleHooks();
} elseif ($context->is($context::AJAX)) {
$this->installOnSaleHooks();
} else {
/** */
add_action('wp', array($this, 'installOnSaleHooks'));
}
if ($context->getOption('show_cross_out_subtotal_in_cart_totals')) {
add_filter('woocommerce_cart_subtotal', array($this, 'hookCartSubtotal'), 10, 3);
}
if ($context->getOption('use_first_range_as_min_qty')) {
// for simple
add_filter('woocommerce_quantity_input_args', array($this, 'hookItemPageQtyArgs'), 10, 2);
// for variable
add_filter('woocommerce_available_variation', array($this, 'hookWcAvailableVariation'), 10, 3);
}
if($context->getOption('force_displaying_variation_price', false)) {
add_filter("woocommerce_show_variation_price","__return_true");
}
$this->priceFunctions = new PriceFunctions($context);
$this->discountRangeFormatter = new DiscountRangeFormatter($context);
$this->defaultFormatter = new DefaultFormatter($context);
}
/**
* @return array
*/
protected static function getHookList()
{
return array(
'woocommerce_get_price_html' => array(
array(__CLASS__, 'hookPriceHtml'),
),
'woocommerce_product_is_on_sale' => array(
array(__CLASS__, 'hookIsOnSale'),
),
'woocommerce_product_get_sale_price' => array(
array(__CLASS__, 'hookGetSalePrice'),
),
'woocommerce_product_get_regular_price' => array(
array(__CLASS__, 'hookGetRegularPrice'),
),
);
}
/**
* @param callable $callback
* @param array $args
*
* @return mixed
*/
public static function processWithout($callback, ...$args)
{
if ( ! is_callable($callback) && ! isset($callback[0], $callback[1])) {
return null;
}
$list = static::getHookList();
$highLander = new HighLander();
$queries = array();
foreach ($list as $tag => $hooks) {
$query = new ClassMethodFilterQuery();
$query->setList($hooks)->setAction($query::ACTION_REMOVE)->useTag($tag);
$queries[] = $query;
}
$highLander->setQueries($queries);
$highLander->execute();
$result = call_user_func_array($callback, $args);
$highLander->restore();
return $result;
}
public static function removeHooks()
{
$list = static::getHookList();
$highLander = new HighLander();
$queries = array();
foreach ($list as $tag => $hooks) {
$query = new ClassMethodFilterQuery();
$query->setList($hooks)->setAction($query::ACTION_REMOVE)->useTag($tag);
$queries[] = $query;
}
$highLander->setQueries($queries);
$highLander->execute();
}
public function installOnSaleHooks()
{
$context = $this->context;
$priority = PHP_INT_MAX - 1;
/**
* do NOT install hooks if 'do_not_modify_price_at_product_page' is enabled!
* Without it, with activated options 'do_not_modify_price_at_product_page' and 'show_onsale_badge', it will
* affect on default price html. In \WC_Product::get_price_html() product is ON SALE, so we will see the same price
* as striked and clear.
*
* @see \WC_Product::get_price_html()
*/
if ($context->getOption('show_onsale_badge') && $this->priceHtmlIsModifyNeeded()) {
add_filter('woocommerce_product_is_on_sale', array($this, 'hookIsOnSale'), $priority, 2);
add_filter('woocommerce_product_get_sale_price', array($this, 'hookGetSalePrice'), $priority, 2);
if (!$context->getOption('regular_price_for_striked_price')) {
add_filter('woocommerce_product_get_regular_price', array($this, 'hookGetRegularPrice'), $priority, 2);
}
}
if (!$context->isBaseVersion() AND apply_filters('adp_show_onsale_badge_for_variable', false)) {
add_filter('woocommerce_product_variation_get_sale_price', array($this, 'hookGetSalePrice'), $priority,
2);
add_filter('woocommerce_product_variation_get_regular_price', array($this, 'hookGetRegularPrice'),
$priority, 2);
}
}
/**
* @param string $priceHtml
* @param WC_Product $product
*
* @return string
*/
public function hookPriceHtml($priceHtml, $product)
{
if ( ! ($product instanceof WC_Product)) {
return $priceHtml;
}
$context = $this->context;
if ( ! apply_filters("adp_get_price_html_is_mod_needed", true, $product, $context)) {
return $priceHtml;
}
if ( ! $this->priceHtmlIsModifyNeeded()) {
return $priceHtml;
}
$qty = floatval(1);
// only if bulk rule must override QTY input
if ($this->context->getOption('use_first_range_as_min_qty')) {
$args = $this->hookItemPageQtyArgs(array(), $product);
if (isset($args['input_value'])) {
$qty = (float)$args['input_value'];
}
}
// todo: mix and match can apply custom qty for sub bundle items
$processedProduct = $this->processor->calculateProduct($product, $qty);
$processedProduct = apply_filters("adp_get_price_html_processed_product", $processedProduct, $product, $qty);
if (is_null($processedProduct)) {
return $priceHtml;
}
$priceDisplay = ProductPriceDisplay::create($context, $processedProduct);
if ( $priceDisplay === null ) {
return $priceHtml;
}
$priceDisplay->withStriked(self::priceHtmlIsAllowToStrikethroughPrice($context));
return $priceDisplay->getFormattedPriceHtml($priceHtml);
}
/**
* @param bool $onSale
* @param WC_Product $product
*
* @return bool
*/
public function hookIsOnSale($onSale, $product)
{
if ( ! apply_filters("adp_get_price_html_is_mod_needed", true, $product, $this->context)) {
return $onSale;
}
$processedProduct = $this->processor->calculateProduct($product);
if (is_null($processedProduct)) {
return $onSale;
}
return $processedProduct->isDiscounted();
}
/**
* @param string $value
* @param WC_Product $product
*
* @return string|float
*/
public function hookGetSalePrice($value, $product)
{
if ( ! apply_filters("adp_get_price_html_is_mod_needed", true, $product, $this->context)) {
return $value;
}
$processed = $this->processor->calculateProduct($product);
if (is_null($processed)) {
return $value;
}
if ($processed->areRulesApplied()) {
if ($processed instanceof ProcessedProductSimple) {
if(defined('WOOCO_PREMIUM') && get_class($processed) == ProcessedProductContainer::class) {
return $value;
}
$value = $processed->getCalculatedPrice();
} elseif ($processed instanceof ProcessedVariableProduct) {
$value = $processed->getLowestPrice();
}
}
return $value;
}
/**
* @param string $value
* @param WC_Product $product
*
* @return string|float
*/
public function hookGetRegularPrice($value, $product)
{
if ( ! apply_filters("adp_get_price_html_is_mod_needed", true, $product, $this->context)) {
return $value;
}
$processed = $this->processor->calculateProduct($product);
if (is_null($processed)) {
return $value;
}
if ($processed->areRulesApplied()) {
if ($processed instanceof ProcessedProductSimple) {
$value = $processed->getOriginalPrice();
}
}
return $value;
}
/**
* @return bool
*/
public function priceHtmlIsModifyNeeded()
{
$context = $this->context;
return $context->is($context::WC_PRODUCT_PAGE) ? ! $context->getOption('do_not_modify_price_at_product_page',
false) : true;
}
/**
* @param Context $context
*
* @return bool
*/
public static function priceHtmlIsAllowToStrikethroughPrice($context)
{
return true;
}
/**
* @param string $cartSubtotalHtml
* @param bool $compound
* @param WC_Cart $wcCart
*
* @return string
*/
public function hookCartSubtotal($cartSubtotalHtml, $compound, $wcCart)
{
$context = $this->context;
// if ( ! $context->is( $context::WC_CART_PAGE ) ) {
// return $cartSubtotalHtml;
// }
// Compatible with GP Premium (GeneratePress theme)
if ( defined('GP_PREMIUM_VERSION') ) {
if (generatepress_wc_get_setting( 'cart_menu_item' )) {
return $cartSubtotalHtml;
}
}
if ($compound || $wcCart->is_empty()) {
return $cartSubtotalHtml;
}
$wcSessionFacade = new WcCustomerSessionFacade(WC()->session);
if ($this->context->getOption('regular_price_for_striked_price')) {
$initialTotals = $wcSessionFacade->getRegularTotals();
} else {
$initialTotals = $wcSessionFacade->getInitialTotals();
}
if ( ! empty($initialTotals['subtotal']) || ! empty($initialTotals['subtotal_tax'])) {
$initialCartSubtotal = $initialTotals['subtotal'];
$initialCartSubtotalTax = $initialTotals['subtotal_tax'];
} else {
return $cartSubtotalHtml;
}
$suffix = '';
if ($wcCart->display_prices_including_tax()) {
$initialCartSubtotal += $initialCartSubtotalTax;
$cartSubtotal = $wcCart->get_subtotal() + $wcCart->get_subtotal_tax();
if ($wcCart->get_subtotal_tax() > 0 && ! wc_prices_include_tax()) {
$suffix = ' ' . WC()->countries->inc_tax_or_vat() . '';
}
} else {
$cartSubtotal = $wcCart->get_subtotal();
if ($wcCart->get_subtotal_tax() > 0 && wc_prices_include_tax()) {
$suffix = ' ' . WC()->countries->ex_tax_or_vat() . '';
}
}
$initialCartSubtotal = apply_filters('wdp_initial_cart_subtotal', $initialCartSubtotal, $wcCart);
$cartSubtotal = apply_filters('wdp_cart_subtotal', $cartSubtotal, $wcCart);
if ($cartSubtotal < $initialCartSubtotal) {
$cartSubtotalHtml = $this->priceFunctions->formatSalePrice($initialCartSubtotal, $cartSubtotal) . $suffix;
}
return $cartSubtotalHtml;
}
/**
* @param $args
* @param $product
*
* @return array
* @throws \Exception
*/
public function hookItemPageQtyArgs($args, $product)
{
$context = $this->context;
if ( ! $this->context->is($context::WC_PRODUCT_PAGE)) {
return $args;
}
/** @var RangeDiscountTable $rangeDiscountTable */
$rangeDiscountTable = Factory::get("VolumePricingTable_RangeDiscountTable", $this->context, $this->engine);
if ( ! ($rule = $rangeDiscountTable->findRuleForProductTable($product))) {
return $args;
}
if ( ! ($handler = $rule->getProductRangeAdjustmentHandler())) {
return $args;
}
$ranges = $handler->getRanges();
$range = reset($ranges);
if ($range) {
$args['input_value'] = $range->getFrom(); // Start from this value (default = 1)
$args['min_value'] = $range->getFrom(); // Min quantity (default = 0)
}
return apply_filters( 'adp_quantity_input_args', $args, $product);
}
/**
* @param array $args
* @param WC_Product $product
* @param WC_Product_Variation $variation
*
* @return array
* @throws \Exception
*/
public function hookWcAvailableVariation($args, $product, $variation)
{
$newArgsParent = $this->hookItemPageQtyArgs(array(), $product);
$newArgs = $this->hookItemPageQtyArgs(array(), $variation);
if (isset($newArgs['min_value'])) {
$args['min_qty'] = $newArgs['min_value'];
} elseif (isset($newArgsParent['min_value'])) {
$args['min_qty'] = $newArgsParent['min_value'];
}
return $args;
}
}