PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 1.9.12 1.9.11 1.9.10 1.9.9 All 158 releases
woocommerce-pos / includes / Products.php

Products.php in WCPOS – Point of Sale (POS) plugin for WooCommerce trunk, at includes/Products.php

238 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * POS Product Class.
4 *
5 * @author Paul Kilmurray <paul@kilbot.com>
6 *
7 * @see https://wcpos.com
8 * @package WCPOS\WooCommercePOS
9 */
10
11 namespace WCPOS\WooCommercePOS;
12
13 use Automattic\WooCommerce\StoreApi\Exceptions\NotPurchasableException;
14 use WC_Product;
15 use WC_Product_Variation;
16 use WCPOS\WooCommercePOS\Services\Settings;
17 use WP_Query;
18
19 /**
20 * Products class.
21 */
22 class Products {
23 /**
24 * Constructor.
25 */
26 public function __construct() {
27 add_action( 'woocommerce_product_set_stock', array( $this, 'product_set_stock' ) );
28 add_action( 'woocommerce_variation_set_stock', array( $this, 'product_set_stock' ) );
29
30 $pos_only_products = Settings::instance()->pos_only_products_enabled();
31
32 if ( $pos_only_products ) {
33 add_action( 'pre_get_posts', array( $this, 'hide_pos_only_products' ) );
34 add_filter( 'woocommerce_variation_is_visible', array( $this, 'hide_pos_only_variations' ), 10, 4 );
35 add_action( 'woocommerce_store_api_validate_add_to_cart', array( $this, 'store_api_prevent_pos_only_add_to_cart' ) );
36
37 // NOTE: this hook is marked as deprecated.
38 add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'prevent_pos_only_add_to_cart' ), 10, 2 );
39
40 // add_filter( 'woocommerce_get_product_subcategories_args', array( $this, 'filter_category_count_exclude_pos_only' ) );.
41 }
42
43 /**
44 * Allow decimal quantities for products and variations.
45 *
46 * @TODO - this will affect the online store as well, should I make it POS only?
47 */
48 $allow_decimal_quantities = Settings::instance()->decimal_qty_enabled();
49 if ( $allow_decimal_quantities ) {
50 remove_filter( 'woocommerce_stock_amount', 'intval' );
51 add_filter( 'woocommerce_stock_amount', 'floatval' );
52 add_action( 'woocommerce_before_product_object_save', array( $this, 'save_decimal_quantities' ) );
53 }
54 }
55
56 /**
57 * Bump modified date on stock change.
58 * - variation->id = parent id.
59 *
60 * @param WC_Product $product The product object.
61 */
62 public function product_set_stock( WC_Product $product ): void {
63 $post_modified = current_time( 'mysql' );
64 $post_modified_gmt = current_time( 'mysql', true );
65 wp_update_post(
66 array(
67 'ID' => $product->get_id(),
68 'post_modified' => $post_modified,
69 'post_modified_gmt' => $post_modified_gmt,
70 )
71 );
72 }
73
74 /**
75 * Hide POS only products and variations from front-end queries.
76 *
77 * @param \WP_Query $query The WP_Query instance (passed by reference).
78 *
79 * @return void
80 */
81 public function hide_pos_only_products( \WP_Query $query ): void {
82 // Only run for front-end queries, not admin or POS requests.
83 if ( is_admin() || woocommerce_pos_request() ) {
84 return;
85 }
86
87 // WP_Query post_type can be string or array.
88 $post_types = (array) $query->get( 'post_type' );
89 $settings_instance = Settings::instance();
90 $exclude_ids = array();
91
92 // Handle product queries.
93 if ( \in_array( 'product', $post_types, true ) ) {
94 $settings = $settings_instance->get_pos_only_product_visibility_settings();
95 if ( isset( $settings['ids'] ) && ! empty( $settings['ids'] ) ) {
96 $exclude_ids = array_merge(
97 $exclude_ids,
98 array_map( 'intval', (array) $settings['ids'] )
99 );
100 }
101 }
102
103 // Handle product_variation queries.
104 if ( \in_array( 'product_variation', $post_types, true ) ) {
105 $settings = $settings_instance->get_pos_only_variations_visibility_settings();
106 if ( isset( $settings['ids'] ) && ! empty( $settings['ids'] ) ) {
107 $exclude_ids = array_merge(
108 $exclude_ids,
109 array_map( 'intval', (array) $settings['ids'] )
110 );
111 }
112 }
113
114 // Apply exclusions if any.
115 if ( ! empty( $exclude_ids ) ) {
116 $existing_excludes = $query->get( 'post__not_in' );
117 if ( ! \is_array( $existing_excludes ) ) {
118 $existing_excludes = array();
119 }
120 $query->set( 'post__not_in', array_merge( $existing_excludes, $exclude_ids ) );
121 }
122 }
123
124 /**
125 * Remove POS Only variations from the storefront.
126 *
127 * @param bool $visible Whether the variation is visible.
128 * @param int $variation_id The variation ID.
129 * @param int $product_id The product ID.
130 * @param WC_Product_Variation $variation The variation object.
131 */
132 public function hide_pos_only_variations( $visible, $variation_id, $product_id, $variation ) {
133 if ( is_shop() || is_product_category() || is_product() ) {
134 $settings_instance = Settings::instance();
135 $pos_only = $settings_instance->is_variation_pos_only( $variation_id );
136
137 if ( $pos_only ) {
138 return false;
139 }
140 }
141
142 return $visible;
143 }
144
145 /**
146 * Filter category count to exclude pos_only products.
147 *
148 * @param array $args The arguments for getting product subcategories.
149 *
150 * @return array The modified arguments.
151 */
152 public function filter_category_count_exclude_pos_only( $args ) {
153 // @TODO: Do we need this?
154
155 return $args;
156 }
157
158 /**
159 * Prevent POS Only products from being added to the cart.
160 *
161 * NOTE: this hook is marked as deprecated.
162 *
163 * @param bool $passed Whether the product can be added to the cart.
164 * @param int $product_id The product ID.
165 *
166 * @return bool
167 */
168 public function prevent_pos_only_add_to_cart( $passed, $product_id ) {
169 $settings_instance = Settings::instance();
170 $product_pos_only = $settings_instance->is_product_pos_only( $product_id );
171 $variation_pos_only = $settings_instance->is_variation_pos_only( $product_id );
172
173 if ( $product_pos_only || $variation_pos_only ) {
174 return false;
175 }
176
177 return $passed;
178 }
179
180 /**
181 * Prevent POS Only products from being added to the cart via the Store API.
182 *
183 * @param WC_Product $product Product.
184 *
185 * @throws NotPurchasableException If the product is POS only.
186 *
187 * @return void
188 */
189 public function store_api_prevent_pos_only_add_to_cart( WC_Product $product ): void {
190 $settings_instance = Settings::instance();
191 if ( $product->is_type( 'variation' ) ) {
192 $pos_only = $settings_instance->is_variation_pos_only( $product->get_id() );
193 } else {
194 $pos_only = $settings_instance->is_product_pos_only( $product->get_id() );
195 }
196
197 if ( $pos_only ) {
198 // @phpstan-ignore-next-line.
199 throw new NotPurchasableException(
200 'woocommerce_pos_product_not_purchasable',
201 esc_html( $product->get_name() )
202 );
203 }
204 }
205
206 /**
207 * Save decimal quantities for products and variations.
208 *
209 * @param WC_Product $product Product.
210 */
211 public function save_decimal_quantities( WC_Product $product ): void {
212 // Without stock management there is no quantity to derive a status from;
213 // WooCommerce respects the manually chosen stock status, so leave it untouched.
214 if ( ! $product->get_manage_stock() ) {
215 return;
216 }
217
218 $stock_quantity = $product->get_stock_quantity();
219 $stock_notification_threshold = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) );
220
221 // Mirrors WC_Product::validate_props() minus its (int) cast on the stock
222 // quantity, which would truncate fractional stock (e.g. 0.5) to 0 and
223 // mark it out of stock. Re-check against core on WooCommerce updates.
224 $stock_is_above_notification_threshold = ( $stock_quantity > $stock_notification_threshold );
225 $backorders_are_allowed = ( 'no' !== $product->get_backorders() );
226
227 if ( $stock_is_above_notification_threshold ) {
228 $new_stock_status = 'instock';
229 } elseif ( $backorders_are_allowed ) {
230 $new_stock_status = 'onbackorder';
231 } else {
232 $new_stock_status = 'outofstock';
233 }
234
235 $product->set_stock_status( $new_stock_status );
236 }
237 }
238