Files
WooNooW/includes/Frontend/TemplateOverride.php
Dwindi Ramadhana fe98e6233d refactor: Simplify to single SPA entry page architecture
User feedback: 'SPA means Single Page, why 4 pages?'

Correct architecture:
- 1 SPA entry page (e.g., /store)
- SPA Mode determines initial route:
  * Full SPA → starts at shop page
  * Checkout Only → starts at cart page
  * Disabled → never loads
- React Router handles rest via /#/ routing

Changes:
- Admin UI: Changed from 4 page selectors to 1 SPA entry page
- Backend: spa_pages array → spa_page integer
- Template: Initial route based on spa_mode setting
- Simplified is_spa_page() checks (single ID comparison)

Benefits:
- User can set /store as homepage (Settings → Reading)
- Landing page → CTA → direct to cart/checkout
- Clean single entry point
- Mode controls behavior, not multiple pages

Example flow:
- Visit https://site.com/store
- Full SPA: loads shop, navigate via /#/product/123
- Checkout Only: loads cart, navigate via /#/checkout
- Homepage: set /store as homepage, SPA loads on site root

Next: Add direct-to-cart CTA with product parameter
2025-12-30 20:33:15 +07:00

366 lines
13 KiB
PHP

<?php
namespace WooNooW\Frontend;
/**
* Template Override
* Overrides WooCommerce templates to use WooNooW SPA
*/
class TemplateOverride {
/**
* Initialize
*/
public static function init() {
// Use blank template for full-page SPA
add_filter('template_include', [__CLASS__, 'use_spa_template'], 999);
// Disable canonical redirects for SPA routes
add_filter('redirect_canonical', [__CLASS__, 'disable_canonical_redirect'], 10, 2);
// Override WooCommerce shop page
add_filter('woocommerce_show_page_title', '__return_false');
// Replace WooCommerce content with our SPA
add_action('woocommerce_before_main_content', [__CLASS__, 'start_spa_wrapper'], 5);
add_action('woocommerce_after_main_content', [__CLASS__, 'end_spa_wrapper'], 999);
// Remove WooCommerce default content
remove_action('woocommerce_before_shop_loop', 'woocommerce_result_count', 20);
remove_action('woocommerce_before_shop_loop', 'woocommerce_catalog_ordering', 30);
remove_action('woocommerce_before_main_content', 'woocommerce_output_content_wrapper', 10);
remove_action('woocommerce_after_main_content', 'woocommerce_output_content_wrapper_end', 10);
// Override single product template
add_filter('woocommerce_locate_template', [__CLASS__, 'override_template'], 10, 3);
// Remove theme header and footer when SPA is active
add_action('get_header', [__CLASS__, 'remove_theme_header']);
add_action('get_footer', [__CLASS__, 'remove_theme_footer']);
}
/**
* Disable canonical redirects for SPA routes
* This prevents WordPress from redirecting /product/slug URLs
*/
public static function disable_canonical_redirect($redirect_url, $requested_url) {
$settings = get_option('woonoow_customer_spa_settings', []);
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
// Only disable redirects in full SPA mode
if ($mode !== 'full') {
return $redirect_url;
}
// Check if this is a SPA route
$spa_routes = ['/product/', '/cart', '/checkout', '/my-account'];
foreach ($spa_routes as $route) {
if (strpos($requested_url, $route) !== false) {
// This is a SPA route, disable WordPress redirect
return false;
}
}
return $redirect_url;
}
/**
* Use SPA template (blank page)
*/
public static function use_spa_template($template) {
// Check if current page is a designated SPA page
if (self::is_spa_page()) {
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
if (file_exists($spa_template)) {
return $spa_template;
}
}
// Legacy: Check SPA mode settings
$settings = get_option('woonoow_customer_spa_settings', []);
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
// Mode 1: Disabled - but still check for shortcodes (legacy)
if ($mode === 'disabled') {
// Check if page has woonoow shortcodes
global $post;
if ($post && (
has_shortcode($post->post_content, 'woonoow_shop') ||
has_shortcode($post->post_content, 'woonoow_cart') ||
has_shortcode($post->post_content, 'woonoow_checkout') ||
has_shortcode($post->post_content, 'woonoow_account')
)) {
// Use blank template for shortcode pages too
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
if (file_exists($spa_template)) {
return $spa_template;
}
}
return $template;
}
// Check if current URL is a SPA route (for direct access)
$request_uri = $_SERVER['REQUEST_URI'];
$spa_routes = ['/shop', '/product/', '/cart', '/checkout', '/my-account'];
$is_spa_route = false;
foreach ($spa_routes as $route) {
if (strpos($request_uri, $route) !== false) {
$is_spa_route = true;
break;
}
}
// If it's a SPA route in full mode, use SPA template
if ($mode === 'full' && $is_spa_route) {
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
if (file_exists($spa_template)) {
// Set status to 200 to prevent 404
status_header(200);
return $spa_template;
}
}
// Mode 3: Checkout-Only (partial SPA)
if ($mode === 'checkout_only') {
$checkout_pages = isset($settings['checkoutPages']) ? $settings['checkoutPages'] : [
'checkout' => true,
'thankyou' => true,
'account' => true,
'cart' => false,
];
$should_override = false;
if (!empty($checkout_pages['checkout']) && is_checkout() && !is_order_received_page()) {
$should_override = true;
}
if (!empty($checkout_pages['thankyou']) && is_order_received_page()) {
$should_override = true;
}
if (!empty($checkout_pages['account']) && is_account_page()) {
$should_override = true;
}
if (!empty($checkout_pages['cart']) && is_cart()) {
$should_override = true;
}
if ($should_override) {
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
if (file_exists($spa_template)) {
return $spa_template;
}
}
return $template;
}
// Mode 2: Full SPA
if ($mode === 'full') {
// Override all WooCommerce pages
if (is_woocommerce() || is_product() || is_cart() || is_checkout() || is_account_page()) {
$spa_template = plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-full-page.php';
if (file_exists($spa_template)) {
return $spa_template;
}
}
}
return $template;
}
/**
* Start SPA wrapper
*/
public static function start_spa_wrapper() {
// Check if we should use SPA
if (!self::should_use_spa()) {
return;
}
// Determine page type
$page_type = 'shop';
$data_attrs = 'data-page="shop"';
if (is_product()) {
$page_type = 'product';
global $post;
$data_attrs = 'data-page="product" data-product-id="' . esc_attr($post->ID) . '"';
} elseif (is_cart()) {
$page_type = 'cart';
$data_attrs = 'data-page="cart"';
} elseif (is_checkout()) {
$page_type = 'checkout';
$data_attrs = 'data-page="checkout"';
} elseif (is_account_page()) {
$page_type = 'account';
$data_attrs = 'data-page="account"';
}
// Output SPA mount point
echo '<div id="woonoow-customer-app" ' . $data_attrs . '>';
echo '<div class="woonoow-loading">';
echo '<p>' . esc_html__('Loading...', 'woonoow') . '</p>';
echo '</div>';
echo '</div>';
// Hide WooCommerce content
echo '<div style="display: none;">';
}
/**
* End SPA wrapper
*/
public static function end_spa_wrapper() {
if (!self::should_use_spa()) {
return;
}
// Close hidden wrapper
echo '</div>';
}
/**
* Check if we should use SPA
*/
private static function should_use_spa() {
// Check if frontend mode is enabled
$mode = get_option('woonoow_frontend_mode', 'shortcodes');
if ($mode === 'disabled') {
return false;
}
// For full SPA mode, always use SPA
if ($mode === 'full_spa') {
return true;
}
// For shortcode mode, check if we're on WooCommerce pages
if (is_shop() || is_product() || is_cart() || is_checkout() || is_account_page()) {
return true;
}
return false;
}
/**
* Remove theme header when SPA is active
*/
public static function remove_theme_header() {
if (self::should_remove_theme_elements()) {
remove_all_actions('wp_head');
// Re-add essential WordPress head actions
add_action('wp_head', 'wp_enqueue_scripts', 1);
add_action('wp_head', 'wp_print_styles', 8);
add_action('wp_head', 'wp_print_head_scripts', 9);
add_action('wp_head', 'wp_resource_hints', 2);
add_action('wp_head', 'wp_site_icon', 99);
}
}
/**
* Remove theme footer when SPA is active
*/
public static function remove_theme_footer() {
if (self::should_remove_theme_elements()) {
remove_all_actions('wp_footer');
// Re-add essential WordPress footer actions
add_action('wp_footer', 'wp_print_footer_scripts', 20);
}
}
/**
* Check if current page is the designated SPA page
*/
private static function is_spa_page() {
global $post;
if (!$post) {
return false;
}
// Get SPA page ID from appearance settings
$appearance_settings = get_option('woonoow_appearance_settings', []);
$spa_page_id = isset($appearance_settings['general']['spa_page']) ? $appearance_settings['general']['spa_page'] : 0;
// Check if current page matches the SPA page
if ($spa_page_id && $post->ID == $spa_page_id) {
return true;
}
return false;
}
/**
* Check if we should remove theme header/footer
*/
private static function should_remove_theme_elements() {
// Remove for designated SPA pages
if (self::is_spa_page()) {
return true;
}
$settings = get_option('woonoow_customer_spa_settings', []);
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
// Check if we're on a WooCommerce page in full mode
if ($mode === 'full') {
if (is_shop() || is_product() || is_cart() || is_checkout() || is_account_page() || is_woocommerce()) {
return true;
}
}
// Also remove for pages with shortcodes (even in disabled mode)
global $post;
if ($post && (
has_shortcode($post->post_content, 'woonoow_shop') ||
has_shortcode($post->post_content, 'woonoow_cart') ||
has_shortcode($post->post_content, 'woonoow_checkout') ||
has_shortcode($post->post_content, 'woonoow_account')
)) {
return true;
}
// Special check for Shop page (archive)
if (function_exists('is_shop') && is_shop()) {
$shop_page_id = get_option('woocommerce_shop_page_id');
if ($shop_page_id) {
$shop_page = get_post($shop_page_id);
if ($shop_page && has_shortcode($shop_page->post_content, 'woonoow_shop')) {
return true;
}
}
}
return false;
}
/**
* Override WooCommerce templates
*/
public static function override_template($template, $template_name, $template_path) {
// Only override if SPA is enabled
if (!self::should_use_spa()) {
return $template;
}
// Templates to override
$override_templates = [
'archive-product.php',
'single-product.php',
'cart/cart.php',
'checkout/form-checkout.php',
];
// Check if this template should be overridden
foreach ($override_templates as $override) {
if (strpos($template_name, $override) !== false) {
// Return empty template (SPA will handle rendering)
return plugin_dir_path(dirname(dirname(__FILE__))) . 'templates/spa-wrapper.php';
}
}
return $template;
}
}