| 1 |
<?php |
| 2 |
|
| 3 |
namespace StoreEngine\Frontend; |
| 4 |
|
| 5 |
if ( ! defined( 'ABSPATH' ) ) { |
| 6 |
exit; |
| 7 |
} |
| 8 |
|
| 9 |
use StoreEngine\Classes\TemplateLoader; |
| 10 |
use StoreEngine\frontend\template\Block; |
| 11 |
use StoreEngine\Utils\Helper; |
| 12 |
use WP_Query; |
| 13 |
|
| 14 |
class Template extends TemplateLoader { |
| 15 |
public static function init() { |
| 16 |
$self = new self(); |
| 17 |
$self->dispatch_hook(); |
| 18 |
if ( Helper::is_fse_theme() ) { |
| 19 |
Block::init(); |
| 20 |
} else { |
| 21 |
add_filter( 'template_include', array( $self, 'template_loader' ) ); |
| 22 |
} |
| 23 |
} |
| 24 |
|
| 25 |
public function dispatch_hook() { |
| 26 |
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) ); |
| 27 |
add_filter( 'astra_blog_post_per_page_exclusions', [ $this, 'astra_product_per_page_exclusions' ] ); |
| 28 |
add_filter( 'comments_template', [ $this, 'load_comments_template' ] ); |
| 29 |
add_filter( 'pre_render_block', [ $this, 'prevent_shortcode_block_autop' ], 10, 2 ); |
| 30 |
|
| 31 |
// Redirect to log-in wp-screen. |
| 32 |
//add_action( 'template_redirect', array( $this, 'frontend_dashboard_template_redirect' ) ); |
| 33 |
} |
| 34 |
|
| 35 |
/** |
| 36 |
* Stop the core/shortcode block from running wpautop() on StoreEngine markup. |
| 37 |
* |
| 38 |
* In FSE block templates WordPress runs do_shortcode() BEFORE do_blocks() |
| 39 |
* (see get_the_block_template_html()), so by the time the core/shortcode |
| 40 |
* block renders, its inner content is already the fully-expanded StoreEngine |
| 41 |
* HTML. The block's render callback then calls wpautop() on it, which wraps |
| 42 |
* our inline elements (e.g. the price <label> list) in stray <p> tags and |
| 43 |
* injects <br> — breaking the single-product layout. |
| 44 |
* |
| 45 |
* We short-circuit the block render for our own output only: detection keys |
| 46 |
* on the `storeengine-` class prefix, which exists only AFTER expansion. In |
| 47 |
* the classic the_content path do_blocks() runs first, so the inner content |
| 48 |
* is still the raw `[storeengine_*]` tag (an underscore, never `storeengine-`) |
| 49 |
* and this never matches — leaving that path's existing behaviour intact. |
| 50 |
* |
| 51 |
* @param string|null $pre_render Pre-rendered content. Default null. |
| 52 |
* @param array $parsed_block The parsed block. |
| 53 |
* |
| 54 |
* @return string|null |
| 55 |
*/ |
| 56 |
public function prevent_shortcode_block_autop( $pre_render, $parsed_block ) { |
| 57 |
if ( null !== $pre_render || 'core/shortcode' !== ( $parsed_block['blockName'] ?? '' ) ) { |
| 58 |
return $pre_render; |
| 59 |
} |
| 60 |
|
| 61 |
$inner = $parsed_block['innerHTML'] ?? ''; |
| 62 |
if ( false === strpos( $inner, 'storeengine-' ) ) { |
| 63 |
return $pre_render; |
| 64 |
} |
| 65 |
|
| 66 |
// Already-expanded markup; do_shortcode() is a no-op safeguard. No wpautop. |
| 67 |
return do_shortcode( $inner ); |
| 68 |
} |
| 69 |
|
| 70 |
public function load_comments_template( $template ) { |
| 71 |
if ( get_post_type() !== 'storeengine_product' ) { |
| 72 |
return $template; |
| 73 |
} |
| 74 |
|
| 75 |
$check_dirs = array( |
| 76 |
trailingslashit( get_stylesheet_directory() ) . Helper::template_path(), |
| 77 |
trailingslashit( get_template_directory() ) . Helper::template_path(), |
| 78 |
trailingslashit( get_stylesheet_directory() ), |
| 79 |
trailingslashit( get_template_directory() ), |
| 80 |
trailingslashit( Helper::plugin_path() ) . 'templates/', |
| 81 |
); |
| 82 |
|
| 83 |
if ( STOREENGINE_TEMPLATE_DEBUG_MODE ) { |
| 84 |
$check_dirs = array( array_pop( $check_dirs ) ); |
| 85 |
} |
| 86 |
|
| 87 |
foreach ( $check_dirs as $dir ) { |
| 88 |
if ( file_exists( trailingslashit( $dir ) . 'single-product-comments.php' ) ) { |
| 89 |
return trailingslashit( $dir ) . 'single-product-comments.php'; |
| 90 |
} |
| 91 |
} |
| 92 |
|
| 93 |
return $template; |
| 94 |
} |
| 95 |
|
| 96 |
/** |
| 97 |
* Hook into pre_get_posts to do the main product query. |
| 98 |
* |
| 99 |
* @param WP_Query $q Query instance. |
| 100 |
*/ |
| 101 |
public function pre_get_posts( WP_Query $q ) { |
| 102 |
if ( ! $q->is_main_query() || $q->is_feed() || is_admin() ) { |
| 103 |
return; |
| 104 |
} |
| 105 |
|
| 106 |
if ( is_post_type_archive( Helper::PRODUCT_POST_TYPE ) ) { |
| 107 |
// Astra requires `post_type` to be string for `astra_blog_post_per_page_exclusions` in_array check, we're setting array value. |
| 108 |
// Removing this action safely removes the per_page settings for archive template set by astra. |
| 109 |
remove_action( 'parse_tax_query', 'astra_blog_post_per_page' ); |
| 110 |
|
| 111 |
if ( isset( $_GET['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 112 |
$orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 113 |
} else { |
| 114 |
$orderby = Helper::get_settings( 'product_archive_products_order', '' ); |
| 115 |
} |
| 116 |
|
| 117 |
$paged = ( get_query_var( 'paged' ) ) ? absint( get_query_var( 'paged' ) ) : 1; |
| 118 |
$q->set( 'post_type', apply_filters( 'storeengine/frontend/product_archive_post_types', array( Helper::PRODUCT_POST_TYPE ) ) ); |
| 119 |
$q->set( 'posts_per_page', (int) Helper::get_settings( 'product_archive_products_per_page', 12 ) ); |
| 120 |
$q->set( 'paged', $paged ); |
| 121 |
$q->set( 'orderby', $orderby ); |
| 122 |
$q->set( 'order', 'title' === $orderby ? 'ASC' : 'DESC' ); |
| 123 |
$q->set( 'meta_query', [ |
| 124 |
'relation' => 'OR', |
| 125 |
[ |
| 126 |
'key' => '_storeengine_product_hide', |
| 127 |
'value' => true, |
| 128 |
'compare' => 'NOT EXISTS', |
| 129 |
], |
| 130 |
[ |
| 131 |
'key' => '_storeengine_product_hide', |
| 132 |
'value' => true, |
| 133 |
'compare' => '!=', |
| 134 |
], |
| 135 |
] ); |
| 136 |
} elseif ( $q->is_tax() ) { |
| 137 |
$q->set( 'meta_query', [ |
| 138 |
'relation' => 'OR', |
| 139 |
[ |
| 140 |
'key' => '_storeengine_product_hide', |
| 141 |
'value' => true, |
| 142 |
'compare' => 'NOT EXISTS', |
| 143 |
], |
| 144 |
[ |
| 145 |
'key' => '_storeengine_product_hide', |
| 146 |
'value' => true, |
| 147 |
'compare' => '!=', |
| 148 |
], |
| 149 |
] ); |
| 150 |
} |
| 151 |
} |
| 152 |
|
| 153 |
public function astra_product_per_page_exclusions( array $post_types ) { |
| 154 |
$post_types[] = Helper::PRODUCT_POST_TYPE; |
| 155 |
|
| 156 |
return $post_types; |
| 157 |
} |
| 158 |
|
| 159 |
public function frontend_dashboard_template_redirect() { |
| 160 |
if ( ! is_user_logged_in() && (int) Helper::get_settings( 'dashboard_page' ) === get_the_ID() ) { |
| 161 |
wp_safe_redirect( storeengine_login_url( Helper::get_dashboard_url() ) ); |
| 162 |
exit(); |
| 163 |
} |
| 164 |
} |
| 165 |
} |
| 166 |
|