PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Services / Renderer / PricingTableRenderer.php

PricingTableRenderer.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Services/Renderer/PricingTableRenderer.php

484 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Renderer;
4
5
6 use FluentCart\Api\StoreSettings;
7 use FluentCart\App\Helpers\Helper;
8 use FluentCart\Framework\Support\Arr;
9 use FluentCart\Framework\Support\Str;
10 use FluentCart\App\Helpers\CurrenciesHelper;
11 use FluentCart\App\Hooks\Handlers\ShortCodes\Buttons\AddToCartShortcode;
12 use FluentCart\App\Hooks\Handlers\ShortCodes\Buttons\DirectCheckoutShortcode;
13
14 class PricingTableRenderer
15 {
16 protected $viewData;
17
18 protected $variants;
19
20 protected $storeSettings;
21
22 protected $sign;
23
24 protected $repeatInterval = null;
25
26 protected $itemPrice = null;
27
28 protected $comparePrice = null;
29
30 protected $paymentInfo = '';
31
32 protected $setupFeeInfo = '';
33
34 protected $featureItems = [];
35
36 protected $showCartButton = 1;
37
38 protected $paymentType = '';
39
40 protected $licenseEnable = 'no';
41
42
43 protected $productPerRow = 4;
44
45
46 public function __construct($viewData)
47 {
48 $this->viewData = $viewData;
49 $this->variants = $this->viewData['variants'];
50 $this->storeSettings = new StoreSettings();
51
52 $signName = $this->storeSettings->get('currency');
53
54 $this->sign = CurrenciesHelper::getCurrencySign($signName);
55
56 $this->showCartButton = Arr::get($this->viewData, 'show_cart_button', 1);
57 $productPerRow = absint(Arr::get($this->viewData, 'product_per_row', 4));
58 $this->productPerRow = $productPerRow > 0 ? $productPerRow : 4;
59 }
60
61 public function render()
62 {
63 $groupBy = Arr::get($this->viewData, 'group_by', 'repeat_interval');
64 $groups = $this->groupVariants($groupBy);
65 $useTabs = $groupBy !== 'none' && count($groups) > 1;
66
67 ?>
68 <div class="fluent-cart-pricing-table" data-fluent-cart-pricing-table role="region"
69 aria-label="<?php esc_attr_e('Pricing table', 'fluent-cart'); ?>">
70 <?php if ($useTabs) : ?>
71 <?php $this->renderTabs($groups); ?>
72 <?php else : ?>
73 <div
74 class="fluent-cart-pricing-table-variants-iterator"
75 role="list"
76 style="--fluent-cart-pricing-table-product-per-row: <?php echo esc_attr($this->productPerRow); ?>;"
77 >
78 <?php $this->renderVariant($this->variants); ?>
79 </div>
80 <?php endif; ?>
81 </div>
82 <?php
83 }
84
85 protected function groupVariants(string $groupBy): array
86 {
87 if ($groupBy === 'none') {
88 return ['onetime' => $this->variants];
89 }
90
91 $key = $groupBy === 'payment_type' ? 'payment_type' : 'repeat_interval';
92
93 $groups = [];
94 foreach ($this->variants as $variant) {
95 $bucket = Arr::get($variant, "other_info.{$key}") ?: 'onetime';
96 if (Arr::get($variant, 'other_info.installment') === 'yes') {
97 $bucket = 'installment';
98 }
99 $groups[$bucket][] = $variant;
100 }
101
102 return $groups;
103 }
104
105 protected function renderTabs(array $groups)
106 {
107 $groupKeys = array_keys($groups);
108 $activeIndex = (int) Arr::get($this->viewData, 'active_tab', 0);
109 if (!isset($groupKeys[$activeIndex])) {
110 $activeIndex = 0;
111 }
112 $activeKey = $groupKeys[$activeIndex];
113 $activeVariantMap = Arr::get($this->viewData, 'active_variant', []);
114 if (!\is_array($activeVariantMap)) {
115 $activeVariantMap = [];
116 }
117
118 ?>
119 <div class="fluent-cart-pricing-table-tab" data-fluent-cart-pricing-table-tab>
120 <div class="fluent-cart-pricing-table-tab-nav">
121 <div class="fluent-cart-pricing-table-tab-nav-inner-wrap">
122 <div class="fluent-cart-pricing-table-tab-nav-inner" role="tablist">
123 <div class="tab-active-bar" data-tab-active-bar></div>
124 <?php foreach ($groupKeys as $key) :
125 $isActive = $key === $activeKey;
126 ?>
127 <div class="fluent-cart-pricing-table-tab-nav-item<?php echo $isActive ? ' active' : ''; ?>"
128 data-tab="<?php echo esc_attr($key); ?>">
129 <?php echo esc_html($this->getTabLabel($key)); ?>
130 </div>
131 <?php endforeach; ?>
132 </div>
133 </div>
134 </div>
135 <div class="fluent-cart-pricing-table-tab-content" data-tab-contents>
136 <?php foreach ($groups as $key => $groupVariants) :
137 $isActive = $key === $activeKey;
138 $currentActiveVariant = Arr::get($activeVariantMap, $key, '');
139 ?>
140 <div id="<?php echo esc_attr($key); ?>"
141 class="fluent-cart-pricing-table-tab-pane<?php echo $isActive ? ' active' : ''; ?>"
142 data-tab-content
143 data-active_variant="<?php echo esc_attr($currentActiveVariant); ?>"
144 role="tabpanel">
145 <div
146 class="fluent-cart-pricing-table-variants-iterator"
147 role="list"
148 style="--fluent-cart-pricing-table-product-per-row: <?php echo esc_attr($this->productPerRow); ?>;"
149 >
150 <?php $this->renderVariant($groupVariants); ?>
151 </div>
152 </div>
153 <?php endforeach; ?>
154 </div>
155 </div>
156 <?php
157 }
158
159 protected function getTabLabel(string $key): string
160 {
161 $labels = [
162 'daily' => __('Daily', 'fluent-cart'),
163 'weekly' => __('Weekly', 'fluent-cart'),
164 'monthly' => __('Monthly', 'fluent-cart'),
165 'yearly' => __('Yearly', 'fluent-cart'),
166 'onetime' => __('One Time', 'fluent-cart'),
167 'subscription' => __('Subscription', 'fluent-cart'),
168 'installment' => __('Installment', 'fluent-cart'),
169 ];
170
171 return $labels[$key] ?? ucfirst(str_replace('_', ' ', $key));
172 }
173
174 protected function isVariantActive($variant): bool
175 {
176 $groupBy = Arr::get($this->viewData, 'group_by', 'repeat_interval');
177
178 if ($groupBy === 'none') {
179 $variantGroupKey = 'onetime';
180 } else {
181 $groupKey = $groupBy === 'payment_type' ? 'payment_type' : 'repeat_interval';
182
183 $variantGroupKey = Arr::get($variant, "other_info.{$groupKey}") ?: 'onetime';
184
185 if (Arr::get($variant, 'other_info.installment') === 'yes') {
186 $variantGroupKey = 'installment';
187 }
188 }
189
190 $activeVariantMap = Arr::get($this->viewData, 'active_variant', []);
191 if (!\is_array($activeVariantMap)) {
192 $activeVariantMap = [];
193 }
194
195 $variantId = Arr::get($variant, 'id');
196
197 return isset($activeVariantMap[$variantGroupKey]) && $activeVariantMap[$variantGroupKey] == $variantId;
198 }
199
200 public function renderVariant(?array $variants = null)
201 {
202 $variants = $variants ?? $this->variants;
203
204 foreach ($variants as $index => $variant) {
205 $this->repeatInterval = Arr::get($variant, 'other_info.repeat_interval', null);
206
207 $this->itemPrice = esc_html(Helper::toDecimal(Arr::get($variant, 'item_price', 0), false, false, false));
208
209 $this->comparePrice = esc_html(Helper::toDecimal(Arr::get($variant, 'compare_price', 0), false, false, false, false));
210
211 $otherInfo = Arr::get($variant, 'other_info', null);
212
213 $this->paymentInfo = Helper::generateSubscriptionInfo($otherInfo, $this->itemPrice);
214
215 $this->setupFeeInfo = Helper::generateSetupFeeInfo($otherInfo);
216
217 $featureDescription = Arr::get($variant, 'other_info.description', '');
218
219 $this->featureItems = explode("\n", $featureDescription);
220
221 $this->paymentType = Arr::get($variant, 'payment_type');
222
223 $licenseMeta = Arr::get($variant, 'product.licenses_meta.meta_value', null);
224
225 $this->licenseEnable = Arr::get($licenseMeta, 'enabled', 'no');
226
227 $isActiveVariant = $this->isVariantActive($variant);
228
229 // Parse badge settings from viewData
230 $badgeData = Arr::get($this->viewData, 'badge', '');
231 $badgeConfig = is_string($badgeData) ? self::stringToAssocArray($badgeData) : (is_array($badgeData) ? $badgeData : []);
232 $badgeText = Arr::get($badgeConfig, 'text', __('Recommended', 'fluent-cart'));
233 $badgePosition = Arr::get($badgeConfig, 'position', 'right');
234
235 ?>
236
237 <div class="fluent-cart-pricing-table-variant<?php echo $isActiveVariant ? ' active' : ''; ?>" data-fluent-cart-pricing-table-variant role="listitem">
238 <?php if ($isActiveVariant) : ?>
239 <div class="fluent-cart-pricing-table-variant-badge <?php echo esc_attr($badgePosition); ?>">
240 <?php echo esc_html($badgeText); ?>
241 </div>
242 <?php endif; ?>
243
244 <div class="fluent-cart-pricing-table-variant-contents">
245 <div class="fluent-cart-pricing-table-variant-title">
246 <?php echo esc_html(Arr::get($variant, 'product.post_title', '')); ?>
247 </div>
248
249 <div class="fluent-cart-pricing-table-variant-sub-title">
250 <?php echo esc_html(Arr::get($variant, 'variation_title', '')); ?>
251 </div>
252
253 <div class="variant-divider" aria-hidden="true"></div>
254
255 <div class="fluent-cart-pricing-table-variant-price-wrap">
256 <div
257 class="fluent-cart-pricing-table-variant-price"
258 aria-label="<?php
259 printf(
260 /* translators: 1: Currency sign, 2: Item price, 3: Billing interval or unit */
261 esc_attr__('Price: %1$s%2$s per %3$s', 'fluent-cart'),
262 esc_attr($this->sign),
263 esc_attr($this->itemPrice),
264 esc_attr($this->repeatInterval ?? __('unit', 'fluent-cart'))
265 );
266 ?>">
267 <sup><?php echo esc_html($this->sign) ?></sup>
268
269 <?php echo esc_html($this->itemPrice) ?>
270
271 <?php if (isset($this->repeatInterval)): ?>
272 <span class="repeat-interval">
273 /<?php echo esc_html($this->repeatInterval) ?>
274 </span>
275 <?php endif; ?>
276
277 </div>
278
279 <div class="fluent-cart-pricing-table-variant-compare-price">
280 <span><?php echo esc_html($this->sign) ?></span>
281 <?php
282 $aria_label = sprintf(
283 /* translators: 1: Currency sign, 2: Compare price */
284 esc_attr__('Compare at: %1$s%2$s', 'fluent-cart'),
285 $this->sign,
286 $this->comparePrice
287 );
288 ?>
289 <span aria-label="<?php echo esc_attr($aria_label); ?>">
290 <del><?php echo esc_html($this->comparePrice); ?></del>
291 </span>
292 </div>
293 </div>
294
295 <div class="fluent-cart-pricing-table-variant-payment-type">
296 <span> <?php echo esc_html($this->paymentInfo); ?> </span>
297 <span class="setup-fee"
298 aria-label="<?php esc_attr_e('Setup fee information', 'fluent-cart'); ?>">
299 <?php echo esc_html($this->setupFeeInfo); ?>
300 </span>
301 </div>
302
303 <div class="fluent-cart-pricing-table-variant-description">
304 <?php $this->renderFeatureItems(); ?>
305 </div>
306
307 </div>
308
309 <div class="fluent-cart-pricing-table-variant-buttons">
310 <?php
311 if (
312 $this->showCartButton == 1 &&
313 $this->paymentType !== 'subscription' &&
314 $this->licenseEnable !== 'yes'
315 ) {
316 $this->renderCartButton($variant);
317 }
318
319 if (Arr::get($this->viewData, 'show_checkout_button', 1) == 1) {
320 $this->renderDirectCheckoutButton($variant);
321 }
322
323
324 ?>
325 </div>
326
327
328 </div>
329
330 <?php };
331 }
332
333 public function renderFeatureItems()
334 {
335
336 ?>
337 <ul class="fluent-cart-pricing-table-variant-features" role="list">
338 <?php
339 foreach ($this->featureItems as $index => $featureItem) {
340 if (!empty($featureItem)) : ?>
341
342 <li class="fluent-cart-pricing-table-variant-feature" role="listitem">
343 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"
344 aria-hidden="true">
345 <path d="M17.3332 9.00004C17.3332 4.39767 13.6022 0.666706 8.99984 0.666706C4.39746 0.666706 0.666504 4.39767 0.666504 9.00004C0.666504 13.6024 4.39746 17.3334 8.99984 17.3334C13.6022 17.3334 17.3332 13.6024 17.3332 9.00004Z"
346 fill="currentColor"></path>
347 <path d="M5.6665 9.62502C5.6665 9.62502 6.99984 10.3855 7.6665 11.5C7.6665 11.5 9.6665 7.12502 12.3332 5.66669"
348 stroke="white" stroke-width="1.5" stroke-linecap="round"
349 stroke-linejoin="round"></path>
350 </svg>
351
352 <?php echo esc_html($featureItem); ?>
353 </li>
354
355 <?php endif;
356 }
357 ?>
358 </ul>
359
360 <?php
361
362 }
363
364 public function renderCartButton($variant)
365 {
366 $buttonOptions = Arr::get($this->viewData, 'button_options', '');
367 $buttonValues = self::stringToAssocArray($buttonOptions);
368 $buttonText = !empty($buttonValues['cartButtonText']) ? $buttonValues['cartButtonText'] : __('Add To Cart', 'fluent-cart');
369
370 if ($variant['stock_status'] === Helper::IN_STOCK) {
371 $data = [
372 'add_to_cart_button_text' => $buttonText,
373 'product_id' => Arr::get($variant, 'id', ''),
374 'quantity' => 1,
375 'variation_type' => Arr::get($variant, 'variation_type', ''),
376 'payment_type' => Arr::get($variant, 'payment_type', ''),
377 'variation_id' => Arr::get($variant, 'id', ''),
378 ];
379
380 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in view template
381 echo AddToCartShortcode::make()->enqueueAssets()->render($data);
382 } else {
383 $this->renderOutOfStockButton();
384 }
385
386 }
387
388 public function renderOutOfStockButton()
389 {
390 ?>
391 <button
392 class="fct-out-of-stock-button"
393 disabled
394 aria-disabled="true"
395 aria-label="<?php
396 /* translators: Button aria-label shown when a product is out of stock */
397 esc_attr_e('This item is out of stock and cannot be added to cart', 'fluent-cart');
398 ?>"
399 >
400 <?php
401 echo esc_html(
402 $this->storeSettings->get(
403 'out_of_stock_button_text',
404 __('Not Available', 'fluent-cart')
405 )
406 );
407 ?>
408 </button>
409 <?php
410 }
411
412
413 public function renderDirectCheckoutButton($variant)
414 {
415 $directCheckoutButtonText = $this->storeSettings->get('direct_checkout_button_text', __('Buy Now', 'fluent-cart'));
416
417 $buttonOptions = Arr::get($this->viewData, 'button_options', '');
418 $buttonValues = self::stringToAssocArray($buttonOptions);
419 $buttonText = !empty($buttonValues['text']) ? $buttonValues['text'] : $directCheckoutButtonText;
420
421 $checkoutUrl = add_query_arg([
422 'fluent-cart' => 'instant_checkout'
423 ], site_url());
424
425 if ($variant['stock_status'] === Helper::IN_STOCK) {
426
427 // Handle product parameters
428 $productId = Arr::get($variant, 'id');
429 $quantity = Arr::get($this->viewData, 'quantity', 1);
430
431 // Build base parameters
432 $params = [
433 'item_id' => $productId,
434 'quantity' => $quantity,
435 ];
436
437 // Merge in any additional params
438 $extraParams = Arr::get($this->viewData, 'url_params', '');
439 if (!empty($extraParams)) {
440 // If it's a query string like "foo=bar&baz=qux"
441 parse_str($extraParams, $extraArray);
442 $params = array_merge($params, $extraArray);
443 }
444
445 $checkoutUrl = add_query_arg($params, $checkoutUrl);
446
447 $data = [
448 'direct_checkout_button_text' => $buttonText,
449 'checkout_url' => $checkoutUrl,
450 'product_id' => Arr::get($variant, 'id'),
451 'is_pricing_table' => true,
452 'quantity' => 1,
453 'variation_type' => Arr::get($variant, 'variation_type', ''),
454 'stock_availability' => Arr::get($variant, 'stock_status', ''),
455 'variation_id' => Arr::get($variant, 'id', ''),
456 ];
457
458 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in view template
459 echo DirectCheckoutShortcode::make()->enqueueAssets()->render($data);
460 }
461 }
462
463 private static function stringToAssocArray($string): array
464 {
465 $result = [];
466
467 if (!empty($string)) {
468 $pairs = explode(', ', $string);
469
470 foreach ($pairs as $pair) {
471 // Check if $pair contains '=' using Str::contains() before splitting
472 if (Str::contains($pair, '=')) {
473 list($key, $value) = explode('=', $pair);
474 $result[trim($key)] = trim($value);
475 }
476 }
477 }
478
479 return $result;
480 }
481
482
483 }
484