getDefaultSettings( $parentProductID ) ); // If the product is variable, but no specific variation is passed - check for default if ( ! $variationID && TierPricingTablePlugin::isVariableProductSupported( $parentProduct ) ) { $variationID = $this->getDefaultVariation( $parentProduct ); } $ajaxVariationsThreshold = apply_filters( 'tiered_pricing_table/ajax_variations_threshold', 10 ); // Use variation if exists, otherwise the parent product is used $productId = $variationID ? $variationID : $parentProduct->get_id(); $product = wc_get_product( $productId ); $supportedTypes = array_merge( TierPricingTablePlugin::getSupportedSimpleProductTypes(), TierPricingTablePlugin::getSupportedVariableProductTypes() ); // Exit if product is not valid if ( ! $product || ! in_array( $parentProduct->get_type(), $supportedTypes ) ) { return; } $settings = apply_filters( 'tiered_pricing_table/display_settings', $settings, $productId ); if ( 'tooltip' === $settings['display_type'] ) { wp_enqueue_script( 'jquery-ui-tooltip' ); } $hidden = ( 'tooltip' === $settings['display_type'] || ! $settings['display'] ); do_action( 'tiered_pricing_table/before_rendering_tiered_pricing', $parentProduct, $variationID, $settings ); $loadVariationByAjax = true; ?>
get_children() ) > $ajaxVariationsThreshold; $variableProductSamePrices = AdvanceOptionsForVariableProduct::isVariableProductSamePrices( $parentProduct->get_id() ); ?> data-load-variation-by-ajax="" data-variable-product-same-prices="" > renderPricingTableHTML( $parentProduct, $product, $settings ); ?>
getDefaultSettings( $parentProduct->get_id() ) ); // Don't get rules from variable product when variation is empty if ( in_array( $parentProduct->get_type(), TierPricingTablePlugin::getSupportedVariableProductTypes() ) && ! ( $product instanceof WC_Product_Variation ) ) { // Empty rule $priceRule = new PricingRule( $product->get_id() ); } else { $priceRule = PriceManager::getPricingRule( $product->get_id() ); } // If rules are not empty if ( ! empty( $priceRule->getRules() ) ) { $displayType = $settings['display_type']; $availableTemplates = TierPricingTablePlugin::getAvailablePricingLayouts(); $templateType = array_key_exists( $settings['display_type'], $availableTemplates ) ? $displayType : 'table'; if ( 'tooltip' === $displayType ) { $templateType = 'table'; } if ( 'table' === $displayType ) { $style = $settings['table_style']; if ( $style !== 'default' ) { $templateType = 'table-' . $style; } else { $templateType = 'table'; } } if ( 'blocks' === $displayType ) { $style = $settings['blocks_style']; if ( $style !== 'default' ) { $templateType = 'blocks-' . $style; } else { $templateType = 'blocks'; } } if ( 'options' === $displayType ) { $style = $settings['options_style']; if ( $style !== 'default' ) { $templateType = 'options-' . $style; } else { $templateType = 'options'; } } // layouts with a single style option: dropdown, plain text $styledLayouts = array( 'dropdown' => 'dropdown_style', 'plain-text' => 'plain_text_style', ); if ( isset( $styledLayouts[ $displayType ] ) ) { $style = $settings[ $styledLayouts[ $displayType ] ] ?? 'default'; $templateType = 'default' !== $style && '' !== $style ? $displayType . '-' . $style : $displayType; } $template = "tiered-pricing-$templateType.php"; do_action( 'tiered_pricing_table/before_rendering_tiered_pricing/inner', $priceRule, $product, $settings ); $this->checkForDuplicateQuantities( $priceRule ); $this->getContainer()->getFileManager()->includeTemplate( 'frontend/' . $template, array( 'pricing_rule' => $priceRule, 'price_rules' => $priceRule->getRules(), 'real_price' => $product->get_price(), 'product_name' => $product->get_name(), 'product_id' => $product->get_id(), 'product' => $product, 'minimum' => $priceRule->getMinimum( true ), 'settings' => $settings, 'pricing_type' => $priceRule->getType(), 'id' => $this->getUniqueTieredPricingId(), ) ); $this->renderMoreTiersLink( $parentProduct ); } else { // Provide fallback data for products without pricing rules, // which allows the frontend JS (e.g. dynamic totals, you save, etc.) // to function for all products via the 'tiered_price_update' event. $price_incl_taxes = wc_get_price_including_tax( $product, array( 'price' => $product->get_price(), ) ); $price_excl_taxes = wc_get_price_excluding_tax( $product, array( 'price' => $product->get_price(), ) ); $sale_price = $product->get_sale_price(); if ( $sale_price ) { $sale_price = wc_get_price_to_display( $product, array( 'price' => $sale_price, ) ); } $regular_price = wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price(), ) ); $price = wc_get_price_to_display( $product, array( 'price' => $product->get_price(), ) ); ?> is_on_sale() ) { $saving = (float) $product->get_regular_price() - (float) $product->get_sale_price(); } } else { $base = CalculationLogic::calculateDiscountBasedOnRegularPrice() ? $product->get_regular_price() : $product->get_price(); $saving = (float) $base - (float) $pricingRule->getTierPrice( $quantity, false ); } $amountLabel = wc_price( wc_get_price_to_display( $product, array( 'price' => max( 0, $saving ) ) ) ); } if ( 'off' === $style ) { switch ( $format ) { case 'amount': // translators: %s: amount saved per item return sprintf( __( '%s off', 'tier-pricing-table' ), $amountLabel ); case 'both': // translators: 1: discount percentage, 2: amount saved per item return sprintf( __( '%1$s off, save %2$s', 'tier-pricing-table' ), $percentLabel, $amountLabel ); default: // translators: %s: discount percentage return sprintf( __( '%s off', 'tier-pricing-table' ), $percentLabel ); } } switch ( $format ) { case 'amount': return $amountLabel; case 'both': return $percentLabel . ' (' . $amountLabel . ')'; default: return $percentLabel; } } /** * The "Spacing" and "Cell padding" options as a style attribute for a layout's root element: CSS * variables the stylesheet reads for the gap between tiers and the padding inside table cells. * Empty when neither option is set (each style keeps its own spacing and padding). */ public static function layoutStyleAttribute( array $settings ): string { $variables = array(); foreach ( array( 'layout_spacing' => '--tiered-pricing-gap', 'cell_padding' => '--tiered-pricing-cell-padding' ) as $option => $variable ) { $value = $settings[ $option ] ?? ''; if ( '' === $value || null === $value || ! is_numeric( $value ) ) { continue; } $variables[] = $variable . ': ' . max( 0, min( 80, (int) $value ) ) . 'px'; } return $variables ? ' style="' . esc_attr( implode( '; ', $variables ) ) . '"' : ''; } /** * @deprecated 7.1.9 Use layoutStyleAttribute(). */ public static function spacingAttribute( array $settings ): string { return self::layoutStyleAttribute( $settings ); } /** * Tiers the last rendered layout left out because of the "tiers per grid item" limit. */ protected static $hiddenTiers = 0; /** * Tier rows in the configured order (ascending by quantity, or the biggest discount first), cut to * the "tiers per grid item" limit when one is set. * * @param string[] $rows Rendered rows, ascending. * @param array $settings Renderer settings. * * @return string HTML (the rows were escaped when rendered) */ public static function orderTiers( array $rows, array $settings ): string { if ( 'desc' === ( $settings['tiers_order'] ?? 'asc' ) ) { $rows = array_reverse( $rows ); } $limit = (int) ( $settings['tiers_limit'] ?? 0 ); self::$hiddenTiers = 0; if ( $limit > 0 && count( $rows ) > $limit ) { self::$hiddenTiers = count( $rows ) - $limit; $rows = array_slice( $rows, 0, $limit ); } return implode( '', $rows ); } /** * The "+N more tiers" link to the product page, after a layout the limit shortened. */ protected function renderMoreTiersLink( WC_Product $parentProduct ) { $hidden = self::$hiddenTiers; self::$hiddenTiers = 0; if ( $hidden < 1 ) { return; } /* translators: %d: number of tiers not listed */ $label = sprintf( _n( '+%d more tier', '+%d more tiers', $hidden, 'tier-pricing-table' ), $hidden ); echo '' . esc_html( $label ) . ''; } protected function getDefaultSettings( $productId = false ): array { $hasRules = true; if ( $productId ) { $product = wc_get_product( $productId ); if ( $product ) { $hasRules = $this->productHasPricingRules( $product ); } } $showTotalPrice = $hasRules ? $this->getContainer()->getSettings()->get( 'show_total_price', 'no' ) === 'yes' : $this->getContainer()->getSettings()->get( 'show_total_price_non_tiered', 'no' ) === 'yes'; $settings = array( 'display_context' => 'product-page', 'display' => GeneralSection::isAutomaticDisplayEnabled(), 'display_type' => $this->getContainer()->getSettings()->get( 'display_type', 'table' ), 'title' => $this->getContainer()->getSettings()->get( 'table_title', '' ), 'table_class' => $this->getContainer()->getSettings()->get( 'table_css_class', '' ), 'quantity_column_title' => $this->getContainer()->getSettings()->get( 'head_quantity_text', __( 'Quantity', 'tier-pricing-table' ) ), 'price_column_title' => $this->getContainer()->getSettings()->get( 'head_price_text', __( 'Price', 'tier-pricing-table' ) ), 'discount_column_title' => $this->getContainer()->getSettings()->get( 'head_discount_text', __( 'Discount (%)', 'tier-pricing-table' ) ), 'quantity_type' => $this->getContainer()->getSettings()->get( 'quantity_type', 'range' ), 'show_discount_column' => $this->getContainer()->getSettings()->get( 'show_discount_column', 'yes' ) === 'yes', 'clickable_rows' => $this->getContainer()->getSettings()->get( 'clickable_table_rows', 'yes' ) === 'yes', 'active_tier_color' => $this->getContainer()->getSettings()->get( 'selected_quantity_color', '#3858e9' ), 'discount_format' => $this->getContainer()->getSettings()->get( 'discount_format', 'percentage' ), 'layout_spacing' => $this->getContainer()->getSettings()->get( 'layout_spacing', '' ), 'cell_padding' => $this->getContainer()->getSettings()->get( 'cell_padding', '' ), 'discount_badge_color' => $this->getContainer()->getSettings()->get( 'discount_badge_color', '' ), 'active_tier_border' => $this->getContainer()->getSettings()->get( 'table_active_border', 'yes' ), 'tiers_order' => $this->getContainer()->getSettings()->get( 'tiers_order', 'asc' ), 'tooltip_border' => $this->getContainer()->getSettings()->get( 'tooltip_border', 'yes' ) === 'yes', 'table_style' => GeneralSection::getPricingTableStyle(), 'compact_layout' => $this->getContainer()->getSettings()->get( 'compact_layout', 'no' ), 'blocks_style' => GeneralSection::getPricingBlocksStyle(), 'options_style' => GeneralSection::getPricingOptionsStyle(), 'dropdown_style' => GeneralSection::getPricingDropdownStyle(), 'plain_text_style' => GeneralSection::getPricingPlainTextStyle(), 'options_show_total' => GeneralSection::isShowOptionTotal(), 'options_show_original_product_price' => GeneralSection::isShowOriginalProductPrice(), 'options_show_default_option' => GeneralSection::isDefaultOptionEnabled(), 'options_default_option_text' => GeneralSection::getDefaultOptionText(), 'options_option_text' => GeneralSection::getOptionText(), 'plain_text_show_default_option' => GeneralSection::isPlainTextFirstTierEnabled(), 'plain_text_option_text' => GeneralSection::getPlainTextTemplate(), 'plain_text_default_option_text' => GeneralSection::getPlainTextFirstTierTemplate(), 'update_price_on_product_page' => $this->getContainer()->getSettings()->get( 'update_price_on_product_page', 'yes' ) === 'yes', 'show_tiered_price_as_discount' => $this->getContainer()->getSettings()->get( 'show_tiered_price_as_discount', 'yes' ) === 'yes', 'show_total_price' => $showTotalPrice, ); $default_quantity_measurement = array( 'singular' => '', 'plural' => '', ); $quantity_measurement = $default_quantity_measurement; if ( in_array( $settings['display_type'], array( 'table', 'horizontal-table', 'tooltip' ) ) ) { $quantity_measurement = $this->getContainer()->getSettings()->get( 'table_quantity_measurement', $default_quantity_measurement ); } if ( 'blocks' === $settings['display_type'] ) { $quantity_measurement = $this->getContainer()->getSettings()->get( 'blocks_quantity_measurement', array( 'singular' => _n( 'piece', 'pieces', 1, 'tier-pricing-table' ), 'plural' => _n( 'piece', 'pieces', 2, 'tier-pricing-table' ), ) ); } $settings['quantity_measurement_singular'] = $quantity_measurement['singular']; $settings['quantity_measurement_plural'] = $quantity_measurement['plural']; if ( $productId ) { $template = TieredPricingTab::getProductTemplate( $productId ); if ( 'default' !== $template ) { $settings['display_type'] = $template; } $baseUnitName = TieredPricingTab::getProductBaseUnitName( $productId ); if ( $baseUnitName['singular'] && $baseUnitName['plural'] ) { $settings['quantity_measurement_singular'] = $baseUnitName['singular']; $settings['quantity_measurement_plural'] = $baseUnitName['plural']; } } return $settings; } protected function getUniqueTieredPricingId() { return preg_replace( '/[0-9]+/', '', strtolower( wp_generate_password( 20, false ) ) ); } protected function getDefaultVariation( WC_Product_Variable $product ): ?int { $defaultVariation = AdvanceOptionsForVariableProduct::getDefaultVariation( $product->get_id() ); if ( $defaultVariation ) { return $defaultVariation->get_id(); } $defaultAttributes = $product->get_default_attributes(); if ( ! empty( $defaultAttributes ) ) { $defaultAttributesKeys = array_map( function ( $attribute ) { return 'attribute_' . $attribute; }, array_keys( $defaultAttributes ) ); $defaultAttributes = array_combine( $defaultAttributesKeys, $defaultAttributes ); return ( new WC_Product_Data_Store_CPT() )->find_matching_product_variation( $product, $defaultAttributes ); } return null; } public function productHasPricingRules( WC_Product $product ): bool { if ( TierPricingTablePlugin::isSimpleProductSupported( $product ) ) { $pricingRule = PriceManager::getPricingRule( $product->get_id() ); return ! empty( $pricingRule->getRules() ); } if ( TierPricingTablePlugin::isVariableProductSupported( $product ) && $product instanceof WC_Product_Variable ) { // Do not check if product has tiered pricing for variable product by default. // If a variable product has many variations, it can take many resources to check every variation for rules existing. // 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 if ( ! apply_filters( 'tiered_pricing_table/check_if_variable_product_has_rules', false, $product ) ) { return true; } $hasTieredPricing = $this->getContainer()->getCache()->getProductData( $product, 'product_has_rules' ); if ( 'no' === $hasTieredPricing ) { return false; } if ( 'yes' === $hasTieredPricing ) { return true; } foreach ( $product->get_available_variations() as $productVariation ) { $pricingRule = PriceManager::getPricingRule( $productVariation['variation_id'] ); if ( ! empty( $pricingRule->getRules() ) ) { $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'yes' ); return true; } } $this->getContainer()->getCache()->setProductData( $product, 'product_has_rules', 'no' ); } return false; } public function checkForDuplicateQuantities( PricingRule $pricingRule ) { // Show notice only for admin users if ( ! current_user_can( 'manage_options' ) ) { return; } $minimum = $pricingRule->getMinimum( true ); $pricingRules = $pricingRule->getRules(); $firstRuleQuantity = ! empty( $pricingRules ) ? array_keys( $pricingRules )[0] : false; if ( $firstRuleQuantity && $firstRuleQuantity <= $minimum ) { ?>