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

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