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
tier-pricing-table / src / Addons / ProductCatalogLoop / QuantityFieldHandler.php

QuantityFieldHandler.php in Tiered Pricing Table for WooCommerce 8.0.2, at src/Addons/ProductCatalogLoop/QuantityFieldHandler.php

170 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\Addons\ProductCatalogLoop;
2
3 use TierPricingTable\Core\ServiceContainer;
4 use TierPricingTable\TierPricingTablePlugin;
5 use WC_Product;
6 use WP_Block;
7
8 class QuantityFieldHandler {
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
16 public function __construct() {
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 );
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' ) );
26 }
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
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
111 $product = wc_get_product( get_the_ID() );
112
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
120 woocommerce_quantity_input( array(
121 'min_value' => 1,
122 'max_value' => $product->backorders_allowed() ? '' : $product->get_stock_quantity(),
123 ) );
124 }
125 }
126
127 public function closeWrapper() {
128 if ( $this->wrapperOpen ) {
129 echo '</div>';
130 $this->wrapperOpen = false;
131 }
132 }
133
134 public function enqueueQuantityHandlerScript() {
135
136 $handle = 'tpt-catalog-quantity-input-handler';
137
138 wp_register_script( $handle, '', array( 'jquery' ), TierPricingTablePlugin::VERSION, true );
139
140 wp_enqueue_script( $handle );
141
142 $inlineJs = '
143 jQuery(".type-product").on("click", ".quantity input", function() {
144 return false;
145 });
146
147 jQuery(".type-product").on("change input", ".quantity .qty", function() {
148 var add_to_cart_button = jQuery(this).closest(".product").find(".add_to_cart_button");
149
150 // For AJAX add-to-cart actions
151 add_to_cart_button.attr("data-quantity", jQuery(this).val());
152
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 }
157 });
158
159 // Trigger on Enter press
160 jQuery(".woocommerce .products").on("keypress", ".quantity .qty", function(e) {
161 if ((e.which || e.keyCode) === 13) {
162 jQuery(this).parents(".product").find(".add_to_cart_button").trigger("click");
163 }
164 });
165 ';
166
167 wp_add_inline_script( $handle, $inlineJs );
168 }
169 }
170