rules ) { $this->rules = Rules::runtime(); } return $this->rules; } public function privacy(): void { $post = get_post(); $content = $post ? $post->post_content : ''; if ( ! is_product() && ! is_shop() && ! is_product_taxonomy() && ! str_contains( $content, 'wp:woocommerce/' ) && ! preg_match( '/\[(?:products|product_page|product_category|sale_products|featured_products|recent_products|best_selling_products|top_rated_products)\b/', $content ) ) { return; } foreach ( $this->rules() as $rule ) { if ( ! empty( $rule['enabled'] ) && ( ! empty( $rule['show_single'] ) || ! empty( $rule['show_archive'] ) ) ) { $this->private_response(); break; } } } private function private_response(): bool { if ( ! $this->private_response && ! headers_sent() ) { nocache_headers(); $this->private_response = true; } return $this->private_response; } public function assets(): void { foreach ( $this->rules() as $rule ) { if ( ! empty( $rule['enabled'] ) && ( ! empty( $rule['show_single'] ) || ! empty( $rule['show_archive'] ) ) ) { wp_enqueue_style( 'digidiscount-store', plugins_url( 'assets/store.css', DIGIDISCOUNT_FILE ), array(), DIGIDISCOUNT_VERSION ); break; } } } private function context(): array { if ( WC()->cart ) { return Cart::context( WC()->cart ); } $user = wp_get_current_user(); return apply_filters( 'digidiscount_cart_context', array( 'currency' => get_woocommerce_currency(), 'now' => time(), 'coupons' => array(), 'user_id' => $user->ID, 'roles' => $user->roles, 'gateway' => '' ), null ); } private function line( $product, int $quantity ): array { $id = $product->get_parent_id() ?: $product->get_id(); $categories = wc_get_product_term_ids( $id, 'product_cat' ); foreach ( $categories as $category ) { $categories = array_merge( $categories, get_ancestors( $category, 'product_cat', 'taxonomy' ) ); } $price = wc_prices_include_tax() ? wc_get_price_including_tax( $product, array( 'qty' => $quantity ) ) : (float) $product->get_price() * $quantity; return array( 'key' => 'display', 'product_id' => $id, 'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0, 'qty' => $quantity, 'total' => (int) round( $price * 10 ** wc_get_price_decimals() ), 'categories' => array_unique( array_map( 'intval', $categories ) ), 'tags' => wc_get_product_term_ids( $id, 'product_tag' ), 'brands' => taxonomy_exists( 'product_brand' ) ? wc_get_product_term_ids( $id, 'product_brand' ) : array(), 'sale' => $product->is_on_sale(), 'gift' => false ); } private function eligible( array $rule, array $line, array $context ): bool { if ( empty( $rule['enabled'] ) || $rule['currency'] !== $context['currency'] || ! Engine::eligible( $line, $rule ) || ( $rule['start'] && $context['now'] < $rule['start'] ) || ( $rule['end'] && $context['now'] >= $rule['end'] ) || ( empty( $rule['allow_coupons'] ) && $context['coupons'] ) ) { return false; } $relaxed = apply_filters( 'digidiscount_display_rule', $rule ); return (bool) apply_filters( 'digidiscount_rule_matches', true, $relaxed, array( 'display' => $line ), $context ); } private function quotable( array $rule ): bool { $quotable = ! $rule['min_total'] && ! $rule['max_discount'] && 'fixed' !== $rule['kind'] && in_array( $rule['type'], array( 'product', 'category', 'cart', 'threshold' ), true ); return (bool) apply_filters( 'digidiscount_display_quotable', $quotable, $rule ); } private function quantity( array $rule ): int { $quantity = max( 1, (int) ceil( $rule['min_qty'] ) ); return max( 1, (int) apply_filters( 'digidiscount_display_quantity', $quantity, $rule ) ); } public function quote( $product, string $place, ?array $rules = null ): array { if ( ! $product->is_type( array( 'simple', 'variation' ) ) || '' === $product->get_price() || ! wc_coupons_enabled() ) { return array(); } $rules = $rules ?? $this->rules(); $context = $this->context(); foreach ( $context['coupons'] as $code ) { if ( ( new \WC_Coupon( $code ) )->get_individual_use() ) { return array(); } } $candidates = array(); $fallback = array(); $index = array_column( $rules, null, 'id' ); foreach ( $rules as $rule ) { if ( empty( $rule[ 'show_' . $place ] ) ) { continue; } $scenarios = apply_filters( 'digidiscount_display_scenarios', array( $context ), $rule, $context ); $quantity = $this->quantity( $rule ); if ( $quantity > 10000 || ( $product->is_sold_individually() && $quantity > 1 ) ) { continue; } $line = $this->line( $product, $quantity ); foreach ( $scenarios as $scenario ) { if ( ! $this->eligible( $rule, $line, $scenario ) ) { continue; } $conditions = array(); if ( $quantity > 1 ) { /* translators: %d: Number of eligible product units. */ $conditions[] = sprintf( __( 'Price for %d items', 'digidiscount' ), $quantity ); } if ( $rule['max_qty'] ) { /* translators: %d: Maximum qualifying item quantity. */ $conditions[] = sprintf( __( 'Up to %d items', 'digidiscount' ), (int) $rule['max_qty'] ); } $conditions = apply_filters( 'digidiscount_display_conditions', $conditions, $scenario, array( $rule ) ); if ( ! $this->quotable( $rule ) ) { $fallback[] = array( 'kind' => 'conditions', 'name' => $rule['name'], 'conditions' => array_merge( $conditions, $this->requirements( $rule ) ), 'message' => $rule['message'], 'priority' => $rule['priority'] ); continue; } $result = Engine::calculate( array( 'display' => $line ), $rules, $scenario, get_option( 'digidiscount_mode', 'best' ) ); $discount = $result['discounts']['display'] ?? 0; if ( ! $discount || ! $line['total'] ) { continue; } $visible = true; $messages = array(); $applied_rules = array(); $maximum = 0; foreach ( $result['rules'] as $entry ) { $applied = $index[ $entry['id'] ]; $visible = $visible && ! empty( $applied[ 'show_' . $place ] ) && $this->quotable( $applied ); $applied_rules[] = $applied; if ( $applied['max_qty'] ) { $maximum = $maximum ? min( $maximum, (int) $applied['max_qty'] ) : (int) $applied['max_qty']; } if ( $applied['message'] ) { $messages[] = $applied['message']; } } if ( ! $visible ) { continue; } $conditions = array(); if ( $quantity > 1 ) { /* translators: %d: Number of eligible product units. */ $conditions[] = sprintf( __( 'Price for %d items', 'digidiscount' ), $quantity ); } if ( $maximum ) { /* translators: %d: Maximum qualifying item quantity. */ $conditions[] = sprintf( __( 'Up to %d items', 'digidiscount' ), $maximum ); } $conditions = apply_filters( 'digidiscount_display_conditions', $conditions, $scenario, $applied_rules ); $old = wc_get_price_to_display( $product, array( 'qty' => $quantity ) ); $new = max( 0, $old * ( 1 - $discount / $line['total'] ) ); $candidates[] = array( 'kind' => 'price', 'old' => $old, 'new' => $new, 'qty' => $quantity, 'percent' => round( $discount / $line['total'] * 100, 1 ), 'name' => implode( ' + ', array_column( $result['rules'], 'name' ) ), 'conditions' => $conditions, 'message' => implode( ' ', array_unique( $messages ) ), 'conditional' => (bool) $conditions ); } } if ( $candidates ) { usort( $candidates, static fn( $a, $b ) => ( $a['new'] / $a['qty'] <=> $b['new'] / $b['qty'] ) ?: ( $a['qty'] <=> $b['qty'] ) ); return $candidates[0]; } if ( $fallback ) { usort( $fallback, static fn( $a, $b ) => $a['priority'] <=> $b['priority'] ); return $fallback[0]; } return array(); } private function requirements( array $rule ): array { $conditions = array(); $money = static fn( $value ) => html_entity_decode( wp_strip_all_tags( wc_price( $value / 10 ** wc_get_price_decimals() ) ), ENT_QUOTES, 'UTF-8' ); if ( $rule['min_total'] ) { /* translators: %s: Formatted minimum purchase amount, including currency. */ $conditions[] = sprintf( __( 'Minimum purchase: %s', 'digidiscount' ), $money( $rule['min_total'] ) ); } if ( $rule['max_discount'] ) { /* translators: %s: Formatted maximum discount, including currency. */ $conditions[] = sprintf( __( 'Maximum saving per order: %s', 'digidiscount' ), $money( $rule['max_discount'] ) ); } $offer = array(); if ( 'percent' === $rule['kind'] ) { /* translators: %s: Localized discount percentage, without the percent sign. */ $offer[] = sprintf( __( 'Save %s%% on eligible items.', 'digidiscount' ), wc_format_localized_decimal( $rule['amount'] ) ); } else { /* translators: %s: Formatted campaign discount, including currency. */ $offer[] = sprintf( __( 'Campaign saving: %s. Final allocation is calculated in your cart.', 'digidiscount' ), $money( $rule['amount'] ) ); } $conditions = array_merge( $conditions, apply_filters( 'digidiscount_display_offer', $offer, $rule ) ); return apply_filters( 'digidiscount_display_requirements', $conditions, $rule ); } public function markup( array $quote ): string { if ( ! $quote ) { return ''; } $html = '' . esc_html( $quote['name'] ) . ''; if ( 'price' === $quote['kind'] && $quote['percent'] > 0 ) { $html .= '−' . esc_html( wc_format_localized_decimal( $quote['percent'] ) ) . '%'; } $html .= ''; if ( 'price' === $quote['kind'] ) { $old = $quote['old_html'] ?? wc_price( $quote['old'] ); $new = $quote['new_html'] ?? wc_price( $quote['new'] ); $old_label = __( 'Price before this campaign', 'digidiscount' ) . ': ' . html_entity_decode( wp_strip_all_tags( $old ), ENT_QUOTES, 'UTF-8' ); $new_label = __( 'Campaign price', 'digidiscount' ) . ': ' . html_entity_decode( wp_strip_all_tags( $new ), ENT_QUOTES, 'UTF-8' ); $html .= '' . esc_html__( 'Campaign price', 'digidiscount' ) . '' . wp_kses_post( $old ) . '' . wp_kses_post( $new ) . ''; } foreach ( $quote['conditions'] as $condition ) { $html .= '' . esc_html( $condition ) . ''; } if ( $quote['message'] ) { $html .= '' . esc_html( $quote['message'] ) . ''; } $html .= '' . esc_html__( 'Applied in your cart. Other items and coupons can change the final offer.', 'digidiscount' ) . ''; return $html; } public function render( string $html, $product, string $place ): string { if ( $product->is_type( 'variable' ) ) { $children = $product->get_visible_children(); $old = array(); $new = array(); $names = array(); $any = false; $conditional = false; foreach ( array_slice( $children, 0, 100 ) as $id ) { $variation = wc_get_product( $id ); if ( ! $variation || '' === $variation->get_price() || ! $variation->variation_is_visible() || ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $variation->is_in_stock() ) ) { continue; } $price = wc_get_price_to_display( $variation ); $quote = $this->quote( $variation, $place ); $old[] = $price; $new[] = $quote && 'price' === $quote['kind'] && 1 === $quote['qty'] ? $quote['new'] : $price; if ( $quote ) { $any = true; $names[] = $quote['name']; $conditional = $conditional || 'price' !== $quote['kind'] || ! empty( $quote['conditional'] ); } } if ( ! $any || ! $old ) { return $html; } $choice = __( 'Choose an option to see its campaign price and conditions.', 'digidiscount' ); if ( $conditional || count( $children ) > 100 || ( min( $old ) === min( $new ) && max( $old ) === max( $new ) ) ) { return $html . $this->markup( array( 'kind' => 'conditions', 'name' => __( 'Campaign available', 'digidiscount' ), 'conditions' => array( $choice ), 'message' => '' ) ); } $range = static fn( $values ) => min( $values ) === max( $values ) ? wc_price( min( $values ) ) : wc_format_price_range( min( $values ), max( $values ) ); return $this->markup( array( 'kind' => 'price', 'name' => count( array_unique( $names ) ) === 1 ? $names[0] : __( 'Campaign prices', 'digidiscount' ), 'old_html' => $range( $old ), 'new_html' => $range( $new ), 'percent' => 0, 'conditions' => array( $choice ), 'message' => '' ) ) . $product->get_price_suffix(); } $quote = $this->quote( $product, $place ); if ( ! $quote ) { return $html; } $suffix = 'price' === $quote['kind'] ? $product->get_price_suffix( (float) $product->get_price() * $quote['new'] / $quote['old'], $quote['qty'] ) : ''; return ( 'price' === $quote['kind'] ? '' : $html ) . $this->markup( $quote ) . $suffix; } public function price_html( $html, $product ): string { if ( $this->rendering || is_admin() || wp_doing_cron() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || is_cart() || is_checkout() || ! $html ) { return $html; } $place = is_product() && ( $product->get_parent_id() ?: $product->get_id() ) === get_queried_object_id() ? 'single' : 'archive'; $this->rendering = true; try { $rendered = $this->render( $html, $product, $place ); if ( $rendered !== $html && ! $this->private_response() ) { return $html; } return $rendered; } finally { $this->rendering = false; } } public function variation( $data, $parent, $variation ): array { if ( ( is_admin() && ! wp_doing_ajax() ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { return $data; } $this->rendering = true; try { $base = $variation->get_price_html(); if ( '' === $base ) { $data['price_html'] = ''; return $data; } $html = $this->render( $base, $variation, 'single' ); if ( $html !== $base && $this->private_response() ) { $data['price_html'] = '' . $html . ''; } elseif ( str_contains( $data['price_html'] ?? '', 'dd-campaign-price' ) ) { $data['price_html'] = '' . $base . ''; } } finally { $this->rendering = false; } return $data; } }