get_id(); } return $classes; }, 10, 2 ); add_filter( 'woocommerce_available_variation', function ( $data, WC_Product $product ) { $data['parent_id'] = $product->get_id(); return $data; }, 10, 2 ); // Wrap price add_action( 'woocommerce_get_price_html', array($this, 'wrapPrice'), 101, 2 ); // Render price table add_action( $this->getContainer()->getSettings()->get( 'position_hook', 'woocommerce_before_add_to_cart_button' ), array($this, 'renderPricingTableOnProductPage'), -999 ); // Get table for variation add_action( 'wc_ajax_get_pricing_table', array($this, 'getVariationPricingTable'), 10, 1 ); // Render tooltip if enabled add_filter( 'woocommerce_get_price_html', array($this, 'renderTooltip'), 999, 2 ); add_filter( 'woocommerce_get_price_suffix', function ( $suffix, \WC_product $product, $price, $qty ) { // Allow 3rd-party to control this if ( !apply_filters( 'tiered_pricing_table/frontend/modify_price_suffix', true, $suffix, $product, $price, $qty ) ) { return $suffix; } if ( empty( $suffix ) ) { return $suffix; } $html = ''; $suffix = get_option( 'woocommerce_price_display_suffix' ); if ( $suffix && wc_tax_enabled() && 'taxable' === $product->get_tax_status() ) { if ( '' === $price ) { $price = $product->get_price(); } $replacements = array( '{price_including_tax}' => '' . wc_price( wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price, ) ) ) . '', '{price_excluding_tax}' => '' . wc_price( wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price, ) ) ) . '', ); $html = str_replace( array_keys( $replacements ), array_values( $replacements ), ' ' . wp_kses_post( $suffix ) . '' ); } return $html; }, 10, 4 ); } /** * Wrap product price for managing it by JS * * @param mixed $priceHTML * @param WC_Product $product * * @return string */ public function wrapPrice( $priceHTML, WC_Product $product ) { if ( is_admin() && !defined( 'DOING_AJAX' ) ) { return $priceHTML; } // Do not wrap price in quick edit and inline edit in admin if ( isset( $_POST['action'] ) && in_array( $_POST['action'], ['inline-save'], true ) ) { return $priceHTML; } // Allow 3rd-party to control this if ( !apply_filters( 'tiered_pricing_table/frontend/wrap_price', true, $product, $priceHTML ) ) { return $priceHTML; } $parentProductID = ( $product->get_parent_id() ? $product->get_parent_id() : $product->get_id() ); $variationID = ( TierPricingTablePlugin::isVariationProductSupported( $product ) ? $product->get_id() : null ); $productPagePrice = get_queried_object_id() === $parentProductID && is_product(); $priceDisplayContext = ( $productPagePrice ? 'product-page' : 'shop-loop' ); $priceType = apply_filters( 'tiered_pricing_table/frontend/default_price_behaviour_type', 'dynamic', $product, $priceHTML, $priceDisplayContext ); if ( $productPagePrice && $product->get_type() !== 'variation' ) { // Do not format prices if tiered pricing formatting enabled on the product page. if ( 'same_as_catalog' === ProductPagePriceSubsection::getFormatPriceType() ) { $priceType = 'static'; } } $isVariable = in_array( $product->get_type(), TierPricingTablePlugin::getSupportedVariableProductTypes() ); $pricingRules = PriceManager::getPricingRule( $product->get_id() ); // Do not wrap if there is no pricing rules if ( !$isVariable && empty( $pricingRules->getRules() ) ) { $priceType = 'no-rules'; } $supportedTypes = array_merge( TierPricingTablePlugin::getSupportedSimpleProductTypes(), array('variation') ); $wrapVariableProductPrice = apply_filters( 'tiered_pricing_table/frontend/wrap_variable_price', true, $product ); // Is "show total price" is enabled, we can wrap the variable product price, or it's forced by the hook if ( $wrapVariableProductPrice || $this->getContainer()->getSettings()->get( 'show_total_price', 'no' ) === 'yes' ) { $supportedTypes = array_merge( $supportedTypes, TierPricingTablePlugin::getSupportedVariableProductTypes() ); } if ( in_array( $product->get_type(), $supportedTypes ) ) { ob_start(); ?> '; } return $priceHTML; } /** * Render a pricing table on the product page */ public function renderPricingTableOnProductPage() { global $post; if ( !$post || !is_product() ) { return; } $variationId = $this->getVariationIdFromURL( $post->ID ); PricingTable::getInstance()->renderPricingTable( $post->ID, $variationId ); } public function getVariationIdFromURL( $productId ) : ?int { $attributes = array(); $attributeWordLen = strlen( 'attribute_' ); foreach ( $_REQUEST as $key => $value ) { if ( strlen( $key ) < $attributeWordLen ) { continue; } $string = substr( $key, 0, $attributeWordLen ); if ( strcasecmp( $string, 'attribute_' ) === 0 ) { $attributes[$key] = $value; } } if ( empty( $attributes ) ) { return null; } $product = wc_get_product( $productId ); if ( !$product ) { return null; } if ( !TierPricingTablePlugin::isVariableProductSupported( $product ) ) { return null; } return ( new WC_Product_Data_Store_CPT() )->find_matching_product_variation( $product, $attributes ); } /** * Render tooltip near product price if selected display type is "tooltip" * * @param ?string $price * @param WC_Product $_product * * @return string */ public function renderTooltip( ?string $price, WC_Product $_product ) : ?string { // Do not render if not display if ( 'yes' !== $this->getContainer()->getSettings()->get( 'display', 'yes' ) ) { return $price; } $displayType = TieredPricingTab::getProductTemplate( ( $_product->get_parent_id() ? $_product->get_parent_id() : $_product->get_id() ) ); $displayType = ( 'default' === $displayType ? $this->getContainer()->getSettings()->get( 'display_type', 'table' ) : $displayType ); // Do not display if display type is not the tooltip if ( 'tooltip' !== $displayType ) { return $price; } if ( is_product() ) { $addTooltip = false; $page_product_id = get_queried_object_id(); if ( $_product->is_type( 'variation' ) && $_product->get_parent_id() === $page_product_id ) { $addTooltip = true; } elseif ( $_product->get_id() === $page_product_id && TierPricingTablePlugin::isSimpleProductSupported( $_product ) ) { $addTooltip = true; } if ( !$addTooltip ) { return $price; } $pricingRule = PriceManager::getPricingRule( $_product->get_id() ); if ( !empty( $pricingRule->getRules() ) ) { return $price . $this->getContainer()->getFileManager()->renderTemplate( 'frontend/tooltip.php', array( 'color' => $this->getContainer()->getSettings()->get( 'tooltip_color', '#96598A' ), 'size' => $this->getContainer()->getSettings()->get( 'tooltip_size', 15 ) . 'px', ) ); } } return $price; } /** * Fired when user chooses a variation. Renders tiered pricing table for the variation * * @throws Exception * @global WP_Post $post . */ public function getVariationPricingTable() { $product_id = ( isset( $_POST['variation_id'] ) ? sanitize_text_field( $_POST['variation_id'] ) : false ); $nonce = ( isset( $_POST['nonce'] ) ? sanitize_key( $_POST['nonce'] ) : false ); $displayContext = ( isset( $_POST['display_context'] ) ? sanitize_text_field( $_POST['display_context'] ) : 'product-page' ); $renderSettings = apply_filters( 'tiered_pricing_table/frontend/variation_render_settings', array(), $product_id, $displayContext ); // Some cache plugins may cache the nonce, so we need to leave the ability to disable nonce verification. // Checking nonce is not critical here. $verifyNonce = apply_filters( 'tiered_pricing_table/frontend/load_variation/verify_nonce', false, $nonce, 'get_pricing_table' ); if ( $verifyNonce && !wp_verify_nonce( $nonce, 'get_pricing_table' ) ) { return; } $product = wc_get_product( $product_id ); if ( $product ) { $parentProduct = wc_get_product( $product->get_parent_id() ); if ( $product ) { PricingTable::getInstance()->renderPricingTableHTML( $parentProduct, $product, $renderSettings ); } } } }