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.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
← All changes | app/Modules/Templating/Bricks/BricksLoader.php +241 -2 1.3.26 → 1.6.5 View file →
@@ -2,13 +2,15 @@
2 2
3 3 namespace FluentCart\App\Modules\Templating\Bricks;
4 4
5 5 use Bricks\Elements;
6 -use Bricks\Query;
7 6 use FluentCart\Framework\Support\Arr;
7 +use FluentCart\App\Models\Product;
8 8
9 9 class BricksLoader
10 10 {
11 + const TEMPLATE_TYPE_PRODUCT = 'fct_product';
12 +
11 13 public function register()
12 14 {
13 15 if (!defined('BRICKS_VERSION')) {
14 16 return;
@@ -17,8 +19,14 @@
17 19 add_action('init', [$this, 'loadElements'], 20);
18 20
19 21 (new DynamicData())->register();
20 22
23 + add_filter('bricks/setup/control_options', [$this, 'addTemplateTypes']);
24 + add_filter('bricks/database/content_type', [$this, 'setContentType'], 10, 2);
25 + add_filter('bricks/active_templates', [$this, 'setActiveTemplates'], 10, 3);
26 +
27 + add_action('wp_footer', [$this, 'addDynamicFieldClassesScript']);
28 +
21 29 add_filter('fluent_cart/template/disable_taxonomy_fallback', function ($result) {
22 30 $bricks_data = \Bricks\Database::get_template_data('archive');
23 31
24 32 if ($bricks_data) {
@@ -39,9 +47,9 @@
39 47 'ProductShortDescription' => 'fct-product-short-description',
40 48 'ProductContent' => 'fct-product-content',
41 49 'ProductStock' => 'fct-product-stock',
42 50 'PriceRange' => 'fct-price-range',
43 - 'ProductAddToCart' => 'fct-product-buy-section',
51 + 'BuySection' => 'fct-product-buy-section',
44 52 'ProductGallery' => 'fct-product-gallery',
45 53 'ProductsCollection' => 'fct-products',
46 54 ];
47 55
@@ -52,8 +60,121 @@
52 60 }
53 61
54 62 }
55 63
64 + /**
65 + * Add FluentCart template types to the Bricks template type dropdown.
66 + */
67 + public function addTemplateTypes($controlOptions)
68 + {
69 + if (!isset($controlOptions['templateTypes']) || !is_array($controlOptions['templateTypes'])) {
70 + return $controlOptions;
71 + }
72 +
73 + $templateTypes = $controlOptions['templateTypes'];
74 +
75 + if (isset($templateTypes[self::TEMPLATE_TYPE_PRODUCT])) {
76 + return $controlOptions;
77 + }
78 +
79 + $fluentCartType = [
80 + self::TEMPLATE_TYPE_PRODUCT => __('FluentCart - Product', 'fluent-cart'),
81 + ];
82 +
83 + if (array_key_exists('content', $templateTypes)) {
84 + $keys = array_keys($templateTypes);
85 + $offset = array_search('content', $keys, true) + 1;
86 +
87 + $templateTypes = array_slice($templateTypes, 0, $offset, true)
88 + + $fluentCartType
89 + + array_slice($templateTypes, $offset, null, true);
90 + } else {
91 + $templateTypes = array_merge($templateTypes, $fluentCartType);
92 + }
93 +
94 + $controlOptions['templateTypes'] = $templateTypes;
95 +
96 + return $controlOptions;
97 + }
98 +
99 + /**
100 + * Let Bricks resolve FluentCart single product templates as product templates.
101 + */
102 + public function setContentType($contentType, $postId)
103 + {
104 + if (is_search()) {
105 + return $contentType;
106 + }
107 +
108 + if (is_singular('fluent-products')) {
109 + return self::TEMPLATE_TYPE_PRODUCT;
110 + }
111 +
112 + return $contentType;
113 + }
114 +
115 + /**
116 + * Make the FluentCart product template type render on single product pages.
117 + */
118 + public function setActiveTemplates($activeTemplates, $postId, $contentType)
119 + {
120 + if ($contentType !== self::TEMPLATE_TYPE_PRODUCT || !is_singular('fluent-products')) {
121 + return $activeTemplates;
122 + }
123 +
124 + if (!empty($activeTemplates[self::TEMPLATE_TYPE_PRODUCT])) {
125 + $activeTemplates['content'] = $activeTemplates[self::TEMPLATE_TYPE_PRODUCT];
126 + return $activeTemplates;
127 + }
128 +
129 + $templateId = $this->getProductTemplateId($postId);
130 +
131 + if (!$templateId) {
132 + return $activeTemplates;
133 + }
134 +
135 + $activeTemplates['content'] = $templateId;
136 + $activeTemplates[self::TEMPLATE_TYPE_PRODUCT] = $templateId;
137 +
138 + return $activeTemplates;
139 + }
140 +
141 + protected function getProductTemplateId($postId)
142 + {
143 + if (!class_exists('\Bricks\Templates')) {
144 + return 0;
145 + }
146 +
147 + $templateIds = \Bricks\Templates::get_templates_by_type(self::TEMPLATE_TYPE_PRODUCT);
148 +
149 + if (empty($templateIds) || !is_array($templateIds)) {
150 + return 0;
151 + }
152 +
153 + $foundTemplates = [];
154 + $defaultTemplateId = 0;
155 +
156 + foreach ($templateIds as $templateId) {
157 + $conditions = \Bricks\Helpers::get_template_setting('templateConditions', $templateId);
158 +
159 + if (!$conditions) {
160 + if (!$defaultTemplateId) {
161 + $defaultTemplateId = absint($templateId);
162 + }
163 + continue;
164 + }
165 +
166 + $foundTemplates = \Bricks\Database::screen_conditions($foundTemplates, $templateId, $conditions, $postId, '');
167 + }
168 +
169 + if (!empty($foundTemplates)) {
170 + $scores = array_keys($foundTemplates);
171 + return $foundTemplates[max($scores)];
172 + }
173 +
174 + return $defaultTemplateId;
175 + }
176 +
56 177 public function preloadProductCollectionsAjax($view, $args)
57 178 {
58 179 $products = $args['products'];
59 180 $clientId = Arr::get($args, 'client_id', '');
@@ -78,6 +199,124 @@
78 199 }
79 200 wp_reset_postdata();
80 201
81 202 return ob_get_clean();
203 + }
204 +
205 + public function addDynamicFieldClassesScript()
206 + {
207 + ?>
208 + <script>
209 + document.addEventListener('DOMContentLoaded', function() {
210 + const classMap = {
211 + 'product-title': 'fct-dynamic-product-title',
212 + 'product-image': 'fct-dynamic-product-image',
213 + 'product-excerpt': 'fct-dynamic-product-excerpt',
214 + 'product-price': 'fct-dynamic-product-price',
215 + 'product-button': 'fct-dynamic-product-button'
216 + };
217 +
218 + const applyDynamicFieldClass = function(marker) {
219 + if (marker.hasAttribute('data-fct-field-class-applied')) {
220 + return;
221 + }
222 +
223 + const className = classMap[marker.getAttribute('data-fct-field')];
224 +
225 + if (!className) {
226 + marker.setAttribute('data-fct-field-class-applied', '1');
227 + return;
228 + }
229 +
230 + let dynamicDiv = marker.closest('.dynamic');
231 +
232 + if (!dynamicDiv) {
233 + dynamicDiv = marker.parentElement;
234 + while (dynamicDiv && !dynamicDiv.classList.contains('dynamic')) {
235 + dynamicDiv = dynamicDiv.parentElement;
236 + }
237 + }
238 +
239 + if (dynamicDiv) {
240 + dynamicDiv.classList.add(className);
241 + }
242 +
243 + marker.setAttribute('data-fct-field-class-applied', '1');
244 + };
245 +
246 + let scheduled = false;
247 + const pendingNodes = new Set();
248 +
249 + const processNode = function(node) {
250 + if (!node || node.nodeType !== 1) {
251 + return;
252 + }
253 +
254 + const markers = node.matches('[data-fct-field]')
255 + ? [node]
256 + : node.querySelectorAll('[data-fct-field]');
257 +
258 + markers.forEach(applyDynamicFieldClass);
259 + };
260 +
261 + const scheduleNode = function(node) {
262 + pendingNodes.add(node);
263 +
264 + if (scheduled) {
265 + return;
266 + }
267 +
268 + scheduled = true;
269 + requestAnimationFrame(function() {
270 + pendingNodes.forEach(processNode);
271 + pendingNodes.clear();
272 + scheduled = false;
273 + });
274 + };
275 +
276 + document.querySelectorAll('[data-fluent-cart-shop-app-product-list]').forEach(function(productList) {
277 + processNode(productList);
278 +
279 + new MutationObserver(function(mutations) {
280 + mutations.forEach(function(mutation) {
281 + mutation.addedNodes.forEach(scheduleNode);
282 + });
283 + }).observe(productList, {
284 + childList: true,
285 + subtree: true
286 + });
287 + });
288 + });
289 + </script>
290 + <?php
291 + }
292 +
293 + /**
294 + * Get product options for select controls.
295 + * Limits to 50 recent products for editor performance.
296 + * Users can search or use manual product ID for others.
297 + */
298 + public static function getProductOptions()
299 + {
300 + if (!class_exists('\FluentCart\App\Models\Product')) {
301 + return [];
302 + }
303 +
304 + $options = [];
305 +
306 + try {
307 + $products = Product::query()
308 + ->where('post_status', 'publish')
309 + ->orderBy('post_date', 'DESC')
310 + ->limit(50)
311 + ->get();
312 +
313 + foreach ($products as $product) {
314 + $options[$product->ID] = $product->post_title;
315 + }
316 + } catch (\Exception $e) {
317 + // Silently fail if models aren't available
318 + }
319 +
320 + return $options;
82 321 }
83 322 }