PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 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 All 48 releases
← All changes | app/Modules/Templating/Bricks/BricksHelper.php +161 -0 1.3.21 → 1.6.5 View file →
@@ -2,8 +2,9 @@
2 2
3 3 namespace FluentCart\App\Modules\Templating\Bricks;
4 4
5 5 use Bricks\Frontend;
6 +use FluentCart\App\Helpers\Helper;
6 7 use FluentCart\Framework\Support\Arr;
7 8
8 9 class BricksHelper
9 10 {
@@ -9,8 +10,16 @@
9 10 {
10 11
11 12 static public $forcedPost = null;
12 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 +
13 22 public static function getFormCurrentPost()
14 23 {
15 24 return self::$forcedPost;
16 25 }
@@ -39,10 +48,17 @@
39 48 }
40 49
41 50 public static function renderCollectionCard($settings, $post, $post_index = 1, $uid = '')
42 51 {
52 + self::$imageBadgeSettings = $settings;
53 +
43 54 $content = Frontend::get_content_wrapper($settings, Arr::get($settings, 'fields', []), $post);
44 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 +
45 61 if ($post_index === 1) {
46 62 echo "<div data-fluent-client-id='" . esc_attr($uid) . "' data-template-provider='bricks' data-fct-product-card class='fct-product-card repeater-item'>";
47 63 } else {
48 64 echo "<div data-fct-product-card class='fct-product-card repeater-item'>";
@@ -60,6 +76,151 @@
60 76 echo '</a>';
61 77 }
62 78
63 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;
64 225 }
65 226 }