PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.0.3
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.0.3
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
437 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',
166 'currency_symbol',
167 'converted_ad_hoc_min_amount',
168 'converted_ad_hoc_max_amount',
169 'setup_fee_text',
170 'interval_text',
171 'short_interval_text',
172 'interval_count_text',
173 'payments_text',
174 'trial_text',
175 ]
176 ) : null,
177 'prices' => array_map(
178 fn( $price ) => $price->only(
179 [
180 'id',
181 'archived',
182 'amount',
183 'display_amount',
184 'scratch_amount',
185 'scratch_display_amount',
186 'ad_hoc',
187 'is_on_sale',
188 'is_zero_decimal',
189 'currency',
190 'currency_symbol',
191 'converted_ad_hoc_min_amount',
192 'converted_ad_hoc_max_amount',
193 'setup_fee_text',
194 'interval_text',
195 'short_interval_text',
196 'interval_count_text',
197 'payments_text',
198 'trial_text',
199 ]
200 ),
201 $product->active_prices
202 ),
203 'variants' => array_map(
204 fn( $variant ) => $variant->only(
205 [
206 'id',
207 'option_1',
208 'option_2',
209 'option_3',
210 'price',
211 'amount',
212 'display_amount',
213 'available_stock',
214 ]
215 ),
216 $product->variants->data ?? array()
217 ),
218 'quantity' => 1,
219 'busy' => false,
220 'adHocAmount' => ( ! empty( $product->initial_price->ad_hoc ) ? $product->initial_price->amount : 0 ) / ( ! empty( $product->initial_price->is_zero_decimal ) ? 1 : 100 ),
221 'variantValues' => array_filter(
222 array(
223 'option_1' => $selected_variant->option_1 ?? null,
224 'option_2' => $selected_variant->option_2 ?? null,
225 'option_3' => $selected_variant->option_3 ?? null,
226 )
227 ),
228 'text' => __( 'Add to Cart', 'surecart' ),
229 'outOfStockText' => __( 'Sold Out', 'surecart' ),
230 'unavailableText' => __( 'Unavailable For Purchase', 'surecart' ),
231 ),
232 );
233 }
234
235 /**
236 * Get the state.
237 *
238 * @param array $state The state to add to the existing state.
239 *
240 * @return array
241 */
242 public function state( $state = [] ) {
243 $product = sc_get_product();
244 if ( empty( $product ) ) {
245 return [];
246 }
247 $selected_price = $product->initial_price;
248 $selected_variant = $this->getSelectedVariant();
249
250 return wp_parse_args(
251 $state,
252 [
253 'quantity' => 1,
254 'selectedDisplayAmount' => $product->display_amount,
255 'isOnSale' => function () {
256 $context = wp_interactivity_get_context();
257 $selected_price = $context['selectedPrice'];
258 return $selected_price['is_on_sale'] ?? false;
259 },
260 'selectedAmount' => function () {
261 $context = wp_interactivity_get_context();
262 $state = wp_interactivity_state();
263 $selected_price = $context['selectedPrice'];
264 $prices = $context['prices'];
265
266 if ( ! empty( $prices ) && count( $prices ) > 1 ) {
267 return $selected_price['amount'];
268 }
269
270 return $state['selectedVariant']['amount'] ?? $selected_price['amount'];
271 },
272 'busy' => false,
273 'shouldDisplayImage' => function () {
274 $context = wp_interactivity_get_context();
275 $state = wp_interactivity_state();
276
277 if ( empty( $context['variants'] ) ) {
278 return true;
279 }
280
281 return $state['isOptionValueSelected']();
282 },
283 'adHocAmount' => ( ! empty( $selected_price->ad_hoc ) ? $selected_price->amount : 0 ) / ( ! empty( $selected_price->is_zero_decimal ) ? 1 : 100 ),
284 'selectedVariant' => ! empty( $selected_variant ) ? $selected_variant->only(
285 [
286 'id',
287 'option_1',
288 'option_2',
289 'option_3',
290 'price',
291 'amount',
292 'display_amount',
293 'available_stock',
294 ]
295 ) : [],
296 'isOptionUnavailable' => function () {
297 $context = wp_interactivity_get_context();
298 $variants = $context['variants'];
299 $option = $context['option_value'];
300 $product = $context['product'];
301 $variant_values = $context['variantValues'];
302 $option_number = $context['optionNumber'];
303
304 // stock is not enabled.
305 if ( $product['has_unlimited_stock'] ) {
306 return false;
307 }
308
309 if ( 1 === $option_number ) {
310 $items = array_filter(
311 $variants ?? [],
312 function ( $variant ) use ( $option ) {
313 return $variant['option_1'] === $option;
314 }
315 );
316 $highest_stock = max(
317 array_map(
318 function ( $item ) {
319 return $item['available_stock'];
320 },
321 $items ?? []
322 )
323 );
324
325 return $highest_stock <= 0;
326 }
327
328 if ( 2 === $option_number ) {
329 $items = array_filter(
330 $variants ?? [],
331 function ( $variant ) use ( $variant_values, $option ) {
332 return $variant['option_1'] === $variant_values['option_1'] && $variant['option_2'] === $option;
333 }
334 );
335 $highest_stock = max(
336 array_map(
337 function ( $item ) {
338 return $item['available_stock'];
339 },
340 $items
341 )
342 );
343 return $highest_stock <= 0;
344 }
345
346 $items = array_filter(
347 $variants ?? [],
348 function ( $variant ) use ( $variant_values, $option ) {
349 return $variant['option_1'] === $variant_values['option_1'] && $variant['option_2'] === $variant_values['option_2'] && $variant['option_3'] === $option;
350 }
351 );
352
353 $highest_stock = max(
354 array_map(
355 function ( $item ) {
356 return $item['available_stock'];
357 },
358 $items
359 )
360 );
361
362 return $highest_stock <= 0;
363 },
364 'isOptionValueSelected' => function () {
365 $context = wp_interactivity_get_context();
366
367 if ( empty( $context['optionValue'] ) ) {
368 return true;
369 }
370
371 $values = array_map(
372 function ( $value ) {
373 return strtolower( $value );
374 },
375 array_values( $context['variantValues'] )
376 );
377
378 return in_array( strtolower( $context['optionValue'] ), $values );
379 },
380 'imageDisplay' => function () {
381 $state = wp_interactivity_state();
382 return $state['shouldDisplayImage']() ? 'inherit' : 'none';
383 },
384 'isSoldOut' => function () {
385 $context = wp_interactivity_get_context();
386 $state = wp_interactivity_state();
387 $product = $context['product'];
388 if ( $product['has_unlimited_stock'] ) {
389 return false;
390 }
391 if ( ! empty( $context['variants'] ) && empty( $state['selectedVariant'] ) ) {
392 return false;
393 }
394 return ! empty( $state['selectedVariant']['id'] ) ? $state['selectedVariant']['available_stock'] <= 0 : $product['available_stock'] <= 0;
395 },
396 'isUnavailable' => function () {
397 $context = wp_interactivity_get_context();
398 $state = wp_interactivity_state();
399 if ( ! empty( $context['product']->archived ) || ! empty( $state['isSoldOut']() ) ) {
400 return true;
401 }
402 if ( ! empty( $context['variants'] ) && empty( $state['selectedVariant'] ) ) {
403 return true;
404 }
405 return false;
406 },
407 'isOptionSelected' => function () {
408 $context = wp_interactivity_get_context();
409 $option_number = $context['optionNumber'] ?? '';
410 if ( ! isset( $context['variantValues'][ "option_$option_number" ] ) || ! isset( $context['option_value'] ) ) {
411 return false;
412 }
413 return $context['variantValues'][ "option_$option_number" ] === $context['option_value'];
414 },
415 'isPriceSelected' => function () {
416 $context = wp_interactivity_get_context();
417 if ( ! isset( $context['price'] ) || ! isset( $context['selectedPrice'] ) ) {
418 return false;
419 }
420 return $context['price']['id'] === $context['selectedPrice']['id'];
421 },
422 'buttonText' => function () {
423 $state = wp_interactivity_state();
424 $context = wp_interactivity_get_context();
425 if ( $state['isSoldOut']() ) {
426 return $context['outOfStockText'] ?? $context['text'];
427 }
428 if ( $state['isUnavailable']() ) {
429 return $context['unavailableText'] ?? $context['text'];
430 }
431 return $context['text'];
432 },
433 ]
434 );
435 }
436 }
437