PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.21
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.21
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
fluent-cart / app / Hooks / Handlers / ShortCodes / ShopAppHandler.php

ShopAppHandler.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.21, at app/Hooks/Handlers/ShortCodes/ShopAppHandler.php

476 lines 19.5 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\ShortCodes;
4
5 use FluentCart\Api\CurrencySettings;
6 use FluentCart\Api\Resource\ShopResource;
7 use FluentCart\Api\StoreSettings;
8 use FluentCart\Api\Taxonomy;
9 use FluentCart\App\App;
10 use FluentCart\App\Helpers\Helper;
11 //use FluentCart\App\Hooks\Handlers\ShortCodes\Buttons\AddToCartShortcode;
12 use FluentCart\App\Models\ProductDetail;
13 use FluentCart\App\Modules\Templating\AssetLoader;
14 use FluentCart\App\Services\Renderer\ShopAppRenderer;
15 use FluentCart\App\Services\TemplateService;
16 use FluentCart\App\Vite;
17 use FluentCart\Framework\Support\Arr;
18 use FluentCart\Framework\Support\Str;
19
20 class ShopAppHandler
21 {
22 protected array $viewData = [];
23 protected array $defaultViewData = [];
24 protected array $shortcodeAttributes = [];
25
26 protected string $slug = '';
27 protected string $assetsPath = '';
28 protected bool $shouldLoadRangePlugin = false;
29
30 protected array $urlFilters = [];
31
32 const SHORT_CODE = 'fluent_cart_products';
33
34 public function register()
35 {
36 add_action('wp_enqueue_scripts', function () {
37 if (App::request()->get('action') === 'elementor') {
38 return;
39 }
40
41 if (TemplateService::getCurrentFcPageType() === 'shop') {
42 $this->enqueueStyles();
43 } else if (has_shortcode(get_the_content(), static::SHORT_CODE) || has_block('fluent-cart/products')) {
44 $this->enqueueStyles();
45 }
46 }, 5);
47 add_shortcode(static::SHORT_CODE, function ($shortcodeAttributes, $content, $block) {
48 return $this->handelShortcodeCall($shortcodeAttributes);
49 });
50 }
51
52 public function handelShortcodeCall($shortcodeAttributes)
53 {
54 $urlFilters = Arr::get(App::request()->all(), 'filters', []);
55 if (!is_array($urlFilters)) {
56 $urlFilters = [];
57 }
58
59 $this->urlFilters = $urlFilters;
60
61 $this->buildPaths();
62 $this->buildShortcodeAttributes($shortcodeAttributes);
63 $this->buildFilters();
64 $this->enqueueAssets();
65 $this->prepareInitialViewData();
66
67 return $this->renderView();
68 }
69
70 private function buildPaths()
71 {
72 $app = App::getInstance();
73 $this->slug = $app->config->get('app.slug');
74 $this->assetsPath = $app['url.assets'];
75 }
76
77 private function buildShortcodeAttributes($shortcodeAttributes)
78 {
79 $this->shortcodeAttributes = shortcode_atts(array(
80 'per_page' => '10',
81 // 'view_mode' => 'default',
82 'view_mode' => 'grid',
83 'with_cart' => 'no',
84 'cats' => '',
85 'exclude_carts' => '',
86 'show_cat_filter' => 'no',
87 'block_class' => '',
88 'paginator' => 'scroll',
89 'uid' => 'fluent_products_container_' . Helper::getUidSerial(),
90 'enable_filter' => false,
91 'enable_wildcard_filter' => false,
92 'enable_wildcard_for_post_content' => false,
93 'default_filters' => json_encode(['enabled' => false]),
94 'use_default_style' => true,
95 'search_grid_size' => 1,
96 'product_grid_size' => 4,
97 'product_box_grid_size' => 4,
98 'colors' => json_encode([]),
99 'price_format' => 'starts_from',
100 'order_type' => 'DESC',
101 'live_filter' => false,
102 'custom_filters' => json_encode([]),
103 'filters' => json_encode([]),
104 'ids' => '',
105 'exclude_ids' => '',
106 'category' => '',
107 'category_id' => '',
108 'tag' => '',
109 'tag_id' => '',
110 'fulfillment_type' => '',
111 'product_type' => '',
112 'on_sale' => '',
113 'sort_by' => '',
114 'columns' => '',
115 'orderby' => '',
116 'order' => '',
117 'limit' => '',
118 ), $shortcodeAttributes, static::SHORT_CODE);
119
120 // Alias: limit → per_page
121 if (!empty($this->shortcodeAttributes['limit']) && is_numeric($this->shortcodeAttributes['limit'])) {
122 $this->shortcodeAttributes['per_page'] = $this->shortcodeAttributes['limit'];
123 }
124
125 // Alias: columns → product_box_grid_size (controls the CSS grid columns)
126 if (!empty($this->shortcodeAttributes['columns']) && is_numeric($this->shortcodeAttributes['columns'])) {
127 $this->shortcodeAttributes['product_box_grid_size'] = $this->shortcodeAttributes['columns'];
128 }
129
130 // Map orderby+order → sort_by (WooCommerce-style)
131 if (!empty($this->shortcodeAttributes['orderby']) && empty($this->shortcodeAttributes['sort_by'])) {
132 $order = strtoupper($this->shortcodeAttributes['order'] ?: 'DESC');
133 $orderbyMap = [
134 'date' => ['ASC' => 'date-oldest', 'DESC' => 'date-newest'],
135 'title' => ['ASC' => 'name-asc', 'DESC' => 'name-desc'],
136 'price' => ['ASC' => 'price-low', 'DESC' => 'price-high'],
137 'id' => ['ASC' => 'date-oldest', 'DESC' => 'date-newest'],
138 ];
139 $orderby = strtolower($this->shortcodeAttributes['orderby']);
140 if (isset($orderbyMap[$orderby][$order])) {
141 $this->shortcodeAttributes['sort_by'] = $orderbyMap[$orderby][$order];
142 }
143 }
144
145 $this->shortcodeAttributes['per_page'] = is_numeric($this->shortcodeAttributes['per_page']) ? (int) $this->shortcodeAttributes['per_page'] : 10;
146
147 $viewMode = $this->shortcodeAttributes['view_mode'];
148
149 if (!($viewMode === 'list' || $viewMode === 'grid')) {
150 $viewMode = 'grid';
151 }
152
153 $this->shortcodeAttributes['view_mode'] = $viewMode;
154
155 $this->handelGridSizes('product_box_grid_size', 3);
156 $this->handelGridSizes('search_grid_size', 2);
157 $this->handelGridSizes('product_grid_size');
158
159 if ($this->shortcodeAttributes['enable_filter'] && Arr::get($shortcodeAttributes, 'filters', false)) {
160 if (is_array($shortcodeAttributes['filters'])) {
161 $this->shortcodeAttributes['filters'] = $shortcodeAttributes['filters'];
162 } else {
163 $jsonData = stripslashes(html_entity_decode($shortcodeAttributes['filters']));
164 $this->shortcodeAttributes['filters'] = json_decode($jsonData, true);
165 }
166 } else {
167 $this->shortcodeAttributes['filters'] = [
168 'enabled' => false
169 ];
170 }
171 }
172
173 public function handelGridSizes($key, $defaultValue = 6)
174 {
175 $this->shortcodeAttributes[$key] = intval($this->shortcodeAttributes[$key]);
176
177 if (empty($this->shortcodeAttributes[$key])) {
178 return;
179 }
180
181 if ($this->shortcodeAttributes[$key] > 6 || $this->shortcodeAttributes[$key] < 1) {
182 $this->shortcodeAttributes[$key] = $defaultValue;
183 }
184 }
185
186 private function buildFilters()
187 {
188 $this->viewData['filters'] = Arr::get($this->shortcodeAttributes, 'filters', []);
189
190 $filters = [];
191
192
193 foreach ($this->viewData['filters'] ?? [] as $key => $val) {
194 //Filter Out The filters are disabled
195 $enabled = Arr::get($val, 'enabled', false);
196
197 $isEnabled = in_array($enabled, [true, '1', 'true'], true);
198
199
200 if (!$isEnabled) {
201 continue;
202 }
203
204 $filters[$key]['label'] = Arr::get($val, 'label', ucfirst($key));
205 $filters[$key]['filter_type'] = Arr::get($val, 'filter_type', '');
206
207 if (is_array($val) && Arr::get($val, 'enabled', false) !== false && Arr::get($val, 'is_meta', false) !== false) {
208 $prefilled = Arr::get($this->urlFilters, $key);
209 $filters[$key]['options'] = $this->getMetaFilterOptions($key, $prefilled);
210 }
211
212 if ($filters[$key]['filter_type'] === 'range') {
213 $this->shouldLoadRangePlugin = true;
214 $minValue = Helper::toDecimalWithoutComma(ProductDetail::query()->min('min_price'));
215 $maxValue = Helper::toDecimalWithoutComma(ProductDetail::query()->max('max_price'));
216
217 $minFromUrl = Arr::get($this->urlFilters, $key . '_from', 0);
218 $maxFromUrl = Arr::get($this->urlFilters, $key . '_to', $maxValue);
219
220 $filters[$key]['min_value'] = ($minFromUrl < $minValue) ? $minValue : (min($minFromUrl, $maxValue));
221 $filters[$key]['max_value'] = ($maxFromUrl < 0) ? 0 : (min($maxFromUrl, $maxValue));
222
223 $filters[$key]['min'] = $minValue;
224 $filters[$key]['max'] = $maxValue;
225 }
226 }
227
228 $this->viewData['filters'] = $filters;
229 }
230
231 private function getMetaFilterOptions($key, $prefilled = []): array
232 {
233 return Taxonomy::getFormattedTerms($key, false, null, 'value', 'label', $prefilled);
234 }
235
236 private function prepareInitialViewData()
237 {
238
239 $allProducts = $this->getInitialProducts();
240 $this->defaultViewData = [
241 'products' => Arr::get($allProducts, 'products', []),
242 'placeholder_image' => Vite::getAssetUrl('images/placeholder.svg'),
243 'paginator' => $this->shortcodeAttributes['paginator'],
244 'view_mode' => $this->shortcodeAttributes['view_mode'],
245 'price_format' => $this->shortcodeAttributes['price_format'],
246 'per_page' => $this->shortcodeAttributes['per_page'],
247 'enable_filter' => $this->shortcodeAttributes['enable_filter'],
248 'enable_wildcard_filter' => $this->shortcodeAttributes['enable_wildcard_filter'],
249 'enable_wildcard_for_post_content' => $this->shortcodeAttributes['enable_wildcard_for_post_content'],
250 'default_filters' => $this->shortcodeAttributes['default_filters'],
251 'custom_filters' => is_array($this->shortcodeAttributes['custom_filters'])? $this->shortcodeAttributes['custom_filters']: json_decode($this->shortcodeAttributes['custom_filters'], true),
252 'use_default_style' => $this->shortcodeAttributes['use_default_style'],
253 'colors' => is_array($this->shortcodeAttributes['colors'])? $this->shortcodeAttributes['colors']: json_decode($this->shortcodeAttributes['colors'], true),
254 'store_settings' => new StoreSettings(),
255 'filters' => $this->shortcodeAttributes['filters']
256 ];
257 }
258
259 protected function getGridAttributes(): array
260 {
261 $product_grid_size = $this->shortcodeAttributes['product_grid_size'];
262 $product_grid_size = empty($product_grid_size) ? 4 : $product_grid_size;
263
264 $search_grid_size = $this->shortcodeAttributes['search_grid_size'];
265 $search_grid_size = empty($search_grid_size) ? 1 : $search_grid_size;
266
267
268 $product_default_grid_size = $product_grid_size;
269
270 if (!empty($this->defaultViewData['enable_filter'])) {
271 $product_default_grid_size += $search_grid_size;
272 }
273 return [
274 'search_grid_size' => $search_grid_size,
275 'product_grid_size' => $product_grid_size,
276 'product_default_grid_size' => $product_default_grid_size,
277 'product_box_grid_size' => $this->shortcodeAttributes['product_box_grid_size'] ?? 0
278 ];
279 }
280
281 public function getBlockClassName(): array
282 {
283 $blockClass = $this->shortcodeAttributes['block_class'];
284 return [
285 'block_class' => $blockClass,
286 ];
287 }
288
289 private function getViewData(): array
290 {
291 return array_merge(
292 $this->defaultViewData,
293 $this->viewData,
294 $this->getGridAttributes(),
295 $this->getBlockClassName(),
296 [
297 'shortcode_settings' => $this->shortcodeAttributes,
298 ]
299 );
300 }
301
302 private function getInitialProducts()
303 {
304 $params = $this->getDefaultConfig();
305
306 $products = ShopResource::get($params);
307
308 return [
309 'products' => ($products['products']->setCollection(
310 $products['products']->getCollection()->transform(function ($product) {
311 $product->setAppends(['view_url', 'has_subscription']);
312 return $product;
313 })
314 )),
315 'total' => $products['total']
316 ];
317 }
318
319 private function getDefaultConfig()
320 {
321 $paginatorMethod = $this->shortcodeAttributes['paginator'] === 'numbers' ? 'simple' : 'cursor';
322
323 $defaultFilters = $this->shortcodeAttributes['default_filters'];
324 $customFilters = $this->shortcodeAttributes['custom_filters'];
325
326 $filters = $this->shortcodeAttributes['filters'];
327 $enableFilters = Arr::get($filters, 'enabled', false) === true;
328
329
330 $allowOutOfStock = (Arr::get($defaultFilters, 'enabled', false) === true) &&
331 filter_var(Arr::get($defaultFilters, 'allow_out_of_stock', false), FILTER_VALIDATE_BOOLEAN);
332
333 if (Arr::get($defaultFilters, 'enabled') != 1) {
334 $defaultFilters = [];
335 }
336
337 $status = ["post_status" => ["column" => "post_status", "operator" => "in", "value" => ["publish"]]];
338
339 $urlTerms = Helper::parseTermIdsForFilter($this->urlFilters);
340 $defaultTerms = Helper::parseTermIdsForFilter($defaultFilters);
341 $mergedTerms = Helper::mergeTermIdsForFilter($defaultTerms, $urlTerms);
342
343 // --- Shortcode attribute: include/exclude IDs ---
344 $includeIds = array_values(array_filter(array_map('intval', array_map('trim', explode(',', $this->shortcodeAttributes['ids'])))));
345 $excludeIds = array_values(array_filter(array_map('intval', array_map('trim', explode(',', $this->shortcodeAttributes['exclude_ids'])))));
346
347 // --- Shortcode attribute: category (by slug) and category_id ---
348 $categoryTermIds = [];
349 if (!empty($this->shortcodeAttributes['category'])) {
350 $categoryTermIds = Taxonomy::getTermIdsBySlugs($this->shortcodeAttributes['category'], 'product-categories');
351 }
352 if (!empty($this->shortcodeAttributes['category_id'])) {
353 $catIds = array_values(array_filter(array_map('intval', array_map('trim', explode(',', $this->shortcodeAttributes['category_id'])))));
354 $categoryTermIds = array_unique(array_merge($categoryTermIds, $catIds));
355 }
356 if (!empty($categoryTermIds)) {
357 $existing = Arr::get($mergedTerms, 'product-categories', []);
358 $mergedTerms['product-categories'] = array_unique(array_merge($existing, $categoryTermIds));
359 }
360
361 // --- Shortcode attribute: tag (by slug) and tag_id ---
362 $tagTermIds = [];
363 if (!empty($this->shortcodeAttributes['tag'])) {
364 $tagTermIds = Taxonomy::getTermIdsBySlugs($this->shortcodeAttributes['tag'], 'product-tags');
365 }
366 if (!empty($this->shortcodeAttributes['tag_id'])) {
367 $tIds = array_values(array_filter(array_map('intval', array_map('trim', explode(',', $this->shortcodeAttributes['tag_id'])))));
368 $tagTermIds = array_unique(array_merge($tagTermIds, $tIds));
369 }
370 if (!empty($tagTermIds)) {
371 $existing = Arr::get($mergedTerms, 'product-tags', []);
372 $mergedTerms['product-tags'] = array_unique(array_merge($existing, $tagTermIds));
373 }
374
375 // --- Shortcode attribute: sort_by ---
376 if (!empty($this->shortcodeAttributes['sort_by'])) {
377 $filters['sort_by'] = sanitize_text_field($this->shortcodeAttributes['sort_by']);
378 }
379
380 // --- Shortcode attribute: fulfillment_type / product_type, stock_availability, on_sale ---
381 // product_type is an alias for fulfillment_type
382 $productType = sanitize_text_field($this->shortcodeAttributes['product_type'] ?: $this->shortcodeAttributes['fulfillment_type']);
383 $onSale = in_array(strtolower($this->shortcodeAttributes['on_sale']), ['yes', '1', 'true'], true);
384
385 // merge $this->urlFilters and $filters
386 $filters = array_merge($filters, $this->urlFilters);
387
388 $params = [
389 "select" => '*',
390 "with" => ['detail', 'variants', 'categories', 'licensesMeta'],
391 "selected_status" => true,
392 "status" => $status,
393 "shop_app_default_filters" => $defaultFilters,
394 "default_filters" => $defaultFilters,
395 "taxonomy_filters" => $mergedTerms,
396 "paginate" => $this->shortcodeAttributes['per_page'],
397 "per_page" => $this->shortcodeAttributes['per_page'],
398 'filters' => $filters,
399 'paginate_using' => $paginatorMethod,
400 'pagination_type' => $paginatorMethod,
401 'allow_out_of_stock' => $allowOutOfStock,
402 'order_type' => $this->shortcodeAttributes['order_type'],
403 'live_filter' => $this->shortcodeAttributes['live_filter'],
404 'enable_filters' => $enableFilters,
405 'custom_filters' => $customFilters,
406 'include_ids' => $includeIds,
407 'exclude_ids' => $excludeIds,
408 'product_type' => $productType,
409 'on_sale' => $onSale,
410 'product_box_grid_size' => $this->shortcodeAttributes['product_box_grid_size'],
411 'view_mode' => $this->shortcodeAttributes['view_mode'],
412 'price_format' => $this->shortcodeAttributes['price_format'],
413 ];
414
415 return $params;
416 }
417
418 public function renderView()
419 {
420 ob_start();
421 (new ShopAppRenderer($this->getInitialProducts(), $this->getDefaultConfig()))
422 ->render();
423 return ob_get_clean();
424 }
425
426 public function enqueueAssets()
427 {
428 if (App::request()->get('action') === 'elementor') {
429 return;
430 }
431
432 AssetLoader::loadProductArchiveAssets();
433
434
435 //SingleProductHandler::enqueueAssets();
436 }
437
438 public function enqueueStyles()
439 {
440
441 }
442
443 public function enqueueScripts()
444 {
445
446 }
447
448
449 public function getTermIdsForFilter($defaultFilters): array
450 {
451 $ids = [];
452
453 $taxonomies = Taxonomy::getTaxonomies();
454 foreach ($taxonomies as $key => $taxonomy) {
455
456 $termIds = Arr::get($defaultFilters, $key, '');
457
458
459 if (is_array($termIds)) {
460 $ids = array_merge($ids, $termIds);
461 continue;
462 }
463 if (strlen($termIds) || Str::contains($termIds, ',')) {
464 $termIds = explode(',', $termIds);
465
466 } else {
467 $termIds = [];
468 }
469
470 $ids = array_merge($ids, $termIds);
471
472 }
473 return $ids;
474 }
475 }
476