PluginProbe
Tiered Pricing Table for WooCommerce / 8.1.0
Tiered Pricing Table for WooCommerce v8.1.0
8.1.0 8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 All 92 releases
tier-pricing-table / src / PricingTable.php

PricingTable.php in Tiered Pricing Table for WooCommerce 8.1.0, at src/PricingTable.php

648 lines 24.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable;
2
3 use TierPricingTable\Admin\ProductPage\AdvanceOptionsForVariableProduct;
4 use TierPricingTable\Admin\ProductPage\TieredPricingTab;
5 use TierPricingTable\Core\ServiceContainerTrait;
6 use TierPricingTable\Settings\Sections\GeneralSection\GeneralSection;
7 use WC_Product;
8 use WC_Product_Data_Store_CPT;
9 use WC_Product_Variable;
10 use WC_Product_Variation;
11
12 class PricingTable {
13
14 use ServiceContainerTrait;
15
16 private static $instance;
17
18 private function __construct() {}
19
20 public static function getInstance(): self {
21 if ( is_null( self::$instance ) ) {
22 self::$instance = new self();
23 }
24
25 return self::$instance;
26 }
27
28 /**
29 * Main function for rendering pricing table for product
30 *
31 * @param int $parentProductID
32 * @param int $variationID
33 * @param array $settings
34 */
35 public function renderPricingTable( $parentProductID = 0, $variationID = null, array $settings = array() ) {
36
37 if ( apply_filters( 'tiered_pricing_table/should_render_pricing_table', true, $parentProductID, $variationID,
38 $settings ) === false ) {
39 return;
40 }
41
42 $parentProduct = wc_get_product( $parentProductID );
43
44 if ( ! $parentProduct ) {
45 return;
46 }
47
48 $settings = wp_parse_args( $settings, $this->getDefaultSettings( $parentProductID ) );
49
50 // If the product is variable, but no specific variation is passed - check for default
51 if ( ! $variationID && TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) ) {
52 $variationID = $this->getDefaultVariation( $parentProduct );
53 }
54
55 $ajaxVariationsThreshold = apply_filters( 'tiered_pricing_table/ajax_variations_threshold', 10 );
56
57 // Use variation if exists, otherwise the parent product is used
58 $productId = $variationID ? $variationID : $parentProduct->get_id();
59 $product = wc_get_product( $productId );
60
61 $supportedTypes = array_merge( TierPricingTablePlugin::getSupportedSimpleProductTypes(),
62 TierPricingTablePlugin::getSupportedVariableProductTypes() );
63
64 // Exit if product is not valid
65 if ( ! $product || ! in_array( $parentProduct->get_type(), $supportedTypes ) ) {
66 return;
67 }
68
69 $settings = apply_filters( 'tiered_pricing_table/display_settings', $settings, $productId );
70
71 if ( 'tooltip' === $settings['display_type'] ) {
72 wp_enqueue_script( 'jquery-ui-tooltip' );
73 }
74
75 $hidden = ( 'tooltip' === $settings['display_type'] || ! $settings['display'] );
76
77 do_action( 'tiered_pricing_table/before_rendering_tiered_pricing', $parentProduct, $variationID, $settings );
78
79 $loadVariationByAjax = true;
80
81 ?>
82 <div class="clear"></div>
83 <div class="tpt__tiered-pricing <?php echo esc_attr( $hidden ? 'tpt__hidden' : '' ); ?>"
84 data-settings="<?php echo esc_attr( json_encode( $settings ) ); ?>"
85 data-display-context="<?php echo esc_attr( $settings['display_context'] ); ?>"
86 data-display-type="<?php echo esc_attr( $settings['display_type'] ); ?>"
87 data-product-id="<?php echo esc_attr( $parentProduct->get_id() ); ?>"
88 data-product-type="<?php echo esc_attr( $parentProduct->get_type() ); ?>"
89
90 <?php if ( TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) ) : ?>
91 <?php
92 $loadVariationByAjax = count( $parentProduct->get_children() ) > $ajaxVariationsThreshold;
93 $variableProductSamePrices = AdvanceOptionsForVariableProduct::isVariableProductSamePrices( $parentProduct->get_id() );
94 ?>
95 data-load-variation-by-ajax="<?php echo esc_attr( wc_bool_to_string( $loadVariationByAjax ) ); ?>"
96 data-variable-product-same-prices="<?php echo esc_attr( wc_bool_to_string( $variableProductSamePrices ) ); ?>"
97 <?php endif; ?>>
98
99 <?php $this->renderPricingTableHTML( $parentProduct, $product, $settings ); ?>
100 </div>
101
102 <?php if ( TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) && ! $loadVariationByAjax ) : ?>
103 <div class="tpt__tiered-pricing-preloaded-variations" style="display:none">
104 <?php foreach ( $parentProduct->get_available_variations( 'objects' ) as $childProduct ) : ?>
105 <div class="tiered-pricing-preloaded-variation"
106 data-variation-id="<?php echo esc_attr( $childProduct->get_id() ); ?>"
107 data-display-context="<?php echo esc_attr( $settings['display_context'] ); ?>">
108 <?php $this->renderPricingTableHTML( $parentProduct, $childProduct, $settings ); ?>
109 </div>
110 <?php endforeach; ?>
111 </div>
112 <?php endif; ?>
113 <?php
114 /**
115 * After the pricing layout and its wrapper. The savings badge uses it for its "after the table" position.
116 *
117 * @param WC_Product $parentProduct
118 * @param int|null $variationID
119 * @param array $settings
120 */
121 do_action( 'tiered_pricing_table/after_rendering_tiered_pricing', $parentProduct, $variationID, $settings );
122 }
123
124 /**
125 * Render pricing table without wrapper
126 *
127 * @param WC_Product $parentProduct
128 * @param WC_Product $product
129 * @param array $settings
130 */
131 public function renderPricingTableHTML(
132 WC_Product $parentProduct,
133 WC_Product $product,
134 array $settings = array()
135 ) {
136
137 $settings = wp_parse_args( $settings, $this->getDefaultSettings( $parentProduct->get_id() ) );
138
139 // Don't get rules from variable product when variation is empty
140 if ( in_array( $parentProduct->get_type(),
141 TierPricingTablePlugin::getSupportedVariableProductTypes() ) && ! ( $product instanceof WC_Product_Variation ) ) {
142 // Empty rule
143 $priceRule = new PricingRule( $product->get_id() );
144 } else {
145 $priceRule = PriceManager::getPricingRule( $product->get_id() );
146 }
147
148 // If rules are not empty
149 if ( ! empty( $priceRule->getRules() ) ) {
150
151 $displayType = $settings['display_type'];
152 $availableTemplates = TierPricingTablePlugin::getAvailablePricingLayouts();
153
154 $templateType = array_key_exists( $settings['display_type'], $availableTemplates ) ? $displayType : 'table';
155
156 if ( 'tooltip' === $displayType ) {
157 $templateType = 'table';
158 }
159
160 if ( 'table' === $displayType ) {
161 $style = $settings['table_style'];
162
163 if ( $style !== 'default' ) {
164 $templateType = 'table-' . $style;
165 } else {
166 $templateType = 'table';
167 }
168 }
169
170 if ( 'blocks' === $displayType ) {
171 $style = $settings['blocks_style'];
172
173 if ( $style !== 'default' ) {
174 $templateType = 'blocks-' . $style;
175 } else {
176 $templateType = 'blocks';
177 }
178 }
179
180 if ( 'options' === $displayType ) {
181 $style = $settings['options_style'];
182
183 if ( $style !== 'default' ) {
184 $templateType = 'options-' . $style;
185 } else {
186 $templateType = 'options';
187 }
188 }
189
190 // layouts with a single style option: dropdown, plain text
191 $styledLayouts = array(
192 'dropdown' => 'dropdown_style',
193 'plain-text' => 'plain_text_style',
194 );
195
196 if ( isset( $styledLayouts[ $displayType ] ) ) {
197 $style = $settings[ $styledLayouts[ $displayType ] ] ?? 'default';
198 $templateType = 'default' !== $style && '' !== $style ? $displayType . '-' . $style : $displayType;
199 }
200
201 $template = "tiered-pricing-$templateType.php";
202
203 do_action( 'tiered_pricing_table/before_rendering_tiered_pricing/inner', $priceRule, $product, $settings );
204
205 $this->checkForDuplicateQuantities( $priceRule );
206
207 $this->getContainer()->getFileManager()->includeTemplate( 'frontend/' . $template, array(
208 'pricing_rule' => $priceRule,
209 'price_rules' => $priceRule->getRules(),
210 'real_price' => $product->get_price(),
211 'product_name' => $product->get_name(),
212 'product_id' => $product->get_id(),
213 'product' => $product,
214 'minimum' => $priceRule->getMinimum( true ),
215 'settings' => $settings,
216 'pricing_type' => $priceRule->getType(),
217 'id' => $this->getUniqueTieredPricingId(),
218 ) );
219
220 $this->renderMoreTiersLink( $parentProduct );
221 } else {
222 // Provide fallback data for products without pricing rules,
223 // which allows the frontend JS (e.g. dynamic totals, you save, etc.)
224 // to function for all products via the 'tiered_price_update' event.
225 $price_incl_taxes = wc_get_price_including_tax( $product, array(
226 'price' => $product->get_price(),
227 ) );
228
229 $price_excl_taxes = wc_get_price_excluding_tax( $product, array(
230 'price' => $product->get_price(),
231 ) );
232
233 $sale_price = $product->get_sale_price();
234
235 if ( $sale_price ) {
236 $sale_price = wc_get_price_to_display( $product, array(
237 'price' => $sale_price,
238 ) );
239 }
240
241 $regular_price = wc_get_price_to_display( $product, array(
242 'price' => $product->get_regular_price(),
243 ) );
244
245 $price = wc_get_price_to_display( $product, array(
246 'price' => $product->get_price(),
247 ) );
248
249 ?>
250 <div style="display:none;"
251 class="tiered-pricing-fallback-data"
252 data-product-id="<?php echo esc_attr( $product->get_id() ); ?>"
253 data-price-rules="{}"
254 data-minimum="<?php echo esc_attr( $priceRule->getMinimum( true ) ); ?>"
255 data-product-name="<?php echo esc_attr( $product->get_name() ); ?>"
256 data-regular-price="<?php echo esc_attr( $regular_price ); ?>"
257 data-sale-price="<?php echo esc_attr( $sale_price ); ?>"
258 data-price="<?php echo esc_attr( $price ); ?>"
259 data-product-price-suffix="<?php echo esc_attr( $product->get_price_suffix() ); ?>"
260 data-tiered-price="<?php echo esc_attr( $price ); ?>"
261 data-tiered-price-exclude-taxes="<?php echo esc_attr( $price_excl_taxes ); ?>"
262 data-tiered-price-include-taxes="<?php echo esc_attr( $price_incl_taxes ); ?>"
263 ></div>
264 <?php
265 }
266 }
267
268 /**
269 * Discount of a tier as the store shows it: a percentage, the amount saved per item, or both.
270 *
271 * @param float $percent Discount in percent, as the templates compute it.
272 * @param PricingRule $pricingRule
273 * @param WC_Product $product
274 * @param int|null $quantity Tier quantity, or null for the base tier (its discount is the sale price).
275 * @param array $settings Renderer settings.
276 * @param string $style 'plain' ("10%", "$4.50", "10% ($4.50)") or 'off' ("10% off", "$4.50 off", "10% off, save $4.50").
277 *
278 * @return string HTML
279 */
280 /**
281 * Decimals shown on a percentage discount ("10.14%" with 2, "10%" with 0).
282 *
283 * @param float $percent The discount percentage.
284 * @param WC_Product|null $product
285 * @param PricingRule|null $pricingRule
286 *
287 * @return int
288 */
289 public static function discountDecimals( float $percent, ?WC_Product $product = null, ?PricingRule $pricingRule = null ): int {
290 /**
291 * Filter the number of decimals of displayed percentage discounts.
292 *
293 * @param int $decimals Default 2. Return 0 to show whole numbers.
294 * @param float $percent The discount percentage before rounding.
295 * @param WC_Product|null $product
296 * @param PricingRule|null $pricingRule
297 */
298 $decimals = apply_filters( 'tiered_pricing_table/discount/decimals', 2, $percent, $product, $pricingRule );
299
300 return max( 0, (int) $decimals );
301 }
302
303 public static function formatDiscount(
304 float $percent,
305 PricingRule $pricingRule,
306 WC_Product $product,
307 ?int $quantity,
308 array $settings,
309 string $style = 'plain'
310 ): string {
311 $format = $settings['discount_format'] ?? 'percentage';
312 $format = in_array( $format, array( 'percentage', 'amount', 'both' ), true ) ? $format : 'percentage';
313
314 $percentLabel = round( $percent, self::discountDecimals( $percent, $product, $pricingRule ) ) . '%';
315 $amountLabel = '';
316
317 if ( 'percentage' !== $format ) {
318 $saving = 0;
319
320 if ( null === $quantity ) {
321 if ( $product->is_on_sale() ) {
322 $saving = (float) $product->get_regular_price() - (float) $product->get_sale_price();
323 }
324 } else {
325 $base = CalculationLogic::calculateDiscountBasedOnRegularPrice() ? $product->get_regular_price() : $product->get_price();
326 $saving = (float) $base - (float) $pricingRule->getTierPrice( $quantity, false );
327 }
328
329 $amountLabel = wc_price( wc_get_price_to_display( $product, array( 'price' => max( 0, $saving ) ) ) );
330 }
331
332 if ( 'off' === $style ) {
333 switch ( $format ) {
334 case 'amount':
335 // translators: %s: amount saved per item
336 return sprintf( __( '%s off', 'tier-pricing-table' ), $amountLabel );
337 case 'both':
338 // translators: 1: discount percentage, 2: amount saved per item
339 return sprintf( __( '%1$s off, save %2$s', 'tier-pricing-table' ), $percentLabel, $amountLabel );
340 default:
341 // translators: %s: discount percentage
342 return sprintf( __( '%s off', 'tier-pricing-table' ), $percentLabel );
343 }
344 }
345
346 switch ( $format ) {
347 case 'amount':
348 return $amountLabel;
349 case 'both':
350 return $percentLabel . ' (' . $amountLabel . ')';
351 default:
352 return $percentLabel;
353 }
354 }
355
356 /**
357 * The "Spacing" and "Cell padding" options as a style attribute for a layout's root element: CSS
358 * variables the stylesheet reads for the gap between tiers and the padding inside table cells.
359 * Empty when neither option is set (each style keeps its own spacing and padding).
360 */
361 public static function layoutStyleAttribute( array $settings ): string {
362 $variables = array();
363
364 foreach ( array( 'layout_spacing' => '--tiered-pricing-gap', 'cell_padding' => '--tiered-pricing-cell-padding' ) as $option => $variable ) {
365 $value = $settings[ $option ] ?? '';
366
367 if ( '' === $value || null === $value || ! is_numeric( $value ) ) {
368 continue;
369 }
370
371 $variables[] = $variable . ': ' . max( 0, min( 80, (int) $value ) ) . 'px';
372 }
373
374 return $variables ? ' style="' . esc_attr( implode( '; ', $variables ) ) . '"' : '';
375 }
376
377 /**
378 * @deprecated 7.1.9 Use layoutStyleAttribute().
379 */
380 public static function spacingAttribute( array $settings ): string {
381 return self::layoutStyleAttribute( $settings );
382 }
383
384 /**
385 * Tiers the last rendered layout left out because of the "tiers per grid item" limit.
386 */
387 protected static $hiddenTiers = 0;
388
389 /**
390 * Tier rows in the configured order (ascending by quantity, or the biggest discount first), cut to
391 * the "tiers per grid item" limit when one is set.
392 *
393 * @param string[] $rows Rendered rows, ascending.
394 * @param array $settings Renderer settings.
395 *
396 * @return string HTML (the rows were escaped when rendered)
397 */
398 public static function orderTiers( array $rows, array $settings ): string {
399 if ( 'desc' === ( $settings['tiers_order'] ?? 'asc' ) ) {
400 $rows = array_reverse( $rows );
401 }
402
403 $limit = (int) ( $settings['tiers_limit'] ?? 0 );
404 self::$hiddenTiers = 0;
405
406 if ( $limit > 0 && count( $rows ) > $limit ) {
407 self::$hiddenTiers = count( $rows ) - $limit;
408 $rows = array_slice( $rows, 0, $limit );
409 }
410
411 return implode( '', $rows );
412 }
413
414 /**
415 * The "+N more tiers" link to the product page, after a layout the limit shortened.
416 */
417 protected function renderMoreTiersLink( WC_Product $parentProduct ) {
418 $hidden = self::$hiddenTiers;
419 self::$hiddenTiers = 0;
420
421 if ( $hidden < 1 ) {
422 return;
423 }
424
425 /* translators: %d: number of tiers not listed */
426 $label = sprintf( _n( '+%d more tier', '+%d more tiers', $hidden, 'tier-pricing-table' ), $hidden );
427
428 echo '<a class="tiered-pricing-more-tiers" href="' . esc_url( $parentProduct->get_permalink() ) . '">' . esc_html( $label ) . '</a>';
429 }
430
431 protected function getDefaultSettings( $productId = false ): array {
432 $hasRules = true;
433
434 if ( $productId ) {
435 $product = wc_get_product( $productId );
436 if ( $product ) {
437 $hasRules = $this->productHasPricingRules( $product );
438 }
439 }
440
441 $showTotalPrice = $hasRules ? $this->getContainer()->getSettings()->get( 'show_total_price',
442 'no' ) === 'yes' : $this->getContainer()->getSettings()->get( 'show_total_price_non_tiered',
443 'no' ) === 'yes';
444
445 $settings = array(
446 'display_context' => 'product-page',
447 'display' => GeneralSection::isAutomaticDisplayEnabled(),
448 'display_type' => $this->getContainer()->getSettings()->get( 'display_type', 'table' ),
449 'title' => $this->getContainer()->getSettings()->get( 'table_title', '' ),
450 'table_class' => $this->getContainer()->getSettings()->get( 'table_css_class', '' ),
451 'quantity_column_title' => $this->getContainer()->getSettings()->get( 'head_quantity_text',
452 __( 'Quantity', 'tier-pricing-table' ) ),
453 'price_column_title' => $this->getContainer()->getSettings()->get( 'head_price_text',
454 __( 'Price', 'tier-pricing-table' ) ),
455 'discount_column_title' => $this->getContainer()->getSettings()->get( 'head_discount_text',
456 __( 'Discount (%)', 'tier-pricing-table' ) ),
457 'quantity_type' => $this->getContainer()->getSettings()->get( 'quantity_type', 'range' ),
458 'show_discount_column' => $this->getContainer()->getSettings()->get( 'show_discount_column',
459 'yes' ) === 'yes',
460 'clickable_rows' => $this->getContainer()->getSettings()->get( 'clickable_table_rows',
461 'yes' ) === 'yes',
462 'active_tier_color' => $this->getContainer()->getSettings()->get( 'selected_quantity_color',
463 '#3858e9' ),
464 'discount_format' => $this->getContainer()->getSettings()->get( 'discount_format', 'percentage' ),
465 'layout_spacing' => $this->getContainer()->getSettings()->get( 'layout_spacing', '' ),
466 'cell_padding' => $this->getContainer()->getSettings()->get( 'cell_padding', '' ),
467 'discount_badge_color' => $this->getContainer()->getSettings()->get( 'discount_badge_color', '' ),
468 'active_tier_border' => $this->getContainer()->getSettings()->get( 'table_active_border', 'yes' ),
469 'tiers_order' => $this->getContainer()->getSettings()->get( 'tiers_order', 'asc' ),
470 'tooltip_border' => $this->getContainer()->getSettings()->get( 'tooltip_border',
471 'yes' ) === 'yes',
472
473 'table_style' => GeneralSection::getPricingTableStyle(),
474 'compact_layout' => $this->getContainer()->getSettings()->get( 'compact_layout', 'no' ),
475 'blocks_style' => GeneralSection::getPricingBlocksStyle(),
476 'options_style' => GeneralSection::getPricingOptionsStyle(),
477
478 'dropdown_style' => GeneralSection::getPricingDropdownStyle(),
479 'plain_text_style' => GeneralSection::getPricingPlainTextStyle(),
480
481 'options_show_total' => GeneralSection::isShowOptionTotal(),
482 'options_show_original_product_price' => GeneralSection::isShowOriginalProductPrice(),
483 'options_show_default_option' => GeneralSection::isDefaultOptionEnabled(),
484
485 'options_default_option_text' => GeneralSection::getDefaultOptionText(),
486 'options_option_text' => GeneralSection::getOptionText(),
487
488 'plain_text_show_default_option' => GeneralSection::isPlainTextFirstTierEnabled(),
489 'plain_text_option_text' => GeneralSection::getPlainTextTemplate(),
490 'plain_text_default_option_text' => GeneralSection::getPlainTextFirstTierTemplate(),
491
492 'update_price_on_product_page' => $this->getContainer()->getSettings()->get( 'update_price_on_product_page',
493 'yes' ) === 'yes',
494 'show_tiered_price_as_discount' => $this->getContainer()->getSettings()->get( 'show_tiered_price_as_discount',
495 'yes' ) === 'yes',
496 'show_total_price' => $showTotalPrice,
497 );
498
499 $default_quantity_measurement = array(
500 'singular' => '',
501 'plural' => '',
502 );
503
504 $quantity_measurement = $default_quantity_measurement;
505
506 if ( in_array( $settings['display_type'], array( 'table', 'horizontal-table', 'tooltip' ) ) ) {
507 $quantity_measurement = $this->getContainer()->getSettings()->get( 'table_quantity_measurement',
508 $default_quantity_measurement );
509 }
510
511 if ( 'blocks' === $settings['display_type'] ) {
512 $quantity_measurement = $this->getContainer()->getSettings()->get( 'blocks_quantity_measurement', array(
513 'singular' => _n( 'piece', 'pieces', 1, 'tier-pricing-table' ),
514 'plural' => _n( 'piece', 'pieces', 2, 'tier-pricing-table' ),
515 ) );
516 }
517
518 $settings['quantity_measurement_singular'] = $quantity_measurement['singular'];
519 $settings['quantity_measurement_plural'] = $quantity_measurement['plural'];
520
521 if ( $productId ) {
522 $template = TieredPricingTab::getProductTemplate( $productId );
523
524 if ( 'default' !== $template ) {
525 $settings['display_type'] = $template;
526 }
527
528 $baseUnitName = TieredPricingTab::getProductBaseUnitName( $productId );
529
530 if ( $baseUnitName['singular'] && $baseUnitName['plural'] ) {
531 $settings['quantity_measurement_singular'] = $baseUnitName['singular'];
532 $settings['quantity_measurement_plural'] = $baseUnitName['plural'];
533 }
534 }
535
536 return $settings;
537 }
538
539 protected function getUniqueTieredPricingId() {
540 return preg_replace( '/[0-9]+/', '', strtolower( wp_generate_password( 20, false ) ) );
541 }
542
543 protected function getDefaultVariation( WC_Product_Variable $product ): ?int {
544
545 $defaultVariation = AdvanceOptionsForVariableProduct::getDefaultVariation( $product->get_id() );
546
547 if ( $defaultVariation ) {
548 return $defaultVariation->get_id();
549 }
550
551 $defaultAttributes = $product->get_default_attributes();
552
553 if ( ! empty( $defaultAttributes ) ) {
554
555 $defaultAttributesKeys = array_map( function ( $attribute ) {
556 return 'attribute_' . $attribute;
557 }, array_keys( $defaultAttributes ) );
558
559 $defaultAttributes = array_combine( $defaultAttributesKeys, $defaultAttributes );
560
561 return ( new WC_Product_Data_Store_CPT() )->find_matching_product_variation( $product, $defaultAttributes );
562 }
563
564 return null;
565 }
566
567 public function productHasPricingRules( WC_Product $product ): bool {
568
569 if ( TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
570 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
571
572 return ! empty( $pricingRule->getRules() );
573 }
574
575 if ( TierPricingTablePlugin::isVariableProductSupported( $product ) && $product instanceof WC_Product_Variable ) {
576
577 // Do not check if product has tiered pricing for variable product by default.
578 // If a variable product has many variations, it can take many resources to check every variation for rules existing.
579 // The downside is that the plugin will send an AJAX request for each variant selection on the product page even if a product does not have any tiered pricing
580 if ( ! apply_filters( 'tiered_pricing_table/check_if_variable_product_has_rules', false, $product ) ) {
581 return true;
582 }
583
584 $hasTieredPricing = $this->getContainer()->getCache()->getProductData( $product, 'product_has_rules' );
585
586 if ( 'no' === $hasTieredPricing ) {
587 return false;
588 }
589
590 if ( 'yes' === $hasTieredPricing ) {
591 return true;
592 }
593
594 foreach ( $product->get_available_variations() as $productVariation ) {
595 $pricingRule = PriceManager::getPricingRule( $productVariation['variation_id'] );
596
597 if ( ! empty( $pricingRule->getRules() ) ) {
598 $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'yes' );
599
600 return true;
601 }
602 }
603
604 $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'no' );
605 }
606
607 return false;
608 }
609
610 public function checkForDuplicateQuantities( PricingRule $pricingRule ) {
611 // Show notice only for admin users
612 if ( ! current_user_can( 'manage_options' ) ) {
613 return;
614 }
615 $minimum = $pricingRule->getMinimum( true );
616 $pricingRules = $pricingRule->getRules();
617 $firstRuleQuantity = ! empty( $pricingRules ) ? array_keys( $pricingRules )[0] : false;
618
619 if ( $firstRuleQuantity && $firstRuleQuantity <= $minimum ) {
620 ?>
621
622 <div style="font-size: .8em;">
623 <div class="woocommerce-error" role="alert" style="margin-bottom: 0">
624 <?php esc_html_e( 'Pricing Conflict Detected: Your Minimum Order Quantity is equal to or greater than your first pricing tier.',
625 'tier-pricing-table' ); ?>
626 </div>
627 <div style="color: #555;
628 padding: 10px 12px;
629 border: 1px solid #b32c2e;
630 background: #fff5f5;">
631 <p style="padding: 0; margin: 0 0 10px 0;">
632 <?php esc_html_e( 'To fix this, please ensure your first tiered pricing rule starts at a higher quantity than your Minimum Order Quantity.',
633 'tier-pricing-table' ); ?>
634 </p>
635 <p style="padding: 0; margin: 0">
636 <strong><?php esc_html_e( 'Why?',
637 'tier-pricing-table' ); ?></strong> <?php esc_html_e( 'The plugin automatically creates a base tier using your standard WooCommerce product price. This base tier covers any quantities ordered between the Minimum Order Quantity and your first discounted tier.',
638 'tier-pricing-table' ); ?>
639 </p>
640 <br>
641 <b><?php esc_html_e( 'This notice is only visible to store administrators.',
642 'tier-pricing-table' ); ?></b>
643 </div>
644 </div>
645 <?php
646 }
647 }
648 }