PluginProbe
Tiered Pricing Table for WooCommerce / 6.4.0
Tiered Pricing Table for WooCommerce v6.4.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.4.0, at src/PricingTable.php

398 lines 15.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 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 ( 'table' === $displayType ) {
157 $style = $settings['table_style'];
158
159 if ( $style !== 'default' ) {
160 $templateType = 'table-' . $style;
161 } else {
162 $templateType = 'table';
163 }
164 }
165
166 if ( 'blocks' === $displayType ) {
167 $style = $settings['blocks_style'];
168
169 if ( $style !== 'default' ) {
170 $templateType = 'blocks-' . $style;
171 } else {
172 $templateType = 'blocks';
173 }
174 }
175
176 if( 'options' === $displayType ) {
177 $style = $settings['options_style'];
178
179 if ( $style !== 'default' ) {
180 $templateType = 'options-' . $style;
181 } else {
182 $templateType = 'options';
183 }
184 }
185
186 $template = "tiered-pricing-$templateType.php";
187
188 do_action( 'tiered_pricing_table/before_rendering_tiered_pricing/inner', $priceRule, $product, $settings );
189
190 $this->checkForDuplicateQuantities( $priceRule );
191
192 $this->getContainer()->getFileManager()->includeTemplate( 'frontend/' . $template, array(
193 'pricing_rule' => $priceRule,
194 'price_rules' => $priceRule->getRules(),
195 'real_price' => $product->get_price(),
196 'product_name' => $product->get_name(),
197 'product_id' => $product->get_id(),
198 'product' => $product,
199 'minimum' => $priceRule->getMinimum( true ),
200 'settings' => $settings,
201 'pricing_type' => $priceRule->getType(),
202 'id' => $this->getUniqueTieredPricingId(),
203 ) );
204 }
205 }
206
207 protected function getDefaultSettings( $productId = false ): array {
208 $settings = array(
209 'display_context' => 'product-page',
210 'display' => $this->getContainer()->getSettings()->get( 'display', 'yes' ) === 'yes',
211 'display_type' => $this->getContainer()->getSettings()->get( 'display_type', 'table' ),
212 'title' => $this->getContainer()->getSettings()->get( 'table_title', '' ),
213 'table_class' => $this->getContainer()->getSettings()->get( 'table_css_class', '' ),
214 'quantity_column_title' => $this->getContainer()->getSettings()->get( 'head_quantity_text',
215 __( 'Quantity', 'tier-pricing-table' ) ),
216 'price_column_title' => $this->getContainer()->getSettings()->get( 'head_price_text',
217 __( 'Price', 'tier-pricing-table' ) ),
218 'discount_column_title' => $this->getContainer()->getSettings()->get( 'head_discount_text',
219 __( 'Discount (%)', 'tier-pricing-table' ) ),
220 'quantity_type' => $this->getContainer()->getSettings()->get( 'quantity_type', 'range' ),
221 'show_discount_column' => $this->getContainer()->getSettings()->get( 'show_discount_column',
222 'yes' ) === 'yes',
223 'clickable_rows' => $this->getContainer()->getSettings()->get( 'clickable_table_rows',
224 'yes' ) === 'yes',
225 'active_tier_color' => $this->getContainer()->getSettings()->get( 'selected_quantity_color',
226 '#3858e9' ),
227 'tooltip_border' => $this->getContainer()->getSettings()->get( 'tooltip_border',
228 'yes' ) === 'yes',
229
230 'table_style' => GeneralSection::getPricingTableStyle(),
231 'slim_design' => $this->getContainer()->getSettings()->get( 'slim_design', 'no' ),
232 'blocks_style' => GeneralSection::getPricingBlocksStyle(),
233 'options_style' => GeneralSection::getPricingOptionsStyle(),
234
235 'options_show_total' => GeneralSection::isShowOptionTotal(),
236 'options_show_original_product_price' => GeneralSection::isShowOriginalProductPrice(),
237 'options_show_default_option' => GeneralSection::isDefaultOptionEnabled(),
238
239 'options_default_option_text' => GeneralSection::getDefaultOptionText(),
240 'options_option_text' => GeneralSection::getOptionText(),
241
242 'plain_text_show_default_option' => GeneralSection::isPlainTextFirstTierEnabled(),
243 'plain_text_option_text' => GeneralSection::getPlainTextTemplate(),
244 'plain_text_default_option_text' => GeneralSection::getPlainTextFirstTierTemplate(),
245
246 'update_price_on_product_page' => $this->getContainer()->getSettings()->get( 'update_price_on_product_page',
247 'yes' ) === 'yes',
248 'show_tiered_price_as_discount' => $this->getContainer()->getSettings()->get( 'show_tiered_price_as_discount',
249 'yes' ) === 'yes',
250 'show_total_price' => $this->getContainer()->getSettings()->get( 'show_total_price',
251 'no' ) === 'yes',
252 );
253
254 $default_quantity_measurement = array(
255 'singular' => '',
256 'plural' => '',
257 );
258
259 $quantity_measurement = $default_quantity_measurement;
260
261 if ( in_array( $settings['display_type'], array( 'table', 'horizontal-table', 'tooltip' ) ) ) {
262 $quantity_measurement = $this->getContainer()->getSettings()->get( 'table_quantity_measurement',
263 $default_quantity_measurement );
264 }
265
266 if ( 'blocks' === $settings['display_type'] ) {
267 $quantity_measurement = $this->getContainer()->getSettings()->get( 'blocks_quantity_measurement', array(
268 'singular' => _n( 'piece', 'pieces', 1, 'tier-pricing-table' ),
269 'plural' => _n( 'piece', 'pieces', 2, 'tier-pricing-table' ),
270 ) );
271 }
272
273 $settings['quantity_measurement_singular'] = $quantity_measurement['singular'];
274 $settings['quantity_measurement_plural'] = $quantity_measurement['plural'];
275
276 if ( $productId ) {
277 $template = TieredPricingTab::getProductTemplate( $productId );
278
279 if ( 'default' !== $template ) {
280 $settings['display_type'] = $template;
281 }
282
283 $baseUnitName = TieredPricingTab::getProductBaseUnitName( $productId );
284
285 if ( $baseUnitName['singular'] && $baseUnitName['plural'] ) {
286 $settings['quantity_measurement_singular'] = $baseUnitName['singular'];
287 $settings['quantity_measurement_plural'] = $baseUnitName['plural'];
288 }
289 }
290
291 return $settings;
292 }
293
294 protected function getUniqueTieredPricingId() {
295 return preg_replace( '/[0-9]+/', '', strtolower( wp_generate_password( 20, false ) ) );
296 }
297
298 protected function getDefaultVariation( WC_Product_Variable $product ): ?int {
299
300 $defaultVariation = AdvanceOptionsForVariableProduct::getDefaultVariation( $product->get_id() );
301
302 if ( $defaultVariation ) {
303 return $defaultVariation->get_id();
304 }
305
306 $defaultAttributes = $product->get_default_attributes();
307
308 if ( ! empty( $defaultAttributes ) ) {
309
310 $defaultAttributesKeys = array_map( function ( $attribute ) {
311 return 'attribute_' . $attribute;
312 }, array_keys( $defaultAttributes ) );
313
314 $defaultAttributes = array_combine( $defaultAttributesKeys, $defaultAttributes );
315
316 return ( new WC_Product_Data_Store_CPT() )->find_matching_product_variation( $product, $defaultAttributes );
317 }
318
319 return null;
320 }
321
322 public function productHasPricingRules( WC_Product $product ): bool {
323
324 if ( TierPricingTablePlugin::isSimpleProductSupported( $product ) ) {
325 $pricingRule = PriceManager::getPricingRule( $product->get_id() );
326
327 return ! empty( $pricingRule->getRules() );
328 }
329
330 if ( TierPricingTablePlugin::isVariableProductSupported( $product ) && $product instanceof WC_Product_Variable ) {
331
332 // Do not check if product has tiered pricing for variable product by default.
333 // If a variable product has many variations, it can take many resources to check every variation for rules existing.
334 // 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
335 if ( ! apply_filters( 'tiered_pricing_table/check_if_variable_product_has_rules', false, $product ) ) {
336 return true;
337 }
338
339 $hasTieredPricing = $this->getContainer()->getCache()->getProductData( $product, 'product_has_rules' );
340
341 if ( 'no' === $hasTieredPricing ) {
342 return false;
343 }
344
345 if ( 'yes' === $hasTieredPricing ) {
346 return true;
347 }
348
349 foreach ( $product->get_available_variations() as $productVariation ) {
350 $pricingRule = PriceManager::getPricingRule( $productVariation['variation_id'] );
351
352 if ( ! empty( $pricingRule->getRules() ) ) {
353 $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'yes' );
354
355 return true;
356 }
357 }
358
359 $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'no' );
360 }
361
362 return false;
363 }
364
365 public function checkForDuplicateQuantities( PricingRule $pricingRule ) {
366 // Show notice only for admin users
367 if ( ! current_user_can( 'manage_options' ) ) {
368 return;
369 }
370 $minimum = $pricingRule->getMinimum( true );
371 $pricingRules = $pricingRule->getRules();
372 $firstRuleQuantity = ! empty( $pricingRules ) ? array_keys( $pricingRules )[0] : false;
373
374 if ( $firstRuleQuantity && $firstRuleQuantity <= $minimum ) {
375 ?>
376
377 <div style="font-size: .8em;">
378 <div class="woocommerce-error" role="alert" style="margin-bottom: 0">
379 <?php esc_html_e( 'Pricing Conflict Detected: Your Minimum Order Quantity is equal to or greater than your first pricing tier.', 'tier-pricing-table' ); ?>
380 </div>
381 <div style="color: #555;
382 padding: 10px 12px;
383 border: 1px solid #b32c2e;
384 background: #fff5f5;">
385 <p style="padding: 0; margin: 0 0 10px 0;">
386 <?php esc_html_e( 'To fix this, please ensure your first tiered pricing rule starts at a higher quantity than your Minimum Order Quantity.', 'tier-pricing-table' ); ?>
387 </p>
388 <p style="padding: 0; margin: 0">
389 <strong><?php esc_html_e( 'Why?', '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.', 'tier-pricing-table' ); ?>
390 </p>
391 <br>
392 <b><?php esc_html_e( 'This notice is only visible to store administrators.', 'tier-pricing-table' ); ?></b>
393 </div>
394 </div>
395 <?php
396 }
397 }
398 }