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 +195 -2 7.1.4 → 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.
@@ -229,9 +250,9 @@
229 250 <div style="display:none;"
230 251 class="tiered-pricing-fallback-data"
231 252 data-product-id="<?php echo esc_attr( $product->get_id() ); ?>"
232 253 data-price-rules="{}"
233 - data-minimum="1"
254 + data-minimum="<?php echo esc_attr( $priceRule->getMinimum( true ) ); ?>"
234 255 data-product-name="<?php echo esc_attr( $product->get_name() ); ?>"
235 256 data-regular-price="<?php echo esc_attr( $regular_price ); ?>"
236 257 data-sale-price="<?php echo esc_attr( $sale_price ); ?>"
237 258 data-price="<?php echo esc_attr( $price ); ?>"
@@ -243,8 +264,171 @@
243 264 <?php
244 265 }
245 266 }
246 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 +
247 431 protected function getDefaultSettings( $productId = false ): array {
248 432 $hasRules = true;
249 433
250 434 if ( $productId ) {
@@ -259,9 +443,9 @@
259 443 'no' ) === 'yes';
260 444
261 445 $settings = array(
262 446 'display_context' => 'product-page',
263 - 'display' => $this->getContainer()->getSettings()->get( 'display', 'yes' ) === 'yes',
447 + 'display' => GeneralSection::isAutomaticDisplayEnabled(),
264 448 'display_type' => $this->getContainer()->getSettings()->get( 'display_type', 'table' ),
265 449 'title' => $this->getContainer()->getSettings()->get( 'table_title', '' ),
266 450 'table_class' => $this->getContainer()->getSettings()->get( 'table_css_class', '' ),
267 451 'quantity_column_title' => $this->getContainer()->getSettings()->get( 'head_quantity_text',
@@ -276,8 +460,14 @@
276 460 'clickable_rows' => $this->getContainer()->getSettings()->get( 'clickable_table_rows',
277 461 'yes' ) === 'yes',
278 462 'active_tier_color' => $this->getContainer()->getSettings()->get( 'selected_quantity_color',
279 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' ),
280 470 'tooltip_border' => $this->getContainer()->getSettings()->get( 'tooltip_border',
281 471 'yes' ) === 'yes',
282 472
283 473 'table_style' => GeneralSection::getPricingTableStyle(),
@@ -283,8 +473,11 @@
283 473 'table_style' => GeneralSection::getPricingTableStyle(),
284 474 'compact_layout' => $this->getContainer()->getSettings()->get( 'compact_layout', 'no' ),
285 475 'blocks_style' => GeneralSection::getPricingBlocksStyle(),
286 476 'options_style' => GeneralSection::getPricingOptionsStyle(),
477 +
478 + 'dropdown_style' => GeneralSection::getPricingDropdownStyle(),
479 + 'plain_text_style' => GeneralSection::getPricingPlainTextStyle(),
287 480
288 481 'options_show_total' => GeneralSection::isShowOptionTotal(),
289 482 'options_show_original_product_price' => GeneralSection::isShowOriginalProductPrice(),
290 483 'options_show_default_option' => GeneralSection::isDefaultOptionEnabled(),