PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / classes / product / variable-product.php

variable-product.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/classes/product/variable-product.php

232 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Classes\Product;
4
5 use StoreEngine\classes\AbstractProduct;
6 use StoreEngine\Classes\Variation;
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 class VariableProduct extends AbstractProduct {
13
14 /**
15 * @return Variation[]
16 */
17 public function get_variants(): array {
18 $cache_key = self::CACHE_KEY . $this->get_id() . '_variants';
19 $has_cache = wp_cache_get( $cache_key, self::CACHE_GROUP );
20 if ( $has_cache ) {
21 return $has_cache;
22 }
23
24 global $wpdb;
25 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
26 $results = $wpdb->get_results( $wpdb->prepare(
27 "SELECT
28 *
29 FROM
30 {$wpdb->prefix}storeengine_product_variations
31 WHERE
32 product_id = %d", $this->get_id()
33 ) );
34
35 if ( ! $results ) {
36 return [];
37 }
38
39 $variation_ids = wp_list_pluck( $results, 'id' );
40 $placeholders = array_fill( 0, count( $variation_ids ), '%d' );
41 $placeholders = implode( ',', $placeholders );
42 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
43 $variation_ids = $wpdb->prepare( "($placeholders)", $variation_ids );
44
45 // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery -- prepared above.
46 $variation_metadata_db = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}storeengine_product_variation_meta WHERE variation_id IN $variation_ids" );
47 // phpcs:enabled PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery -- prepared above.
48
49 $variation_metadata = [];
50 foreach ( $variation_metadata_db as $variation_meta ) {
51 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
52 $variation_metadata[ (int) $variation_meta->variation_id ][ $variation_meta->meta_key ] = $variation_meta->meta_value;
53 }
54
55 // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery
56 $variation_attributes = $wpdb->get_results(
57 "SELECT
58 t.term_id AS term_id,
59 tt.term_taxonomy_id AS term_taxonomy_id,
60 pl.variation_id AS variation_id,
61 t.name AS name,
62 t.slug AS slug,
63 tt.description AS description,
64 tt.taxonomy AS taxonomy,
65 tt.count AS count,
66 pl.term_order AS term_order
67 FROM
68 {$wpdb->prefix}storeengine_variation_term_relations pl
69 LEFT JOIN {$wpdb->terms} t ON t.term_id = pl.term_id
70 LEFT JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = pl.term_id
71 WHERE
72 pl.variation_id IN $variation_ids
73 AND t.term_id <> ''
74 ORDER BY
75 pl.term_order DESC"
76 );
77 // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery
78
79 if ( is_array( $variation_attributes ) ) {
80 $variation_attributes_list = [];
81 foreach ( $variation_attributes as $variation_attribute ) {
82 $variation_attributes_list[ $variation_attribute->variation_id ][] = $variation_attribute;
83 }
84
85 foreach ( $results as $result ) {
86 $result->attributes = $variation_attributes_list[ $result->id ] ?? [];
87 }
88 }
89
90 $variants = [];
91
92 foreach ( $results as $result ) {
93 $variation = ( new Variation() )->set_data( $result );
94 $variation->set_metadata( $variation_metadata[ (int) $result->id ] ?? [] );
95 $variants[] = $variation;
96 }
97
98 wp_cache_set( $cache_key, $variants, self::CACHE_GROUP );
99
100 return $variants;
101 }
102
103 /**
104 * Variants that should appear in the storefront picker + JS data feed.
105 *
106 * A variant counts as "available" when EITHER:
107 * - its own `price` column is set (the per-variant increment), OR
108 * - it carries a `_pricing_id` meta pointing at a price tier on
109 * the parent product.
110 *
111 * Either is enough for the variant to be sellable — the cart resolves
112 * the final price downstream. Rows with neither are considered
113 * misconfigured and stay hidden.
114 *
115 * Stock status is NOT checked here; out-of-stock variants still need
116 * to be rendered so the customer can see them and (with the
117 * back-in-stock addon active) subscribe for restock alerts. The
118 * frontend gates Add-to-Cart based on the per-variant `is_in_stock`
119 * value published in the JS feed.
120 *
121 * @return Variation[]
122 */
123 public function get_available_variants(): array {
124 return array_values( array_filter(
125 $this->get_variants(),
126 function ( $v ) {
127 $has_price = null !== $v->get_price();
128 $has_price_id = method_exists( $v, 'get_pricing_id' ) && (int) $v->get_pricing_id() > 0;
129 return $has_price || $has_price_id;
130 }
131 ) );
132 }
133
134 /**
135 * Returns true if any variation manages stock — in which case parent-level
136 * stock fields are ignored and stock is sourced from the variations.
137 */
138 public function any_variant_manages_stock(): bool {
139 foreach ( $this->get_variants() as $variant ) {
140 if ( $variant->manages_stock() ) {
141 return true;
142 }
143 }
144
145 return false;
146 }
147
148 public function manages_stock(): bool {
149 if ( $this->any_variant_manages_stock() ) {
150 return true;
151 }
152
153 return parent::manages_stock();
154 }
155
156 public function is_in_stock(): bool {
157 if ( $this->any_variant_manages_stock() ) {
158 foreach ( $this->get_variants() as $variant ) {
159 if ( $variant->is_in_stock() ) {
160 return true;
161 }
162 }
163
164 return false;
165 }
166
167 return parent::is_in_stock();
168 }
169
170 public function is_low_stock(): bool {
171 if ( $this->any_variant_manages_stock() ) {
172 foreach ( $this->get_variants() as $variant ) {
173 if ( $variant->is_low_stock() ) {
174 return true;
175 }
176 }
177
178 return false;
179 }
180
181 return parent::is_low_stock();
182 }
183
184 public function get_stock_status(): string {
185 if ( $this->any_variant_manages_stock() ) {
186 $any_in_stock = false;
187 $any_on_backorder = false;
188
189 foreach ( $this->get_variants() as $variant ) {
190 $status = $variant->get_stock_status();
191 if ( 'instock' === $status ) {
192 $any_in_stock = true;
193 } elseif ( 'onbackorder' === $status ) {
194 $any_on_backorder = true;
195 }
196 }
197
198 if ( $any_in_stock ) {
199 return 'instock';
200 }
201 if ( $any_on_backorder ) {
202 return 'onbackorder';
203 }
204
205 return 'outofstock';
206 }
207
208 return parent::get_stock_status();
209 }
210
211 public function get_aggregate_stock_quantity(): ?int {
212 if ( ! $this->any_variant_manages_stock() ) {
213 return parent::get_stock_quantity();
214 }
215
216 $total = 0;
217 $has = false;
218
219 foreach ( $this->get_variants() as $variant ) {
220 if ( $variant->manages_stock() ) {
221 $qty = $variant->get_stock_quantity();
222 if ( null !== $qty ) {
223 $total += $qty;
224 $has = true;
225 }
226 }
227 }
228
229 return $has ? $total : null;
230 }
231 }
232