PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.1
1.6.6 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 All 49 releases
fluent-cart / app / Hooks / Handlers / BlockEditors / SaleBadgeBlockEditor.php

SaleBadgeBlockEditor.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.1, at app/Hooks/Handlers/BlockEditors/SaleBadgeBlockEditor.php

189 lines 6.9 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\Hooks\Handlers\BlockEditors;
4
5 use FluentCart\App\Services\Translations\TransStrings;
6 use FluentCart\App\Vite;
7 use FluentCart\Framework\Support\Arr;
8 use FluentCart\App\Models\Product;
9
10 class SaleBadgeBlockEditor extends BlockEditor
11 {
12 protected static string $editorName = 'sale-badge';
13
14 public function ancestor(): array
15 {
16 return [
17 'fluent-cart/products',
18 'fluent-cart/product-carousel',
19 'fluent-cart/product-info',
20 'fluent-cart/related-product',
21 ];
22 }
23
24 public function supports(): array
25 {
26 return [
27 'html' => false,
28 'align' => ['left', 'center', 'right'],
29 'color' => [
30 'text' => true,
31 'background' => true,
32 'gradients' => true,
33 ],
34 'typography' => [
35 'fontSize' => true,
36 '__experimentalFontWeight' => true,
37 '__experimentalLetterSpacing' => true,
38 '__experimentalTextTransform' => true,
39 '__experimentalDefaultControls' => [
40 'fontSize' => true,
41 ],
42 ],
43 'spacing' => [
44 'padding' => true,
45 'margin' => true,
46 ],
47 '__experimentalBorder' => [
48 'color' => true,
49 'radius' => true,
50 'style' => true,
51 'width' => true,
52 ],
53 'shadow' => true,
54 ];
55 }
56
57 public function getScripts(): array
58 {
59 return [
60 [
61 'source' => 'admin/BlockEditor/SaleBadge/SaleBadgeBlockEditor.jsx',
62 'dependencies' => ['wp-blocks', 'wp-components']
63 ]
64 ];
65 }
66
67 public function getStyles(): array
68 {
69 return [
70 'admin/BlockEditor/SaleBadge/style/sale-badge-block-editor.scss'
71 ];
72 }
73
74 public function localizeData(): array
75 {
76 return [
77 $this->getLocalizationKey() => [
78 'slug' => $this->slugPrefix,
79 'name' => static::getEditorName(),
80 'title' => __('Sale Badge', 'fluent-cart'),
81 'description' => __('Displays a sale badge when a product is on sale.', 'fluent-cart'),
82 ],
83 'fluent_cart_block_translation' => TransStrings::blockStrings(),
84 ];
85 }
86
87 public function render(array $shortCodeAttribute, $block = null): string
88 {
89 // Enqueue frontend styles
90 Vite::enqueueStyle(
91 'fluent-cart-sale-badge',
92 'admin/BlockEditor/SaleBadge/style/sale-badge-block-editor.scss'
93 );
94
95 // 1. Validate attributes
96 $badgeStyle = Arr::get($shortCodeAttribute, 'badge_style', 'badge');
97 if (!in_array($badgeStyle, ['badge', 'ribbon', 'tag'], true)) {
98 $badgeStyle = 'badge';
99 }
100
101 // Default to 'top-left' but allow empty string to pass through — when the block
102 // is used outside a visual container (e.g. inline in a column), the JS editor clears
103 // badge_position to '' so the badge renders inline without absolute positioning.
104 // This differs intentionally from SoldOutBadgeBlockEditor which always forces a position.
105 $badgePosition = Arr::get($shortCodeAttribute, 'badge_position', 'top-left');
106 if ($badgePosition && !in_array($badgePosition, ['top-left', 'top-right', 'bottom-left', 'bottom-right'], true)) {
107 $badgePosition = '';
108 }
109
110 $priceSource = Arr::get($shortCodeAttribute, 'price_source', 'default_variant');
111 if (!in_array($priceSource, ['default_variant', 'best_discount'], true)) {
112 $priceSource = 'default_variant';
113 }
114
115 $showPercentage = !empty($shortCodeAttribute['show_percentage']);
116 $badgeText = sanitize_text_field(Arr::get($shortCodeAttribute, 'badge_text', __('Sale!', 'fluent-cart')));
117 $percentageText = sanitize_text_field(Arr::get($shortCodeAttribute, 'percentage_text', '-{percent}%'));
118
119 // 2. Get product — explicit product_id takes priority, then current context
120 $productId = absint(Arr::get($shortCodeAttribute, 'product_id', 0));
121 if ($productId) {
122 $product = Product::query()->where('post_status', 'publish')->with(['detail', 'variants'])->find($productId);
123 } else {
124 $product = fluent_cart_get_current_product();
125 }
126
127 if (!$product || !$product->detail || !$product->variants || $product->variants->isEmpty()) {
128 return '';
129 }
130
131 // 3. Determine sale status based on price_source setting
132 $isOnSale = false;
133 $discountPercent = 0;
134
135 if ($priceSource === 'default_variant') {
136 $defaultVariantId = $product->detail->default_variation_id ?? null;
137
138 $variant = $defaultVariantId
139 ? ($product->variants->firstWhere('id', $defaultVariantId) ?? $product->variants->first())
140 : $product->variants->first();
141
142 if ($variant && $variant->compare_price > $variant->item_price && $variant->compare_price > 0) {
143 $isOnSale = true;
144 $discountPercent = max(0, min(100, round((($variant->compare_price - $variant->item_price) / $variant->compare_price) * 100)));
145 }
146 } else {
147 // best_discount — scan all variants, use highest discount
148 foreach ($product->variants as $variant) {
149 if ($variant->compare_price > $variant->item_price && $variant->compare_price > 0) {
150 $isOnSale = true;
151 $discount = max(0, min(100, round((($variant->compare_price - $variant->item_price) / $variant->compare_price) * 100)));
152 if ($discount > $discountPercent) {
153 $discountPercent = $discount;
154 }
155 }
156 }
157 }
158
159 // 4. If not on sale, render nothing
160 if (!$isOnSale) {
161 return '';
162 }
163
164 // 5. Build badge text
165 if ($showPercentage && $discountPercent > 0) {
166 $displayText = str_replace('{percent}', $discountPercent, $percentageText);
167 } else {
168 $displayText = $badgeText;
169 }
170
171 // 6. Build CSS classes (values are pre-validated via in_array above)
172 $classes = ['fct-sale-badge'];
173 $classes[] = 'fct-sale-badge--' . esc_attr($badgeStyle);
174 if ($badgePosition) {
175 $classes[] = 'fct-sale-badge--' . esc_attr($badgePosition);
176 }
177
178 $wrapper_attributes = get_block_wrapper_attributes([
179 'class' => implode(' ', $classes),
180 ]);
181
182 return sprintf(
183 '<span %s>%s</span>',
184 $wrapper_attributes,
185 esc_html($displayText)
186 );
187 }
188 }
189