PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
1.7.0 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 All 50 releases
← All changes | app/Modules/Templating/TemplateActions.php +86 -4 1.3.21 → 1.7.0 View file →
@@ -12,8 +12,9 @@
12 12 use FluentCart\App\Modules\Data\ProductQuery;
13 13 use FluentCart\App\Services\Renderer\ProductListRenderer;
14 14 use FluentCart\App\Services\Renderer\ProductRenderer;
15 15 use FluentCart\App\Services\Renderer\ShopAppRenderer;
16 +use FluentCart\App\Services\Schema\ProductSchema;
16 17 use FluentCart\App\Services\TemplateService;
17 18 use FluentCart\Framework\Support\Arr;
18 19
19 20 class TemplateActions
@@ -22,8 +23,16 @@
22 23 {
23 24 add_action('fluent_cart/template/main_content', [$this, 'renderMainContent']);
24 25 add_action('fluent_cart/template/product_archive', [$this, 'renderProductArchive']);
25 26 add_action('fluent_cart/product/render_product_header', [$this, 'renderProductHeader']);
27 + // Block themes render reviews from the product template, so the
28 + // built-in listener is not attached there — the hook still fires for
29 + // third-party listeners. Safe to decide here: the theme is resolved
30 + // at after_setup_theme, before init runs this.
31 + if (!function_exists('wp_is_block_theme') || !wp_is_block_theme()) {
32 + add_action('fluent_cart/product/after_product_content', [$this, 'renderProductReviews']);
33 + }
34 + add_action('wp_head', [$this, 'renderProductJsonLd']);
26 35
27 36 // Universal hook for after_product_content — fires on both classic and block themes
28 37 // Block themes render <!-- wp:post-content --> which runs the_content filter
29 38 add_filter('the_content', function ($content) {
@@ -46,9 +55,9 @@
46 55 }
47 56
48 57 return $content;
49 58 }, 999);
50 -
59 +
51 60 add_shortcode('fluent_cart_product_header', function ($atts = []) {
52 61 $atts = shortcode_atts([
53 62 'id' => 0,
54 63 ], $atts);
@@ -94,9 +103,25 @@
94 103 if (!$product || !$product->detail) {
95 104 return __('Product not found', 'fluent-cart');
96 105 }
97 106
107 + // On the product page this shortcode is the block theme's
108 + // equivalent of what filterSingleProductContent appends for
109 + // classic themes, so it answers to the same setting and filter.
110 + // Placed anywhere else it is an explicit choice and still renders.
111 + if (is_singular(FluentProducts::CPT_NAME)) {
112 + $showRelevant = (new StoreSettings())->get('show_relevant_product_in_single_page') == 'yes';
113 + $showRelevant = apply_filters(
114 + 'fluent_cart/single_product_page/show_relevant_products',
115 + $showRelevant,
116 + $productId
117 + );
98 118
119 + if (!$showRelevant) {
120 + return '';
121 + }
122 + }
123 +
99 124 $products = ShopResource::getSimilarProducts($productId, false);
100 125 if (empty($products)) {
101 126 return '';
102 127 }
@@ -104,9 +129,10 @@
104 129 ob_start();
105 130 (new ProductListRenderer(
106 131 $products,
107 132 __('Related Products', 'fluent-cart'),
108 - 'fct-similar-product-list-container'
133 + 'fct-similar-product-list-container',
134 + ['rating_context' => 'relevant']
109 135 ))->render();
110 136
111 137 $content = ob_get_clean();
112 138
@@ -195,9 +221,12 @@
195 221 ];
196 222 }
197 223
198 224 $products = $productsQuery->get();
199 - (new ShopAppRenderer($products, [
225 + (new ShopAppRenderer([
226 + 'products' => $products,
227 + 'total' => $products->total(),
228 + ], [
200 229 'default_filters' => $productsQuery->getDefaultFilters(),
201 230 'custom_filters' => [
202 231 'taxonomies' => [
203 232 $taxFilters
@@ -280,9 +309,10 @@
280 309 ob_start();
281 310 (new ProductListRenderer(
282 311 $products,
283 312 __('Related Products', 'fluent-cart'),
284 - 'fct-similar-product-list-container'
313 + 'fct-similar-product-list-container',
314 + ['rating_context' => 'relevant']
285 315 ))->render();
286 316
287 317 $relevantProducts = ob_get_clean();
288 318 $content .= $relevantProducts;
@@ -329,7 +359,59 @@
329 359 ], '', $content);
330 360
331 361 // Remove extra whitespace between tags to clean up the output
332 362 return preg_replace('/>\s+</', '><', $cleaned);
363 + }
364 +
365 + /**
366 + * Whether the product page should carry its review section at all.
367 + *
368 + * Sits beside the relevant-products switch in Product Page settings and
369 + * works the same way, so one control governs the section wherever it is
370 + * rendered from — a review block, or the append below.
371 + *
372 + * @param int $postId
373 + * @return bool
374 + */
375 + public static function shouldShowReviewsOnProductPage($postId): bool
376 + {
377 + $show = (new StoreSettings())->get('show_reviews_in_single_page', 'yes') == 'yes';
378 +
379 + return (bool) apply_filters('fluent_cart/single_product_page/show_reviews', $show, $postId);
380 + }
381 +
382 + private function isReviewModuleAvailable()
383 + {
384 + return \FluentCart\Api\ModuleSettings::isActive('reviews')
385 + && class_exists(\FluentCart\App\Services\ProductReviewService::class);
386 + }
387 +
388 + public function renderProductReviews($postId)
389 + {
390 + if (!$postId || !$this->isReviewModuleAvailable()) {
391 + return;
392 + }
393 +
394 + $renderer = new \FluentCart\App\Services\Renderer\ProductReviewRenderer($postId);
395 + $renderer->render();
396 + $renderer->renderForm();
397 + }
398 +
399 + public function renderProductJsonLd()
400 + {
401 + if (!is_singular(\FluentCart\App\CPT\FluentProducts::CPT_NAME)) {
402 + return;
403 + }
404 +
405 + $postId = get_the_ID();
406 + if (!$postId) {
407 + return;
408 + }
409 +
410 + // The node itself — product, offers, aggregate rating, the reviews
411 + // the page shows — is ProductSchema's; this is only the page gate.
412 + // The reviews module gate lives there too, on the review parts
413 + // alone: a product is for sale whether or not it takes reviews.
414 + ProductSchema::render($postId);
333 415 }
334 416
335 417 }