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

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