PluginProbe
Tiered Pricing Table for WooCommerce / 8.0.2
Tiered Pricing Table for WooCommerce v8.0.2
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
← All changes | src/Addons/ProductCatalogLoop/QuantityFieldHandler.php +116 -3 6.5.0 → 8.0.2 View file →
@@ -1,19 +1,123 @@
1 1 <?php namespace TierPricingTable\Addons\ProductCatalogLoop;
2 2
3 +use TierPricingTable\Core\ServiceContainer;
3 4 use TierPricingTable\TierPricingTablePlugin;
5 +use WC_Product;
6 +use WP_Block;
4 7
5 8 class QuantityFieldHandler {
6 9
10 + /** The Interactivity API module behind the box next to the Product Button block. */
11 + const BLOCK_MODULE = 'tiered-pricing-table-catalog-quantity';
12 +
13 + /** Whether the wrapper around the quantity box and the add-to-cart button is open for the current loop item. */
14 + protected bool $wrapperOpen = false;
15 +
7 16 public function __construct() {
8 17 add_action( 'woocommerce_after_shop_loop_item', array( $this, 'renderQuantityField' ), 9 );
18 + // WooCommerce prints the add-to-cart button at priority 10; the wrapper closes right after it
19 + add_action( 'woocommerce_after_shop_loop_item', array( $this, 'closeWrapper' ), 11 );
9 20 add_action( 'wp_enqueue_scripts', array( $this, 'enqueueQuantityHandlerScript' ) );
21 +
22 + // Product Collection and block themes: the box goes into the Product Button block, before the
23 + // catalog pricing is added around that block (priority 10)
24 + add_filter( 'render_block_woocommerce/product-button', array( $this, 'renderInProductButtonBlock' ), 9, 3 );
25 + add_action( 'wp_enqueue_scripts', array( $this, 'registerBlockModule' ) );
10 26 }
11 27
28 + /**
29 + * Simple products that can be bought in any quantity get the box.
30 + */
31 + protected function wantsQuantityField( ?WC_Product $product ): bool {
32 + return $product && TierPricingTablePlugin::isSimpleProductSupported( $product ) && ! $product->is_sold_individually() && $product->is_purchasable() && $product->is_in_stock();
33 + }
34 +
35 + public function registerBlockModule() {
36 + if ( function_exists( 'wp_register_script_module' ) ) {
37 + wp_register_script_module( self::BLOCK_MODULE,
38 + ServiceContainer::getInstance()->getFileManager()->locateJSAsset( 'frontend/catalog-quantity' ),
39 + array( '@wordpress/interactivity' ), TierPricingTablePlugin::VERSION );
40 + }
41 + }
42 +
43 + /**
44 + * The quantity box next to the Product Button block's button, on one line. The block keeps the
45 + * quantity it adds in its Interactivity API context; the box's module writes the typed quantity there.
46 + *
47 + * @param string $content
48 + * @param array $block
49 + * @param WP_Block|mixed $instance
50 + *
51 + * @return string
52 + */
53 + public function renderInProductButtonBlock( $content, $block, $instance ) {
54 + if ( ! function_exists( 'wp_enqueue_script_module' ) || ! ( $instance instanceof WP_Block ) || empty( $instance->context['postId'] ) ) {
55 + return $content;
56 + }
57 +
58 + // only the button of a product list: inside the add-to-cart-with-options form the block has its own quantity selector
59 + if ( false === strpos( $content, 'actions.addCartItem' ) ) {
60 + return $content;
61 + }
62 +
63 + $product = wc_get_product( (int) $instance->context['postId'] );
64 +
65 + if ( ! $this->wantsQuantityField( $product ) ) {
66 + return $content;
67 + }
68 +
69 + $quantity = woocommerce_quantity_input( array(
70 + 'min_value' => 1,
71 + 'max_value' => $product->backorders_allowed() ? '' : $product->get_stock_quantity(),
72 + ), $product, false );
73 +
74 + if ( ! $quantity ) {
75 + return $content;
76 + }
77 +
78 + // the box is its own interactive region; the input feeds the button block's context
79 + $quantity = preg_replace( '/<div class="quantity/', '<div data-wp-interactive="tiered-pricing-table/catalog-quantity" class="quantity', $quantity, 1 );
80 + $quantity = preg_replace( '/<input\b/', '<input data-wp-on--input="actions.update" data-wp-on--change="actions.update" data-wp-on--keydown="actions.submitOnEnter"', $quantity, 1 );
81 +
82 + // the block's own top spacing sits on the button; the row takes it over so both parts align
83 + $rowStyle = '';
84 +
85 + if ( preg_match( '/<button\b[^>]*\sstyle="([^"]*)"/', $content, $m ) && preg_match( '/margin-top:[^;"]+/', $m[1], $marginTop ) ) {
86 + $rowStyle = ' style="' . esc_attr( $marginTop[0] ) . ';"';
87 + $content = preg_replace_callback( '/(<button\b[^>]*\sstyle=")([^"]*)(")/', function ( $mm ) use ( $marginTop ) {
88 + return $mm[1] . trim( str_replace( $marginTop[0], '', $mm[2] ), '; ' ) . $mm[3];
89 + }, $content, 1 );
90 + }
91 +
92 + $wrapped = preg_replace_callback( '/<button\b.*?<\/button>/s', function ( $mm ) use ( $quantity, $rowStyle ) {
93 + return '<div class="tpt-loop-purchase"' . $rowStyle . '>' . $quantity . $mm[0] . '</div>';
94 + }, $content, 1, $count );
95 +
96 + if ( ! $count ) {
97 + return $content;
98 + }
99 +
100 + wp_enqueue_script_module( self::BLOCK_MODULE );
101 +
102 + return $wrapped;
103 + }
104 +
12 105 public function renderQuantityField() {
106 + // the product button block has no place for the field, and WooCommerce fires this classic hook there too
107 + if ( ProductCatalogLoop::isRenderingProductBlocks() ) {
108 + return;
109 + }
110 +
13 111 $product = wc_get_product( get_the_ID() );
14 112
15 - if ( $product && ( TierPricingTablePlugin::isSimpleProductSupported( $product ) ) && ! $product->is_sold_individually() && $product->is_purchasable() && $product->is_in_stock() ) {
113 + if ( $this->wantsQuantityField( $product ) ) {
114 + // the quantity box and the add-to-cart button share one line (a flex row, see main.css)
115 + if ( apply_filters( 'tiered_pricing_table/catalog/quantity_inline', true, $product ) ) {
116 + echo '<div class="tpt-loop-purchase">';
117 + $this->wrapperOpen = true;
118 + }
119 +
16 120 woocommerce_quantity_input( array(
17 121 'min_value' => 1,
18 122 'max_value' => $product->backorders_allowed() ? '' : $product->get_stock_quantity(),
19 123 ) );
@@ -19,8 +123,15 @@
19 123 ) );
20 124 }
21 125 }
22 126
127 + public function closeWrapper() {
128 + if ( $this->wrapperOpen ) {
129 + echo '</div>';
130 + $this->wrapperOpen = false;
131 + }
132 + }
133 +
23 134 public function enqueueQuantityHandlerScript() {
24 135
25 136 $handle = 'tpt-catalog-quantity-input-handler';
26 137
@@ -38,10 +149,12 @@
38 149
39 150 // For AJAX add-to-cart actions
40 151 add_to_cart_button.attr("data-quantity", jQuery(this).val());
41 152
42 - // For non-AJAX add-to-cart actions
43 - add_to_cart_button.attr("href", "?add-to-cart=" + add_to_cart_button.attr("data-product_id") + "&quantity=" + jQuery(this).val());
153 + // For non-AJAX add-to-cart actions (the Product Button block is a <button> and keeps its own quantity)
154 + if (add_to_cart_button.is("a")) {
155 + add_to_cart_button.attr("href", "?add-to-cart=" + add_to_cart_button.attr("data-product_id") + "&quantity=" + jQuery(this).val());
156 + }
44 157 });
45 158
46 159 // Trigger on Enter press
47 160 jQuery(".woocommerce .products").on("keypress", ".quantity .qty", function(e) {