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