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

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