product = $product;
$this->variants = $product->variants;
$this->storeSettings = new StoreSettings();
$this->viewType = $this->storeSettings->get('variation_view', 'both');
$this->columnType = $this->storeSettings->get('variation_columns', 'masonry');
$defaultVariationId = $config['default_variation_id'] ?? '';
// 'image', 'text','both'
$this->viewType = apply_filters('fluent_cart/single_product/variation_view_type', $this->viewType, [
'product' => $product,
'variants' => $this->variants,
'defaultVariationId' => $defaultVariationId,
]);
// 'one', 'two','three', 'four', 'masonry'
$this->columnType = apply_filters('fluent_cart/single_product/variation_column_type', $this->columnType, [
'product' => $product,
'variants' => $this->variants,
'defaultVariationId' => $defaultVariationId,
]);
$hasExplicitDefault = true;
if (!$defaultVariationId) {
$variationIds = $product->variants->pluck('id')->toArray();
$defaultVariationId = $product->detail->default_variation_id;
if (!$defaultVariationId || !in_array($defaultVariationId, $variationIds)) {
$defaultVariationId = Arr::get($variationIds, '0');
$hasExplicitDefault = false;
}
}
// Always set resolved default variation id
$this->defaultVariationId = $defaultVariationId;
// Gallery defaults to featured image (key 0) when no explicit default variation is set
$this->defaultGalleryImageId = $hasExplicitDefault ? $defaultVariationId : 0;
$this->product->variants->load('bundleChildren.product');
foreach ($this->product->variants as $variant) {
if ($variant->id == $this->defaultVariationId) {
$this->defaultVariant = $variant;
}
$paymentType = Arr::get($variant->other_info, 'payment_type');
if ($paymentType === 'onetime') {
$this->hasOnetime = true;
} else if ($paymentType === 'subscription') {
$this->hasSubscription = true;
}
}
$this->buildProductGroups();
}
public function buildProductGroups()
{
$groupKey = 'repeat_interval';
$otherInfo = (array)Arr::get($this->product->detail, 'other_info');
$groupBy = Arr::get($otherInfo, 'group_pricing_by', 'repeat_interval'); //repeat_interval,payment_type,none
if ($groupBy !== 'none') {
if ($groupBy === 'payment_type') {
$groupKey = 'payment_type';
}
$paymentTypes = [];
if ($groupBy === 'repeat_interval') {
foreach ($this->variants as $key => $variant) {
$paymentType = 'onetime';
$type = Arr::get($variant, 'payment_type');
if ($type === 'subscription') {
$isInstallment = Arr::get($variant, 'other_info.installment', 'no');
if ($isInstallment === 'yes' && App::isProActive()) {
$paymentType = 'installment';
} else {
$paymentType = Arr::get($variant, 'other_info.repeat_interval', 'onetime');;
}
}
$paymentTypes[] = $paymentType;
if (!isset($this->variantsByPaymentTypes[$paymentType])) {
$this->variantsByPaymentTypes[$paymentType] = [];
}
$this->variantsByPaymentTypes[$paymentType][] = $variant;
if ($this->defaultVariationId == $variant['id']) {
$this->activeTab = $paymentType;
}
}
} else {
foreach ($this->variants as $key => $variant) {
$paymentType = 'onetime';
$type = Arr::get($variant, 'payment_type');
if ($type === 'subscription') {
$isInstallment = Arr::get($variant, 'other_info.installment');
if ($isInstallment === 'yes' && App::isProActive()) {
$paymentType = 'installment';
} else {
$paymentType = 'subscription';
}
}
$paymentTypes[] = $paymentType;
if (!isset($this->variantsByPaymentTypes[$paymentType])) {
$this->variantsByPaymentTypes[$paymentType] = [];
}
$this->variantsByPaymentTypes[$paymentType][] = $variant;
if ($this->defaultVariationId == $variant['id']) {
$this->activeTab = $paymentType;
}
}
}
$paymentTypes = array_unique($paymentTypes);
$intervalOptions = Helper::getAvailableSubscriptionIntervalOptions();
$groupLanguageMap = [
'onetime' => __('One Time', 'fluent-cart'),
'subscription' => __('Subscription', 'fluent-cart'),
'installment' => __('Installment', 'fluent-cart'),
];
foreach ($intervalOptions as $interval) {
$groupLanguageMap[$interval['value']] = $interval['label'];
}
foreach ($paymentTypes as $paymentType) {
$this->paymentTypes[$paymentType ?: 'onetime'] = Arr::get($groupLanguageMap, $paymentType ?: 'onetime');
}
}
}
public function render()
{
?>
renderGallery(); ?>
renderTitle();
$this->renderProductMeta();
$this->renderExcerpt();
$this->renderPrices();
if ($this->product->detail->variation_type === 'simple' && !$this->hasSubscription) {
foreach ($this->product->variants as $variant) {
$this->renderVariationsBundleProduct($variant);
}
}
$this->renderBuySection();
?>
renderStockAvailability(); ?>
renderSku(); ?>
product->detail, 'other_info');
$groupBy = Arr::get($otherInfo, 'group_pricing_by', 'repeat_interval'); //repeat_interval,payment_type,none
$this->renderBuySectionWrapperStart();
$this->renderVariationDisplay($atts);
$this->renderItemPrice();
$this->renderQuantity();
?>
renderPurchaseButtons(Arr::get($atts, 'button_atts', [])); ?>
renderBuySectionWrapperEnd();
}
public function renderVariationDisplay($atts = [])
{
$otherInfo = (array)Arr::get($this->product->detail, 'other_info');
$groupBy = Arr::get($otherInfo, 'group_pricing_by', 'repeat_interval'); //repeat_interval,payment_type,none
if (count($this->paymentTypes) === 1 || $groupBy === 'none') {
$this->renderVariants(Arr::get($atts, 'variation_atts', []));
} else {
$this->renderTab(Arr::get($atts, 'variation_atts', []));
}
}
public function renderGalleryThumb()
{
$thumbnails = [];
$featuredMedia = $this->product->thumbnail ?? Vite::getAssetUrl('images/placeholder.svg');
if (!$featuredMedia) {
$featuredMedia = [];
}
$galleryImage = get_post_meta($this->product->ID, 'fluent-products-gallery-image', true);
if (!empty($galleryImage)) {
$thumbnails[0] = [
'media' => $galleryImage,
];
}
foreach ($this->variants as $variant) {
if (!empty($variant['media']['meta_value'])) {
$thumbnails[$variant['id']] = [
'media' => $variant['media']['meta_value'],
];
} else {
$this->defaultImageUrl = $featuredMedia;
$this->defaultImageAlt = Arr::get($variant, 'variation_title', '');
}
}
$images = empty($thumbnails) ? [] : $thumbnails;
$this->images = $images;
if (!empty($images)) {
$imageId = $this->defaultGalleryImageId;
if (isset($images[$imageId])) {
$imageMetaValue = $images[$imageId];
$this->defaultImageUrl = Arr::get($imageMetaValue, 'media.0.url', '');
$this->defaultImageAlt = Arr::get($imageMetaValue, 'media.0.title', '');
}
}
?>
images, 'media.*.url');
if(count($totalThumbImages) == 1 && is_countable($totalThumbImages[0]) && count($totalThumbImages[0]) == 1){
return '';
}
// Collect ALL gallery images as JSON for lightbox (even when max thumbnails limits visible thumbs)
$allGalleryImages = [];
foreach ($this->images as $imageId => $image) {
if (empty($image['media']) || !is_array($image['media'])) {
continue;
}
foreach ($image['media'] as $item) {
$url = Arr::get($item, 'url', '');
if (empty($url)) {
continue;
}
$allGalleryImages[] = [
'url' => $url,
'title' => Arr::get($item, 'title', ''),
'variation_id' => (string) $imageId,
];
}
}
?>
renderGalleryThumbControl($maxThumbnails); ?>
images as $imageId => $image) {
if (empty($image['media']) || !is_array($image['media'])) {
continue;
}
foreach ($image['media'] as $item) {
if (!empty(Arr::get($item, 'url', ''))) {
$totalImages++;
}
}
}
// Then render up to max
foreach ($this->images as $imageId => $image) {
if (empty($image['media']) || !is_array($image['media'])) {
continue;
}
foreach ($image['media'] as $item) {
if (empty(Arr::get($item, 'url', ''))) {
continue;
}
if ($maxThumbnails !== null && $count >= (int) $maxThumbnails) {
$this->renderGallerySeeMoreButton($totalImages - (int) $maxThumbnails);
return;
}
$this->renderGalleryThumbControlButton($item, $imageId);
$count++;
}
}
}
public function renderGallerySeeMoreButton($remainingCount)
{
?>
defaultVariationId ? 'is-hidden' : '';
$itemUrl = Arr::get($item, 'url', '');
$itemTitle = Arr::get($item, 'title', '');
$isSelected = !$this->galleryActiveSet && $imageId == $this->defaultGalleryImageId;
if ($isSelected) {
$this->galleryActiveSet = true;
}
?>
'all', // horizontal, vertical
'thumb_position' => 'bottom', // bottom, left, right, top
'scrollable_thumbs' => 'no', // yes / no
'max_thumbnails' => null, // null = no limit, integer = max visible
];
$atts = wp_parse_args($args, $defaults);
$thumbnailMode = $atts['thumbnail_mode'];
$wrapperAtts = [
'class' => 'fct-product-gallery-wrapper ' . 'thumb-pos-' . $atts['thumb_position'] . ' thumb-mode-' . $thumbnailMode,
'data-fct-product-gallery' => '',
'data-fluent-cart-product-gallery-wrapper' => '',
'data-thumbnail-mode' => $thumbnailMode,
'data-product-id' => $this->product->ID,
'data-scrollable-thumbs' => $atts['scrollable_thumbs'],
];
?>
>
renderGalleryThumb();
$this->renderGalleryThumbControls($atts['max_thumbnails']);
?>
product->post_title); ?>
product->detail->getStockAvailability();
if (!Arr::get($stockAvailability, 'manage_stock')) {
return '';
}
$isStock = $this->product->isStock();
// Check default variant stock for both simple and variable products
if ($this->defaultVariant) {
$isStock = $isStock && $this->defaultVariant->isStock();
}
$stockLabel = $isStock ? $stockAvailability['availability'] : __('Out of Stock', 'fluent-cart');
$statusClass = $isStock ? ($stockAvailability['class'] ?? '') : 'out-of-stock';
echo sprintf(
'',
esc_attr($statusClass),
$wrapper_attributes, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html__('Availability:', 'fluent-cart'),
esc_html($stockLabel)
);
}
public function renderSku($wrapper_attributes = '', $showLabel = true, $label = '', $variant = null)
{
if (!$variant) {
$variant = $this->defaultVariant ?: $this->product->variants->first();
}
if (!$variant || empty($variant->sku)) {
return;
}
if (!$label) {
$label = __('SKU:', 'fluent-cart');
}
$labelHtml = '';
if ($showLabel && $label) {
$labelHtml = sprintf('%s ', esc_html($label));
}
echo sprintf(
'',
$wrapper_attributes, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$labelHtml,
esc_html($variant->sku)
);
}
public function renderExcerpt()
{
$excerpt = $this->product->post_excerpt;
if (!$excerpt) {
return;
}
?>
product->ID);
if (!$post || empty($post->post_content)) {
return;
}
?>
post_content); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
product->detail->variation_type === 'simple') {
// we have to render for the simple product
$first_price = $this->product->variants()->first();
$itemPrice = $first_price ? $first_price->item_price : 0;
$itemPrice = apply_filters('fluent_cart/product/display_price', $itemPrice, [
'product' => $this->product,
'variation' => $first_price,
]);
$itemPrice = (int)$itemPrice;
$comparePrice = $first_price ? (int)$first_price->compare_price : 0;
if ($comparePrice <= $itemPrice) {
$comparePrice = 0;
}
do_action('fluent_cart/product/single/before_price_block', [
'product' => $this->product,
'current_price' => $itemPrice,
'scope' => 'price_range'
]);
?>
$this->product,
'current_price' => $itemPrice,
'scope' => 'price_range'
]); ?>
$this->product,
'current_price' => $itemPrice,
'scope' => 'price_range'
]);
return;
}
$min_price = $this->product->detail->min_price;
$max_price = $this->product->detail->max_price;
do_action('fluent_cart/product/single/before_price_range_block', [
'product' => $this->product,
'current_price' => $min_price,
'scope' => 'price_range'
]);
?>
$min_price): ?>
-
$this->product,
'current_price' => $min_price,
'scope' => 'price_range'
]); ?>
$this->product,
'current_price' => $min_price,
'scope' => 'price_range'
]);
}
public function renderVariants($atts = [])
{
if ($this->product->detail->variation_type === 'simple') {
return;
}
$variants = $this->product->variants;
if (!$variants || $variants->isEmpty()) {
return;
}
// Sort by serial_index ascending
$variants = $variants->sortBy('serial_index')->values();
$classes = array_filter([
'fct-product-variants',
'column-type-' . $this->columnType,
Arr::get($atts, 'wrapper_class', ''),
]);
?>
$this->product,
'variant' => $variant,
'scope' => 'product_variant_item'
]);
$this->renderVariationItem($variant, $this->defaultVariationId);
do_action('fluent_cart/product/single/after_variant_item', [
'product' => $this->product,
'variant' => $variant,
'scope' => 'product_variant_item'
]);
} ?>
product->detail->variation_type === 'simple' && !$this->hasSubscription) {
return; // for simple product we already rendered the price
}
do_action('fluent_cart/product/single/before_price_block', [
'product' => $this->product,
'current_price' => $this->defaultVariant ? $this->defaultVariant->item_price : 0,
'scope' => 'product_variant_price'
]);
foreach ($this->product->variants as $variant) {
if ($this->shouldRenderPriceInPriceSection()) {
$this->renderVariantPricingWrapperStart($variant);
$paymentType = Arr::get($variant->other_info, 'payment_type', 'onetime');
if (!$this->hasSubscription) {
$this->renderVariationComparePrice($variant);
$this->applyVariationPriceFilter($variant, $paymentType);
} else {
$atts = [
'class' => 'fct-product-payment-type fluent-cart-product-variation-content' . ($this->defaultVariant->id != $variant->id ? ' is-hidden' : ''),
'data-fluent-cart-product-payment-type' => '',
'data-variation-id' => $variant->id
];
$this->renderComparePriceWrapperStart($atts);
$this->renderVariationComparePrice($variant);
if ($paymentType === 'onetime') {
echo esc_html(Helper::toDecimal($variant->item_price));
} else {
$this->applyVariationPriceFilter($variant, $paymentType);
}
$this->renderComparePriceWrapperEnd();
}
$this->renderVariantPricingWrapperEnd();
}
}
foreach ($this->product->variants as $variant) {
$this->renderVariationsBundleProduct($variant);
}
do_action('fluent_cart/product/single/after_price_block', [
'product' => $this->product,
'current_price' => $this->defaultVariant ? $this->defaultVariant->item_price : 0,
'scope' => 'product_variant_price'
]);
}
public function shouldRenderPriceInPriceSection(): bool
{
return !($this->viewType === 'text' && $this->columnType === 'one');
}
public function applyVariationPriceFilter($variant, $paymentType = 'onetime')
{
$priceText = $paymentType === 'onetime' ? Helper::toDecimal($variant->item_price) : $variant->getSubscriptionTermsText(true);
echo wp_kses_post(apply_filters('fluent_cart/single_product/variation_price', esc_html($priceText), [
'product' => $this->product,
'variant' => $variant,
'scope' => 'product_variant_price'
]));
do_action('fluent_cart/product/after_price', [
'product' => $this->product,
'current_price' => $variant->item_price,
'scope' => 'product_variant_price'
]);
}
public function renderComparePriceWrapperStart($atts = [])
{
?>
renderAttributes($atts); ?> >
compare_price) {
return;
} ?>
compare_price)); ?>
bundleChildren) == 0) {
return;
}
if(is_object($variant->bundleChildren)) {
$bundleProducts = $variant->bundleChildren->toArray();
}else{
$bundleProducts = $variant->bundleChildren;
}
$total = count($bundleProducts);
?>
product->soldIndividually();
if (!$this->hasOnetime || $soldIndividually) {
return;
}
$attributes = [
'data-fluent-cart-product-quantity-container' => '',
'data-cart-id' => $this->defaultVariant ? $this->defaultVariant->id : '',
'data-variation-type' => $this->product->detail->variation_type,
'data-payment-type' => 'onetime',
'class' => 'fct-product-quantity-container'
];
$defaultVariantData = $this->getDefaultVariantData();
if ($this->hasSubscription && Arr::get($defaultVariantData, 'payment_type') !== 'onetime') {
$attributes['class'] .= ' is-hidden';
}
do_action('fluent_cart/product/single/before_quantity_block', [
'product' => $this->product,
'scope' => 'product_quantity_block'
]);
?>
renderAttributes($attributes); ?>>
$this->product,
'scope' => 'product_quantity_block'
]);
}
public function renderPurchaseButtons($atts = [])
{
$buyNowButtonAtts = $atts;
$this->renderBuyNowButton($buyNowButtonAtts);
$this->renderAddToCartButton($atts);
}
public function renderBuyNowButton($atts = [])
{
// Stock management check using isStock() method
// if (ModuleSettings::isActive('stock_management')) {
// if ($this->product->detail->variation_type === 'simple' && $this->defaultVariant) {
// if (!$this->defaultVariant->isStock()) {
// echo '' . esc_html__('Out of stock', 'fluent-cart') . '';
// return;
// }
// }
// }
$defaults = [
'buy_now_text' => __('Buy Now', 'fluent-cart'),
'add_to_cart_text' => __('Add To Cart', 'fluent-cart'),
];
$atts = wp_parse_args($atts, $defaults);
$enableModalCheckout = Helper::isModalCheckoutEnabled();
$isInStock = true;
if (ModuleSettings::isActive('stock_management')) {
$isInStock = $this->product->isStock() && ($this->defaultVariant && $this->defaultVariant->isStock());
}
$stockStatus = $isInStock ? 'in-stock' : 'out-of-stock';
$variationClass = 'fluent-cart-direct-checkout-button';
if (!$isInStock) {
$variationClass .= ' is-hidden';
}
$buyNowAttributes = [
'data-fluent-cart-direct-checkout-button' => '',
'data-variation-type' => $this->product->detail->variation_type,
'class' => $variationClass,
'data-stock-availability' => $stockStatus,
'data-quantity' => '1',
'data-cart-id' => $this->defaultVariant ? $this->defaultVariant->id : '',
'data-url' => site_url('?fluent-cart=instant_checkout&item_id='),
];
if ($isInStock) {
$buyNowAttributes['href'] = site_url('?fluent-cart=instant_checkout&item_id=') . ($this->defaultVariant ? $this->defaultVariant->id : '') . '&quantity=1';
}
if ($enableModalCheckout) {
$buyNowAttributes['data-fct-instant-checkout-button'] = '';
$buyNowAttributes['data-enable-modal-checkout'] = 'yes';
}
$buyButtonText = apply_filters('fluent_cart/product/buy_now_button_text', $atts['buy_now_text'], [
'product' => $this->product
]);
?>
defaultVariant ? $this->defaultVariant->variation_title : '';
$buyNowAriaLabel = $variantTitle
? sprintf(
/* translators: 1: Button text (e.g. "Buy Now"), 2: Variant name */
__('%1$s - %2$s', 'fluent-cart'),
$buyButtonText,
$variantTitle
)
: $buyButtonText;
?>
renderAttributes($buyNowAttributes); ?> aria-label="">
$text,
'class' => '',
'target' => '',
'rel' => '',
'is_shortcode' => false,
];
$atts = wp_parse_args($atts, $defaults);
$enableModalCheckout = Arr::get($atts, 'enable_modal_checkout', false);
$isInStock = true;
if (ModuleSettings::isActive('stock_management')) {
$isInStock = $this->product->isStock() && ($this->defaultVariant && $this->defaultVariant->isStock());
}
$stockStatus = $isInStock ? 'in-stock' : 'out-of-stock';
$checkoutUrl = add_query_arg([
'fluent-cart' => $enableModalCheckout ? 'modal_checkout' : 'instant_checkout',
'item_id' => $variantId ?? '',
'quantity' => 1
], site_url());
$buyNowClass = trim('wp-block-button__link wp-element-button ' . Arr::get($atts, 'class', ''));
if ($stockStatus === 'out-of-stock') {
$buyNowClass .= ' out-of-stock';
}
$buyNowAttributes = [
'data-fluent-cart-direct-checkout-button' => '',
'data-variation-type' => $this->product->detail->variation_type,
'class' => $buyNowClass,
'data-stock-availability' => $stockStatus,
'data-quantity' => '1',
'data-cart-id' => $variantId ?? '',
'data-url' => $checkoutUrl,
];
if ($stockStatus === 'out-of-stock') {
$buyNowAttributes['aria-disabled'] = 'true';
} else {
$buyNowAttributes['href'] = $checkoutUrl;
}
$target = Arr::get($atts, 'target');
if ($target) {
$buyNowAttributes['target'] = $target;
if (strtolower($target) === '_blank') {
$buyNowAttributes['rel'] = Arr::get($atts, 'rel', 'noopener noreferrer');
}
}
if ($enableModalCheckout) {
$buyNowAttributes['data-fct-instant-checkout-button'] = '';
$buyNowAttributes['data-enable-modal-checkout'] = 'yes';
}
$wrapperAttributes = '';
$isShortcode = !empty($atts['is_shortcode']);
if ($isShortcode) {
foreach ($buyNowAttributes as $attr => $value) {
if ($value === '') {
$wrapperAttributes .= esc_attr($attr) . ' ';
} else {
$wrapperAttributes .= sprintf('%s="%s" ', esc_attr($attr), esc_attr((string)$value));
}
}
$wrapperAttributes = trim($wrapperAttributes);
} else {
$wrapperAttributes = get_block_wrapper_attributes($buyNowAttributes);
}
$buyButtonText = apply_filters('fluent_cart/product/buy_now_button_text', $atts['buy_now_text'], [
'product' => $this->product
]);
?>
aria-label="">
__('Buy Now', 'fluent-cart'),
'add_to_cart_text' => __('Add To Cart', 'fluent-cart'),
];
$atts = wp_parse_args($atts, $defaults);
$cartAttributes = [
'data-fluent-cart-add-to-cart-button' => '',
'data-cart-id' => $this->defaultVariant ? $this->defaultVariant->id : '',
'data-product-id' => $this->product->ID,
'class' => 'fluent-cart-add-to-cart-button',
'data-variation-type' => $this->product->detail->variation_type,
];
$defaultVariantData = $this->getDefaultVariantData();
// If product is subscription-only, hide add-to-cart
if ($this->hasSubscription && Arr::get($defaultVariantData, 'payment_type') !== 'onetime') {
$cartAttributes['class'] .= ' is-hidden';
}
// Check stock availability using both product-level and variant-level
$isOutOfStock = false;
if (ModuleSettings::isActive('stock_management')) {
if (!$this->product->isStock() || ($this->defaultVariant && !$this->defaultVariant->isStock())) {
$isOutOfStock = true;
$cartAttributes['disabled'] = 'disabled';
$cartAttributes['class'] .= ' out-of-stock';
$cartAttributes['aria-disabled'] = 'true';
$atts['add_to_cart_text'] = __('Not Available', 'fluent-cart');
}
}
$addToCartText = apply_filters('fluent_cart/product/add_to_cart_text', $atts['add_to_cart_text'], [
'product' => $this->product
]);
// Render add to cart if product supports onetime, or if out of stock (to show "Not Available")
if ($this->hasOnetime || $isOutOfStock) :
?>
defaultVariant ? $this->defaultVariant->variation_title : '';
$addToCartAriaLabel = $variantTitle
? sprintf(
/* translators: 1: Button text (e.g. "Add To Cart"), 2: Variant name */
__('%1$s - %2$s', 'fluent-cart'),
$addToCartText,
$variantTitle
)
: $addToCartText;
?>
$text,
];
$atts = wp_parse_args($atts, $defaults);
$cartAttributes = [
'data-fluent-cart-add-to-cart-button' => '',
'class' => 'wp-block-button__link wp-element-button fct-loader',
'data-cart-id' => $this->defaultVariant ? $this->defaultVariant->id : '',
'data-product-id' => $this->product->ID,
'data-variation-type' => $this->product->detail->variation_type,
];
if ($extraClass) {
$cartAttributes['class'] .= ' ' . $extraClass;
}
// If the product does NOT support one-time purchase
if (!$this->hasOnetime) {
if (Helper::isAdminUser()) {
$view = '' . esc_html__('Add to Cart is not supported for subscription product', 'fluent-cart') . '
';
FrontendView::make('', $view);
return;
}
return;
}
// Check stock availability using both product-level and variant-level
if (ModuleSettings::isActive('stock_management')) {
if (!$this->product->isStock() || ($this->defaultVariant && !$this->defaultVariant->isStock())) {
$cartAttributes['disabled'] = 'disabled';
$cartAttributes['class'] .= ' out-of-stock';
$cartAttributes['aria-disabled'] = 'true';
$atts['add_to_cart_text'] = __('Not Available', 'fluent-cart');
}
}
$addToCartText = apply_filters('fluent_cart/product/add_to_cart_text', $atts['add_to_cart_text'], [
'product' => $this->product
]);
$wrapperAttributes = '';
$isShortcode = !empty($atts['is_shortcode']);
if ($isShortcode) {
foreach ($cartAttributes as $attr => $value) {
if ($value === '') {
$wrapperAttributes .= esc_attr($attr) . ' ';
} else {
$wrapperAttributes .= sprintf('%s="%s" ', esc_attr($attr), esc_attr((string)$value));
}
}
$wrapperAttributes = trim($wrapperAttributes);
} else {
$wrapperAttributes = get_block_wrapper_attributes($cartAttributes);
}
?>
available;
if (!$variant->manage_stock) {
$availableStocks = 'unlimited';
}
$comparePrice = $variant->compare_price;
if ($comparePrice <= $variant->item_price) {
$comparePrice = '';
}
if ($comparePrice) {
$comparePrice = Helper::toDecimal($comparePrice);
}
$paymentType = Arr::get($variant->other_info, 'payment_type');
$itemClasses = [
'fct-product-variant-item',
'fct_price_type_' . $paymentType,
'fct_variation_view_type_' . $this->viewType,
];
if ($variant->media_id) {
$itemClasses[] = 'fct-item-has-image';
}
if ($variant->id == $defaultId) {
$itemClasses[] = 'selected';
}
$priceSuffix = apply_filters('fluent_cart/product/price_suffix_atts', '', [
'product' => $this->product,
'variant' => $variant,
'scope' => 'variant_item'
]);
$renderingAttributes = [
'data-fluent-cart-product-variant' => '',
'data-cart-id' => $variant->id,
'data-item-stock' => $variant->isStock() ? 'in-stock' : 'out-of-stock',
'data-default-variation-id' => $defaultId,
'data-payment-type' => $paymentType,
'data-available-stock' => $availableStocks,
'data-item-price' => Helper::toDecimal($variant->item_price),
'data-compare-price' => $comparePrice,
'data-price-suffix' => $priceSuffix,
'data-stock-management' => ModuleSettings::isActive('stock_management') ? 'yes' : 'no',
'data-sku' => $variant->sku ?? '',
];
if ($paymentType === 'subscription') {
$renderingAttributes['data-subscription-terms'] = $variant->getSubscriptionTermsText(true);
$repeatInterval = Arr::get($variant->other_info, 'repeat_interval', '');
$hasInstallment = Arr::get($variant->other_info, 'has_installment') === 'yes';
$itemClasses[] = 'fct_sub_interval_' . $repeatInterval;
if ($hasInstallment) {
$itemClasses[] = 'fct_sub_has_installment';
}
}
if ($extraClasses) {
$itemClasses = array_merge($itemClasses, $extraClasses);
}
$itemClasses = array_filter($itemClasses);
$renderingAttributes['class'] = implode(' ', $itemClasses);
$itemPrice = $variant->item_price;
$comparePrice = $variant->compare_price;
if (!$comparePrice || $comparePrice <= $itemPrice) {
$comparePrice = 0;
}
?>
renderAttributes($renderingAttributes); ?>
role="radio"
tabindex="id == $defaultId ? '0' : '-1'; ?>"
aria-checked="id == $defaultId ? 'true' : 'false'; ?>"
aria-label="variation_title); ?>"
>
viewType === 'image'): ?>
renderTooltip($variant); ?>
viewType === 'both' || $this->viewType === 'image') {
$this->renderVariantImage($variant);
}
?>
viewType === 'both' || $this->viewType === 'text') {
echo '
' . esc_html($variant->variation_title) . '
';
}
?>
viewType === 'text' && $paymentType === 'subscription' && $this->columnType === 'one'): ?>
renderSubscriptionInfo($variant); ?>
viewType === 'text' && $this->columnType === 'one'): ?>
variation_title); ?>
thumbnail;
if (!$image) {
$image = Vite::getAssetUrl('images/placeholder.svg');
}
?>
getSubscriptionTermsText(true);
if (!$info) {
return '';
}
?>
$value) {
if ($value !== '') {
echo esc_attr($attr) . '="' . esc_attr((string)$value) . '" ';
} else {
echo esc_attr($attr) . ' ';
}
}
}
protected function renderTab($atts = [])
{
?>
renderTabNav(); ?>
renderTabPane($atts); ?>
paymentTypes as $typeKey => $typeLabel) : ?>
columnType,
Arr::get($atts, 'wrapper_class', ''),
];
foreach ($this->variantsByPaymentTypes as $variantKey => $variants): ?>
sortBy('serial_index')->values();
foreach ($variants as $variant) {
do_action('fluent_cart/product/single/before_variant_item', [
'product' => $this->product,
'variant' => $variant,
'scope' => 'product_variant_item'
]);
$this->renderVariationItem($variant, $this->defaultVariationId);
do_action('fluent_cart/product/single/after_variant_item', [
'product' => $this->product,
'variant' => $variant,
'scope' => 'product_variant_item'
]);
}
?>
variants) || !$this->defaultVariationId) {
return null;
}
foreach ($this->variants as $variant) {
if ($variant['id'] == $this->defaultVariationId) {
return $variant;
}
}
return null;
}
}