PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.0.0-beta2
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.0.0-beta2
4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 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 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Models / Blocks / ProductPageBlock.php
surecart / app / src / Models / Blocks Last commit date
ProductListBlock.php 1 year ago ProductPageBlock.php 1 year ago
ProductPageBlock.php
435 lines
1 <?php
2
3 namespace SureCart\Models\Blocks;
4
5 /**
6 * The product list service.
7 */
8 class ProductPageBlock {
9 /**
10 * The URL.
11 *
12 * @var object
13 */
14 protected $url;
15
16 /**
17 * Constructor.
18 */
19 public function __construct() {
20 $this->url = \SureCart::block()->urlParams();
21 }
22
23 /**
24 * Get the variants query.
25 *
26 * @return array|null
27 */
28 public function getVariantsQuery() {
29 $product = sc_get_product();
30
31 if ( empty( $product ) || empty( $product->variants->data ) ) {
32 return null;
33 }
34
35 // get the initial defaults if there were no args.
36 // we need to turn into slugs for comparison to make sure capitalization,
37 // spacing, etc. doesn't matter.
38 $initial_defaults = array_reduce(
39 $product->variant_options->data ?? [],
40 function ( $carry, $option ) {
41 $name = sanitize_title( $option->name );
42 $carry[ $name ] = sanitize_title( $option->values[0] );
43 return $carry;
44 },
45 []
46 );
47
48 // use initial defaults to only get args needed.
49 $args = [];
50 foreach ( $initial_defaults as $key => $value ) {
51 $args[ $key ] = $this->url->getArg( $key );
52 }
53
54 // merge the args with the initial defaults.
55 $attributes = wp_parse_args(
56 array_filter( $args ),
57 $initial_defaults
58 );
59
60 // loop through the attributes.
61 $keys = [];
62 foreach ( $attributes as $option_name => $value ) {
63 // find the option index based on the name.
64 $option_index = array_search(
65 // get the sanitized option name for comparison.
66 sanitize_title( $option_name ),
67 // get the sanitized option names for comparison.
68 array_map(
69 fn( $name ) => sanitize_title( $name ),
70 array_column( $product->variant_options->data, 'name' )
71 ),
72 true
73 );
74
75 // if the option index is not found, skip.
76 $keys[ 'option_' . ( $option_index + 1 ) ] = $value;
77 }
78
79 return $keys;
80 }
81
82 /**
83 * Get the URL.
84 *
85 * @return object|null
86 */
87 public function getSelectedVariant() {
88 $product = sc_get_product();
89
90 if ( empty( $product ) || empty( $product->variants->data ) ) {
91 return null;
92 }
93
94 // loop through the attributes.
95 $keys = $this->getVariantsQuery();
96
97 $variants = array_values(
98 array_filter(
99 ( $product->variants->data ?? [] ),
100 function ( $variant ) use ( $keys ) {
101 foreach ( $keys as $key => $value ) {
102 if ( sanitize_title( $variant->$key ) !== sanitize_title( $value ) ) {
103 return false;
104 }
105 }
106 return true;
107 }
108 )
109 );
110
111 if ( ! empty( $variants[0] ) ) {
112 return $variants[0];
113 }
114
115 if ( ! empty( $product->first_variant_with_stock ) ) {
116 return $product->first_variant_with_stock;
117 }
118
119 return $product->variants->data[0] ?? null;
120 }
121
122 /**
123 * Get the URL.
124 *
125 * @return object|null
126 */
127 public function urlParams() {
128 return $this->url;
129 }
130
131 /**
132 * Get the context.
133 *
134 * @param array $context The context to add to the existing context.
135 *
136 * @return array
137 */
138 public function context( $context = [] ) {
139 $product = sc_get_product();
140 if ( empty( $product ) ) {
141 return [];
142 }
143
144 $selected_variant = $this->getSelectedVariant();
145
146 return wp_parse_args(
147 $context,
148 array(
149 'formId' => \SureCart::forms()->getDefaultId(),
150 'mode' => \SureCart\Models\Form::getMode( \SureCart::forms()->getDefaultId() ),
151 'checkoutUrl' => \SureCart::pages()->url( 'checkout' ),
152 'urlPrefix' => $this->urlParams()->getKey(),
153 'product' => ! empty( $product ) ? $product->only( [ 'id', 'name', 'has_unlimited_stock', 'available_stock', 'archived', 'permalink' ] ) : null,
154 'selectedPrice' => ! empty( $product->initial_price ) ? $product->initial_price->only(
155 [
156 'id',
157 'archived',
158 'amount',
159 'display_amount',
160 'scratch_amount',
161 'scratch_display_amount',
162 'ad_hoc',
163 'is_on_sale',
164 'is_zero_decimal',
165 'currency_symbol',
166 'converted_ad_hoc_min_amount',
167 'converted_ad_hoc_max_amount',
168 'setup_fee_text',
169 'interval_text',
170 'short_interval_text',
171 'interval_count_text',
172 'payments_text',
173 'trial_text',
174 ]
175 ) : null,
176 'prices' => array_map(
177 fn( $price ) => $price->only(
178 [
179 'id',
180 'archived',
181 'amount',
182 'display_amount',
183 'scratch_amount',
184 'scratch_display_amount',
185 'ad_hoc',
186 'is_on_sale',
187 'is_zero_decimal',
188 'currency_symbol',
189 'converted_ad_hoc_min_amount',
190 'converted_ad_hoc_max_amount',
191 'setup_fee_text',
192 'interval_text',
193 'short_interval_text',
194 'interval_count_text',
195 'payments_text',
196 'trial_text',
197 ]
198 ),
199 $product->active_prices
200 ),
201 'variants' => array_map(
202 fn( $variant ) => $variant->only(
203 [
204 'id',
205 'option_1',
206 'option_2',
207 'option_3',
208 'price',
209 'amount',
210 'display_amount',
211 'available_stock',
212 ]
213 ),
214 $product->variants->data ?? array()
215 ),
216 'quantity' => 1,
217 'busy' => false,
218 'adHocAmount' => ( ! empty( $product->initial_price->ad_hoc ) ? $product->initial_price->amount : 0 ) / ( ! empty( $product->initial_price->is_zero_decimal ) ? 1 : 100 ),
219 'variantValues' => array_filter(
220 array(
221 'option_1' => $selected_variant->option_1 ?? null,
222 'option_2' => $selected_variant->option_2 ?? null,
223 'option_3' => $selected_variant->option_3 ?? null,
224 )
225 ),
226 'text' => __( 'Add to Cart', 'surecart' ),
227 'outOfStockText' => __( 'Sold Out', 'surecart' ),
228 'unavailableText' => __( 'Unavailable For Purchase', 'surecart' ),
229 ),
230 );
231 }
232
233 /**
234 * Get the state.
235 *
236 * @param array $state The state to add to the existing state.
237 *
238 * @return array
239 */
240 public function state( $state = [] ) {
241 $product = sc_get_product();
242 if ( empty( $product ) ) {
243 return [];
244 }
245 $selected_price = $product->initial_price;
246 $selected_variant = $this->getSelectedVariant();
247
248 return wp_parse_args(
249 $state,
250 [
251 'quantity' => 1,
252 'selectedDisplayAmount' => $product->display_amount,
253 'isOnSale' => function () {
254 $context = wp_interactivity_get_context();
255 $selected_price = $context['selectedPrice'];
256 return $selected_price['is_on_sale'] ?? false;
257 },
258 'selectedAmount' => function () {
259 $context = wp_interactivity_get_context();
260 $state = wp_interactivity_state();
261 $selected_price = $context['selectedPrice'];
262 $prices = $context['prices'];
263
264 if ( ! empty( $prices ) && count( $prices ) > 1 ) {
265 return $selected_price['amount'];
266 }
267
268 return $state['selectedVariant']['amount'] ?? $selected_price['amount'];
269 },
270 'busy' => false,
271 'shouldDisplayImage' => function () {
272 $context = wp_interactivity_get_context();
273 $state = wp_interactivity_state();
274
275 if ( empty( $context['variants'] ) ) {
276 return true;
277 }
278
279 return $state['isOptionValueSelected']();
280 },
281 'adHocAmount' => ( ! empty( $selected_price->ad_hoc ) ? $selected_price->amount : 0 ) / ( ! empty( $selected_price->is_zero_decimal ) ? 1 : 100 ),
282 'selectedVariant' => ! empty( $selected_variant ) ? $selected_variant->only(
283 [
284 'id',
285 'option_1',
286 'option_2',
287 'option_3',
288 'price',
289 'amount',
290 'display_amount',
291 'available_stock',
292 ]
293 ) : [],
294 'isOptionUnavailable' => function () {
295 $context = wp_interactivity_get_context();
296 $variants = $context['variants'];
297 $option = $context['option_value'];
298 $product = $context['product'];
299 $variant_values = $context['variantValues'];
300 $option_number = $context['optionNumber'];
301
302 // stock is not enabled.
303 if ( $product['has_unlimited_stock'] ) {
304 return false;
305 }
306
307 if ( 1 === $option_number ) {
308 $items = array_filter(
309 $variants ?? [],
310 function ( $variant ) use ( $option ) {
311 return $variant['option_1'] === $option;
312 }
313 );
314 $highest_stock = max(
315 array_map(
316 function ( $item ) {
317 return $item['available_stock'];
318 },
319 $items ?? []
320 )
321 );
322
323 return $highest_stock <= 0;
324 }
325
326 if ( 2 === $option_number ) {
327 $items = array_filter(
328 $variants ?? [],
329 function ( $variant ) use ( $variant_values, $option ) {
330 return $variant['option_1'] === $variant_values['option_1'] && $variant['option_2'] === $option;
331 }
332 );
333 $highest_stock = max(
334 array_map(
335 function ( $item ) {
336 return $item['available_stock'];
337 },
338 $items
339 )
340 );
341 return $highest_stock <= 0;
342 }
343
344 $items = array_filter(
345 $variants ?? [],
346 function ( $variant ) use ( $variant_values, $option ) {
347 return $variant['option_1'] === $variant_values['option_1'] && $variant['option_2'] === $variant_values['option_2'] && $variant['option_3'] === $option;
348 }
349 );
350
351 $highest_stock = max(
352 array_map(
353 function ( $item ) {
354 return $item['available_stock'];
355 },
356 $items
357 )
358 );
359
360 return $highest_stock <= 0;
361 },
362 'isOptionValueSelected' => function () {
363 $context = wp_interactivity_get_context();
364
365 if ( empty( $context['optionValue'] ) ) {
366 return true;
367 }
368
369 $values = array_map(
370 function ( $value ) {
371 return strtolower( $value );
372 },
373 array_values( $context['variantValues'] )
374 );
375
376 return in_array( strtolower( $context['optionValue'] ), $values );
377 },
378 'imageDisplay' => function () {
379 $state = wp_interactivity_state();
380 return $state['shouldDisplayImage']() ? 'inherit' : 'none';
381 },
382 'isSoldOut' => function () {
383 $context = wp_interactivity_get_context();
384 $state = wp_interactivity_state();
385 $product = $context['product'];
386 if ( $product['has_unlimited_stock'] ) {
387 return false;
388 }
389 if ( ! empty( $context['variants'] ) && empty( $state['selectedVariant'] ) ) {
390 return false;
391 }
392 return ! empty( $state['selectedVariant']['id'] ) ? $state['selectedVariant']['available_stock'] <= 0 : $product['available_stock'] <= 0;
393 },
394 'isUnavailable' => function () {
395 $context = wp_interactivity_get_context();
396 $state = wp_interactivity_state();
397 if ( ! empty( $context['product']->archived ) || ! empty( $state['isSoldOut']() ) ) {
398 return true;
399 }
400 if ( ! empty( $context['variants'] ) && empty( $state['selectedVariant'] ) ) {
401 return true;
402 }
403 return false;
404 },
405 'isOptionSelected' => function () {
406 $context = wp_interactivity_get_context();
407 $option_number = $context['optionNumber'] ?? '';
408 if ( ! isset( $context['variantValues'][ "option_$option_number" ] ) || ! isset( $context['option_value'] ) ) {
409 return false;
410 }
411 return $context['variantValues'][ "option_$option_number" ] === $context['option_value'];
412 },
413 'isPriceSelected' => function () {
414 $context = wp_interactivity_get_context();
415 if ( ! isset( $context['price'] ) || ! isset( $context['selectedPrice'] ) ) {
416 return false;
417 }
418 return $context['price']['id'] === $context['selectedPrice']['id'];
419 },
420 'buttonText' => function () {
421 $state = wp_interactivity_state();
422 $context = wp_interactivity_get_context();
423 if ( $state['isSoldOut']() ) {
424 return $context['outOfStockText'] ?? $context['text'];
425 }
426 if ( $state['isUnavailable']() ) {
427 return $context['unavailableText'] ?? $context['text'];
428 }
429 return $context['text'];
430 },
431 ]
432 );
433 }
434 }
435