Fix button roundtrip in editor, alignment persistence, and test email rendering
This commit is contained in:
@@ -144,11 +144,11 @@ class Assets {
|
||||
$theme_settings = array_replace_recursive($default_settings, $spa_settings);
|
||||
|
||||
// Get appearance settings and preload them
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
if (empty($appearance_settings)) {
|
||||
// Use defaults from AppearanceController
|
||||
$appearance_settings = \WooNooW\Admin\AppearanceController::get_default_settings();
|
||||
}
|
||||
$stored_settings = get_option('woonoow_appearance_settings', []);
|
||||
$default_appearance = \WooNooW\Admin\AppearanceController::get_default_settings();
|
||||
|
||||
// Merge stored settings with defaults to ensure new fields (like gradient colors) exist
|
||||
$appearance_settings = array_replace_recursive($default_appearance, $stored_settings);
|
||||
|
||||
// Get WooCommerce currency settings
|
||||
$currency_settings = [
|
||||
@@ -198,12 +198,23 @@ class Assets {
|
||||
$spa_page_id = $appearance_settings['general']['spa_page'] ?? 0;
|
||||
$spa_page = $spa_page_id ? get_post($spa_page_id) : null;
|
||||
|
||||
// Check if SPA page is set as WordPress frontpage
|
||||
// Check if SPA Entry Page is set as WordPress frontpage
|
||||
$frontpage_id = (int) get_option('page_on_front');
|
||||
$is_spa_frontpage = $frontpage_id && $spa_page_id && $frontpage_id === (int) $spa_page_id;
|
||||
$is_spa_wp_frontpage = $frontpage_id && $spa_page_id && $frontpage_id === (int) $spa_page_id;
|
||||
|
||||
// If SPA is frontpage, base path is /, otherwise use page slug
|
||||
$base_path = $is_spa_frontpage ? '' : ($spa_page ? '/' . $spa_page->post_name : '/store');
|
||||
// Get SPA Landing page (explicit setting, separate from Entry Page)
|
||||
// This determines what content to show at the SPA root route "/"
|
||||
$spa_frontpage_id = $appearance_settings['general']['spa_frontpage'] ?? 0;
|
||||
$front_page_slug = '';
|
||||
if ($spa_frontpage_id) {
|
||||
$spa_frontpage = get_post($spa_frontpage_id);
|
||||
if ($spa_frontpage) {
|
||||
$front_page_slug = $spa_frontpage->post_name;
|
||||
}
|
||||
}
|
||||
|
||||
// If SPA Entry Page is WP frontpage, base path is /, otherwise use Entry Page slug
|
||||
$base_path = $is_spa_wp_frontpage ? '' : ($spa_page ? '/' . $spa_page->post_name : '/store');
|
||||
|
||||
// Check if BrowserRouter is enabled (default: true for SEO)
|
||||
$use_browser_router = $appearance_settings['general']['use_browser_router'] ?? true;
|
||||
@@ -223,6 +234,8 @@ class Assets {
|
||||
'appearanceSettings' => $appearance_settings,
|
||||
'basePath' => $base_path,
|
||||
'useBrowserRouter' => $use_browser_router,
|
||||
'frontPageSlug' => $front_page_slug,
|
||||
'spaMode' => $appearance_settings['general']['spa_mode'] ?? 'full',
|
||||
];
|
||||
|
||||
?>
|
||||
@@ -270,11 +283,11 @@ class Assets {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get Customer SPA settings
|
||||
$spa_settings = get_option('woonoow_customer_spa_settings', []);
|
||||
$mode = isset($spa_settings['mode']) ? $spa_settings['mode'] : 'disabled';
|
||||
// Get SPA mode from appearance settings (the correct source)
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$mode = $appearance_settings['general']['spa_mode'] ?? 'full';
|
||||
|
||||
// If disabled, don't load
|
||||
// If disabled, only load for pages with shortcodes
|
||||
if ($mode === 'disabled') {
|
||||
// Special handling for WooCommerce Shop page (it's an archive, not a regular post)
|
||||
if (function_exists('is_shop') && is_shop()) {
|
||||
|
||||
@@ -49,10 +49,13 @@ class PageSSR
|
||||
// Generate section ID for anchor links
|
||||
$section_id = $section['id'] ?? 'section-' . uniqid();
|
||||
|
||||
$element_styles = $section['elementStyles'] ?? [];
|
||||
$styles = $section['styles'] ?? []; // Section wrapper styles (bg, overlay)
|
||||
|
||||
// Render based on section type
|
||||
$method = 'render_' . str_replace('-', '_', $type);
|
||||
if (method_exists(__CLASS__, $method)) {
|
||||
return self::$method($resolved_props, $layout, $color_scheme, $section_id);
|
||||
return self::$method($resolved_props, $layout, $color_scheme, $section_id, $element_styles, $styles);
|
||||
}
|
||||
|
||||
// Fallback: generic section wrapper
|
||||
@@ -95,10 +98,25 @@ class PageSSR
|
||||
// Section Renderers
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Helper to generate style attribute string
|
||||
*/
|
||||
private static function generate_style_attr($styles) {
|
||||
if (empty($styles)) return '';
|
||||
|
||||
$css = [];
|
||||
if (!empty($styles['color'])) $css[] = "color: {$styles['color']}";
|
||||
if (!empty($styles['backgroundColor'])) $css[] = "background-color: {$styles['backgroundColor']}";
|
||||
if (!empty($styles['fontSize'])) $css[] = "font-size: {$styles['fontSize']}"; // Note: assumes value has unit or is handled by class, but inline style works for specific values
|
||||
// Add more mapping if needed, or rely on frontend to send valid CSS values
|
||||
|
||||
return empty($css) ? '' : 'style="' . implode(';', $css) . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Hero section
|
||||
*/
|
||||
public static function render_hero($props, $layout, $color_scheme, $id)
|
||||
public static function render_hero($props, $layout, $color_scheme, $id, $element_styles = [], $section_styles = [])
|
||||
{
|
||||
$title = esc_html($props['title'] ?? '');
|
||||
$subtitle = esc_html($props['subtitle'] ?? '');
|
||||
@@ -106,21 +124,50 @@ class PageSSR
|
||||
$cta_text = esc_html($props['cta_text'] ?? '');
|
||||
$cta_url = esc_url($props['cta_url'] ?? '');
|
||||
|
||||
$html = "<section id=\"{$id}\" class=\"wn-section wn-hero wn-hero--{$layout} wn-scheme--{$color_scheme}\">";
|
||||
// Section Styles (Background & Spacing)
|
||||
$bg_color = $section_styles['backgroundColor'] ?? '';
|
||||
$bg_image = $section_styles['backgroundImage'] ?? '';
|
||||
$overlay_opacity = $section_styles['backgroundOverlay'] ?? 0;
|
||||
$pt = $section_styles['paddingTop'] ?? '';
|
||||
$pb = $section_styles['paddingBottom'] ?? '';
|
||||
$height_preset = $section_styles['heightPreset'] ?? '';
|
||||
|
||||
if ($image) {
|
||||
$html .= "<img src=\"{$image}\" alt=\"{$title}\" class=\"wn-hero__image\" />";
|
||||
$section_css = "";
|
||||
if ($bg_color) $section_css .= "background-color: {$bg_color};";
|
||||
if ($bg_image) $section_css .= "background-image: url('{$bg_image}'); background-size: cover; background-position: center;";
|
||||
if ($pt) $section_css .= "padding-top: {$pt};";
|
||||
if ($pb) $section_css .= "padding-bottom: {$pb};";
|
||||
if ($height_preset === 'screen') $section_css .= "min-height: 100vh; display: flex; align-items: center;";
|
||||
|
||||
$section_attr = $section_css ? "style=\"{$section_css}\"" : "";
|
||||
|
||||
$html = "<section id=\"{$id}\" class=\"wn-section wn-hero wn-hero--{$layout} wn-scheme--{$color_scheme}\" {$section_attr}>";
|
||||
|
||||
// Overlay
|
||||
if ($overlay_opacity > 0) {
|
||||
$opacity = $overlay_opacity / 100;
|
||||
$html .= "<div class=\"wn-hero__overlay\" style=\"background-color: rgba(0,0,0,{$opacity}); position: absolute; inset: 0;\"></div>";
|
||||
}
|
||||
|
||||
// Element Styles
|
||||
$title_style = self::generate_style_attr($element_styles['title'] ?? []);
|
||||
$subtitle_style = self::generate_style_attr($element_styles['subtitle'] ?? []);
|
||||
$cta_style = self::generate_style_attr($element_styles['cta_text'] ?? []); // Button
|
||||
|
||||
// Image (if not background)
|
||||
if ($image && !$bg_image && $layout !== 'default') {
|
||||
$html .= "<img src=\"{$image}\" alt=\"{$title}\" class=\"wn-hero__image\" />";
|
||||
}
|
||||
|
||||
$html .= '<div class="wn-hero__content">';
|
||||
$html .= '<div class="wn-hero__content" style="position: relative; z-index: 10;">';
|
||||
if ($title) {
|
||||
$html .= "<h1 class=\"wn-hero__title\">{$title}</h1>";
|
||||
$html .= "<h1 class=\"wn-hero__title\" {$title_style}>{$title}</h1>";
|
||||
}
|
||||
if ($subtitle) {
|
||||
$html .= "<p class=\"wn-hero__subtitle\">{$subtitle}</p>";
|
||||
$html .= "<p class=\"wn-hero__subtitle\" {$subtitle_style}>{$subtitle}</p>";
|
||||
}
|
||||
if ($cta_text && $cta_url) {
|
||||
$html .= "<a href=\"{$cta_url}\" class=\"wn-hero__cta\">{$cta_text}</a>";
|
||||
$html .= "<a href=\"{$cta_url}\" class=\"wn-hero__cta\" {$cta_style}>{$cta_text}</a>";
|
||||
}
|
||||
$html .= '</div>';
|
||||
$html .= '</section>';
|
||||
@@ -128,50 +175,154 @@ class PageSSR
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Universal Row Renderer (Shared logic for Content & ImageText)
|
||||
*/
|
||||
private static function render_universal_row($props, $layout, $color_scheme, $element_styles, $options = []) {
|
||||
$title = esc_html($props['title']['value'] ?? ($props['title'] ?? ''));
|
||||
$text = $props['text']['value'] ?? ($props['text'] ?? ($props['content']['value'] ?? ($props['content'] ?? ''))); // Handle both props/values
|
||||
$image = esc_url($props['image']['value'] ?? ($props['image'] ?? ''));
|
||||
|
||||
// Options
|
||||
$has_image = !empty($image);
|
||||
$image_pos = $layout ?: 'left';
|
||||
|
||||
// Element Styles
|
||||
$title_style = self::generate_style_attr($element_styles['title'] ?? []);
|
||||
$text_style = self::generate_style_attr($element_styles['text'] ?? ($element_styles['content'] ?? []));
|
||||
|
||||
// Wrapper Classes
|
||||
$wrapper_class = "wn-max-w-7xl wn-mx-auto wn-px-4";
|
||||
$grid_class = "wn-mx-auto";
|
||||
|
||||
if ($has_image && in_array($image_pos, ['left', 'right', 'image-left', 'image-right'])) {
|
||||
$grid_class .= " wn-grid wn-grid-cols-1 wn-lg-grid-cols-2 wn-gap-12 wn-items-center";
|
||||
} else {
|
||||
$grid_class .= " wn-max-w-4xl";
|
||||
}
|
||||
|
||||
$html = "<div class=\"{$wrapper_class}\">";
|
||||
$html .= "<div class=\"{$grid_class}\">";
|
||||
|
||||
// Image Output
|
||||
$image_html = "";
|
||||
if ($current_pos_right = ($image_pos === 'right' || $image_pos === 'image-right')) {
|
||||
$order_class = 'wn-lg-order-last';
|
||||
} else {
|
||||
$order_class = 'wn-lg-order-first';
|
||||
}
|
||||
|
||||
if ($has_image) {
|
||||
$image_html = "<div class=\"wn-relative wn-w-full wn-aspect-[4/3] wn-rounded-2xl wn-overflow-hidden wn-shadow-lg {$order_class}\">";
|
||||
$image_html .= "<img src=\"{$image}\" alt=\"{$title}\" class=\"wn-absolute wn-inset-0 wn-w-full wn-h-full wn-object-cover\" />";
|
||||
$image_html .= "</div>";
|
||||
}
|
||||
|
||||
// Content Output
|
||||
$content_html = "<div class=\"wn-flex wn-flex-col\">";
|
||||
if ($title) {
|
||||
$content_html .= "<h2 class=\"wn-text-3xl wn-font-bold wn-mb-6\" {$title_style}>{$title}</h2>";
|
||||
}
|
||||
if ($text) {
|
||||
// Apply prose classes similar to React
|
||||
$content_html .= "<div class=\"wn-prose wn-prose-lg wn-max-w-none\" {$text_style}>{$text}</div>";
|
||||
}
|
||||
$content_html .= "</div>";
|
||||
|
||||
// Render based on order (Grid handles order via CSS classes for left/right, but fallback for DOM order)
|
||||
if ($has_image) {
|
||||
// For grid layout, we output both. CSS order handles visual.
|
||||
$html .= $image_html . $content_html;
|
||||
} else {
|
||||
$html .= $content_html;
|
||||
}
|
||||
|
||||
$html .= "</div></div>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Content section (for post body, rich text)
|
||||
*/
|
||||
public static function render_content($props, $layout, $color_scheme, $id)
|
||||
public static function render_content($props, $layout, $color_scheme, $id, $element_styles = [], $section_styles = [])
|
||||
{
|
||||
$content = $props['content'] ?? '';
|
||||
// Apply WordPress content filters (shortcodes, autop, etc.)
|
||||
$content = apply_filters('the_content', $content);
|
||||
// Normalize prop structure for universal renderer if needed
|
||||
if (is_string($props['content'])) {
|
||||
$props['content'] = ['value' => $content];
|
||||
} else {
|
||||
$props['content']['value'] = $content;
|
||||
}
|
||||
|
||||
// Section Styles (Background)
|
||||
$bg_color = $section_styles['backgroundColor'] ?? '';
|
||||
$padding = $section_styles['paddingTop'] ?? '';
|
||||
$height_preset = $section_styles['heightPreset'] ?? '';
|
||||
|
||||
$css = "";
|
||||
if($bg_color) $css .= "background-color:{$bg_color};";
|
||||
|
||||
return "<section id=\"{$id}\" class=\"wn-section wn-content wn-scheme--{$color_scheme}\">{$content}</section>";
|
||||
// Height Logic
|
||||
if ($height_preset === 'screen') {
|
||||
$css .= "min-height: 100vh; display: flex; align-items: center;";
|
||||
$padding = '5rem'; // Default padding for screen to avoid edge collision
|
||||
} elseif ($height_preset === 'small') {
|
||||
$padding = '2rem';
|
||||
} elseif ($height_preset === 'large') {
|
||||
$padding = '8rem';
|
||||
} elseif ($height_preset === 'medium') {
|
||||
$padding = '4rem';
|
||||
}
|
||||
|
||||
if($padding) $css .= "padding:{$padding} 0;";
|
||||
|
||||
$style_attr = $css ? "style=\"{$css}\"" : "";
|
||||
|
||||
$inner_html = self::render_universal_row($props, 'left', $color_scheme, $element_styles);
|
||||
|
||||
return "<section id=\"{$id}\" class=\"wn-section wn-content wn-scheme--{$color_scheme}\" {$style_attr}>{$inner_html}</section>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Image + Text section
|
||||
*/
|
||||
public static function render_image_text($props, $layout, $color_scheme, $id)
|
||||
public static function render_image_text($props, $layout, $color_scheme, $id, $element_styles = [], $section_styles = [])
|
||||
{
|
||||
$title = esc_html($props['title'] ?? '');
|
||||
$text = wp_kses_post($props['text'] ?? '');
|
||||
$image = esc_url($props['image'] ?? '');
|
||||
|
||||
$html = "<section id=\"{$id}\" class=\"wn-section wn-image-text wn-image-text--{$layout} wn-scheme--{$color_scheme}\">";
|
||||
|
||||
if ($image) {
|
||||
$html .= "<div class=\"wn-image-text__image\"><img src=\"{$image}\" alt=\"{$title}\" /></div>";
|
||||
$bg_color = $section_styles['backgroundColor'] ?? '';
|
||||
$padding = $section_styles['paddingTop'] ?? '';
|
||||
$height_preset = $section_styles['heightPreset'] ?? '';
|
||||
|
||||
$css = "";
|
||||
if($bg_color) $css .= "background-color:{$bg_color};";
|
||||
|
||||
// Height Logic
|
||||
if ($height_preset === 'screen') {
|
||||
$css .= "min-height: 100vh; display: flex; align-items: center;";
|
||||
$padding = '5rem';
|
||||
} elseif ($height_preset === 'small') {
|
||||
$padding = '2rem';
|
||||
} elseif ($height_preset === 'large') {
|
||||
$padding = '8rem';
|
||||
} elseif ($height_preset === 'medium') {
|
||||
$padding = '4rem';
|
||||
}
|
||||
|
||||
$html .= '<div class="wn-image-text__content">';
|
||||
if ($title) {
|
||||
$html .= "<h2 class=\"wn-image-text__title\">{$title}</h2>";
|
||||
}
|
||||
if ($text) {
|
||||
$html .= "<div class=\"wn-image-text__text\">{$text}</div>";
|
||||
}
|
||||
$html .= '</div>';
|
||||
$html .= '</section>';
|
||||
|
||||
return $html;
|
||||
|
||||
if($padding) $css .= "padding:{$padding} 0;";
|
||||
$style_attr = $css ? "style=\"{$css}\"" : "";
|
||||
|
||||
$inner_html = self::render_universal_row($props, $layout, $color_scheme, $element_styles);
|
||||
|
||||
return "<section id=\"{$id}\" class=\"wn-section wn-image-text wn-scheme--{$color_scheme}\" {$style_attr}>{$inner_html}</section>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Feature Grid section
|
||||
*/
|
||||
public static function render_feature_grid($props, $layout, $color_scheme, $id)
|
||||
public static function render_feature_grid($props, $layout, $color_scheme, $id, $element_styles = [])
|
||||
{
|
||||
$heading = esc_html($props['heading'] ?? '');
|
||||
$items = $props['items'] ?? [];
|
||||
@@ -182,21 +333,36 @@ class PageSSR
|
||||
$html .= "<h2 class=\"wn-feature-grid__heading\">{$heading}</h2>";
|
||||
}
|
||||
|
||||
// Feature Item Styles (Card)
|
||||
$item_style_attr = self::generate_style_attr($element_styles['feature_item'] ?? []); // BG, Border, Shadow handled by CSS classes mostly, but colors here
|
||||
$item_bg = $element_styles['feature_item']['backgroundColor'] ?? '';
|
||||
|
||||
$html .= '<div class="wn-feature-grid__items">';
|
||||
foreach ($items as $item) {
|
||||
$item_title = esc_html($item['title'] ?? '');
|
||||
$item_desc = esc_html($item['description'] ?? '');
|
||||
$item_icon = esc_html($item['icon'] ?? '');
|
||||
|
||||
$html .= '<div class="wn-feature-grid__item">';
|
||||
// Allow overriding item specific style if needed, but for now global
|
||||
$html .= "<div class=\"wn-feature-grid__item\" {$item_style_attr}>";
|
||||
|
||||
// Render Icon SVG
|
||||
if ($item_icon) {
|
||||
$html .= "<span class=\"wn-feature-grid__icon\">{$item_icon}</span>";
|
||||
$icon_svg = self::get_icon_svg($item_icon);
|
||||
if ($icon_svg) {
|
||||
$html .= "<div class=\"wn-feature-grid__icon\">{$icon_svg}</div>";
|
||||
}
|
||||
}
|
||||
|
||||
if ($item_title) {
|
||||
$html .= "<h3 class=\"wn-feature-grid__item-title\">{$item_title}</h3>";
|
||||
// Feature title style
|
||||
$f_title_style = self::generate_style_attr($element_styles['feature_title'] ?? []);
|
||||
$html .= "<h3 class=\"wn-feature-grid__item-title\" {$f_title_style}>{$item_title}</h3>";
|
||||
}
|
||||
if ($item_desc) {
|
||||
$html .= "<p class=\"wn-feature-grid__item-desc\">{$item_desc}</p>";
|
||||
// Feature description style
|
||||
$f_desc_style = self::generate_style_attr($element_styles['feature_description'] ?? []);
|
||||
$html .= "<p class=\"wn-feature-grid__item-desc\" {$f_desc_style}>{$item_desc}</p>";
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
@@ -209,7 +375,7 @@ class PageSSR
|
||||
/**
|
||||
* Render CTA Banner section
|
||||
*/
|
||||
public static function render_cta_banner($props, $layout, $color_scheme, $id)
|
||||
public static function render_cta_banner($props, $layout, $color_scheme, $id, $element_styles = [])
|
||||
{
|
||||
$title = esc_html($props['title'] ?? '');
|
||||
$text = esc_html($props['text'] ?? '');
|
||||
@@ -238,13 +404,29 @@ class PageSSR
|
||||
/**
|
||||
* Render Contact Form section
|
||||
*/
|
||||
public static function render_contact_form($props, $layout, $color_scheme, $id)
|
||||
public static function render_contact_form($props, $layout, $color_scheme, $id, $element_styles = [])
|
||||
{
|
||||
$title = esc_html($props['title'] ?? '');
|
||||
$webhook_url = esc_url($props['webhook_url'] ?? '');
|
||||
$redirect_url = esc_url($props['redirect_url'] ?? '');
|
||||
$fields = $props['fields'] ?? ['name', 'email', 'message'];
|
||||
|
||||
// Extract styles
|
||||
$btn_bg = $element_styles['button']['backgroundColor'] ?? '';
|
||||
$btn_color = $element_styles['button']['color'] ?? '';
|
||||
$field_bg = $element_styles['fields']['backgroundColor'] ?? '';
|
||||
$field_color = $element_styles['fields']['color'] ?? '';
|
||||
|
||||
$btn_style = "";
|
||||
if ($btn_bg) $btn_style .= "background-color: {$btn_bg};";
|
||||
if ($btn_color) $btn_style .= "color: {$btn_color};";
|
||||
$btn_attr = $btn_style ? "style=\"{$btn_style}\"" : "";
|
||||
|
||||
$field_style = "";
|
||||
if ($field_bg) $field_style .= "background-color: {$field_bg};";
|
||||
if ($field_color) $field_style .= "color: {$field_color};";
|
||||
$field_attr = $field_style ? "style=\"{$field_style}\"" : "";
|
||||
|
||||
$html = "<section id=\"{$id}\" class=\"wn-section wn-contact-form wn-scheme--{$color_scheme}\">";
|
||||
|
||||
if ($title) {
|
||||
@@ -259,19 +441,38 @@ class PageSSR
|
||||
$html .= '<div class="wn-contact-form__field">';
|
||||
$html .= "<label>{$field_label}</label>";
|
||||
if ($field === 'message') {
|
||||
$html .= "<textarea name=\"{$field}\" placeholder=\"{$field_label}\"></textarea>";
|
||||
$html .= "<textarea name=\"{$field}\" placeholder=\"{$field_label}\" {$field_attr}></textarea>";
|
||||
} else {
|
||||
$html .= "<input type=\"text\" name=\"{$field}\" placeholder=\"{$field_label}\" />";
|
||||
$html .= "<input type=\"text\" name=\"{$field}\" placeholder=\"{$field_label}\" {$field_attr} />";
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '<button type="submit">Submit</button>';
|
||||
$html .= "<button type=\"submit\" {$btn_attr}>Submit</button>";
|
||||
$html .= '</form>';
|
||||
$html .= '</section>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get SVG for known icons
|
||||
*/
|
||||
private static function get_icon_svg($name) {
|
||||
$icons = [
|
||||
'Star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
|
||||
'Zap' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
|
||||
'Shield' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',
|
||||
'Heart' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',
|
||||
'Award' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="7"/><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/></svg>',
|
||||
'Clock' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
|
||||
'Truck' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>',
|
||||
'User' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>',
|
||||
'Settings' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
|
||||
];
|
||||
|
||||
return $icons[$name] ?? $icons['Star'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic section fallback
|
||||
|
||||
@@ -166,7 +166,14 @@ class TemplateOverride
|
||||
'top'
|
||||
);
|
||||
} else {
|
||||
// Rewrite /slug/anything to serve the SPA page
|
||||
// Rewrite /slug to serve the SPA page (base URL)
|
||||
add_rewrite_rule(
|
||||
'^' . preg_quote($spa_slug, '/') . '/?$',
|
||||
'index.php?page_id=' . $spa_page_id,
|
||||
'top'
|
||||
);
|
||||
|
||||
// Rewrite /slug/anything to serve the SPA page with path
|
||||
// React Router handles the path after that
|
||||
add_rewrite_rule(
|
||||
'^' . preg_quote($spa_slug, '/') . '/(.*)$',
|
||||
@@ -306,8 +313,30 @@ class TemplateOverride
|
||||
wp_redirect($build_route('my-account'), 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Redirect structural pages with WooNooW sections to SPA
|
||||
if (is_singular('page') && $post) {
|
||||
// Skip the SPA page itself and frontpage
|
||||
if ($post->ID == $spa_page_id || $post->ID == $frontpage_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if page has WooNooW structure
|
||||
$structure = get_post_meta($post->ID, '_wn_page_structure', true);
|
||||
if (!empty($structure) && !empty($structure['sections'])) {
|
||||
// Redirect to SPA with page slug route
|
||||
$page_slug = $post->post_name;
|
||||
wp_redirect($build_route($page_slug), 302);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve SPA template directly for frontpage SPA routes
|
||||
* When SPA page is set as WordPress frontpage, intercept known routes
|
||||
* and serve the SPA template directly (bypasses WooCommerce templates)
|
||||
*/
|
||||
/**
|
||||
* Serve SPA template directly for frontpage SPA routes
|
||||
* When SPA page is set as WordPress frontpage, intercept known routes
|
||||
@@ -331,8 +360,19 @@ class TemplateOverride
|
||||
return; // SPA is not frontpage, let normal routing handle it
|
||||
}
|
||||
|
||||
// Get the current request path
|
||||
// Get the current request path relative to site root
|
||||
$request_uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
$home_path = parse_url(home_url(), PHP_URL_PATH);
|
||||
|
||||
// Normalize request URI for subdirectory installs
|
||||
if ($home_path && $home_path !== '/') {
|
||||
$home_path = rtrim($home_path, '/');
|
||||
if (strpos($request_uri, $home_path) === 0) {
|
||||
$request_uri = substr($request_uri, strlen($home_path));
|
||||
if (empty($request_uri)) $request_uri = '/';
|
||||
}
|
||||
}
|
||||
|
||||
$path = parse_url($request_uri, PHP_URL_PATH);
|
||||
$path = '/' . trim($path, '/');
|
||||
|
||||
@@ -365,6 +405,27 @@ class TemplateOverride
|
||||
}
|
||||
}
|
||||
|
||||
// Check for structural pages with WooNooW sections
|
||||
if (!$should_serve_spa && !empty($path) && $path !== '/') {
|
||||
// Try to find a page by slug matching the path
|
||||
$slug = trim($path, '/');
|
||||
|
||||
// Handle nested slugs (get the last part as the page slug)
|
||||
if (strpos($slug, '/') !== false) {
|
||||
$slug_parts = explode('/', $slug);
|
||||
$slug = end($slug_parts);
|
||||
}
|
||||
|
||||
$page = get_page_by_path($slug);
|
||||
if ($page) {
|
||||
// Check if this page has WooNooW structure
|
||||
$structure = get_post_meta($page->ID, '_wn_page_structure', true);
|
||||
if (!empty($structure) && !empty($structure['sections'])) {
|
||||
$should_serve_spa = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not a SPA route
|
||||
if (!$should_serve_spa) {
|
||||
return;
|
||||
@@ -396,8 +457,8 @@ class TemplateOverride
|
||||
*/
|
||||
public static function disable_canonical_redirect($redirect_url, $requested_url)
|
||||
{
|
||||
$settings = get_option('woonoow_customer_spa_settings', []);
|
||||
$mode = isset($settings['mode']) ? $settings['mode'] : 'disabled';
|
||||
$settings = get_option('woonoow_appearance_settings', []);
|
||||
$mode = isset($settings['general']['spa_mode']) ? $settings['general']['spa_mode'] : 'disabled';
|
||||
|
||||
// Only disable redirects in full SPA mode
|
||||
if ($mode !== 'full') {
|
||||
@@ -405,6 +466,7 @@ class TemplateOverride
|
||||
}
|
||||
|
||||
// Check if this is a SPA route
|
||||
// We include /product/ and standard endpoints
|
||||
$spa_routes = ['/product/', '/cart', '/checkout', '/my-account'];
|
||||
|
||||
foreach ($spa_routes as $route) {
|
||||
@@ -733,6 +795,20 @@ class TemplateOverride
|
||||
*/
|
||||
public static function serve_ssr_content($page_id, $type = 'page', $post_obj = null)
|
||||
{
|
||||
// Generate cache key
|
||||
$cache_id = $post_obj ? $post_obj->ID : $page_id;
|
||||
$cache_key = "wn_ssr_{$type}_{$cache_id}";
|
||||
|
||||
// Check cache TTL (default 1 hour, filterable)
|
||||
$cache_ttl = apply_filters('woonoow_ssr_cache_ttl', HOUR_IN_SECONDS);
|
||||
|
||||
// Try to get cached content
|
||||
$cached = get_transient($cache_key);
|
||||
if ($cached !== false) {
|
||||
echo $cached;
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get page structure
|
||||
if ($type === 'page') {
|
||||
$structure = get_post_meta($page_id, '_wn_page_structure', true);
|
||||
@@ -783,7 +859,8 @@ class TemplateOverride
|
||||
wp_trim_words(wp_strip_all_tags($post_obj->post_content), 30);
|
||||
}
|
||||
|
||||
// Output SSR HTML
|
||||
// Output SSR HTML - start output buffering for caching
|
||||
ob_start();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
@@ -825,6 +902,14 @@ class TemplateOverride
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
// Get buffered output
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Cache the output for bots (uses cache TTL from filter)
|
||||
set_transient($cache_key, $output, $cache_ttl);
|
||||
|
||||
// Output and exit
|
||||
echo $output;
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user