PluginProbe
Tiered Pricing Table for WooCommerce / 6.1.0
Tiered Pricing Table for WooCommerce v6.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 2.3.3 All 91 releases
tier-pricing-table / src / PricingTable.php

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

389 lines 14.3 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 if ( ! $this->productHasPricingRules( $parentProduct ) ) {
49 return;
50 }
51
52 $settings = wp_parse_args( $settings, $this->getDefaultSettings( $parentProductID ) );
53
54 // If the product is variable, but no specific variation is passed - check for default
55 if ( ! $variationID && TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) ) {
56 $variationID = $this->getDefaultVariation( $parentProduct );
57 }
58
59 $ajaxVariationsThreshold = apply_filters( 'tiered_pricing_table/ajax_variations_threshold', 10 );
60
61 // Use variation if exists, otherwise the parent product is used
62 $productId = $variationID ? $variationID : $parentProduct->get_id();
63 $product = wc_get_product( $productId );
64
65 $supportedTypes = array_merge( TierPricingTablePlugin::getSupportedSimpleProductTypes(),
66 TierPricingTablePlugin::getSupportedVariableProductTypes() );
67
68 // Exit if product is not valid
69 if ( ! $product || ! in_array( $parentProduct->get_type(), $supportedTypes ) ) {
70 return;
71 }
72
73 $settings = apply_filters( 'tiered_pricing_table/display_settings', $settings, $productId );
74
75 if ( 'tooltip' === $settings['display_type'] ) {
76 wp_enqueue_script( 'jquery-ui-tooltip' );
77 }
78
79 $hidden = ( 'tooltip' === $settings['display_type'] || ! $settings['display'] );
80
81 do_action( 'tiered_pricing_table/before_rendering_tiered_pricing', $parentProduct, $variationID, $settings );
82
83 $loadVariationByAjax = true;
84
85 ?>
86 <div class="clear"></div>
87 <div class="tpt__tiered-pricing <?php echo esc_attr( $hidden ? 'tpt__hidden' : '' ); ?>"
88 data-settings="<?php echo esc_attr( json_encode( $settings ) ); ?>"
89 data-display-context="<?php echo esc_attr( $settings['display_context'] ); ?>"
90 data-display-type="<?php echo esc_attr( $settings['display_type'] ); ?>"
91 data-product-id="<?php echo esc_attr( $parentProduct->get_id() ); ?>"
92 data-product-type="<?php echo esc_attr( $parentProduct->get_type() ); ?>"
93
94 <?php if ( TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) ) : ?>
95 <?php
96 $loadVariationByAjax = count( $parentProduct->get_children() ) > $ajaxVariationsThreshold;
97 $variableProductSamePrices = AdvanceOptionsForVariableProduct::isVariableProductSamePrices( $parentProduct->get_id() );
98 ?>
99 data-load-variation-by-ajax="<?php echo esc_attr( wc_bool_to_string( $loadVariationByAjax ) ); ?>"
100 data-variable-product-same-prices="<?php echo esc_attr( wc_bool_to_string( $variableProductSamePrices ) ); ?>"
101 <?php endif; ?>>
102
103 <?php $this->renderPricingTableHTML( $parentProduct, $product, $settings ); ?>
104 </div>
105
106 <?php if ( TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) && ! $loadVariationByAjax ) : ?>
107 <div class="tpt__tiered-pricing-preloaded-variations" style="display:none">
108 <?php foreach ( $parentProduct->get_available_variations( 'objects' ) as $childProduct ) : ?>
109 <div class="tiered-pricing-preloaded-variation"
110 data-variation-id="<?php echo esc_attr( $childProduct->get_id() ); ?>"
111 data-display-context="<?php echo esc_attr( $settings['display_context'] ); ?>">
112 <?php $this->renderPricingTableHTML( $parentProduct, $childProduct, $settings ); ?>
113 </div>
114 <?php endforeach; ?>
115 </div>
116 <?php endif; ?>
117 <?php
118 }
119
120 /**
121 * Render pricing table without wrapper
122 *
123 * @param WC_Product $parentProduct
124 * @param WC_Product $product
125 * @param array $settings
126 */
127 public function renderPricingTableHTML(
128 WC_Product $parentProduct,
129 WC_Product $product,
130 array $settings = array()
131 ) {
132
133 $settings = wp_parse_args( $settings, $this->getDefaultSettings( $parentProduct->get_id() ) );
134
135 // Don't get rules from variable product when variation is empty
136 if ( in_array( $parentProduct->get_type(),
137 TierPricingTablePlugin::getSupportedVariableProductTypes() ) && ! ( $product instanceof WC_Product_Variation ) ) {
138 // Empty rule
139 $priceRule = new PricingRule( $product->get_id() );
140 } else {
141 $priceRule = PriceManager::getPricingRule( $product->get_id() );
142 }
143
144 // If rules are not empty
145 if ( ! empty( $priceRule->getRules() ) ) {
146
147 $displayType = $settings['display_type'];
148 $availableTemplates = TierPricingTablePlugin::getAvailablePricingLayouts();
149
150 $templateType = array_key_exists( $settings['display_type'], $availableTemplates ) ? $displayType : 'table';
151
152 if ( 'tooltip' === $displayType ) {
153 $templateType = 'table';
154 }
155
156 if ( 'blocks' === $displayType ) {
157 $style = $settings['blocks_style'];
158
159 if ( $style !== 'default' ) {
160 $templateType = 'blocks-' . $style;
161 } else {
162 $templateType = 'blocks';
163 }
164 }
165
166 if( 'options' === $displayType ) {
167 $style = $settings['options_style'];
168
169 if ( $style !== 'default' ) {
170 $templateType = 'options-' . $style;
171 } else {
172 $templateType = 'options';
173 }
174 }
175
176 $template = "tiered-pricing-$templateType.php";
177
178 do_action( 'tiered_pricing_table/before_rendering_tiered_pricing/inner', $priceRule, $product, $settings );
179
180 $this->checkForDuplicateQuantities( $priceRule );
181
182 $this->getContainer()->getFileManager()->includeTemplate( 'frontend/' . $template, array(
183 'pricing_rule' => $priceRule,
184 'price_rules' => $priceRule->getRules(),
185 'real_price' => $product->get_price(),
186 'product_name' => $product->get_name(),
187 'product_id' => $product->get_id(),
188 'product' => $product,
189 'minimum' => $priceRule->getMinimum( true ),
190 'settings' => $settings,
191 'pricing_type' => $priceRule->getType(),
192 'id' => $this->getUniqueTieredPricingId(),
193 ) );
194 }
195 }
196
197 protected function getDefaultSettings( $productId = false ): array {
198 $settings = array(
199 'display_context' => 'product-page',
200 'display' => $this->getContainer()->getSettings()->get( 'display', 'yes' ) === 'yes',
201 'display_type' => $this->getContainer()->getSettings()->get( 'display_type', 'table' ),
202 'title' => $this->getContainer()->getSettings()->get( 'table_title', '' ),
203 'table_class' => $this->getContainer()->getSettings()->get( 'table_css_class', '' ),
204 'quantity_column_title' => $this->getContainer()->getSettings()->get( 'head_quantity_text',
205 __( 'Quantity', 'tier-pricing-table' ) ),
206 'price_column_title' => $this->getContainer()->getSettings()->get( 'head_price_text',
207 __( 'Price', 'tier-pricing-table' ) ),
208 'discount_column_title' => $this->getContainer()->getSettings()->get( 'head_discount_text',
209 __( 'Discount (%)', 'tier-pricing-table' ) ),
210 'quantity_type' => $this->getContainer()->getSettings()->get( 'quantity_type', 'range' ),
211 'show_discount_column' => $this->getContainer()->getSettings()->get( 'show_discount_column',
212 'yes' ) === 'yes',
213 'clickable_rows' => $this->getContainer()->getSettings()->get( 'clickable_table_rows',
214 'yes' ) === 'yes',
215 'active_tier_color' => $this->getContainer()->getSettings()->get( 'selected_quantity_color',
216 '#96598A' ),
217 'tooltip_border' => $this->getContainer()->getSettings()->get( 'tooltip_border',
218 'yes' ) === 'yes',
219
220 'blocks_style' => GeneralSection::getPricingBlocksStyle(),
221 'options_style' => GeneralSection::getPricingOptionsStyle(),
222
223 'options_show_total' => GeneralSection::isShowOptionTotal(),
224 'options_show_original_product_price' => GeneralSection::isShowOriginalProductPrice(),
225 'options_show_default_option' => GeneralSection::isDefaultOptionEnabled(),
226
227 'options_default_option_text' => GeneralSection::getDefaultOptionText(),
228 'options_option_text' => GeneralSection::getOptionText(),
229
230 'plain_text_show_default_option' => GeneralSection::isPlainTextFirstTierEnabled(),
231 'plain_text_option_text' => GeneralSection::getPlainTextTemplate(),
232 'plain_text_default_option_text' => GeneralSection::getPlainTextFirstTierTemplate(),
233
234 'update_price_on_product_page' => $this->getContainer()->getSettings()->get( 'update_price_on_product_page',
235 'yes' ) === 'yes',
236 'show_tiered_price_as_discount' => $this->getContainer()->getSettings()->get( 'show_tiered_price_as_discount',
237 'yes' ) === 'yes',
238 'show_total_price' => $this->getContainer()->getSettings()->get( 'show_total_price',
239 'no' ) === 'yes',
240 );
241
242 $default_quantity_measurement = array(
243 'singular' => '',
244 'plural' => '',
245 );
246
247 $quantity_measurement = $default_quantity_measurement;
248
249 if ( in_array( $settings['display_type'], array( 'table', 'horizontal-table', 'tooltip' ) ) ) {
250 $quantity_measurement = $this->getContainer()->getSettings()->get( 'table_quantity_measurement',
251 $default_quantity_measurement );
252 }
253
254 if ( 'blocks' === $settings['display_type'] ) {
255 $quantity_measurement = $this->getContainer()->getSettings()->get( 'blocks_quantity_measurement', array(
256 'singular' => _n( 'piece', 'pieces', 1, 'tier-pricing-table' ),
257 'plural' => _n( 'piece', 'pieces', 2, 'tier-pricing-table' ),
258 ) );
259 }
260
261 $settings['quantity_measurement_singular'] = $quantity_measurement['singular'];
262 $settings['quantity_measurement_plural'] = $quantity_measurement['plural'];
263
264 if ( $productId ) {
265 $template = TieredPricingTab::getProductTemplate( $productId );
266
267 if ( 'default' !== $template ) {
268 $settings['display_type'] = $template;
269 }
270
271 $baseUnitName = TieredPricingTab::getProductBaseUnitName( $productId );
272
273 if ( $baseUnitName['singular'] && $baseUnitName['plural'] ) {
274 $settings['quantity_measurement_singular'] = $baseUnitName['singular'];
275 $settings['quantity_measurement_plural'] = $baseUnitName['plural'];
276 }
277 }
278
279 return $settings;
280 }
281
282 protected function getUniqueTieredPricingId() {
283 return preg_replace( '/[0-9]+/', '', strtolower( wp_generate_password( 20, false ) ) );
284 }
285
286 protected function getDefaultVariation( WC_Product_Variable $product ): ?int {
287
288 $defaultVariation = AdvanceOptionsForVariableProduct::getDefaultVariation( $product->get_id() );
289
290 if ( $defaultVariation ) {
291 return $defaultVariation->get_id();
292 }
293
294 $defaultAttributes = $product->get_default_attributes();
295
296 if ( ! empty( $defaultAttributes ) ) {
297
298 $defaultAttributesKeys = array_map( function ( $attribute ) {
299 return 'attribute_' . $attribute;
300 }, array_keys( $defaultAttributes ) );
301
302 $defaultAttributes = array_combine( $defaultAttributesKeys, $defaultAttributes );
303
304 return ( new WC_Product_Data_Store_CPT() )->find_matching_product_variation( $product, $defaultAttributes );
305 }
306
307 return null;
308 }
309
310 public function productHasPricingRules( WC_Product $product ): bool {
311
312 if ( TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
313 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
314
315 return ! empty( $pricingRule->getRules() );
316 }
317
318 if ( TierPricingTablePlugin::isVariableProductSupported( $product ) && $product instanceof WC_Product_Variable ) {
319
320 // Do not check if product has tiered pricing for variable product by default.
321 // If a variable product has many variations, it can take many resources to check every variation for rules existing.
322 // 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
323 if ( ! apply_filters( 'tiered_pricing_table/check_if_variable_product_has_rules', false, $product ) ) {
324 return true;
325 }
326
327 $hasTieredPricing = $this->getContainer()->getCache()->getProductData( $product, 'product_has_rules' );
328
329 if ( 'no' === $hasTieredPricing ) {
330 return false;
331 }
332
333 if ( 'yes' === $hasTieredPricing ) {
334 return true;
335 }
336
337 foreach ( $product->get_available_variations() as $productVariation ) {
338 $pricingRule = PriceManager::getPricingRule( $productVariation['variation_id'] );
339
340 if ( ! empty( $pricingRule->getRules() ) ) {
341 $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'yes' );
342
343 return true;
344 }
345 }
346
347 $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'no' );
348 }
349
350 return false;
351 }
352
353 public function checkForDuplicateQuantities( PricingRule $pricingRule ) {
354 // Show notice only for admin users
355 if ( ! current_user_can( 'manage_options' ) ) {
356 return;
357 }
358 $minimum = $pricingRule->getMinimum( true );
359 $pricingRules = $pricingRule->getRules();
360 $firstRuleQuantity = ! empty( $pricingRules ) ? array_keys( $pricingRules )[0] : false;
361
362 if ( $firstRuleQuantity && $firstRuleQuantity <= $minimum ) {
363 ?>
364
365 <div style="font-size: .8em;">
366 <div class="woocommerce-error" role="alert" style="margin-bottom: 0">
367 Duplicated quantities detected. Minimum order quantity is equal or higher than the first tiered
368 pricing.
369 </div>
370 <div style="color: #555;
371 padding: 10px 12px;
372 border: 1px solid #b32c2e;
373 background: #fff5f5;">
374 <p style="padding: 0; margin: 0">
375 The minimum order quantity must be less than the first tiered pricing rule quantity.
376 </p>
377 <p style="padding: 0; margin: 0"> The first tier always uses woocommerce price and is applied to
378 quantities between minimum order
379 quantity and
380 the first tiered pricing rule quantity.
381 </p>
382 <br>
383 <b>This notice shown only for administrators.</b>
384 </div>
385 </div>
386 <?php
387 }
388 }
389 }