PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.27
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.27
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 / Services / Renderer / ShopAppRenderer.php

ShopAppRenderer.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.27, at app/Services/Renderer/ShopAppRenderer.php

698 lines 30.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\Services\Renderer;
4
5 use FluentCart\Api\Resource\ShopResource;
6 use FluentCart\Api\Taxonomy;
7 use FluentCart\App\Helpers\Helper;
8 use FluentCart\App\Http\Routes\WebRoutes;
9 use FluentCart\App\Modules\Templating\AssetLoader;
10 use FluentCart\Framework\Pagination\CursorPaginator;
11 use FluentCart\Framework\Support\Arr;
12
13 class ShopAppRenderer
14 {
15 protected $viewMode = 'grid';
16
17 protected $isFilterEnabled = false;
18
19 protected $per_page = 10;
20
21 protected $order_type = 'DESC';
22
23 protected $order_by = 'id';
24
25 protected $liveFilter = true;
26
27 protected $priceFormat = 'starts_from';
28
29 protected $paginator = 'scroll';
30 protected $defaultFilters = [];
31
32 protected $productBoxGridSize = 4;
33
34 protected $config = [];
35
36 protected $filters = [];
37
38 protected $products = [];
39
40 protected $customFilters = [];
41
42 protected $total = 0;
43
44 protected $isWildcardFilterEnabled = false;
45
46 public function __construct($products = [], $config = [])
47 {
48
49 $defaultFilters = Arr::get($config, 'default_filters', []);
50 $customFilters = Arr::get($config, 'custom_filters', []);
51 $this->customFilters = $customFilters;
52 $enableFilter = false;
53 if (is_string($customFilters)) {
54 $customFilters = json_decode($customFilters, true);
55 $this->customFilters = $customFilters;
56 }
57 if (!empty($customFilters)) {
58 $enableFilter = true;
59 if (Arr::has($customFilters, 'enabled')) {
60 $enableFilter = Arr::get($customFilters, 'enabled');
61 }
62 }
63 $this->config = $config;
64 $this->viewMode = !empty($config['view_mode']) ? $config['view_mode'] : 'grid';
65 $this->isFilterEnabled = $enableFilter;
66 $this->per_page = Arr::get($config, 'per_page', Arr::get($defaultFilters, 'per_page', 10));
67 $this->order_type = Arr::get($defaultFilters, 'sort_type', 'DESC');
68 $this->order_by = Arr::get($defaultFilters, 'sort_by', 'id');
69 $this->liveFilter = Arr::get($this->customFilters, 'live_filter', true);
70 $this->priceFormat = $config['price_format'] ?? 'starts_from';
71 $this->paginator = Arr::get($config, 'pagination_type', false);
72
73 if ($this->paginator === 'simple') {
74 $this->paginator = 'numbers';
75 } else if ($this->paginator === 'cursor') {
76 $this->paginator = 'scroll';
77 }
78 $this->productBoxGridSize = $config['product_box_grid_size'] ?? 4;
79 $this->isWildcardFilterEnabled = Arr::get($config, 'enable_wildcard_filter', false);
80
81 if (Arr::get($products, 'products', [])) {
82 $this->products = Arr::get($products, 'products', []);
83 } else {
84 $this->products = $products;
85 }
86
87 if($this->products instanceof CursorPaginator){
88 $this->total = 0;
89 }else{
90 $this->total = Arr::get($products, 'total', 0);
91 }
92 //$this->total = $products['total'];
93
94
95 $this->defaultFilters = array_merge($this->defaultFilters, Arr::get($defaultFilters, 'tax_query', []));
96
97 // Merge shortcode taxonomy_filters into defaultFilters so they persist in AJAX pagination
98 $taxonomyFilters = Arr::get($config, 'taxonomy_filters', []);
99 foreach ($taxonomyFilters as $taxonomy => $termIds) {
100 if (!empty($termIds)) {
101 if (!is_array($termIds)) {
102 $termIds = [$termIds];
103 }
104 $existing = Arr::get($this->defaultFilters, $taxonomy, []);
105 if (is_string($existing) && $existing !== '') {
106 $existing = array_map('trim', explode(',', $existing));
107 } elseif (!is_array($existing)) {
108 $existing = [];
109 }
110 $this->defaultFilters[$taxonomy] = array_unique(array_merge($existing, $termIds));
111 }
112 }
113
114 if (!empty($this->defaultFilters)) {
115 $this->defaultFilters['enabled'] = true;
116 }
117
118 // Merge allow_out_of_stock from config into defaultFilters
119 if (Arr::get($config, 'allow_out_of_stock')) {
120 $this->defaultFilters['allow_out_of_stock'] = true;
121 }
122
123 // Merge sort_by from config filters into defaultFilters for AJAX persistence
124 $configFilters = Arr::get($config, 'filters', []);
125 if (is_string($configFilters)) {
126 $configFilters = json_decode($configFilters, true) ?: [];
127 }
128 $shortcodeSortBy = Arr::get($configFilters, 'sort_by', '');
129 if ($shortcodeSortBy) {
130 $this->defaultFilters['sort_by'] = $shortcodeSortBy;
131 if (empty($this->defaultFilters['enabled'])) {
132 $this->defaultFilters['enabled'] = true;
133 }
134 }
135
136 if (Arr::get($this->customFilters, 'price_range', false)) {
137 $this->filters['price_range'] = [
138 "filter_type" => "range",
139 "is_meta" => false,
140 "label" => "Price",
141 "enabled" => true,
142 ];
143 }
144
145
146 if (isset($this->customFilters['taxonomies'])) {
147 // Convert string to array if needed
148 $taxonomies = $this->customFilters['taxonomies'];
149 if (!is_array($taxonomies)) {
150 $taxonomies = array_map('trim', explode(',', $taxonomies));
151 }
152
153 foreach ($taxonomies as $key => $taxonomy) {
154 if (is_array($taxonomy)) {
155 foreach ($taxonomy as $taxonomyKey => $tax) {
156 $this->filters[$taxonomyKey] = [
157 'enabled' => true,
158 'filter_type' => 'options',
159 'is_meta' => true,
160 'label' => Arr::get($tax, 'label'),
161 'multiple' => false,
162 'options' => []
163 ];
164 foreach ($tax['options'] as $option) {
165 $this->filters[$taxonomyKey]['options'][] = [
166 'value' => $option['term_id'],
167 'label' => $option['name'],
168 'parent' => $option['parent'],
169 'children' => []
170 ];
171 }
172 }
173
174 } else {
175 $this->filters[$taxonomy] = [
176 "filter_type" => "options",
177 "is_meta" => true,
178 "label" => ucfirst(str_replace('-', ' ', $taxonomy)),
179 "enabled" => true,
180 "multiple" => false,
181 ];
182 }
183 }
184 }
185
186
187 // Example of $this->filters
188 // $this->filters = [
189 // "product-categories" => [
190 // "filter_type" => "options",
191 // "is_meta" => true,
192 // "label" => "Product Categories",
193 // "enabled" => true,
194 // "multiple" => false
195 // ],
196 // "product-types" => [
197 // "filter_type" => "options",
198 // "is_meta" => true,
199 // "label" => "Product Types",
200 // "enabled" => true,
201 // "multiple" => false
202 // ]
203 // ];
204
205 }
206
207 public function render()
208 {
209 AssetLoader::loadProductArchiveAssets();
210 $isFullWidth = !$this->isFilterEnabled ? ' fct-full-container-width ' : '';
211 $renderer = new \FluentCart\App\Services\Renderer\ProductFilterRender($this->filters);
212
213 $wrapperAttributes = [
214 'class' => 'fct-products-wrapper-inner mode-' . $this->viewMode . $isFullWidth,
215 'data-fluent-cart-product-wrapper-inner' => '',
216 'data-per-page' => $this->per_page,
217 'data-order-type' => $this->order_type,
218 'data-live-filter' => $this->liveFilter,
219 'data-paginator' => $this->paginator,
220 'data-default-filters' => wp_json_encode($this->defaultFilters)
221 ];
222
223 // Shortcode filter data attributes for AJAX persistence
224 $includeIds = Arr::get($this->config, 'include_ids', []);
225 $excludeIds = Arr::get($this->config, 'exclude_ids', []);
226 $productType = Arr::get($this->config, 'product_type', '');
227 $onSale = Arr::get($this->config, 'on_sale', false);
228
229 if (!empty($includeIds)) {
230 $wrapperAttributes['data-include-ids'] = wp_json_encode($includeIds);
231 }
232 if (!empty($excludeIds)) {
233 $wrapperAttributes['data-exclude-ids'] = wp_json_encode($excludeIds);
234 }
235 if (!empty($productType)) {
236 $wrapperAttributes['data-product-type'] = esc_attr($productType);
237 }
238 if ($onSale) {
239 $wrapperAttributes['data-on-sale'] = '1';
240 }
241 ?>
242 <div class="fct-products-wrapper" data-fluent-cart-shop-app data-fluent-cart-product-wrapper role="main" aria-label="<?php esc_attr_e('Products', 'fluent-cart'); ?>">
243 <?php $this->renderViewSwitcher(); ?>
244 <div <?php RenderHelper::renderAtts($wrapperAttributes); ?>>
245 <?php $this->renderFilter($renderer); ?>
246
247 <div class="fct-products-container grid-columns-<?php echo esc_attr($this->productBoxGridSize); ?>"
248 data-fluent-cart-shop-app-product-list
249 role="list"
250 aria-label="<?php esc_attr_e('Product list', 'fluent-cart'); ?>"
251 >
252 <?php
253 if ($this->products->count() !== 0) {
254 $this->renderProduct();
255 } else {
256 ProductRenderer::renderNoProductFound();
257 }
258 ?>
259 </div>
260 </div>
261
262 <?php
263 if ($this->paginator === 'numbers') {
264 $this->renderPaginator();
265 }
266 ?>
267
268 </div>
269 <?php
270 }
271
272 public function renderViewSwitcher()
273 {
274
275 ?>
276 <div class="fct-shop-view-switcher-wrap">
277 <?php $this->renderViewSwitcherButton(); ?>
278 <?php
279 if ($this->isFilterEnabled) {
280 $this->renderSortByFilter();
281 }
282 ?>
283 </div>
284 <?php
285 }
286
287 public function renderViewSwitcherButton()
288 {
289 ?>
290 <div class="fct-shop-view-switcher">
291 <button type="button" data-fluent-cart-shop-app-grid-view-button=""
292 class="<?php echo $this->viewMode === 'grid' ? 'active' : ''; ?>"
293 title="<?php echo esc_attr__('Grid View', 'fluent-cart'); ?>">
294 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none">
295 <path
296 d="M12.4059 1.59412C13.3334 2.52162 13.3334 4.0144 13.3334 6.99996C13.3334 9.98552 13.3334 11.4783 12.4059 12.4058C11.4784 13.3333 9.98564 13.3333 7.00008 13.3333C4.01452 13.3333 2.52174 13.3333 1.59424 12.4058C0.666748 11.4783 0.666748 9.98552 0.666748 6.99996C0.666748 4.0144 0.666748 2.52162 1.59424 1.59412C2.52174 0.666626 4.01452 0.666626 7.00008 0.666626C9.98564 0.666626 11.4784 0.666626 12.4059 1.59412Z"
297 stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
298 <path d="M13.3335 7L0.66683 7" stroke="currentColor" stroke-linecap="round"></path>
299 <path d="M7 0.666626L7 13.3333" stroke="currentColor" stroke-linecap="round"></path>
300 </svg>
301 </button>
302 <button type="button" data-fluent-cart-shop-app-list-view-button=""
303 class="<?php echo $this->viewMode === 'list' ? 'active' : ''; ?>"
304 title="<?php echo esc_attr__('List View', 'fluent-cart'); ?>">
305 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
306 <path
307 d="M1.33325 7.60008C1.33325 6.8279 1.49441 6.66675 2.26659 6.66675H13.7333C14.5054 6.66675 14.6666 6.8279 14.6666 7.60008V8.40008C14.6666 9.17226 14.5054 9.33341 13.7333 9.33341H2.26659C1.49441 9.33341 1.33325 9.17226 1.33325 8.40008V7.60008Z"
308 stroke="currentColor" stroke-linecap="round"></path>
309 <path
310 d="M1.33325 2.26671C1.33325 1.49453 1.49441 1.33337 2.26659 1.33337H13.7333C14.5054 1.33337 14.6666 1.49453 14.6666 2.26671V3.06671C14.6666 3.83889 14.5054 4.00004 13.7333 4.00004H2.26659C1.49441 4.00004 1.33325 3.83888 1.33325 3.06671V2.26671Z"
311 stroke="currentColor" stroke-linecap="round"></path>
312 <path
313 d="M1.33325 12.9333C1.33325 12.1612 1.49441 12 2.26659 12H13.7333C14.5054 12 14.6666 12.1612 14.6666 12.9333V13.7333C14.6666 14.5055 14.5054 14.6667 13.7333 14.6667H2.26659C1.49441 14.6667 1.33325 14.5055 1.33325 13.7333V12.9333Z"
314 stroke="currentColor" stroke-linecap="round"></path>
315 </svg>
316 </button>
317 </div>
318
319 <?php if ($this->isFilterEnabled) { ?>
320 <button type="button"
321 data-fluent-cart-shop-app-filter-toggle-button=""
322 class="fct-shop-filter-toggle-button hide" title="Toggle List">
323 <span><?php echo esc_html__('Filter', 'fluent-cart'); ?></span>
324 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
325 <path
326 d="M2.5 4C1.67157 4 1 3.32843 1 2.5C1 1.67157 1.67157 1 2.5 1C3.32843 1 4 1.67157 4 2.5C4 3.32843 3.32843 4 2.5 4Z"
327 stroke="#2F3448" stroke-width="1.2"></path>
328 <path
329 d="M9.5 11C10.3284 11 11 10.3284 11 9.5C11 8.67157 10.3284 8 9.5 8C8.67157 8 8 8.67157 8 9.5C8 10.3284 8.67157 11 9.5 11Z"
330 stroke="#2F3448" stroke-width="1.2"></path>
331 <path d="M4 2.5L11 2.5" stroke="#2F3448" stroke-width="1.2" stroke-linecap="round"></path>
332 <path d="M8 9.5L1 9.5" stroke="#2F3448" stroke-width="1.2" stroke-linecap="round"></path>
333 </svg>
334 </button>
335 <?php } ?>
336 <?php
337 }
338
339 public function renderSortByFilter()
340 {
341
342 $currentSort = $this->order_by . '-' . $this->order_type;
343 $dropdownId = 'fct-sort-dropdown-' . uniqid();
344 ?>
345 <div class="fct-shop-sorting-container">
346 <button
347 class="fct-sorting-toggle"
348 data-sort-toggle
349 type="button"
350 aria-expanded="false"
351 aria-controls="<?php echo esc_attr($dropdownId); ?>"
352 aria-haspopup="true">
353 <span class="fct-sorting-label"><?php echo esc_html__('Sort By', 'fluent-cart'); ?></span>
354
355 <svg class="fct-sorting-arrow" xmlns="http://www.w3.org/2000/svg" width="14" height="8" viewBox="0 0 14 8" fill="none" aria-hidden="true" focusable="false">
356 <path d="M1.5 1.25L6.29289 6.04289C6.62623 6.37623 6.79289 6.54289 7 6.54289C7.20711 6.54289 7.37377 6.37623 7.70711 6.04289L12.5 1.25" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
357 </svg>
358 </button>
359
360 <div
361 class="fct-shop-sorting-dropdown"
362 data-sort-dropdown
363 id="<?php echo esc_attr($dropdownId); ?>"
364 role="menu"
365 aria-label="<?php echo esc_attr__('Sort products', 'fluent-cart'); ?>">
366 <fieldset class="fct-shop-sorting">
367 <legend class="screen-reader-text">
368 <?php echo esc_html__('Sort products by', 'fluent-cart'); ?>
369 </legend>
370
371 <div class="fct-shop-sorting-item" data-sort-item role="none">
372 <label>
373 <input
374 type="radio"
375 name="sort_by"
376 value="name-asc"
377 <?php checked($currentSort, 'name-asc'); ?>
378 aria-label="<?php echo esc_attr__('Sort alphabetically A to Z', 'fluent-cart'); ?>">
379 <span class="fct-sorting-radio" aria-hidden="true"></span>
380 <span class="fct-sorting-radio-label">
381 <?php echo esc_html__('Alphabetical (A to Z)', 'fluent-cart'); ?>
382 </span>
383 </label>
384 </div>
385
386 <div class="fct-shop-sorting-item" data-sort-item role="none">
387 <label>
388 <input
389 type="radio"
390 name="sort_by"
391 value="name-desc"
392 <?php checked($currentSort, 'name-desc'); ?>
393 aria-label="<?php echo esc_attr__('Sort alphabetically Z to A', 'fluent-cart'); ?>">
394 <span class="fct-sorting-radio" aria-hidden="true"></span>
395 <span class="fct-sorting-radio-label">
396 <?php echo esc_html__('Alphabetical (Z to A)', 'fluent-cart'); ?>
397 </span>
398 </label>
399 </div>
400
401 <div class="fct-shop-sorting-item" data-sort-item role="none">
402 <label>
403 <input
404 type="radio"
405 name="sort_by"
406 value="price-low"
407 <?php checked($currentSort, 'price-low'); ?>
408 aria-label="<?php echo esc_attr__('Sort by price low to high', 'fluent-cart'); ?>">
409 <span class="fct-sorting-radio" aria-hidden="true"></span>
410 <span class="fct-sorting-radio-label">
411 <?php echo esc_html__('Price (Low to High)', 'fluent-cart'); ?>
412 </span>
413 </label>
414 </div>
415
416 <div class="fct-shop-sorting-item" data-sort-item role="none">
417 <label>
418 <input
419 type="radio"
420 name="sort_by"
421 value="price-high"
422 <?php checked($currentSort, 'price-high'); ?>
423 aria-label="<?php echo esc_attr__('Sort by price high to low', 'fluent-cart'); ?>">
424 <span class="fct-sorting-radio" aria-hidden="true"></span>
425 <span class="fct-sorting-radio-label">
426 <?php echo esc_html__('Price (High to Low)', 'fluent-cart'); ?>
427 </span>
428 </label>
429 </div>
430
431 <div class="fct-shop-sorting-item" data-sort-item role="none">
432 <label>
433 <input
434 type="radio"
435 name="sort_by"
436 value="date-newest"
437 <?php checked($currentSort, 'date-newest'); ?>
438 aria-label="<?php echo esc_attr__('Sort by date newest first', 'fluent-cart'); ?>">
439 <span class="fct-sorting-radio" aria-hidden="true"></span>
440 <span class="fct-sorting-radio-label">
441 <?php echo esc_html__('Date (Newest First)', 'fluent-cart'); ?>
442 </span>
443 </label>
444 </div>
445
446 <div class="fct-shop-sorting-item" data-sort-item role="none">
447 <label>
448 <input
449 type="radio"
450 name="sort_by"
451 value="date-oldest"
452 <?php checked($currentSort, 'date-oldest'); ?>
453 aria-label="<?php echo esc_attr__('Sort by date oldest first', 'fluent-cart'); ?>">
454 <span class="fct-sorting-radio" aria-hidden="true"></span>
455 <span class="fct-sorting-radio-label">
456 <?php echo esc_html__('Date (Oldest First)', 'fluent-cart'); ?>
457 </span>
458 </label>
459 </div>
460 </fieldset>
461 </div>
462 </div>
463 <?php
464 }
465
466 public function renderFilter($renderer)
467 {
468 if (!$this->isFilterEnabled || !$renderer) {
469 return;
470 }
471 ?>
472 <?php if ($this->isFilterEnabled) : ?>
473 <div class="fct-shop-filter-wrapper fluent-cart-shop-app-filter-wrapper" data-fluent-cart-shop-app-filter-wrapper role="search" aria-label="<?php esc_attr_e('Product filters', 'fluent-cart'); ?>">
474 <div class="fluent-cart-shop-app-filter-wrapper-inner">
475
476 <form class="fct-shop-filter-form" data-fluent-cart-product-filter-form role="search"
477 aria-label="<?php esc_attr_e('Product filter form', 'fluent-cart'); ?>">
478 <?php
479 if($this->isWildcardFilterEnabled){
480 $renderer->renderSearch();
481 }
482 ?>
483 <?php $renderer->renderOptions(); ?>
484 <?php if (!$this->liveFilter) : ?>
485 <div class="fct-shop-filter-item">
486 <div class="fct-shop-button-group">
487 <button class="fct-shop-apply-filter-button wp-block-fluent-cart-shopapp-product-filter-apply-button">
488 <?php esc_html_e('Apply Filter', 'fluent-cart'); ?>
489 </button>
490 <button class="fct-shop-reset-filter-button wp-block-fluent-cart-shopapp-product-filter-reset-button"
491 data-fluent-cart-shop-app-reset-button="">
492 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none">
493 <path d="M14.2501 9.75V15.75M9.75012 9.75V15.75M20.2498 5.25L3.74976 5.25001M18.7501 5.25V19.5C18.7501 19.6989 18.6711 19.8897 18.5305 20.0303C18.3898 20.171 18.199 20.25 18.0001 20.25H6.00012C5.80121 20.25 5.61044 20.171 5.46979 20.0303C5.32914 19.8897 5.25012 19.6989 5.25012 19.5V5.25M15.7501 5.25V3.75C15.7501 3.35218 15.5921 2.97064 15.3108 2.68934C15.0295 2.40804 14.6479 2.25 14.2501 2.25H9.75012C9.3523 2.25 8.97077 2.40804 8.68946 2.68934C8.40816 2.97064 8.25012 3.35218 8.25012 3.75V5.25"
494 stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
495 stroke-linejoin="round"></path>
496 </svg>
497 </button>
498 </div>
499 </div>
500 <?php endif; ?>
501 </form>
502 </div>
503 </div>
504 <?php endif; ?>
505 <?php
506 }
507
508
509 public function renderProduct()
510 {
511 $products = $this->products;
512
513 $cursor = '';
514 if ($products instanceof CursorPaginator) {
515 $cursor = wp_parse_args(wp_parse_url($products->nextPageUrl(), PHP_URL_QUERY));
516 }
517 ?>
518 <?php foreach ($products as $index => $product) {
519 $cursorAttr = '';
520 if ($index === 0) {
521 $cursorAttr = Arr::get($cursor, 'cursor', '');
522 }
523
524 (new \FluentCart\App\Services\Renderer\ProductCardRender($product, ['cursor' => $cursorAttr]))->render();
525 ?>
526 <?php } ?>
527 <?php
528 }
529
530 public function renderTitle($product = null)
531 {
532 if (!$product || !$product === null) {
533 return '';
534 }
535
536 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
537 ob_start();
538 $render->renderTitle('class="fct-product-card-title"');
539 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
540 echo ob_get_clean();
541 }
542
543 public function renderImage($product = null)
544 {
545 if (!$product || !$product === null) {
546 return '';
547 }
548
549 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
550 ob_start();
551 $render->renderProductImage();
552 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
553 echo ob_get_clean();
554 }
555
556 public function renderPrice($product = null)
557 {
558 if (!$product || !$product === null) {
559 return '';
560 }
561
562 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
563 ob_start();
564 $render->renderPrices('class="fct-product-card-prices"');
565 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
566 echo ob_get_clean();
567 }
568
569 public function renderButton($product = null)
570 {
571 if (!$product || !$product === null) {
572 return '';
573 }
574
575 $render = new \FluentCart\App\Services\Renderer\ProductCardRender($product);
576 ob_start();
577 $render->showBuyButton();
578 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
579 echo ob_get_clean();
580 }
581
582 private function getInitialProducts()
583 {
584 $params = $this->config;
585
586 $products = ShopResource::get($params);
587
588 return ($products['products']->setCollection(
589 $products['products']->getCollection()->transform(function ($product) {
590 $product->setAppends(['view_url', 'has_subscription']);
591 return $product;
592 })
593 ));
594 }
595
596 public function renderPaginator()
597 {
598 $total = $this->total;
599 $lastPage = max((int)ceil($total / $this->per_page), 1);
600 $currentPage = $this->products->currentPage();
601 $from = ($currentPage - 1) * $this->per_page + 1;
602 $to = min($total, $currentPage * $this->per_page);
603 $perPage = $this->products->perPage();
604 ?>
605 <div class="fct-shop-paginator">
606 <?php $this->renderPaginatorResultWrapper(); ?>
607
608 <?php $this->renderPerPageSelector(); ?>
609
610 </div>
611 <?php
612 }
613
614 public function renderPaginatorResultWrapper($atts = '')
615 {
616 $total = $this->total;
617
618 $lastPage = max((int)ceil($total / $this->per_page), 1);
619 $currentPage = $this->products->currentPage();
620 $from = ($currentPage - 1) * $this->per_page + 1;
621 $to = min($total, $currentPage * $this->per_page);
622 $perPage = $this->products->perPage();
623 ?>
624 <div class="fct-shop-paginator-result-wrapper" aria-label="<?php echo esc_attr__('Pagination information', 'fluent-cart'); ?>">
625 <div
626 <?php echo !empty($atts) ? $atts : 'class="fct-shop-paginator-results wc-block-grid__fluent-cart-shop-app-paginator-results wp-block-fluent-cart-product-paginator-info"'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
627 role="status"
628 aria-live="polite"
629 aria-atomic="true">
630 <?php
631 printf(
632 /* translators: 1: starting item number, 2: ending item number, 3: total number of items */
633 esc_html__('Showing %1$s to %2$s of %3$s Items', 'fluent-cart'),
634 '<span class="fct-shop-paginator-from" data-fluent-cart-shop-app-paginator-info-pagination-from="">' . esc_html($from) . '</span>',
635 '<span class="fct-shop-paginator-to" data-fluent-cart-shop-app-paginator-info-pagination-to="">' . esc_html($to) . '</span>',
636 '<span class="fct-shop-paginator-total" data-fluent-cart-shop-app-paginator-info-pagination-total="">' . esc_html($total) . '</span>'
637 );
638 ?>
639 </div>
640
641 <div class="fct-shop-per-page-selector">
642 <label for="fct-per-page-select" class="screen-reader-text">
643 <?php echo esc_html__('Items per page', 'fluent-cart'); ?>
644 </label>
645 <select
646 id="fct-per-page-select"
647 name="per_page"
648 data-fluent-cart-shop-app-paginator-per-page-selector=""
649 aria-label="<?php echo esc_attr__('Select number of items per page', 'fluent-cart'); ?>">
650 <option value="10" <?php selected($perPage, 10); ?>>
651 <?php echo esc_html__('10 Per page', 'fluent-cart'); ?>
652 </option>
653 <option value="20" <?php selected($perPage, 20); ?>>
654 <?php echo esc_html__('20 Per page', 'fluent-cart'); ?>
655 </option>
656 <option value="30" <?php selected($perPage, 30); ?>>
657 <?php echo esc_html__('30 Per page', 'fluent-cart'); ?>
658 </option>
659 </select>
660 </div>
661 </div>
662 <?php
663 }
664
665 public function renderPerPageSelector()
666 {
667 $total = $this->total;
668 $lastPage = max((int)ceil($total / $this->per_page), 1);
669 $currentPage = $this->products->currentPage();
670 $from = ($currentPage - 1) * $this->per_page + 1;
671 $to = min($total, $currentPage * $this->per_page);
672 $perPage = $this->products->perPage();
673
674 if ($lastPage > 1) : ?>
675 <ul class="fct-shop-paginator-pager"
676 data-fluent-cart-shop-app-paginator-items-wrapper="">
677 <?php for ($page = 1; $page <= $lastPage; $page++):
678 $isCurrent = $page == $currentPage;
679 $classes = $isCurrent ? 'active' : '';
680 ?>
681 <li class="pager-number <?php echo $page == $currentPage ? 'active' : ''; ?>">
682 <button
683 class="<?php echo esc_attr($classes); ?>"
684 data-fluent-cart-shop-app-paginator-item
685 data-page="<?php echo esc_attr($page); ?>"
686 <?php if ($isCurrent) : ?>aria-current="page"<?php endif; ?>
687 >
688 <?php echo esc_html($page); ?>
689 </button>
690 </li>
691 <?php endfor; ?>
692 </ul>
693 <?php
694 endif;
695 }
696
697 }
698