PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.6
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.6
1.6.6 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 All 49 releases
← All changes | app/Services/Renderer/ProductRenderer.php +191 -69 1.6.0 → 1.6.6 View file →
@@ -13,8 +13,9 @@
13 13 use FluentCart\App\Http\Routes\WebRoutes;
14 14 use FluentCart\App\Models\ProductVariation;
15 15 use FluentCart\Framework\Support\Collection;
16 16 use FluentCart\App\Modules\Templating\AssetLoader;
17 +use FluentCart\App\Modules\FluentPlayer\ProductVideoRenderer;
17 18
18 19 class ProductRenderer
19 20 {
20 21 protected $product;
@@ -38,8 +39,10 @@
38 39 protected $defaultGalleryImageId = 0;
39 40
40 41 protected $galleryActiveSet = false;
41 42
43 + protected $productVideoRenderer = null;
44 +
42 45 protected $paymentTypes = [];
43 46
44 47 protected $variantsByPaymentTypes = [];
45 48
@@ -294,8 +297,19 @@
294 297 }
295 298
296 299 public function renderBuySection($atts = [])
297 300 {
301 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
302 +
303 + // The buy section is the root the variation-selector JS binds to
304 + // (data-fluent-cart-product-pricing-section). Gating it removes the
305 + // variation picker along with the buttons, which is what a full catalog
306 + // mode wants; to keep shoppers able to browse options while hiding only
307 + // the purchase affordances, gate 'actions' instead.
308 + if (!RenderGate::shouldRender('buy_section', $gateContext)) {
309 + return;
310 + }
311 +
298 312 // Render no buy section when there is nothing purchasable — avoids a
299 313 // broken quantity + "Not Available" block. Two cases:
300 314 // - no variants at all (any product type), or
301 315 // - an advanced-variation product with no attribute_config yet (e.g.
@@ -421,11 +435,16 @@
421 435 if (empty($this->defaultImageUrl)) {
422 436 $this->defaultImageUrl = $featuredMedia;
423 437 }
424 438
439 + $videoRenderer = $this->getProductVideoRenderer();
440 + $videoRenderer->showFirstByDefault(!$this->hasGalleryImages());
441 + $defaultVideoId = $videoRenderer->getDefaultMediaId();
442 +
425 443 ?>
426 - <div class="fct-product-gallery-thumb" role="region"
427 - aria-label="<?php echo esc_attr($this->product->post_title . ' gallery'); ?>">
444 + <div class="fct-product-gallery-thumb<?php echo $defaultVideoId ? ' is-video-active' : ''; ?>" role="region"
445 + aria-label="<?php echo esc_attr($this->product->post_title . ' gallery'); ?>"
446 + <?php echo $defaultVideoId ? 'data-fct-video-default="' . esc_attr((string) $defaultVideoId) . '"' : ''; ?>>
428 447 <img
429 448 src="<?php echo esc_url($this->defaultImageUrl ?? '') ?>"
430 449 alt="<?php echo esc_attr($this->defaultImageAlt) ?>"
431 450 data-fluent-cart-single-product-page-product-thumbnail
@@ -430,21 +449,54 @@
430 449 alt="<?php echo esc_attr($this->defaultImageAlt) ?>"
431 450 data-fluent-cart-single-product-page-product-thumbnail
432 451 data-default-image-url="<?php echo esc_url($featuredMedia) ?>"
433 452 />
453 + <?php $this->getProductVideoRenderer()->renderInlinePlayers(); ?>
434 454 </div>
435 455 <?php
436 456 }
437 457
458 + /**
459 + * Whether any gallery / variant image exists to show in the main area.
460 + * Only meaningful after renderGalleryThumb() has built $this->images.
461 + */
462 + protected function hasGalleryImages(): bool
463 + {
464 + foreach ($this->images as $image) {
465 + foreach ((array) Arr::get($image, 'media', []) as $item) {
466 + if (!empty(Arr::get($item, 'url'))) {
467 + return true;
468 + }
469 + }
470 + }
471 +
472 + return false;
473 + }
474 +
475 + public function getProductVideoRenderer(): ProductVideoRenderer
476 + {
477 + if ($this->productVideoRenderer === null) {
478 + $this->productVideoRenderer = new ProductVideoRenderer($this->product);
479 + }
480 +
481 + return $this->productVideoRenderer;
482 + }
483 +
438 484 public function renderGalleryThumbControls($maxThumbnails = null)
439 485 {
440 486 $totalThumbImages = Arr::pluck($this->images, 'media.*.url');
441 487
442 - if(count($totalThumbImages) == 1 && is_countable($totalThumbImages[0]) && count($totalThumbImages[0]) == 1){
488 + // A lone image needs no strip, but a video thumb still has to be reachable.
489 + if(count($totalThumbImages) == 1 && is_countable($totalThumbImages[0]) && count($totalThumbImages[0]) == 1 && !$this->getProductVideoRenderer()->isAvailable()){
443 490
444 491 return '';
445 492 }
446 493
494 + // A lone video without images is already showing in the main area.
495 + if (!$this->hasGalleryImages() && count($this->getProductVideoRenderer()->getVideos()) === 1) {
496 + return '';
497 + }
498 +
447 499 // Build variantTermMap and variantFirstMediaMap.
448 500 // For advanced variations, Pro builds both maps via the gallery_variation_data
449 501 // filter — it has access to AttributeGroup types (color/image) that free cannot
450 502 // query directly. For all other product types, free builds variantFirstMediaMap
@@ -537,9 +589,16 @@
537 589 $isAdvVariation = !empty($this->variantTermMap);
538 590 $countedMediaKeys = [];
539 591 $count = 0;
540 592 $totalImages = 0;
593 + $videoRenderer = $this->getProductVideoRenderer();
541 594
595 + // The gallery opens on a video the admin put in front of every image,
596 + // so no image thumb takes the selected slot in that case.
597 + if ($videoRenderer->getDefaultMediaId()) {
598 + $this->galleryActiveSet = true;
599 + }
600 +
542 601 // Count unique images to render. For advanced variations, deduplicate by WP media ID
543 602 // when available, or by URL for externally imported images (media ID = 0).
544 603 foreach ($this->images as $imageId => $image) {
545 604 if (empty($image['media']) || !is_array($image['media'])) {
@@ -581,16 +640,21 @@
581 640 }
582 641 $renderedMediaKeys[$mediaDedupeKey] = true;
583 642 }
584 643 if ($maxThumbnails !== null && $count >= (int) $maxThumbnails) {
644 + $videoRenderer->renderThumbControls(!$this->galleryActiveSet);
585 645 $this->renderGallerySeeMoreButton($totalImages - (int) $maxThumbnails);
586 646 return;
587 647 }
648 + // Videos the admin dragged in front of this image come first.
649 + $videoRenderer->renderThumbControlsBefore($count, !$this->galleryActiveSet);
588 650 $termId = (int) ($this->variantTermMap[(int) $imageId] ?? 0);
589 651 $this->renderGalleryThumbControlButton($item, $imageId, $termId, $mediaId);
590 652 $count++;
591 653 }
592 654 }
655 +
656 + $videoRenderer->renderThumbControls(!$this->galleryActiveSet);
593 657 }
594 658
595 659 public function renderGallerySeeMoreButton($remainingCount)
596 660 {
@@ -663,9 +727,14 @@
663 727 }
664 728
665 729 public function renderGallery($args = [])
666 730 {
731 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
667 732
733 + if (!RenderGate::shouldRender('image', $gateContext)) {
734 + return;
735 + }
736 +
668 737 $defaults = [
669 738 'thumbnail_mode' => 'all', // horizontal, vertical
670 739 'thumb_position' => 'bottom', // bottom, left, right, top
671 740 'scrollable_thumbs' => 'no', // yes / no
@@ -699,13 +768,21 @@
699 768 }
700 769
701 770 public function renderTitle()
702 771 {
772 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
773 +
774 + if (!RenderGate::shouldRender('title', $gateContext)) {
775 + return;
776 + }
777 +
778 + do_action('fluent_cart/product/single/before_title_block', $gateContext);
703 779 ?>
704 780 <div class="fct-product-title">
705 781 <h1 id="fct-product-summary-title"><?php echo esc_html($this->product->post_title); ?></h1>
706 782 </div>
707 783 <?php
784 + do_action('fluent_cart/product/single/after_title_block', $gateContext);
708 785 }
709 786
710 787 public function renderStockAvailability($wrapper_attributes = '')
711 788 {
@@ -907,14 +984,22 @@
907 984 $excerpt = $this->product->post_excerpt;
908 985 if (!$excerpt) {
909 986 return;
910 987 }
988 +
989 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
990 +
991 + if (!RenderGate::shouldRender('excerpt', $gateContext)) {
992 + return;
993 + }
994 +
995 + do_action('fluent_cart/product/single/before_excerpt_block', $gateContext);
911 996 ?>
912 997 <div class="fct-product-excerpt" aria-labelledby="fct-product-summary-title">
913 998 <p><?php echo wp_kses_post($excerpt); ?></p>
914 999 </div>
915 1000 <?php
916 -
1001 + do_action('fluent_cart/product/single/after_excerpt_block', $gateContext);
917 1002 }
918 1003
919 1004 public function renderDescription()
920 1005 {
@@ -944,8 +1029,14 @@
944 1029 }
945 1030
946 1031 public function renderPrices()
947 1032 {
1033 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1034 +
1035 + if (!RenderGate::shouldRender('price', $gateContext)) {
1036 + return;
1037 + }
1038 +
948 1039 if ($this->product->detail->variation_type === 'simple') {
949 1040 // we have to render for the simple product
950 1041
951 1042 $first_price = $this->product->variants()->first();
@@ -959,97 +1050,76 @@
959 1050 $comparePrice = $first_price ? (int)$first_price->compare_price : 0;
960 1051 if ($comparePrice <= $itemPrice) {
961 1052 $comparePrice = 0;
962 1053 }
963 - do_action('fluent_cart/product/single/before_price_block', [
1054 + do_action('fluent_cart/product/single/before_price_block', RenderContext::decorate([
964 1055 'product' => $this->product,
965 1056 'current_price' => $itemPrice,
966 1057 'scope' => 'price_range'
967 - ]);
1058 + ]));
968 1059 ?>
969 - <?php
1060 + <div class="fct-price-range fct-product-prices">
970 1061
971 - if ($comparePrice) {
972 - $aria_label = sprintf(
973 - /* translators: 1: Original price, 2: Current item price */
974 - __('Original Price: %1$s, Price: %2$s', 'fluent-cart'),
975 - Helper::toDecimal($comparePrice),
976 - Helper::toDecimal($itemPrice)
977 - );
978 - } else {
979 - $aria_label = sprintf(
980 - /* translators: 1: Current item price */
981 - __('Price: %1$s', 'fluent-cart'),
982 - Helper::toDecimal($itemPrice)
983 - );
984 - }
985 -
986 - ?>
987 - <div class="fct-price-range fct-product-prices" role="term"
988 - aria-label="<?php echo esc_attr($aria_label); ?>">
989 -
990 1062 <?php if ($comparePrice): ?>
991 1063 <span class="fct-compare-price">
992 - <del aria-label="<?php echo esc_attr(__('Original price', 'fluent-cart')); ?>"><?php echo esc_html(Helper::toDecimal($comparePrice)); ?></del>
1064 + <span class="fct-sr-only"><?php echo esc_html__('Original price:', 'fluent-cart'); ?></span>
1065 + <del><?php echo esc_html(Helper::toDecimal($comparePrice)); ?></del>
993 1066 </span>
994 1067 <?php endif; ?>
995 - <span class="fct-item-price" aria-label="<?php echo esc_attr(__('Current price', 'fluent-cart')); ?>">
1068 + <span class="fct-item-price">
1069 + <span class="fct-sr-only"><?php echo $comparePrice ? esc_html__('Sale price:', 'fluent-cart') : esc_html__('Price:', 'fluent-cart'); ?></span>
996 1070 <?php echo esc_html(Helper::toDecimal($itemPrice)); ?>
997 - <?php do_action('fluent_cart/product/after_price', [
1071 + <?php do_action('fluent_cart/product/after_price', RenderContext::decorate([
998 1072 'product' => $this->product,
1073 + 'variant' => $first_price,
999 1074 'current_price' => $itemPrice,
1000 1075 'scope' => 'price_range'
1001 - ]); ?>
1076 + ])); ?>
1077 + <?php RenderHelper::renderPriceSuffix($this->product, $first_price, 'price_range'); ?>
1002 1078 </span>
1003 1079 </div>
1004 1080 <?php
1005 - do_action('fluent_cart/product/single/after_price_block', [
1081 + do_action('fluent_cart/product/single/after_price_block', RenderContext::decorate([
1006 1082 'product' => $this->product,
1007 1083 'current_price' => $itemPrice,
1008 1084 'scope' => 'price_range'
1009 - ]);
1085 + ]));
1010 1086 return;
1011 1087 }
1012 1088 $min_price = $this->product->detail->min_price;
1013 1089 $max_price = $this->product->detail->max_price;
1014 1090
1015 - do_action('fluent_cart/product/single/before_price_range_block', [
1091 + do_action('fluent_cart/product/single/before_price_range_block', RenderContext::decorate([
1016 1092 'product' => $this->product,
1017 1093 'current_price' => $min_price,
1018 1094 'scope' => 'price_range'
1019 - ]);
1095 + ]));
1020 1096 ?>
1021 - <?php
1022 - $aria_label = sprintf(
1023 - /* translators: 1: Minimum price, 2: Maximum price */
1024 - __('Price range: %1$s - %2$s', 'fluent-cart'),
1025 - Helper::toDecimal($min_price),
1026 - Helper::toDecimal($max_price)
1027 - );
1028 - ?>
1029 - <div class="fct-product-prices fct-price-range" role="term" aria-label="<?php echo esc_attr($aria_label); ?>">
1097 + <div class="fct-product-prices fct-price-range">
1030 1098
1031 1099 <?php if ($max_price && $max_price != $min_price && $max_price > $min_price): ?>
1100 + <span class="fct-sr-only"><?php echo esc_html__('Price range:', 'fluent-cart'); ?></span>
1032 1101 <span class="fct-min-price"><?php echo esc_html(Helper::toDecimal($min_price)); ?></span>
1033 1102 <span class="fct-price-separator" aria-hidden="true">-</span>
1103 + <span class="fct-sr-only"><?php echo esc_html__('to', 'fluent-cart'); ?></span>
1034 1104 <?php endif; ?>
1035 1105 <span class="fct-max-price">
1036 1106 <?php echo esc_html(Helper::toDecimal($max_price)); ?>
1037 1107 </span>
1038 1108
1039 - <?php do_action('fluent_cart/product/after_price', [
1109 + <?php do_action('fluent_cart/product/after_price', RenderContext::decorate([
1040 1110 'product' => $this->product,
1041 1111 'current_price' => $min_price,
1042 1112 'scope' => 'price_range'
1043 - ]); ?>
1113 + ])); ?>
1044 1114
1045 1115 </div>
1046 1116 <?php
1047 - do_action('fluent_cart/product/single/after_price_range_block', [
1117 + do_action('fluent_cart/product/single/after_price_range_block', RenderContext::decorate([
1048 1118 'product' => $this->product,
1049 1119 'current_price' => $min_price,
1050 1120 'scope' => 'price_range'
1051 - ]);
1121 + ]));
1052 1122 }
1053 1123
1054 1124 public function renderVariants($atts = [])
1055 1125 {
@@ -1074,19 +1144,19 @@
1074 1144 ?>
1075 1145 <div class="<?php echo esc_attr(implode(' ', $classes)); ?>" role="radiogroup"
1076 1146 aria-label="<?php esc_attr_e('Product Variants', 'fluent-cart'); ?>">
1077 1147 <?php foreach ($variants as $variant) {
1078 - do_action('fluent_cart/product/single/before_variant_item', [
1148 + do_action('fluent_cart/product/single/before_variant_item', RenderContext::decorate([
1079 1149 'product' => $this->product,
1080 1150 'variant' => $variant,
1081 1151 'scope' => 'product_variant_item'
1082 - ]);
1152 + ]));
1083 1153 $this->renderVariationItem($variant, $this->defaultVariationId);
1084 - do_action('fluent_cart/product/single/after_variant_item', [
1154 + do_action('fluent_cart/product/single/after_variant_item', RenderContext::decorate([
1085 1155 'product' => $this->product,
1086 1156 'variant' => $variant,
1087 1157 'scope' => 'product_variant_item'
1088 - ]);
1158 + ]));
1089 1159 } ?>
1090 1160 </div>
1091 1161 <?php
1092 1162 }
@@ -1092,17 +1162,24 @@
1092 1162 }
1093 1163
1094 1164 public function renderItemPrice()
1095 1165 {
1166 + // Same 'price' gate as renderPrices(): one filter hides the price
1167 + // wherever it appears, rather than making callers hunt for the second
1168 + // place the single product page prints one.
1169 + if (!RenderGate::shouldRender('price', RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant))) {
1170 + return;
1171 + }
1172 +
1096 1173 if ($this->product->detail->variation_type === 'simple' && !$this->hasSubscription) {
1097 1174 return; // for simple product we already rendered the price
1098 1175 }
1099 1176
1100 - do_action('fluent_cart/product/single/before_price_block', [
1177 + do_action('fluent_cart/product/single/before_price_block', RenderContext::decorate([
1101 1178 'product' => $this->product,
1102 1179 'current_price' => $this->defaultVariant ? $this->defaultVariant->item_price : 0,
1103 1180 'scope' => 'product_variant_price'
1104 - ]);
1181 + ]));
1105 1182
1106 1183 foreach ($this->product->variants as $variant) {
1107 1184 if ($this->shouldRenderPriceInPriceSection()) {
1108 1185 $this->renderVariantPricingWrapperStart($variant);
@@ -1136,13 +1213,13 @@
1136 1213
1137 1214
1138 1215
1139 1216
1140 - do_action('fluent_cart/product/single/after_price_block', [
1217 + do_action('fluent_cart/product/single/after_price_block', RenderContext::decorate([
1141 1218 'product' => $this->product,
1142 1219 'current_price' => $this->defaultVariant ? $this->defaultVariant->item_price : 0,
1143 1220 'scope' => 'product_variant_price'
1144 - ]);
1221 + ]));
1145 1222 }
1146 1223
1147 1224 public function shouldRenderPriceInPriceSection(): bool
1148 1225 {
@@ -1169,14 +1246,14 @@
1169 1246 'product' => $this->product,
1170 1247 'variant' => $variant,
1171 1248 'scope' => 'product_variant_price'
1172 1249 ]));
1173 - do_action('fluent_cart/product/after_price', [
1250 + do_action('fluent_cart/product/after_price', RenderContext::decorate([
1174 1251 'product' => $this->product,
1175 1252 'variant' => $variant,
1176 1253 'current_price' => $variant->item_price,
1177 1254 'scope' => 'product_variant_price'
1178 - ]);
1255 + ]));
1179 1256 RenderHelper::renderPriceSuffix($this->product, $variant, 'product_variant_price');
1180 1257 }
1181 1258
1182 1259 public function renderComparePriceWrapperStart($atts = [])
@@ -1199,8 +1276,9 @@
1199 1276 return;
1200 1277 } ?>
1201 1278
1202 1279 <span class="fct-compare-price">
1280 + <span class="fct-sr-only"><?php echo esc_html__('Original price:', 'fluent-cart'); ?></span>
1203 1281 <del><?php echo esc_html(Helper::toDecimal($variant->compare_price)); ?></del>
1204 1282 </span>
1205 1283 <?php
1206 1284 }
@@ -1286,8 +1364,14 @@
1286 1364 <?php }
1287 1365
1288 1366 public function renderQuantity()
1289 1367 {
1368 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1369 +
1370 + if (!RenderGate::shouldRender('quantity', $gateContext)) {
1371 + return;
1372 + }
1373 +
1290 1374 $soldIndividually = $this->product->soldIndividually();
1291 1375
1292 1376 if (!$this->hasOnetime || $soldIndividually) {
1293 1377 return;
@@ -1306,12 +1390,12 @@
1306 1390 if ($this->hasSubscription && Arr::get($defaultVariantData, 'payment_type') !== 'onetime') {
1307 1391 $attributes['class'] .= ' is-hidden';
1308 1392 }
1309 1393
1310 - do_action('fluent_cart/product/single/before_quantity_block', [
1394 + do_action('fluent_cart/product/single/before_quantity_block', RenderContext::decorate([
1311 1395 'product' => $this->product,
1312 1396 'scope' => 'product_quantity_block'
1313 - ]);
1397 + ]));
1314 1398 ?>
1315 1399 <div <?php $this->renderAttributes($attributes); ?>>
1316 1400 <label for="fct-product-qty-input" class="quantity-title">
1317 1401 <?php esc_html_e('Quantity', 'fluent-cart'); ?>
@@ -1355,23 +1439,39 @@
1355 1439 </button>
1356 1440 </div>
1357 1441 </div>
1358 1442 <?php
1359 - do_action('fluent_cart/product/single/after_quantity_block', [
1443 + do_action('fluent_cart/product/single/after_quantity_block', RenderContext::decorate([
1360 1444 'product' => $this->product,
1361 1445 'scope' => 'product_quantity_block'
1362 - ]);
1446 + ]));
1363 1447 }
1364 1448
1365 1449 public function renderPurchaseButtons($atts = [])
1366 1450 {
1451 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1452 +
1453 + if (!RenderGate::shouldRender('actions', $gateContext)) {
1454 + return;
1455 + }
1456 +
1457 + do_action('fluent_cart/product/single/before_actions_block', $gateContext);
1458 +
1367 1459 $buyNowButtonAtts = $atts;
1368 1460 $this->renderBuyNowButton($buyNowButtonAtts);
1369 1461 $this->renderAddToCartButton($atts);
1462 +
1463 + do_action('fluent_cart/product/single/after_actions_block', $gateContext);
1370 1464 }
1371 1465
1372 1466 public function renderBuyNowButton($atts = [])
1373 1467 {
1468 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1469 +
1470 + if (!RenderGate::shouldRenderPurchaseButton('buy_now_button', $gateContext)) {
1471 + return;
1472 + }
1473 +
1374 1474 // Stock management check using isStock() method
1375 1475 // if (ModuleSettings::isActive('stock_management')) {
1376 1476 // if ($this->product->detail->variation_type === 'simple' && $this->defaultVariant) {
1377 1477 // if (!$this->defaultVariant->isStock()) {
@@ -1453,8 +1553,16 @@
1453 1553 }
1454 1554
1455 1555 public function renderBuyNowButtonBlock($atts = [])
1456 1556 {
1557 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1558 +
1559 + // Same gate as the in-section button: a page assembled from standalone
1560 + // button blocks must honour catalog mode too, or the gate leaks.
1561 + if (!RenderGate::shouldRenderPurchaseButton('buy_now_button', $gateContext)) {
1562 + return;
1563 + }
1564 +
1457 1565 $text = Arr::get($atts, 'text', __('Buy Now', 'fluent-cart'));
1458 1566 $variantIds = Arr::get($atts, 'variant_ids', []);
1459 1567 $variantId = Arr::get($variantIds, 0);
1460 1568 $customClass = trim(Arr::get($atts, 'class', ''));
@@ -1539,8 +1647,14 @@
1539 1647 }
1540 1648
1541 1649 public function renderAddToCartButton($atts = [])
1542 1650 {
1651 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1652 +
1653 + if (!RenderGate::shouldRenderPurchaseButton('add_to_cart_button', $gateContext)) {
1654 + return;
1655 + }
1656 +
1543 1657 $defaults = [
1544 1658 'buy_now_text' => __('Buy Now', 'fluent-cart'),
1545 1659 'add_to_cart_text' => __('Add To Cart', 'fluent-cart'),
1546 1660 ];
@@ -1635,8 +1749,15 @@
1635 1749 }
1636 1750
1637 1751 public function renderAddToCartButtonBlock($atts = [])
1638 1752 {
1753 + $gateContext = RenderGate::context($this->product, RenderGate::SCOPE_SINGLE, $this->defaultVariant);
1754 +
1755 + // Same gate as the in-section button — see renderBuyNowButtonBlock().
1756 + if (!RenderGate::shouldRenderPurchaseButton('add_to_cart_button', $gateContext)) {
1757 + return;
1758 + }
1759 +
1639 1760 $text = Arr::get($atts, 'text', __('Add To Cart', 'fluent-cart'));
1640 1761 $customClass = trim(Arr::get($atts, 'class', ''));
1641 1762 $extraClass = trim(Arr::get($atts, 'extra_class', ''));
1642 1763
@@ -1828,9 +1949,9 @@
1828 1949 }
1829 1950 ?>
1830 1951 <?php if ($this->viewType === 'both' || $this->viewType === 'text'): ?>
1831 1952 <div class="fct-product-variant-text">
1832 - <div class="fct-product-variant-title" aria-label="<?php echo esc_attr(__('Variant title', 'fluent-cart')); ?>"><?php echo esc_html($variant->variation_title); ?></div>
1953 + <div class="fct-product-variant-title"><?php echo esc_html($variant->variation_title); ?></div>
1833 1954 <?php if (!$this->shouldRenderPriceInPriceSection() && $paymentType === 'subscription'): ?>
1834 1955 <?php $this->renderSubscriptionInfo($variant); ?>
1835 1956 <?php endif; ?>
1836 1957 </div>
@@ -1840,14 +1961,15 @@
1840 1961 <?php if (!$this->shouldRenderPriceInPriceSection()): ?>
1841 1962 <div class="fct-product-variant-price">
1842 1963 <?php if ($comparePrice): ?>
1843 1964 <div class="fct-product-variant-compare-price">
1844 - <del aria-label="<?php echo esc_attr(__('Original price', 'fluent-cart')); ?>">
1965 + <span class="fct-sr-only"><?php echo esc_html__('Original price:', 'fluent-cart'); ?></span>
1966 + <del>
1845 1967 <span><?php echo esc_html(Helper::toDecimal($comparePrice)); ?></span></del>
1846 1968 </div>
1847 1969 <?php endif; ?>
1848 - <div class="fct-product-variant-item-price"
1849 - aria-label="<?php echo esc_attr(__('Current price', 'fluent-cart')); ?>">
1970 + <div class="fct-product-variant-item-price">
1971 + <span class="fct-sr-only"><?php echo $comparePrice ? esc_html__('Sale price:', 'fluent-cart') : esc_html__('Price:', 'fluent-cart'); ?></span>
1850 1972 <span><?php echo esc_html(Helper::toDecimal($itemPrice)); ?></span>
1851 1973 </div>
1852 1974 </div>
1853 1975 <?php endif; ?>
@@ -1971,21 +2093,21 @@
1971 2093 //Convert to collection safely before sorting
1972 2094 $variants = (new Collection($variants))->sortBy('serial_index')->values();
1973 2095
1974 2096 foreach ($variants as $variant) {
1975 - do_action('fluent_cart/product/single/before_variant_item', [
2097 + do_action('fluent_cart/product/single/before_variant_item', RenderContext::decorate([
1976 2098 'product' => $this->product,
1977 2099 'variant' => $variant,
1978 2100 'scope' => 'product_variant_item'
1979 - ]);
2101 + ]));
1980 2102
1981 2103 $this->renderVariationItem($variant, $this->defaultVariationId);
1982 2104
1983 - do_action('fluent_cart/product/single/after_variant_item', [
2105 + do_action('fluent_cart/product/single/after_variant_item', RenderContext::decorate([
1984 2106 'product' => $this->product,
1985 2107 'variant' => $variant,
1986 2108 'scope' => 'product_variant_item'
1987 - ]);
2109 + ]));
1988 2110 }
1989 2111 ?>
1990 2112 </div>
1991 2113