which runs the_content filter add_filter('the_content', function ($content) { if (!is_singular('fluent-products')) { return $content; } global $post; if (!$post || $post->post_type !== FluentProducts::CPT_NAME) { return $content; } ob_start(); do_action('fluent_cart/product/after_product_content', $post->ID); $extra = ob_get_clean(); if ($extra) { $content .= $extra; } return $content; }, 999); add_shortcode('fluent_cart_product_header', function ($atts = []) { $atts = shortcode_atts([ 'id' => 0, ], $atts); $productId = absint($atts['id']); if (!$productId) { $productId = get_the_ID(); } if (!$productId) { return 'Product ID not found'; } $product = ProductDataSetup::getProductModel($productId); if (!$product || !$product->detail) { return 'Product not found'; } ob_start(); $this->renderProductHeader($product->ID); $content = ob_get_clean(); return $this->tempFixShortcodeContent($content); }); add_shortcode('fluent_cart_related_products', function ($atts = []) { $atts = shortcode_atts([ 'id' => 0, ], $atts); $productId = absint($atts['id']); if (!$productId) { $productId = get_the_ID(); } if (!$productId) { return __('Product ID not found', 'fluent-cart'); } $product = ProductDataSetup::getProductModel($productId); if (!$product || !$product->detail) { return __('Product not found', 'fluent-cart'); } // On the product page this shortcode is the block theme's // equivalent of what filterSingleProductContent appends for // classic themes, so it answers to the same setting and filter. // Placed anywhere else it is an explicit choice and still renders. if (is_singular(FluentProducts::CPT_NAME)) { $showRelevant = (new StoreSettings())->get('show_relevant_product_in_single_page') == 'yes'; $showRelevant = apply_filters( 'fluent_cart/single_product_page/show_relevant_products', $showRelevant, $productId ); if (!$showRelevant) { return ''; } } $products = ShopResource::getSimilarProducts($productId, false); if (empty($products)) { return ''; } ob_start(); (new ProductListRenderer( $products, __('Related Products', 'fluent-cart'), 'fct-similar-product-list-container', ['rating_context' => 'relevant'] ))->render(); $content = ob_get_clean(); return $this->tempFixShortcodeContent($content); }); add_action('fluent_cart/template/before_content', [$this, 'renderArchiveHeader']); } public function renderMainContent() { $isTaxPages = is_tax(get_object_taxonomies('fluent-products')); if ($isTaxPages) { do_action('fluent_cart/template/product_archive'); } } public function renderArchiveHeader() { if (TemplateService::getCurrentFcPageType() !== 'product_taxonomy') { return; } ?>
tags (including those with whitespace),
tags, and new lines
$cleaned = preg_replace([
'/
\s*<\/p>/i', // Remove empty
tags '/
\s*
\s*<\/p>/i', // Remove
tags containing only
'/
/i', // Remove all
tags
'/[\r\n]+/' // Remove all new lines
], '', $content);
// Remove extra whitespace between tags to clean up the output
return preg_replace('/>\s+', '><', $cleaned);
}
/**
* Whether the product page should carry its review section at all.
*
* Sits beside the relevant-products switch in Product Page settings and
* works the same way, so one control governs the section wherever it is
* rendered from — a review block, or the append below.
*
* @param int $postId
* @return bool
*/
public static function shouldShowReviewsOnProductPage($postId): bool
{
$show = (new StoreSettings())->get('show_reviews_in_single_page', 'yes') == 'yes';
return (bool) apply_filters('fluent_cart/single_product_page/show_reviews', $show, $postId);
}
private function isReviewModuleAvailable()
{
return \FluentCart\Api\ModuleSettings::isActive('reviews')
&& class_exists(\FluentCart\App\Services\ProductReviewService::class);
}
public function renderProductReviews($postId)
{
if (!$postId || !$this->isReviewModuleAvailable()) {
return;
}
$renderer = new \FluentCart\App\Services\Renderer\ProductReviewRenderer($postId);
$renderer->render();
$renderer->renderForm();
}
public function renderProductJsonLd()
{
if (!is_singular(\FluentCart\App\CPT\FluentProducts::CPT_NAME)) {
return;
}
$postId = get_the_ID();
if (!$postId) {
return;
}
// The node itself — product, offers, aggregate rating, the reviews
// the page shows — is ProductSchema's; this is only the page gate.
// The reviews module gate lives there too, on the review parts
// alone: a product is for sale whether or not it takes reviews.
ProductSchema::render($postId);
}
}