product = $product;
$this->viewUrl = $product->view_url;
$this->config = $config;
}
public function renderWrapperStart()
{
}
public function renderWrapperEnd()
{
}
public function render()
{
AssetLoader::loadSingleProductAssets();
$cursor = '';
if (!empty($this->config['cursor'])) {
$cursor = 'data-fluent-cart-cursor="' . esc_attr($this->config['cursor']) . '"';
}
$cardWidth = '';
$cardWidthValue = Arr::get($this->config, 'card_width', '');
if ($cardWidthValue) {
$widthValue = $cardWidthValue === '100%' ? '100%' : intval($cardWidthValue) . 'px';
$cardWidth = 'style="width: ' . esc_attr($widthValue) . ';"';
}
?>
aria-label="product->post_title));
?>">
renderProductImage(); ?>
renderTitle(); ?>
renderExcerpt(); ?>
renderPrices(); ?>
showBuyButton(); ?>
product->variants->first();
if (!$variant) {
return;
}
if (!$wrapper_attributes) {
$wrapper_attributes = 'class="fct-package-description" data-fluent-cart-package-description';
}
$this->renderPackageDescriptionForVariant($variant, $wrapper_attributes, $showName, $showDimensions, $showProductWeight, $showTotalWeight);
}
private function renderPackageDescriptionForVariant(
ProductVariation $variant,
$wrapper_attributes,
$showName,
$showDimensions,
$showProductWeight,
$showTotalWeight
) {
$packageSlug = Arr::get($variant->other_info, 'package_slug', '');
$package = Helper::getPackageBySlug($packageSlug);
if (!$package) {
return;
}
$name = Arr::get($package, 'name', '');
$length = Arr::get($package, 'length', '');
$width = Arr::get($package, 'width', '');
$height = Arr::get($package, 'height', '');
$dimensionUnit = Arr::get($package, 'dimension_unit', 'cm');
$packageWeight = floatval(Arr::get($package, 'weight', 0));
$packageWeightUnit = Arr::get($package, 'weight_unit', 'kg');
$storeWeightUnit = Helper::shopConfig('weight_unit') ?: 'kg';
$otherInfo = $variant->other_info ?: [];
$productWeight = floatval(Arr::get($otherInfo, 'weight', 0));
$productWeightUnit = Arr::get($otherInfo, 'weight_unit', $storeWeightUnit);
$convertedProductWeight = Helper::convertWeight($productWeight, $productWeightUnit, $storeWeightUnit);
$convertedPackageWeight = Helper::convertWeight($packageWeight, $packageWeightUnit, $storeWeightUnit);
$totalWeight = $convertedProductWeight + $convertedPackageWeight;
$hasVisibleContent = ($showName && $name)
|| ($showDimensions && ($length || $width || $height))
|| ($showProductWeight && $convertedProductWeight)
|| ($showTotalWeight && $totalWeight);
if (!$hasVisibleContent) {
return;
}
echo sprintf('
', $wrapper_attributes); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '
';
if ($showName && $name) {
echo sprintf(
'| %s | %s |
',
esc_html__('Package', 'fluent-cart'),
esc_html($name)
);
}
if ($showDimensions && ($length || $width || $height)) {
$dimensionParts = array_filter([$length, $width, $height], function ($val) {
return $val !== '' && $val !== null;
});
if ($dimensionParts) {
$formattedDimensions = implode(' × ', $dimensionParts) . ' ' . $dimensionUnit;
echo sprintf(
'| %s | %s |
',
esc_html__('Dimensions', 'fluent-cart'),
esc_html($formattedDimensions)
);
}
}
if ($showProductWeight && $convertedProductWeight) {
$formattedProductWeight = rtrim(rtrim(number_format($convertedProductWeight, 2), '0'), '.');
echo sprintf(
'| %s | %s |
',
esc_html__('Weight', 'fluent-cart'),
esc_html($formattedProductWeight . ' ' . $storeWeightUnit)
);
}
if ($showTotalWeight && $totalWeight && $convertedPackageWeight) {
$formattedTotalWeight = rtrim(rtrim(number_format($totalWeight, 2), '0'), '.');
echo sprintf(
'| %s | %s |
',
esc_html__('Shipping Weight', 'fluent-cart'),
esc_html($formattedTotalWeight . ' ' . $storeWeightUnit)
);
}
echo '
';
echo '
';
}
public function renderExcerpt($atts = '')
{
if (empty($this->product->post_excerpt)) {
return;
}
echo sprintf(
'
%2$s
',
$atts, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
wp_kses_post($this->product->post_excerpt),
);
}
public function renderTitle($atts = '', $config = [])
{
$link = Arr::get($config, 'isLink', true);
$target = Arr::get($config, 'target', '_self');
$titleText = esc_html($this->product->post_title);
if ($link) {
// Render as link
$targetAttr = $target === '_blank' ? 'target="_blank" rel="noopener noreferrer"' : '';
echo sprintf(
'',
$atts, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_attr($this->product->ID),
esc_url($this->product->view_url),
esc_attr($this->product->post_title),
$targetAttr, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html($titleText)
);
} else {
// Render as plain text (no link)
echo sprintf(
'%2$s
',
$atts, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
esc_html($titleText)
);
}
}
public function renderProductImage()
{
$image = $this->product->thumbnail;
$isPlaceholder = false;
if (!$image) {
$image = Vite::getAssetUrl('images/placeholder.svg');
$isPlaceholder = true;
}
$altText = $isPlaceholder
? sprintf(
/* translators: %s: product title */
__('Placeholder image for %s', 'fluent-cart'), $this->product->post_title)
: $this->product->post_title;
do_action('fluent_cart/product/group/before_image_block', [
'product' => $this->product,
'scope' => 'product_card'
]);
?>
$this->product,
'scope' => 'product_card'
]);
}
public function renderPrices($wrapper_attributes = '')
{
$priceFormat = Arr::get($this->config, 'price_format', 'starts_from');
$isSimple = $this->product->detail->variation_type === 'simple';
$minPrice = $this->product->detail->min_price;
$maxPrice = $this->product->detail->max_price;
$comparePrice = 0;
if ($isSimple) {
$firstVariant = $this->product->variants->first();
if ($firstVariant) {
$minPrice = $firstVariant->item_price;
$minPrice = apply_filters('fluent_cart/product/display_price', $minPrice, [
'product' => $this->product,
'variation' => $firstVariant,
]);
$minPrice = (int)$minPrice;
if ($firstVariant->compare_price > $minPrice) {
$comparePrice = $firstVariant->compare_price;
}
}
}
$formattedMinPrice = Helper::toDecimal($minPrice);
$formattedMaxPrice = Helper::toDecimal($maxPrice);
$formattedComparePrice = Helper::toDecimal($comparePrice);
do_action('fluent_cart/product/group/before_price_block', [
'product' => $this->product,
'current_price' => $minPrice,
'scope' => 'product_card'
]);
?>
class="fct-product-card-prices"
role="region"
aria-label="">
$minPrice): ?>
-
$this->product,
'current_price' => $minPrice,
'scope' => 'product_card'
]); ?>
$this->product,
'current_price' => $minPrice,
'scope' => 'product_card'
]);
}
public function showBuyButton($atts = '')
{
$isOutOfStock = ModuleSettings::isActive('stock_management') && !$this->product->isStock();
if ($isOutOfStock) {
$soldOutText = __('Not Available', 'fluent-cart');
?>
product->detail->variation_type === 'simple';
$firstVariant = null;
$buttonHref = $this->viewUrl;
if ($isSimple) {
$firstVariant = $this->product->variants->first();
if ($firstVariant) {
// return '';
}
}
$isInstantCheckout = false;
$hasSubscription = $this->product->has_subscription;
$buttonText = __('View Options', 'fluent-cart');
$ariaLabel = sprintf(
/* translators: %s: product title */
__('View options for %s', 'fluent-cart'), $this->product->post_title);
if ($isSimple) {
if ($hasSubscription) {
$buttonText = __('Buy Now', 'fluent-cart');
$ariaLabel = sprintf(
/* translators: %s: product title */
__('Buy %s now', 'fluent-cart'), $this->product->post_title);
$buttonHref = $firstVariant->getPurchaseUrl();
$isInstantCheckout = true;
} else {
$buttonText = __('Add To Cart', 'fluent-cart');
$ariaLabel = sprintf(
/* translators: %s: product title */
__('Add %s to cart', 'fluent-cart'), $this->product->post_title);
}
}
$buttonAttributes = [
'class' => 'fct-product-view-button fct-single-product-card-view-button',
'data-product-id' => $this->product->ID,
'data-fluent-cart-single-product-card-view-button' => '',
'aria-label' => $ariaLabel
];
if ($firstVariant) {
$buttonAttributes = [
'data-cart-id' => $firstVariant->id,
'class' => 'fluent-cart-add-to-cart-button',
'data-variation-type' => $this->product->detail->variation_type,
'data-fluent-cart-add-to-cart-button' => '',
'data-is-custom' => false,
'aria-label' => $ariaLabel
];
}
$customAttributes = $this->parseAttributes($atts);
if (!empty($customAttributes)) {
if (isset($customAttributes['class']) && isset($buttonAttributes['class'])) {
$buttonAttributes['class'] .= ' ' . $customAttributes['class'];
unset($customAttributes['class']);
}
$buttonAttributes = array_merge($buttonAttributes, $customAttributes);
}
$anchorAttributes = [
'href' => $buttonHref,
'class' => 'fct-product-view-button',
'aria-label' => $ariaLabel,
];
if ($enableModalCheckout) {
$anchorAttributes['data-fct-instant-checkout-button'] = '';
$anchorAttributes['data-enable-modal-checkout'] = 'yes';
}
if ($isInstantCheckout) {
$parsedCustomAttributes = $this->parseAttributes($atts);
if (isset($parsedCustomAttributes['class']) && isset($anchorAttributes['class'])) {
$anchorAttributes['class'] .= ' ' . $parsedCustomAttributes['class'];
unset($parsedCustomAttributes['class']);
}
$anchorAttributes = array_merge($anchorAttributes, $parsedCustomAttributes);
}
?>
renderAttributes($anchorAttributes); ?>>
$value) {
if ($value !== '') {
echo esc_attr($attr) . '="' . esc_attr((string)$value) . '" ';
} else {
echo esc_attr($attr) . ' ';
}
}
}
private function parseAttributes($atts)
{
if (empty($atts)) {
return [];
}
$attributes = [];
// Match attribute="value" or attribute='value' or attribute=value
preg_match_all('/(\w+(?:-\w+)*)=(["\'])(.*?)\2|\b(\w+(?:-\w+)*)=(\S+)/', $atts, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
if (!empty($match[1])) {
// Quoted value
$attributes[$match[1]] = $match[3];
} elseif (!empty($match[4])) {
// Unquoted value
$attributes[$match[4]] = $match[5];
}
}
return $attributes;
}
}