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 / Modules / Templating / Bricks / BricksHelper.php

BricksHelper.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Modules/Templating/Bricks/BricksHelper.php

227 lines 7.8 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\Modules\Templating\Bricks;
4
5 use Bricks\Frontend;
6 use FluentCart\App\Helpers\Helper;
7 use FluentCart\Framework\Support\Arr;
8
9 class BricksHelper
10 {
11
12 static public $forcedPost = null;
13
14 /**
15 * Settings of the Products Collection element currently being rendered.
16 * Exposed so DynamicData::renderValue() can read Sale/Sold Out badge
17 * settings when resolving the {fct_product_image} tag — that filter
18 * only receives ($tag, $post, $context), not the calling element.
19 */
20 static public $imageBadgeSettings = [];
21
22 public static function getFormCurrentPost()
23 {
24 return self::$forcedPost;
25 }
26
27 public static function setFormCurrentPost($post)
28 {
29 self::$forcedPost = $post;
30 }
31
32 public static function getCategoriesOptions()
33 {
34 $categories = get_terms(array(
35 'taxonomy' => 'product-categories',
36 'hide_empty' => false,
37 'orderby' => 'name'
38 ));
39
40 $options = [];
41 if (!is_wp_error($categories) && !empty($categories)) {
42 foreach ($categories as $category) {
43 $options[$category->term_id] = $category->name;
44 }
45 }
46
47 return $options;
48 }
49
50 public static function renderCollectionCard($settings, $post, $post_index = 1, $uid = '')
51 {
52 self::$imageBadgeSettings = $settings;
53
54 $content = Frontend::get_content_wrapper($settings, Arr::get($settings, 'fields', []), $post);
55
56 // Scope the badge settings strictly to the get_content_wrapper() call above —
57 // {fct_product_image} is a generic dynamic tag usable outside Products Collection
58 // too, and it must not inherit stale settings from this render.
59 self::$imageBadgeSettings = [];
60
61 if ($post_index === 1) {
62 echo "<div data-fluent-client-id='" . esc_attr($uid) . "' data-template-provider='bricks' data-fct-product-card class='fct-product-card repeater-item'>";
63 } else {
64 echo "<div data-fct-product-card class='fct-product-card repeater-item'>";
65 }
66
67 $linkedProduct = isset($settings['linkProduct']) ? $settings['linkProduct'] : false;
68
69 if ($linkedProduct && strpos($content, '<a ') === false) {
70 echo '<a href="' . esc_attr(get_the_permalink($post)) . '">';
71 }
72
73 echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
74
75 if ($linkedProduct && strpos($content, '<a ') === false) {
76 echo '</a>';
77 }
78
79 echo '</div>';
80 }
81
82 public static function isTemplate()
83 {
84 $is_template = false;
85
86 if (isset($_GET['bricks']) && $_GET['bricks'] === 'run') {
87 $request_uri = $_SERVER['REQUEST_URI'] ?? '';
88 $is_template = strpos($request_uri, '/template/') !== false;
89 }
90
91 return $is_template;
92 }
93
94 /**
95 * Returns concatenated Sale Badge / Sold Out Badge <span> markup for
96 * $product based on $settings (the Products Collection element's
97 * settings array). Mirrors the detection logic used by core's
98 * SaleBadgeBlockEditor::render() / SoldOutBadgeBlockEditor::render().
99 */
100 public static function renderProductBadges($product, array $settings)
101 {
102 if (!$product) {
103 return '';
104 }
105
106 return self::renderSaleBadge($product, $settings) . self::renderSoldOutBadge($product, $settings);
107 }
108
109 private static function renderSaleBadge($product, array $settings)
110 {
111 if (empty($settings['showSaleBadge'])) {
112 return '';
113 }
114
115 if (!$product->variants || $product->variants->isEmpty()) {
116 return '';
117 }
118
119 $priceSource = Arr::get($settings, 'saleBadgePriceSource', 'default_variant');
120 if (!in_array($priceSource, ['default_variant', 'best_discount'], true)) {
121 $priceSource = 'default_variant';
122 }
123
124 $isOnSale = false;
125 $discountPercent = 0;
126
127 if ($priceSource === 'default_variant') {
128 $defaultVariantId = $product->detail->default_variation_id ?? null;
129
130 $variant = $defaultVariantId
131 ? ($product->variants->firstWhere('id', $defaultVariantId) ?? $product->variants->first())
132 : $product->variants->first();
133
134 if ($variant && $variant->compare_price > $variant->item_price && $variant->compare_price > 0) {
135 $isOnSale = true;
136 $discountPercent = max(0, min(100, round((($variant->compare_price - $variant->item_price) / $variant->compare_price) * 100)));
137 }
138 } else {
139 foreach ($product->variants as $variant) {
140 if ($variant->compare_price > $variant->item_price && $variant->compare_price > 0) {
141 $isOnSale = true;
142 $discount = max(0, min(100, round((($variant->compare_price - $variant->item_price) / $variant->compare_price) * 100)));
143 if ($discount > $discountPercent) {
144 $discountPercent = $discount;
145 }
146 }
147 }
148 }
149
150 if (!$isOnSale) {
151 return '';
152 }
153
154 $showPercentage = !empty($settings['saleBadgeShowPercentage']);
155 $badgeText = sanitize_text_field(Arr::get($settings, 'saleBadgeText', '')) ?: __('Sale!', 'fluent-cart');
156 $percentageText = sanitize_text_field(Arr::get($settings, 'saleBadgePercentageText', '')) ?: '-{percent}%';
157
158 if ($showPercentage && $discountPercent > 0) {
159 $displayText = str_replace('{percent}', $discountPercent, $percentageText);
160 } else {
161 $displayText = $badgeText;
162 }
163
164 $badgeShape = Arr::get($settings, 'saleBadgeShape', 'badge');
165 if (!in_array($badgeShape, ['badge', 'ribbon'], true)) {
166 $badgeShape = 'badge';
167 }
168
169 $badgePosition = Arr::get($settings, 'saleBadgePosition', 'top-left');
170 if (!in_array($badgePosition, ['top-left', 'top-right', 'bottom-left', 'bottom-right'], true)) {
171 $badgePosition = 'top-left';
172 }
173
174 $classes = 'fct-sale-badge fct-sale-badge--' . $badgeShape . ' fct-sale-badge--' . $badgePosition;
175
176 return '<span class="' . esc_attr($classes) . '">' . esc_html($displayText) . '</span>';
177 }
178
179 private static function renderSoldOutBadge($product, array $settings)
180 {
181 if (empty($settings['showSoldOutBadge'])) {
182 return '';
183 }
184
185 if (!$product->detail) {
186 return '';
187 }
188
189 $isOutOfStock = $product->detail->stock_availability === Helper::OUT_OF_STOCK;
190 if (!$isOutOfStock) {
191 return '';
192 }
193
194 $badgeText = sanitize_text_field(Arr::get($settings, 'soldOutBadgeText', '')) ?: __('Out of Stock', 'fluent-cart');
195
196 $badgeShape = Arr::get($settings, 'soldOutBadgeShape', 'badge');
197 if (!in_array($badgeShape, ['badge', 'ribbon'], true)) {
198 $badgeShape = 'badge';
199 }
200
201 $badgePosition = Arr::get($settings, 'soldOutBadgePosition', 'top-left');
202 if (!in_array($badgePosition, ['top-left', 'top-right', 'bottom-left', 'bottom-right'], true)) {
203 $badgePosition = 'top-left';
204 }
205
206 $classes = 'fct-sold-out-badge fct-sold-out-badge--' . $badgeShape . ' fct-sold-out-badge--' . $badgePosition;
207
208 return '<span class="' . esc_attr($classes) . '">' . esc_html($badgeText) . '</span>';
209 }
210
211 public static function getAllowedHtmlForContent()
212 {
213 $allowed_html = wp_kses_allowed_html('post');
214 $allowed_html['iframe'] = [
215 'src' => true,
216 'width' => true,
217 'height' => true,
218 'frameborder' => true,
219 'allow' => true,
220 'allowfullscreen' => true,
221 'title' => true,
222 'referrerpolicy' => true,
223 ];
224 return $allowed_html;
225 }
226 }
227