feat: Page Editor Phase 3 - SSR integration and navigation
- Implement serve_ssr_content with full PageSSR rendering - SEO meta tags (title, description, og:*) - Minimal CSS for bot-friendly presentation - Yoast/Rank Math SEO data integration - Add maybe_serve_ssr_for_bots hook (priority 2 on template_redirect) - Serves SSR for structural pages with WooNooW structure - Serves SSR for CPT items with templates - Add use statements for PageSSR and PlaceholderRenderer - Add Pages link to Appearance submenu in NavigationRegistry - Bump NAV_VERSION to 1.1.0
This commit is contained in:
@@ -13,7 +13,7 @@ if ( ! defined('ABSPATH') ) exit;
|
|||||||
*/
|
*/
|
||||||
class NavigationRegistry {
|
class NavigationRegistry {
|
||||||
const NAV_OPTION = 'wnw_nav_tree';
|
const NAV_OPTION = 'wnw_nav_tree';
|
||||||
const NAV_VERSION = '1.0.9'; // Added Help menu
|
const NAV_VERSION = '1.1.0'; // Added Pages (Page Editor)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize hooks
|
* Initialize hooks
|
||||||
@@ -169,6 +169,7 @@ class NavigationRegistry {
|
|||||||
'icon' => 'palette',
|
'icon' => 'palette',
|
||||||
'children' => [
|
'children' => [
|
||||||
['label' => __('General', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/general'],
|
['label' => __('General', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/general'],
|
||||||
|
['label' => __('Pages', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/pages'],
|
||||||
['label' => __('Header', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/header'],
|
['label' => __('Header', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/header'],
|
||||||
['label' => __('Footer', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/footer'],
|
['label' => __('Footer', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/footer'],
|
||||||
['label' => __('Shop', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/shop'],
|
['label' => __('Shop', 'woonoow'), 'mode' => 'spa', 'path' => '/appearance/shop'],
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace WooNooW\Frontend;
|
namespace WooNooW\Frontend;
|
||||||
|
|
||||||
|
use WooNooW\Frontend\PageSSR;
|
||||||
|
use WooNooW\Frontend\PlaceholderRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Template Override
|
* Template Override
|
||||||
* Overrides WooCommerce templates to use WooNooW SPA
|
* Overrides WooCommerce templates to use WooNooW SPA
|
||||||
@@ -38,6 +41,9 @@ class TemplateOverride
|
|||||||
// Serve SPA directly for frontpage routes (priority 1 = very early, before WC)
|
// Serve SPA directly for frontpage routes (priority 1 = very early, before WC)
|
||||||
add_action('template_redirect', [__CLASS__, 'serve_spa_for_frontpage_routes'], 1);
|
add_action('template_redirect', [__CLASS__, 'serve_spa_for_frontpage_routes'], 1);
|
||||||
|
|
||||||
|
// Serve SSR for bots on pages/CPT with WooNooW structure (priority 2 = after frontpage check)
|
||||||
|
add_action('template_redirect', [__CLASS__, 'maybe_serve_ssr_for_bots'], 2);
|
||||||
|
|
||||||
// Hook to wp_loaded with priority 10 (BEFORE WooCommerce's priority 20)
|
// Hook to wp_loaded with priority 10 (BEFORE WooCommerce's priority 20)
|
||||||
// This ensures we process add-to-cart before WooCommerce does
|
// This ensures we process add-to-cart before WooCommerce does
|
||||||
add_action('wp_loaded', [__CLASS__, 'intercept_add_to_cart'], 10);
|
add_action('wp_loaded', [__CLASS__, 'intercept_add_to_cart'], 10);
|
||||||
@@ -723,15 +729,15 @@ class TemplateOverride
|
|||||||
*
|
*
|
||||||
* @param int $page_id Page ID to render
|
* @param int $page_id Page ID to render
|
||||||
* @param string $type 'page' or 'template'
|
* @param string $type 'page' or 'template'
|
||||||
* @param array|null $post_data Post data for template rendering (CPT items)
|
* @param \WP_Post|null $post_obj Post object for template rendering (CPT items)
|
||||||
*/
|
*/
|
||||||
public static function serve_ssr_content($page_id, $type = 'page', $post_data = null)
|
public static function serve_ssr_content($page_id, $type = 'page', $post_obj = null)
|
||||||
{
|
{
|
||||||
// Get page structure
|
// Get page structure
|
||||||
if ($type === 'page') {
|
if ($type === 'page') {
|
||||||
$structure = get_post_meta($page_id, '_wn_page_structure', true);
|
$structure = get_post_meta($page_id, '_wn_page_structure', true);
|
||||||
} else {
|
} else {
|
||||||
// CPT template
|
// CPT template - type is the post_type like 'post', 'portfolio', etc.
|
||||||
$structure = get_option("wn_template_{$type}", null);
|
$structure = get_option("wn_template_{$type}", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,9 +745,120 @@ class TemplateOverride
|
|||||||
return false; // No structure, let normal WP handle it
|
return false; // No structure, let normal WP handle it
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will be implemented in PageSSR class
|
// Render using PageSSR
|
||||||
// For now, return false to let normal WP handle
|
$post_data = null;
|
||||||
// TODO: Implement PageSSR::render($structure, $post_data)
|
if ($post_obj && $type !== 'page') {
|
||||||
return false;
|
$placeholder_renderer = new PlaceholderRenderer();
|
||||||
|
$post_data = $placeholder_renderer->build_post_data($post_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssr = new PageSSR();
|
||||||
|
$html = $ssr->render($structure['sections'], $post_data);
|
||||||
|
|
||||||
|
if (empty($html)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get page title
|
||||||
|
$title = $type === 'page' ? get_the_title($page_id) : '';
|
||||||
|
if ($post_obj) {
|
||||||
|
$title = get_the_title($post_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SEO data
|
||||||
|
$seo_title = $title . ' - ' . get_bloginfo('name');
|
||||||
|
$seo_description = '';
|
||||||
|
|
||||||
|
// Try to get Yoast/Rank Math SEO data
|
||||||
|
if ($type === 'page') {
|
||||||
|
$seo_title = get_post_meta($page_id, '_yoast_wpseo_title', true) ?:
|
||||||
|
get_post_meta($page_id, 'rank_math_title', true) ?: $seo_title;
|
||||||
|
$seo_description = get_post_meta($page_id, '_yoast_wpseo_metadesc', true) ?:
|
||||||
|
get_post_meta($page_id, 'rank_math_description', true) ?: '';
|
||||||
|
} elseif ($post_obj) {
|
||||||
|
$seo_title = get_post_meta($post_obj->ID, '_yoast_wpseo_title', true) ?:
|
||||||
|
get_post_meta($post_obj->ID, 'rank_math_title', true) ?: $seo_title;
|
||||||
|
$seo_description = get_post_meta($post_obj->ID, '_yoast_wpseo_metadesc', true) ?:
|
||||||
|
get_post_meta($post_obj->ID, 'rank_math_description', true) ?:
|
||||||
|
wp_trim_words(wp_strip_all_tags($post_obj->post_content), 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output SSR HTML
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html <?php language_attributes(); ?>>
|
||||||
|
<head>
|
||||||
|
<meta charset="<?php bloginfo('charset'); ?>">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title><?php echo esc_html($seo_title); ?></title>
|
||||||
|
<?php if ($seo_description): ?>
|
||||||
|
<meta name="description" content="<?php echo esc_attr($seo_description); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
<link rel="canonical" href="<?php echo esc_url(get_permalink($post_obj ?: $page_id)); ?>">
|
||||||
|
<meta property="og:title" content="<?php echo esc_attr($seo_title); ?>">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="<?php echo esc_url(get_permalink($post_obj ?: $page_id)); ?>">
|
||||||
|
<?php if ($seo_description): ?>
|
||||||
|
<meta property="og:description" content="<?php echo esc_attr($seo_description); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
<style>
|
||||||
|
/* Minimal SSR styles for bots */
|
||||||
|
body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; margin: 0; padding: 0; }
|
||||||
|
.wn-ssr { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||||
|
.wn-section { padding: 40px 0; }
|
||||||
|
.wn-section h1, .wn-section h2 { margin-bottom: 16px; }
|
||||||
|
.wn-section p { margin-bottom: 12px; }
|
||||||
|
.wn-section img { max-width: 100%; height: auto; }
|
||||||
|
.wn-hero { background: #f5f5f5; padding: 60px 20px; text-align: center; }
|
||||||
|
.wn-cta-banner { background: #4f46e5; color: white; padding: 40px 20px; text-align: center; }
|
||||||
|
.wn-cta-banner a { color: white; text-decoration: underline; }
|
||||||
|
.wn-feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 24px; }
|
||||||
|
.wn-feature-item { padding: 20px; border: 1px solid #e5e5e5; border-radius: 8px; }
|
||||||
|
</style>
|
||||||
|
<?php wp_head(); ?>
|
||||||
|
</head>
|
||||||
|
<body <?php body_class('wn-ssr-page'); ?>>
|
||||||
|
<div class="wn-ssr">
|
||||||
|
<?php echo $html; ?>
|
||||||
|
</div>
|
||||||
|
<?php wp_footer(); ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SSR for structural pages and CPT items when bot detected
|
||||||
|
* Should be called from template_redirect hook
|
||||||
|
*/
|
||||||
|
public static function maybe_serve_ssr_for_bots()
|
||||||
|
{
|
||||||
|
// Only serve SSR for bots
|
||||||
|
if (!self::is_bot()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a page with WooNooW structure
|
||||||
|
if (is_singular('page')) {
|
||||||
|
$page_id = get_queried_object_id();
|
||||||
|
$structure = get_post_meta($page_id, '_wn_page_structure', true);
|
||||||
|
|
||||||
|
if (!empty($structure) && !empty($structure['sections'])) {
|
||||||
|
self::serve_ssr_content($page_id, 'page');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for CPT items with templates
|
||||||
|
$post_type = get_post_type();
|
||||||
|
if ($post_type && is_singular() && $post_type !== 'page') {
|
||||||
|
$template = get_option("wn_template_{$post_type}", null);
|
||||||
|
|
||||||
|
if (!empty($template) && !empty($template['sections'])) {
|
||||||
|
$post_obj = get_queried_object();
|
||||||
|
self::serve_ssr_content($post_obj->ID, $post_type, $post_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user