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
← All changes | src/PricingTable.php +216 -6 7.1.1 → 8.1.0 View file →
@@ -110,8 +110,16 @@
110 110 <?php endforeach; ?>
111 111 </div>
112 112 <?php endif; ?>
113 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 );
114 122 }
115 123
116 124 /**
117 125 * Render pricing table without wrapper
@@ -178,8 +186,19 @@
178 186 $templateType = 'options';
179 187 }
180 188 }
181 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 +
182 201 $template = "tiered-pricing-$templateType.php";
183 202
184 203 do_action( 'tiered_pricing_table/before_rendering_tiered_pricing/inner', $priceRule, $product, $settings );
185 204
@@ -196,8 +215,10 @@
196 215 'settings' => $settings,
197 216 'pricing_type' => $priceRule->getType(),
198 217 'id' => $this->getUniqueTieredPricingId(),
199 218 ) );
219 +
220 + $this->renderMoreTiersLink( $parentProduct );
200 221 } else {
201 222 // Provide fallback data for products without pricing rules,
202 223 // which allows the frontend JS (e.g. dynamic totals, you save, etc.)
203 224 // to function for all products via the 'tiered_price_update' event.
@@ -207,20 +228,37 @@
207 228
208 229 $price_excl_taxes = wc_get_price_excluding_tax( $product, array(
209 230 'price' => $product->get_price(),
210 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 +
211 249 ?>
212 250 <div style="display:none;"
213 251 class="tiered-pricing-fallback-data"
214 252 data-product-id="<?php echo esc_attr( $product->get_id() ); ?>"
215 253 data-price-rules="{}"
216 - data-minimum="1"
254 + data-minimum="<?php echo esc_attr( $priceRule->getMinimum( true ) ); ?>"
217 255 data-product-name="<?php echo esc_attr( $product->get_name() ); ?>"
218 - data-regular-price="<?php echo esc_attr( $product->get_regular_price() ); ?>"
219 - data-sale-price="<?php echo esc_attr( $product->get_sale_price() ); ?>"
220 - data-price="<?php echo esc_attr( $product->get_price() ); ?>"
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 ); ?>"
221 259 data-product-price-suffix="<?php echo esc_attr( $product->get_price_suffix() ); ?>"
222 - data-tiered-price="<?php echo esc_attr( $product->get_price() ); ?>"
260 + data-tiered-price="<?php echo esc_attr( $price ); ?>"
223 261 data-tiered-price-exclude-taxes="<?php echo esc_attr( $price_excl_taxes ); ?>"
224 262 data-tiered-price-include-taxes="<?php echo esc_attr( $price_incl_taxes ); ?>"
225 263 ></div>
226 264 <?php
@@ -226,8 +264,171 @@
226 264 <?php
227 265 }
228 266 }
229 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 +
230 431 protected function getDefaultSettings( $productId = false ): array {
231 432 $hasRules = true;
232 433
233 434 if ( $productId ) {
@@ -242,9 +443,9 @@
242 443 'no' ) === 'yes';
243 444
244 445 $settings = array(
245 446 'display_context' => 'product-page',
246 - 'display' => $this->getContainer()->getSettings()->get( 'display', 'yes' ) === 'yes',
447 + 'display' => GeneralSection::isAutomaticDisplayEnabled(),
247 448 'display_type' => $this->getContainer()->getSettings()->get( 'display_type', 'table' ),
248 449 'title' => $this->getContainer()->getSettings()->get( 'table_title', '' ),
249 450 'table_class' => $this->getContainer()->getSettings()->get( 'table_css_class', '' ),
250 451 'quantity_column_title' => $this->getContainer()->getSettings()->get( 'head_quantity_text',
@@ -259,8 +460,14 @@
259 460 'clickable_rows' => $this->getContainer()->getSettings()->get( 'clickable_table_rows',
260 461 'yes' ) === 'yes',
261 462 'active_tier_color' => $this->getContainer()->getSettings()->get( 'selected_quantity_color',
262 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' ),
263 470 'tooltip_border' => $this->getContainer()->getSettings()->get( 'tooltip_border',
264 471 'yes' ) === 'yes',
265 472
266 473 'table_style' => GeneralSection::getPricingTableStyle(),
@@ -266,8 +473,11 @@
266 473 'table_style' => GeneralSection::getPricingTableStyle(),
267 474 'compact_layout' => $this->getContainer()->getSettings()->get( 'compact_layout', 'no' ),
268 475 'blocks_style' => GeneralSection::getPricingBlocksStyle(),
269 476 'options_style' => GeneralSection::getPricingOptionsStyle(),
477 +
478 + 'dropdown_style' => GeneralSection::getPricingDropdownStyle(),
479 + 'plain_text_style' => GeneralSection::getPricingPlainTextStyle(),
270 480
271 481 'options_show_total' => GeneralSection::isShowOptionTotal(),
272 482 'options_show_original_product_price' => GeneralSection::isShowOriginalProductPrice(),
273 483 'options_show_default_option' => GeneralSection::isDefaultOptionEnabled(),