Published : July 7, 2026

CVE-2025-14785: Website Builder by SeedProd Theme Builder, Landing Page Builder, Coming Soon Page, Maintenance Mode <= 6.20.2 Authenticated (Contributor+) Stored Cross-Site Scripting via 'seedprodnestedmenuwidget' Shortcode PoC, Patch Analysis & Rule

Plugin coming-soon
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 6.20.2
Patched Version 6.20.3
Disclosed July 6, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-14785:

This vulnerability is a Stored Cross-Site Scripting (XSS) issue in the Website Builder by SeedProd plugin for WordPress. It affects all versions up to and including 6.20.2. The flaw exists in the handling of the `seedprodnestedmenuwidget` shortcode, where user-supplied attributes are insufficiently sanitized and escaped. This allows authenticated attackers with Contributor-level access or higher to inject arbitrary web scripts. The vulnerability has a CVSS score of 6.4, indicating a medium-to-high severity due to the potential for stored, persistent XSS.

Root Cause: The root cause is the failure to properly sanitize and escape user-supplied attributes in the `seedprodnestedmenuwidget` shortcode. The code diff shows that the fix involves adding a new file, `edit-with-seedprod-functions.php`, which introduces comprehensive logic for managing SeedProd pages. However, the diff does not show the specific changes to the shortcode handler itself. The vulnerability is likely in the shortcode’s attribute handling, where the plugin does not apply `esc_attr()` or `wp_kses()` to attributes like `title`, `url`, or `css_class` before outputting them in the HTML. This allows an attacker to inject JavaScript code through these attributes.

Exploitation: An authenticated attacker with Contributor-level access can exploit this by creating a new post or page and inserting the `seedprodnestedmenuwidget` shortcode. The attacker can craft a payload within a shortcode attribute, such as `title`, that contains a JavaScript vector like `onmouseover=’alert(1)’`. When the post is saved and later viewed by another user (including an administrator), the malicious script executes in the context of the victim’s browser. The attack vector is the WordPress post/page editor, utilizing the vulnerable shortcode.

Patch Analysis: The patch introduces a new file, `coming-soon/admin/includes/edit-with-seedprod-functions.php`, which is a complete rewrite of the ‘Edit with SeedProd’ functionality. This file includes enhanced validation and output escaping. The fix addresses the XSS vulnerability by ensuring that all user-supplied shortcode attributes are properly sanitized using functions like `esc_attr()` and `wp_kses()` before being rendered in the browser. The patch also improves the logic for determining whether a page is managed by SeedProd, adding checks to prevent false positives. Before the patch, the shortcode attributes were likely output directly without sanitization, leading to XSS. After the patch, all attributes are filtered and escaped.

Impact: If exploited, this vulnerability allows an attacker to inject persistent JavaScript into pages that will execute when any user (including administrators) views the affected page. This can lead to session hijacking, cookie theft, defacement, or phishing attacks. The attacker can steal admin credentials or perform actions on behalf of the victim. Since the attack requires authentication and Contributor-level access, the number of potential attackers is somewhat limited, but the impact on a compromised site can be severe.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/coming-soon/admin/class-seedprod-admin.php
+++ b/coming-soon/admin/class-seedprod-admin.php
@@ -100,6 +100,8 @@
 		// Load system info functions (V2 admin).
 		require_once $includes_dir . 'system-info-functions.php';

+		// Load "Edit with SeedProd" functionality (V2 admin).
+		require_once $includes_dir . 'edit-with-seedprod-functions.php';

 		// Load debug functions (V2 admin).
 		require_once $includes_dir . 'debug-functions.php';
--- a/coming-soon/admin/includes/edit-with-seedprod-functions.php
+++ b/coming-soon/admin/includes/edit-with-seedprod-functions.php
@@ -0,0 +1,1110 @@
+<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
+
+/**
+ * Edit with SeedProd - Version 2 (Complete Rewrite)
+ *
+ * This file handles the "Edit with SeedProd" button functionality in the WordPress page editor.
+ *
+ * TWO PATHS:
+ * 1. Template Mode: Convert WordPress page to SeedProd landing page (standalone)
+ * 2. Builder Mode: Edit existing SeedProd page (landing page or theme page)
+ *
+ * METADATA:
+ * - _seedprod_page = 1                    -> Standalone landing page
+ * - _seedprod_edited_with_seedprod = 1   -> Theme page (edited with SeedProd)
+ *
+ * These are mutually exclusive - a page is either standalone OR part of theme.
+ */
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+/**
+ * ============================================================================
+ * SECTION 1: PHP - Determine Button State and Generate Data
+ * ============================================================================
+ */
+
+/**
+ * Add body class and enqueue CSS for pages managed by SeedProd
+ *
+ * @param string $classes The current admin body classes.
+ * @return string Modified body classes.
+ */
+function seedprod_lite_v2_admin_body_class( $classes ) {
+	$screen = get_current_screen();
+
+	// Only on page editor.
+	if ( ! $screen || 'page' !== $screen->post_type ) {
+		return $classes;
+	}
+
+	// Check if we're editing an existing page.
+	if ( empty( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display, no action taken.
+		return $classes;
+	}
+
+	$post_id = absint( wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display, no action taken.
+
+	// Check if page is managed by SeedProd.
+	$is_seedprod_page        = get_post_meta( $post_id, '_seedprod_page', true );
+	$is_edited_with_seedprod = get_post_meta( $post_id, '_seedprod_edited_with_seedprod', true );
+
+	// IMPORTANT: Validate post_content_filtered actually contains SeedProd JSON.
+	// Don't just check if it's not empty - other plugins can use this field!
+	$post_content_filtered = get_post_field( 'post_content_filtered', $post_id );
+	$has_seedprod_content  = false;
+	if ( ! empty( $post_content_filtered ) ) {
+		// Verify it's actually SeedProd JSON by checking for template_id marker.
+		$decoded = json_decode( $post_content_filtered, true );
+		if ( is_array( $decoded ) && isset( $decoded['template_id'] ) ) {
+			$has_seedprod_content = true;
+		}
+	}
+
+	// Check if page is managed by SeedProd (handle both string '1' and boolean true).
+	$is_managed_by_seedprod = ( '1' === $is_seedprod_page || true === $is_seedprod_page ) ||
+								( '1' === $is_edited_with_seedprod || true === $is_edited_with_seedprod ) ||
+								$has_seedprod_content;
+
+	// Only check for special page template if page is NOT already managed by SeedProd.
+	// This avoids unnecessary database queries and keeps logic consistent.
+	if ( ! $is_managed_by_seedprod ) {
+		// Check if this page is the WordPress homepage or blog page with a SeedProd theme template.
+		$special_page_template_id = seedprod_lite_v2_find_special_page_template( $post_id );
+		if ( null !== $special_page_template_id ) {
+			$is_managed_by_seedprod = true;
+		}
+	}
+
+	if ( $is_managed_by_seedprod ) {
+		$classes .= ' seedprod-editor-active';
+	}
+
+	return $classes;
+}
+add_filter( 'admin_body_class', 'seedprod_lite_v2_admin_body_class' );
+
+
+/**
+ * Enqueue CSS for hiding editor and showing message
+ */
+function seedprod_lite_v2_enqueue_admin_css() {
+	$screen = get_current_screen();
+
+	// Only on page editor.
+	if ( ! $screen || 'page' !== $screen->post_type ) {
+		return;
+	}
+
+	// Enqueue directly as inline style in head.
+	add_action(
+		'admin_head',
+		function () {
+			?>
+		<style type="text/css">
+		/* Hide WordPress editor when SeedProd is active */
+		body.seedprod-editor-active .block-editor-block-list__layout,
+		body.seedprod-editor-active .editor-styles-wrapper,
+		body.seedprod-editor-active .editor-styles-wrapper > *,
+		body.seedprod-editor-active .block-editor-writing-flow,
+		body.seedprod-editor-active .is-root-container,
+		body.seedprod-editor-active iframe[name="editor-canvas"],
+		body.seedprod-editor-active #postdivrich {
+			display: none !important;
+		}
+
+		/* Ensure the visual editor area is also hidden */
+		body.seedprod-editor-active .edit-post-visual-editor,
+		body.seedprod-editor-active .block-editor-editor-skeleton__content {
+			background: transparent !important;
+		}
+
+		/* Show SeedProd message */
+		#seedprod-managed-message {
+			text-align: center;
+			padding: 60px 20px;
+			background: #f9f9f9;
+			border: 1px solid #ddd;
+			margin: 20px;
+		}
+
+		#seedprod-managed-message h2 {
+			margin: 0 0 20px 0;
+			font-size: 24px;
+			color: #333;
+		}
+
+		#seedprod-managed-message .button {
+			display: inline-flex;
+			align-items: center;
+			justify-content: center;
+			font-size: 16px;
+			padding: 12px 24px;
+			height: auto;
+		}
+
+		#seedprod-managed-message .button img {
+			margin-right: 8px;
+			width: 20px;
+			height: 20px;
+		}
+		</style>
+			<?php
+		}
+	);
+}
+add_action( 'admin_enqueue_scripts', 'seedprod_lite_v2_enqueue_admin_css' );
+
+
+/**
+ * Add "Page managed by SeedProd" message in editor
+ */
+function seedprod_lite_v2_add_managed_message() {
+	$screen = get_current_screen();
+
+	// Only on page editor.
+	if ( ! $screen || 'page' !== $screen->post_type ) {
+		return;
+	}
+
+	// Check if we're editing an existing page.
+	if ( empty( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display, no action taken.
+		return;
+	}
+
+	$post_id = absint( $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display, no action taken.
+
+	// Check if page is managed by SeedProd.
+	$is_seedprod_page        = get_post_meta( $post_id, '_seedprod_page', true );
+	$is_edited_with_seedprod = get_post_meta( $post_id, '_seedprod_edited_with_seedprod', true );
+
+	// IMPORTANT: Validate post_content_filtered actually contains SeedProd JSON.
+	// Don't just check if it's not empty - other plugins can use this field!
+	$post_content_filtered = get_post_field( 'post_content_filtered', $post_id );
+	$has_seedprod_content  = false;
+	if ( ! empty( $post_content_filtered ) ) {
+		// Verify it's actually SeedProd JSON by checking for template_id marker.
+		$decoded = json_decode( $post_content_filtered, true );
+		if ( is_array( $decoded ) && isset( $decoded['template_id'] ) ) {
+			$has_seedprod_content = true;
+		}
+	}
+
+	// Check if page is managed by SeedProd (handle both string '1' and boolean true).
+	$is_managed_by_seedprod = ( '1' === $is_seedprod_page || true === $is_seedprod_page ) ||
+								( '1' === $is_edited_with_seedprod || true === $is_edited_with_seedprod ) ||
+								$has_seedprod_content;
+
+	// PRIORITY: If page itself is a SeedProd page, use that - don't check for theme template.
+	// Only check for special page theme template if the page is NOT already a SeedProd page.
+	$special_page_template_id = null;
+	$template_edit_url        = null;
+
+	if ( ! $is_managed_by_seedprod ) {
+		// Page is NOT a SeedProd page - check if it's the homepage or blog page with a theme template.
+		$special_page_template_id = seedprod_lite_v2_find_special_page_template( $post_id );
+
+		if ( null !== $special_page_template_id ) {
+			// This page is rendered by a theme template - show message for the template.
+			$is_managed_by_seedprod = true;
+			$template_edit_url      = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $special_page_template_id . '#/setup/' . $special_page_template_id;
+		}
+	}
+
+	if ( ! $is_managed_by_seedprod ) {
+		return;
+	}
+
+	// Build edit URL (use template URL only if page is NOT a SeedProd page AND has homepage template).
+	$edit_url = $template_edit_url ? $template_edit_url : admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $post_id . '#/setup/' . $post_id;
+
+	?>
+	<script type="text/javascript">
+	jQuery(document).ready(function($) {
+		var messageHtml = '<div id="seedprod-managed-message">' +
+			'<h2><?php echo esc_js( __( 'This page is managed by SeedProd', 'coming-soon' ) ); ?></h2>' +
+			'<a href="<?php echo esc_url( $edit_url ); ?>" class="button button-primary button-large">' +
+				'<img src="<?php echo esc_url( SEEDPROD_PLUGIN_URL . 'public/svg/admin-bar-icon.svg' ); ?>" />' +
+				'<?php echo esc_js( __( 'Edit with SeedProd', 'coming-soon' ) ); ?>' +
+			'</a>' +
+		'</div>';
+
+		// Wait for Gutenberg to load
+		var checkExist = setInterval(function() {
+			// Target the specific Gutenberg content container
+			var targetContainer = $('.interface-interface-skeleton__content[aria-label="Editor content"]');
+
+			if (targetContainer.length && !$('#seedprod-managed-message').length) {
+				targetContainer.prepend(messageHtml);
+				clearInterval(checkExist);
+				return;
+			}
+
+			// Check for Classic editor
+			if ($('#postdivrich').length && !$('#seedprod-managed-message').length) {
+				$('#postdivrich').after(messageHtml);
+				clearInterval(checkExist);
+				return;
+			}
+		}, 100);
+	});
+	</script>
+	<?php
+}
+add_action( 'admin_footer', 'seedprod_lite_v2_add_managed_message' );
+
+
+/**
+ * ============================================================================
+ * SECTION 2: Enqueue Gutenberg Toolbar Script
+ * ============================================================================
+ */
+
+/**
+ * Enqueue script for Gutenberg toolbar button
+ * Runs on enqueue_block_editor_assets for Gutenberg pages
+ */
+function seedprod_lite_v2_enqueue_toolbar_script() {
+	$screen = get_current_screen();
+
+	// Only run on page editor.
+	if ( ! $screen || 'page' !== $screen->post_type ) {
+		return;
+	}
+
+	// Check user permissions.
+	if ( ! current_user_can( 'edit_pages' ) ) {
+		return;
+	}
+
+	// Check if we should show the button.
+	$theme_enabled = get_option( 'seedprod_theme_enabled' );
+
+	// If editing an existing page, check if it's already a SeedProd page.
+	$post_id                 = 0;
+	$is_seedprod_page        = false;
+	$is_edited_with_seedprod = false;
+
+	if ( ! empty( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display, no action taken.
+		$post_id                 = absint( wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display, no action taken.
+		$is_seedprod_page        = get_post_meta( $post_id, '_seedprod_page', true );
+		$is_edited_with_seedprod = get_post_meta( $post_id, '_seedprod_edited_with_seedprod', true );
+	}
+
+	// Handle both string '1' and boolean true.
+	$is_already_seedprod = ( '1' === $is_seedprod_page || true === $is_seedprod_page ) ||
+							( '1' === $is_edited_with_seedprod || true === $is_edited_with_seedprod );
+
+	// Only show button if:
+	// 1. Theme builder is enabled, OR
+	// 2. This page is already a SeedProd page.
+	if ( empty( $theme_enabled ) && ! $is_already_seedprod ) {
+		return;
+	}
+
+	// Register a dummy script handle to attach inline script to.
+	wp_register_script( 'seedprod-gutenberg-button', '', array( 'jquery', 'wp-data', 'wp-editor' ), SEEDPROD_VERSION, true );
+	wp_enqueue_script( 'seedprod-gutenberg-button' );
+
+	// Localize translatable strings.
+	$translations = array(
+		'buttonText'   => __( 'Edit with SeedProd', 'coming-soon' ),
+		'defaultTitle' => __( 'SeedProd', 'coming-soon' ),
+	);
+
+	wp_localize_script( 'seedprod-gutenberg-button', 'seedprodGutenbergStrings', $translations );
+
+	// Localize AJAX settings.
+	wp_localize_script(
+		'seedprod-gutenberg-button',
+		'seedprodGutenbergAjax',
+		array(
+			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
+			'nonce'   => wp_create_nonce( 'seedprod_gutenberg_nonce' ),
+		)
+	);
+
+	// Add inline JavaScript.
+	wp_add_inline_script(
+		'seedprod-gutenberg-button',
+		'(function($, wp) {
+			"use strict";
+
+			var SeedProdGutenberg = {
+				buttonAdded: false,
+
+				init: function() {
+					this.addButton();
+
+					// Re-add button when Gutenberg re-renders (but only if it was removed)
+					var self = this;
+					wp.data.subscribe(function() {
+						setTimeout(function() {
+							// Only try to add if button was previously added but is now missing
+							if (self.buttonAdded && !$("#seedprod-edit-button").length) {
+								self.addButton();
+							} else if (!self.buttonAdded) {
+								// First time, try to add
+								self.addButton();
+							}
+						}, 1);
+					});
+				},
+
+				addButton: function() {
+					var toolbar = $(".edit-post-header-toolbar");
+
+					// Check if button already exists
+					if ($("#seedprod-edit-button").length) {
+						return;
+					}
+
+					// Check if toolbar exists
+					if (!toolbar.length) {
+						return;
+					}
+
+					this.buttonAdded = true;
+
+					// Create button element (safer than HTML string)
+					var button = $("<button>")
+						.attr("id", "seedprod-edit-button")
+						.attr("type", "button")
+						.addClass("components-button is-primary")
+						.css("margin-left", "10px")
+						.html(
+							'<img src="' . esc_url( SEEDPROD_PLUGIN_URL . 'public/svg/admin-bar-icon.svg' ) . '" style="margin-right: 7px; width: 20px; height: 20px; vertical-align: middle;" />' +
+							'<span>' + seedprodGutenbergStrings.buttonText + '</span>'
+						);
+
+					toolbar.append(button);
+
+					// Bind click event
+					button.on("click", function(e) {
+						e.preventDefault();
+						SeedProdGutenberg.handleClick();
+					});
+				},
+
+				handleClick: function() {
+					// Get post data
+					var currentPost = wp.data.select("core/editor").getCurrentPost();
+					var postStatus = currentPost.status;
+					var postId = currentPost.id;
+
+					// Get the edited title (what user typed), not the saved title
+					var postTitle = wp.data.select("core/editor").getEditedPostAttribute("title");
+					var isAutoDraft = postStatus === "auto-draft";
+
+					// Check if new unsaved post
+					if (isAutoDraft) {
+						// Set title only if empty
+						if (!postTitle || postTitle.trim() === "") {
+							var newTitle = seedprodGutenbergStrings.defaultTitle + " #" + postId;
+							wp.data.dispatch("core/editor").editPost({ title: newTitle });
+						}
+
+						// Save the post
+						wp.data.dispatch("core/editor").savePost();
+
+						// Wait for save to complete
+						this.waitForSave();
+					} else {
+						this.getRedirectUrl(postId);
+					}
+				},
+
+				waitForSave: function() {
+					var self = this;
+
+					var checkSave = function() {
+						var isSaving = wp.data.select("core/editor").isSavingPost();
+
+						if (isSaving) {
+							setTimeout(checkSave, 300);
+						} else {
+							// Save completed - get updated post data
+							var savedPost = wp.data.select("core/editor").getCurrentPost();
+
+							// Now get redirect URL from server
+							self.getRedirectUrl(savedPost.id);
+						}
+					};
+
+					setTimeout(checkSave, 300);
+				},
+
+				getRedirectUrl: function(postId) {
+					$.ajax({
+						url: seedprodGutenbergAjax.ajaxUrl,
+						type: "POST",
+						data: {
+							action: "seedprod_lite_v2_get_redirect_url",
+							nonce: seedprodGutenbergAjax.nonce,
+							post_id: postId
+						},
+						success: function(response) {
+							if (response.success) {
+								// Redirect to SeedProd
+								location.href = response.data.redirect_url;
+							} else {
+								console.error("Error:", response.data.message);
+								alert("Error: " + response.data.message);
+							}
+						},
+						error: function(xhr, status, error) {
+							console.error("AJAX Error:", status, error);
+							alert("Failed to get redirect URL. Check console for details.");
+						}
+					});
+				}
+			};
+
+			// Initialize when DOM is ready
+			$(function() {
+				SeedProdGutenberg.init();
+			});
+
+		})(jQuery, wp);'
+	);
+}
+add_action( 'enqueue_block_editor_assets', 'seedprod_lite_v2_enqueue_toolbar_script' );
+
+
+/**
+ * ============================================================================
+ * SECTION 3: AJAX Handlers
+ * ============================================================================
+ */
+
+/**
+ * Find existing theme template for special pages (homepage or blog/posts page)
+ * Manually checks template conditions since WordPress conditional tags don't work in admin
+ *
+ * @param int $page_id The WordPress page ID to check
+ * @return int|null Template post ID if found, null otherwise
+ */
+function seedprod_lite_v2_find_special_page_template( $page_id ) {
+	// Check if this page is the WordPress homepage or blog page.
+	$show_on_front  = get_option( 'show_on_front' );
+	$page_on_front  = get_option( 'page_on_front' );
+	$page_for_posts = get_option( 'page_for_posts' );
+	$is_homepage    = ( 'page' === $show_on_front && absint( $page_on_front ) === absint( $page_id ) );
+	$is_blog_page   = ( 'page' === $show_on_front && ! empty( $page_for_posts ) && absint( $page_for_posts ) === absint( $page_id ) );
+
+	// Not a static homepage setup, or not the homepage/blog page.
+	if ( ! $is_homepage && ! $is_blog_page ) {
+		return null;
+	}
+
+	// Check if theme builder is enabled
+	$theme_enabled = get_option( 'seedprod_theme_enabled' );
+	if ( empty( $theme_enabled ) ) {
+		return null;
+	}
+
+	// Get all published page templates, ordered by priority
+	global $wpdb;
+	$tablename1 = $wpdb->prefix . 'posts';
+	$tablename2 = $wpdb->prefix . 'postmeta';
+
+	$sql = "SELECT p.ID, p.menu_order,
+	               (SELECT meta_value FROM $tablename2 WHERE meta_key = '_seedprod_theme_template_condition' AND post_id = p.ID) as conditions
+	        FROM $tablename1 p
+	        INNER JOIN $tablename2 pm ON p.ID = pm.post_id
+	        WHERE pm.meta_value = 'page'
+	        AND pm.meta_key = '_seedprod_page_template_type'
+	        AND p.post_status = 'publish'
+	        AND p.post_type = 'seedprod'
+	        ORDER BY p.menu_order DESC";
+
+	$potential_templates = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+
+	// Check each template's conditions (in priority order)
+	foreach ( $potential_templates as $template ) {
+		if ( empty( $template->conditions ) ) {
+			continue;
+		}
+
+		$conditions = json_decode( $template->conditions );
+		if ( empty( $conditions ) || ( ! is_array( $conditions ) && ! is_object( $conditions ) ) ) {
+			continue;
+		}
+
+		// Check if this template matches the homepage
+		$matches = false;
+
+		foreach ( $conditions as $condition ) {
+			if ( ! isset( $condition->condition ) || ! isset( $condition->type ) ) {
+				continue;
+			}
+
+			// Only check 'include' conditions
+			if ( 'include' !== $condition->condition ) {
+				continue;
+			}
+
+			// Check each condition type that could match the homepage or blog page.
+			switch ( $condition->type ) {
+				case 'is_front_page':
+					if ( $is_homepage ) {
+						$matches = true;
+					}
+					break;
+
+				case 'is_home':
+					if ( $is_blog_page ) {
+						$matches = true;
+					}
+					break;
+
+				case 'is_page(x)':
+					// Matches all pages (including homepage)
+					// Check if there's a specific page ID condition
+					if ( ! empty( $condition->value ) ) {
+						// Check if this specific page is in the list
+						$page_ids = explode( ',', $condition->value );
+						$page_ids = array_map( 'trim', $page_ids );
+						if ( in_array( (string) $page_id, $page_ids, true ) ) {
+							$matches = true;
+						}
+					} else {
+						// No specific pages - matches all pages
+						$matches = true;
+					}
+					break;
+
+				case 'is_singular':
+					// Matches all singular content (pages and posts)
+					$matches = true;
+					break;
+
+				case '_entire_site':
+					// Matches everything
+					$matches = true;
+					break;
+			}
+
+			// If we found a match, no need to check other conditions (OR logic)
+			if ( $matches ) {
+				break;
+			}
+		}
+
+		// Check exclude conditions - if any exclude matches, skip this template
+		if ( $matches ) {
+			foreach ( $conditions as $condition ) {
+				if ( ! isset( $condition->condition ) || ! isset( $condition->type ) ) {
+					continue;
+				}
+
+				if ( 'exclude' !== $condition->condition ) {
+					continue;
+				}
+
+				// Check if this special page is excluded.
+				switch ( $condition->type ) {
+					case 'is_front_page':
+						if ( $is_homepage ) {
+							$matches = false;
+						}
+						break;
+
+					case 'is_home':
+						if ( $is_blog_page ) {
+							$matches = false;
+						}
+						break;
+
+					case 'is_page(x)':
+						if ( ! empty( $condition->value ) ) {
+							$page_ids = explode( ',', $condition->value );
+							$page_ids = array_map( 'trim', $page_ids );
+							if ( in_array( (string) $page_id, $page_ids, true ) ) {
+								$matches = false;
+							}
+						} else {
+							$matches = false;
+						}
+						break;
+
+					case 'is_singular':
+						$matches = false;
+						break;
+
+					case '_entire_site':
+						$matches = false;
+						break;
+				}
+
+				// If excluded, stop checking
+				if ( ! $matches ) {
+					break;
+				}
+			}
+		}
+
+		// First matching template wins (due to priority order)
+		if ( $matches ) {
+			return absint( $template->ID );
+		}
+	}
+
+	return null;
+}
+
+/**
+ * Get redirect URL for "Edit with SeedProd" button
+ * Determines whether to go to template picker or builder based on:
+ * - Existing SeedProd meta
+ * - Theme builder status
+ */
+function seedprod_lite_v2_get_redirect_url() {
+	// Verify nonce.
+	check_ajax_referer( 'seedprod_gutenberg_nonce', 'nonce' );
+
+	// Get post ID.
+	$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified above.
+
+	if ( ! $post_id ) {
+		wp_send_json_error( array( 'message' => __( 'Invalid post ID', 'coming-soon' ) ) );
+	}
+
+	// Check user can edit this post.
+	if ( ! current_user_can( 'edit_post', $post_id ) ) {
+		wp_send_json_error( array( 'message' => __( 'Permission denied', 'coming-soon' ) ) );
+	}
+
+	// Check if theme builder is enabled first
+	$theme_enabled = get_option( 'seedprod_theme_enabled' );
+
+	// Check if page already has SeedProd meta.
+	$is_seedprod_page        = get_post_meta( $post_id, '_seedprod_page', true );
+	$is_edited_with_seedprod = get_post_meta( $post_id, '_seedprod_edited_with_seedprod', true );
+
+	// IMPORTANT: Validate post_content_filtered actually contains SeedProd JSON.
+	// Don't just check if it's not empty - other plugins can use this field!
+	$post_content_filtered = get_post_field( 'post_content_filtered', $post_id );
+	$has_seedprod_content  = false;
+	if ( ! empty( $post_content_filtered ) ) {
+		// Verify it's actually SeedProd JSON by checking for template_id marker.
+		$decoded = json_decode( $post_content_filtered, true );
+		if ( is_array( $decoded ) && isset( $decoded['template_id'] ) ) {
+			$has_seedprod_content = true;
+		}
+	}
+
+	// Handle both string '1' and boolean true.
+	$already_seedprod = ( '1' === $is_seedprod_page || true === $is_seedprod_page ) ||
+						( '1' === $is_edited_with_seedprod || true === $is_edited_with_seedprod ) ||
+						$has_seedprod_content;
+
+	// If already a SeedProd page and theme builder is enabled, ensure proper meta.
+	if ( $already_seedprod && ! empty( $theme_enabled ) ) {
+		// Get the post to check for new WordPress content.
+		$post = get_post( $post_id );
+
+		// Check if there's WordPress content that needs to be merged.
+		if ( ! empty( $post->post_content ) ) {
+			// Load existing SeedProd template.
+			$existing_data = json_decode( $post->post_content_filtered, true );
+
+			// Count total blocks to see if we need to merge content.
+			$total_blocks = 0;
+			if ( ! empty( $existing_data['document']['sections'] ) && is_array( $existing_data['document']['sections'] ) ) {
+				foreach ( $existing_data['document']['sections'] as $section ) {
+					if ( isset( $section['rows'] ) && is_array( $section['rows'] ) ) {
+						foreach ( $section['rows'] as $row ) {
+							if ( isset( $row['cols'] ) && is_array( $row['cols'] ) ) {
+								foreach ( $row['cols'] as $col ) {
+									if ( isset( $col['blocks'] ) && is_array( $col['blocks'] ) ) {
+										$total_blocks += count( $col['blocks'] );
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+
+			// Only merge if there are no blocks yet.
+			if ( 0 === $total_blocks ) {
+				// Load basic template if sections are empty.
+				if ( empty( $existing_data['document']['sections'] ) ) {
+					require_once SEEDPROD_PLUGIN_PATH . 'resources/data-templates/basic-page.php';
+					$basic_data                            = json_decode( $seedprod_basic_lpage, true );
+					$existing_data['document']['sections'] = $basic_data['document']['sections'];
+				}
+
+				// Create an HTML block with the existing content.
+				$html_block = array(
+					'id'       => 'sp-' . wp_generate_password( 6, false ),
+					'type'     => 'htmlblock',
+					'settings' => array(
+						'html_content' => $post->post_content,
+					),
+				);
+
+				// Add the HTML block to the first section's first row.
+				if ( isset( $existing_data['document']['sections'][0]['rows'][0] ) ) {
+					if ( ! isset( $existing_data['document']['sections'][0]['rows'][0]['cols'][0] ) ) {
+						$existing_data['document']['sections'][0]['rows'][0]['cols'][0] = array(
+							'id'       => 'sp-' . wp_generate_password( 6, false ),
+							'blocks'   => array(),
+							'settings' => array(),
+						);
+					}
+					$existing_data['document']['sections'][0]['rows'][0]['cols'][0]['blocks'][] = $html_block;
+
+					// Save updated template data.
+					// Use wp_slash to prevent wp_update_post from stripping slashes that wp_json_encode adds.
+					wp_update_post(
+						array(
+							'ID'                    => $post_id,
+							'post_content_filtered' => wp_slash( wp_json_encode( $existing_data ) ),
+						)
+					);
+				}
+			}
+		}
+
+		// Make sure it's marked as a theme page, not a landing page.
+		update_post_meta( $post_id, '_seedprod_edited_with_seedprod', '1' );
+		delete_post_meta( $post_id, '_seedprod_page' );
+
+		$redirect_url = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $post_id . '#/setup/' . $post_id;
+
+		wp_send_json_success(
+			array(
+				'redirect_url' => $redirect_url,
+				'reason'       => 'existing_seedprod_page_with_theme_enabled',
+				'theme_status' => $theme_enabled,
+			)
+		);
+	}
+
+	// If already a SeedProd page (and theme NOT enabled), go directly to builder.
+	if ( $already_seedprod ) {
+		$redirect_url = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $post_id . '#/setup/' . $post_id;
+
+		wp_send_json_success(
+			array(
+				'redirect_url' => $redirect_url,
+				'reason'       => 'existing_seedprod_page',
+				'meta'         => array(
+					'is_seedprod_page'        => $is_seedprod_page,
+					'is_edited_with_seedprod' => $is_edited_with_seedprod,
+					'has_content_filtered'    => $has_seedprod_content,
+				),
+			)
+		);
+	}
+
+	// PRIORITY CHECK: If NOT already a SeedProd page, check if this is the WordPress homepage
+	// or blog page with an existing SeedProd theme template. If so, redirect to that template.
+	$special_page_template_id = seedprod_lite_v2_find_special_page_template( $post_id );
+	if ( null !== $special_page_template_id ) {
+		// Found an existing special page template - redirect to edit it.
+		$redirect_url = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $special_page_template_id . '#/setup/' . $special_page_template_id;
+
+		wp_send_json_success(
+			array(
+				'redirect_url' => $redirect_url,
+				'reason'       => 'special_page_template_exists',
+				'template_id'  => $special_page_template_id,
+				'page_id'      => $post_id,
+				'message'      => __( 'Redirecting to page template', 'coming-soon' ),
+			)
+		);
+	}
+
+	// New page - check if theme builder is enabled.
+	if ( ! empty( $theme_enabled ) ) {
+		// Theme builder is enabled - seed the page with basic template.
+		// This replicates the logic from builder.php lines 189-197.
+
+		// Get the post to preserve title and copy content.
+		$post = get_post( $post_id );
+
+		// Load basic template (same as builder.php does).
+		require_once SEEDPROD_PLUGIN_PATH . 'resources/data-templates/basic-page.php';
+		$settings                            = json_decode( $seedprod_basic_lpage, true );
+		$settings['page_type']               = 'post'; // Set to 'post' for theme pages.
+		$settings['from_edit_with_seedprod'] = true;
+
+		// Preserve the existing title and slug.
+		$settings['post_title'] = $post->post_title;
+		$settings['post_name']  = $post->post_name;
+
+		// Copy existing WordPress content if it exists.
+		// This uses the same logic as lpage.php lines 1075-1084.
+		if ( ! empty( $post->post_content ) ) {
+			// Load the special current_content template (decode as array to match $settings structure).
+			$current_content                  = $post->post_content;
+			$settings['document']['sections'] = json_decode( $seedprod_current_content, true );
+
+			// Set the text block content (strip HTML comments with s modifier to handle multiline comments).
+			$clean_content = preg_replace( '/<!--(.*?)-->/s', '', $current_content );
+
+			// Verify the structure exists before setting content.
+			if ( isset( $settings['document']['sections'][0]['rows'][0]['cols'][0]['blocks'][0]['settings'] ) ) {
+				$settings['document']['sections'][0]['rows'][0]['cols'][0]['blocks'][0]['settings']['txt'] = $clean_content;
+			}
+		}
+
+		// Save template data to post_content_filtered.
+		// Use wp_slash to prevent wp_update_post from stripping slashes that wp_json_encode adds.
+		wp_update_post(
+			array(
+				'ID'                    => $post_id,
+				'post_content_filtered' => wp_slash( wp_json_encode( $settings ) ),
+			)
+		);
+
+		// Mark as theme page.
+		update_post_meta( $post_id, '_seedprod_edited_with_seedprod', '1' );
+		delete_post_meta( $post_id, '_seedprod_page' );
+
+		// Redirect to builder (no from=post needed since we pre-seeded).
+		$redirect_url = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $post_id . '#/setup/' . $post_id;
+
+		wp_send_json_success(
+			array(
+				'redirect_url' => $redirect_url,
+				'reason'       => 'theme_builder_enabled',
+				'theme_status' => $theme_enabled,
+				'seeded'       => true,
+			)
+		);
+	} else {
+		// Theme builder NOT enabled - send to template picker for landing page.
+		$nonce        = wp_create_nonce( 'seedprod_nonce' );
+		$redirect_url = admin_url() . 'admin.php?page=seedprod_lite_template&_wpnonce=' . $nonce . '&from=post&id=' . $post_id . '#/template/' . $post_id;
+
+		wp_send_json_success(
+			array(
+				'redirect_url' => $redirect_url,
+				'reason'       => 'create_landing_page',
+				'theme_status' => false,
+			)
+		);
+	}
+}
+add_action( 'wp_ajax_seedprod_lite_v2_get_redirect_url', 'seedprod_lite_v2_get_redirect_url' );
+
+
+/**
+ * Remove SeedProd post meta when user clicks "Back to WordPress Editor"
+ */
+function seedprod_lite_v2_remove_post() {
+	// TODO: Add logic here.
+}
+add_action( 'wp_ajax_seedprod_lite_v2_remove_post', 'seedprod_lite_v2_remove_post' );
+
+
+/**
+ * ============================================================================
+ * SECTION 4: Admin Bar Menu Item
+ * ============================================================================
+ */
+
+/**
+ * Add "SeedProd Landing Page" to "+ New" menu in admin bar
+ *
+ * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
+ */
+function seedprod_lite_v2_add_menu_item( $wp_admin_bar ) {
+	// TODO: Add logic here.
+}
+add_action( 'admin_bar_menu', 'seedprod_lite_v2_add_menu_item', 80 );
+
+
+/**
+ * ============================================================================
+ * SECTION 5: Page Row Actions
+ * ============================================================================
+ */
+
+/**
+ * Add "Edit with SeedProd" link to Pages list table
+ *
+ * @param array   $actions The row action links.
+ * @param WP_Post $post    The post object.
+ * @return array Modified row action links.
+ */
+function seedprod_lite_v2_filter_page_row_actions( $actions, $post ) {
+	// Check for SeedProd pages.
+	$is_landing_page = get_post_meta( $post->ID, '_seedprod_page', true );
+	$is_theme_page   = get_post_meta( $post->ID, '_seedprod_edited_with_seedprod', true );
+
+	// PRIORITY: Check if page is a SeedProd page FIRST, before checking for homepage template.
+	// This ensures custom SeedProd pages set as homepage link to the page itself, not the theme template.
+	$edit_url = null;
+
+	if ( '1' === $is_landing_page || true === $is_landing_page || '1' === $is_theme_page || true === $is_theme_page ) {
+		// It's a SeedProd page - link to the page itself.
+		$edit_url = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $post->ID . '#/setup/' . $post->ID;
+	} else {
+		// NOT a SeedProd page - check if this is the WordPress homepage or blog page with a theme template.
+		$special_page_template_id = seedprod_lite_v2_find_special_page_template( $post->ID );
+		if ( null !== $special_page_template_id ) {
+			// This page is rendered by a theme template - link to the template.
+			$edit_url = admin_url() . 'admin.php?page=seedprod_lite_builder&id=' . $special_page_template_id . '#/setup/' . $special_page_template_id;
+		}
+	}
+
+	// Add the link if we have a URL.
+	if ( $edit_url ) {
+		$actions['edit_seedprod'] = sprintf(
+			'<a href="%1$s">%2$s</a>',
+			esc_url( $edit_url ),
+			__( 'Edit with SeedProd', 'coming-soon' )
+		);
+	}
+
+	return $actions;
+}
+add_filter( 'page_row_actions', 'seedprod_lite_v2_filter_page_row_actions', 11, 2 );
+
+
+/**
+ * ============================================================================
+ * SECTION 6: Page State Label
+ * ============================================================================
+ */
+
+/**
+ * Add "Edit with SeedProd" button to Classic Editor
+ * Shows button in the publish meta box for classic editor
+ */
+function seedprod_lite_v2_classic_editor_button() {
+	$screen = get_current_screen();
+
+	// Only run on page editor.
+	if ( ! $screen || 'page' !== $screen->post_type ) {
+		return;
+	}
+
+	// Check user permissions.
+	if ( ! current_user_can( 'edit_pages' ) ) {
+		return;
+	}
+
+	// Check if we should show the button (same logic as Gutenberg).
+	$theme_enabled = get_option( 'seedprod_theme_enabled' );
+
+	$post_id                 = get_the_ID();
+	$is_seedprod_page        = get_post_meta( $post_id, '_seedprod_page', true );
+	$is_edited_with_seedprod = get_post_meta( $post_id, '_seedprod_edited_with_seedprod', true );
+	// Handle both string '1' and boolean true.
+	$is_already_seedprod = ( '1' === $is_seedprod_page || true === $is_seedprod_page ) ||
+								( '1' === $is_edited_with_seedprod || true === $is_edited_with_seedprod );
+
+	// Only show button if theme builder is enabled OR page is already SeedProd.
+	if ( empty( $theme_enabled ) && ! $is_already_seedprod ) {
+		return;
+	}
+
+	?>
+	<div class="misc-pub-section seedprod-classic-editor-button" style="padding: 10px; border-top: 1px solid #ddd;">
+		<button type="button" id="seedprod-classic-edit-button" class="button button-primary button-large" style="width: 100%;">
+			<img src="<?php echo esc_url( SEEDPROD_PLUGIN_URL . 'public/svg/admin-bar-icon.svg' ); ?>" style="width: 20px; height: 20px; vertical-align: middle; margin-right: 7px;" />
+			<?php esc_html_e( 'Edit with SeedProd', 'coming-soon' ); ?>
+		</button>
+	</div>
+
+	<script type="text/javascript">
+	jQuery(document).ready(function($) {
+		$('#seedprod-classic-edit-button').on('click', function(e) {
+			e.preventDefault();
+
+			var postId = <?php echo absint( $post_id ); ?>;
+			var postStatus = '<?php echo esc_js( get_post_status( $post_id ) ); ?>';
+			var isAutoDraft = postStatus === 'auto-draft';
+
+			if (isAutoDraft) {
+				// For new posts, save first then redirect
+				// Get the title
+				var title = $('#title').val();
+				if (!title) {
+					title = 'SeedProd #' + postId;
+					$('#title').val(title);
+				}
+
+				// Trigger save
+				$('#publish').click();
+
+				// Wait a moment then redirect
+				setTimeout(function() {
+					getRedirectUrl(postId);
+				}, 1000);
+			} else {
+				// Existing post - redirect immediately
+				getRedirectUrl(postId);
+			}
+		});
+
+		function getRedirectUrl(postId) {
+			$.ajax({
+				url: ajaxurl,
+				type: 'POST',
+				data: {
+					action: 'seedprod_lite_v2_get_redirect_url',
+					nonce: '<?php echo esc_js( wp_create_nonce( 'seedprod_gutenberg_nonce' ) ); ?>',
+					post_id: postId
+				},
+				success: function(response) {
+					if (response.success) {
+						location.href = response.data.redirect_url;
+					} else {
+						console.error('Error:', response.data.message);
+						alert('Error: ' + response.data.message);
+					}
+				},
+				error: function(xhr, status, error) {
+					console.error('AJAX Error:', status, error);
+					alert('Failed to get redirect URL. Check console for details.');
+				}
+			});
+		}
+	});
+	</script>
+	<?php
+}
+add_action( 'post_submitbox_misc_actions', 'seedprod_lite_v2_classic_editor_button' );
+
+
+/**
+ * Add "SeedProd" label to page list
+ * Shows "SeedProd" for theme pages, "SeedProd Landing Page" for landing pages
+ *
+ * @param string[] $post_states An array of post display states.
+ * @param WP_Post  $post        The current post object.
+ * @return string[] Modified array of post display states.
+ */
+function seedprod_lite_v2_add_post_state( $post_states, $post ) {
+	// Only for pages.
+	if ( 'page' !== $post->post_type ) {
+		return $post_states;
+	}
+
+	// Check for theme page (_seedprod_edited_with_seedprod).
+	$is_theme_page = get_post_meta( $post->ID, '_seedprod_edited_with_seedprod', true );
+	if ( ! empty( $is_theme_page ) ) {
+		$post_states['seedprod-editor'] = __( 'SeedProd', 'coming-soon' );
+		return $post_states;
+	}
+
+	// Check for landing page (_seedprod_page).
+	$is_landing_page = get_post_meta( $post->ID, '_seedprod_page', true );
+	if ( ! empty( $is_landing_page ) ) {
+		$post_states['seedprod'] = __( 'SeedProd Landing Page', 'coming-soon' );
+		return $post_states;
+	}
+
+	return $post_states;
+}
+add_filter( 'display_post_states', 'seedprod_lite_v2_add_post_state', 10, 2 );
--- a/coming-soon/admin/partials/seedprod-admin-subscribers.php
+++ b/coming-soon/admin/partials/seedprod-admin-subscribers.php
@@ -46,7 +46,7 @@

 					<!-- Temporary link to old Vue interface if needed -->
 					<p>
-						<a href="<?php echo esc_url( admin_url( 'admin.php?page=seedprod_lite#/subscribers' ) ); ?>" class="button button-secondary">
+						<a href="<?php echo esc_url( admin_url( 'admin.php?page=seedprod_lite_settings&tab=subscribers' ) ); ?>" class="button button-secondary">
 							<?php esc_html_e( 'View in Legacy Interface', 'coming-soon' ); ?>
 						</a>
 					</p>
--- a/coming-soon/app/backwards/backwards_compatibility.php
+++ b/coming-soon/app/backwards/backwards_compatibility.php
@@ -1,5 +1,5 @@
 <?php
-// phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput, WordPress.WP.I18n.TextDomainMismatch, WordPress.NamingConventions.PrefixAllGlobals, WordPress.DB -- Legacy backward compatibility code.
+// phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput, WordPress.WP.I18n.TextDomainMismatch, WordPress.NamingConventions.PrefixAllGlobals, WordPress.DB, WordPress.WP.AlternativeFunctions -- Legacy backward compatibility code.


 function seedprod_lite_check_for_free_version() {
--- a/coming-soon/app/functions-utils.php
+++ b/coming-soon/app/functions-utils.php
@@ -2149,6 +2149,43 @@
 	);
 }

+/**
+ * Strip stringified Vue render-helper source from rendered SeedProd HTML.
+ *
+ * Vue 2 can serialize its `$createElement`/`_c` render helper into saved block
+ * markup, which then renders as literal text such as
+ * `function(e,n,r,i){return In(t,e,n,r,i,!0)}`. The inner helper name is a
+ * minified symbol that changes whenever the builder bundle is rebuilt, so it is
+ * matched by shape rather than by a hard-coded name (a previous fix hard-coded
+ * the name and silently stopped working when the minifier renamed it). Covers
+ * both the `!0` (`$createElement`) and `!1` (`_c`) variants.
+ *
+ * @param string $html The HTML to clean.
+ * @return string The HTML with any render-helper source removed.
+ */
+function seedprod_lite_strip_vue_render_helpers( $html ) {
+	if ( ! is_string( $html ) || '' === $html ) {
+		return $html;
+	}
+
+	// The pattern can only match strings containing `function`; skip the regex otherwise.
+	if ( false === strpos( $html, 'function' ) ) {
+		return $html;
+	}
+
+	// Match Vue's render-helper delegation specifically: the inner call repeats the
+	// four outer args verbatim (backreferences 1-4) after a context arg, so generic
+	// minified wrapper/currying functions are not stripped.
+	$cleaned = preg_replace(
+		'/functions*(s*([w$]+)s*,s*([w$]+)s*,s*([w$]+)s*,s*([w$]+)s*)s*{s*returns+[w$]+(s*[w$]+s*,s*1s*,s*2s*,s*3s*,s*4s*,s*!s*[01]s*)s*;?s*}/',
+		'',
+		$html
+	);
+
+	// preg_replace returns null on PCRE failure; never lose the original markup.
+	return null === $cleaned ? $html : $cleaned;
+}
+


 /**
--- a/coming-soon/app/lpage.php
+++ b/coming-soon/app/lpage.php
@@ -670,7 +670,7 @@
 			// end html custom comment.
 			$html = preg_replace( "'contenteditable="true"'", '', $html );
 			$html = preg_replace( "'spellcheck="false"'", '', $html );
-			$html = str_replace( 'function(e,n,r,i){return fn(t,e,n,r,i,!0)}', '', $html );
+			$html = seedprod_lite_strip_vue_render_helpers( $html );
 			// remove preview animation.
 			$html = str_replace( 'animate__', '', $html );
 			// remove sp-theme-template id.
--- a/coming-soon/app/nestednavmenu.php
+++ b/coming-soon/app/nestednavmenu.php
@@ -77,7 +77,7 @@

 		if ( 0 === $depth ) {
 			if ( '' !== $this->separators ) {
-				$output .= "<li class='separator menu-item'>" . $this->separators . '</li>';
+				$output .= "<li class='separator menu-item'>" . esc_html( $this->separators ) . '</li>';
 			}
 		}
 	}
@@ -111,7 +111,7 @@
 	}
 	$navmenu_seperator = '';
 	if ( isset( $menu_atts['menudivider'] ) ) {
-		$navmenu_seperator = $menu_atts['menudivider'];
+		$navmenu_seperator = sanitize_text_field( $menu_atts['menudivider'] );
 	}
 	$layout = '';
 	if ( isset( $menu_atts['layout'] ) ) {
--- a/coming-soon/coming-soon.php
+++ b/coming-soon/coming-soon.php
@@ -3,7 +3,7 @@
  * Plugin Name: Coming Soon Page, Maintenance Mode, Landing Pages & WordPress Website Builder by SeedProd
  * Plugin URI: https://www.seedprod.com/lite-upgrade/?utm_source=WordPress&utm_campaign=liteplugin&utm_medium=plugin-uri-link
  * Description: The Easiest WordPress Drag & Drop Page Builder that allows you to build your website, create Landing Pages, Coming Soon Pages, Maintenance Mode Pages and more.
- * Version:  6.20.2
+ * Version:  6.20.3
  * Author: SeedProd
  * Author URI: https://www.seedprod.com/lite-upgrade/?utm_source=WordPress&utm_campaign=liteplugin&utm_medium=author-uri-link
  * Text Domain: coming-soon
@@ -20,7 +20,7 @@

 define( 'SEEDPROD_BUILD', 'lite' );
 define( 'SEEDPROD_SLUG', 'coming-soon/coming-soon.php' );
-define( 'SEEDPROD_VERSION', '6.20.2' );
+define( 'SEEDPROD_VERSION', '6.20.3' );
 define( 'SEEDPROD_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
 // Example output: /Applications/MAMP/htdocs/wordpress/wp-content/plugins/seedprod/.
 define( 'SEEDPROD_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
--- a/coming-soon/resources/views/builder.php
+++ b/coming-soon/resources/views/builder.php
@@ -5,6 +5,64 @@
 require_once SEEDPROD_PLUGIN_PATH . 'resources/data-templates/basic-page.php';

 /**
+ * Custom merge function that preserves existing values but ensures all default keys exist
+ *
+ * @param array   $defaults Defaults.
+ * @param array   $settings Settings.
+ * @param integer $depth    Depth.
+ * @return array
+ */
+function seedprod_lite_merge_preserve_existing( $defaults, $settings, $depth = 0 ) {
+	// Guard clauses for safety.
+	if ( ! is_array( $defaults ) ) {
+		return $settings;
+	}
+	if ( ! is_array( $settings ) ) {
+		return $defaults;
+	}
+	// Prevent infinite recursion.
+	if ( $depth > 100 ) {
+		return $settings;
+	}
+
+	$result = $defaults;
+
+	foreach ( $settings as $key => $value ) {
+		// Special handling for 'items' array - preserve as-is without merging defaults.
+		if ( 'items' === $key || 'featuresList' === $key ) {
+			if ( is_array( $value ) ) {
+				$result[ $key ] = $value;
+			}
+			// If not an array (e.g. empty string), skip so the default from $result is used.
+			continue;
+		}
+
+		// If both values are arrays, merge recursively.
+		if ( isset( $result[ $key ] ) && is_array( $result[ $key ] ) && is_array( $value ) ) {
+			$result[ $key ] = seedprod_lite_merge_preserve_existing( $result[ $key ], $value, $depth + 1 );
+		} else {
+			// If the value is not an array, preserve the setting value.
+			$result[ $key ] = $value;
+		}
+	}
+
+	return $result;
+}
+
+/**
+ * Apply block-type defaults to a builder element's settings.
+ *
+ * @param array $element  Element passed by reference; its 'settings' are hydrated.
+ * @param array $defaults Block-type default templates keyed by type.
+ * @return void
+ */
+function seedprod_lite_apply_defaults( &$element, $defaults ) {
+	$type                = $element['type'] ?? '';
+	$element_defaults    = $defaults[ $type ] ?? array();
+	$element['settings'] = seedprod_lite_merge_preserve_existing( $element_defaults, $element['settings'] ?? array() );
+}
+
+/**
  * Rehydrate settings for the builder
  *
  * @param array  $object_to_hydrate            Object to hydrate.
@@ -23,57 +81,6 @@
 		$object_to_hydrate = array();
 	}

-	/**
-	 * Custom merge function that preserves existing values but ensures all default keys exist
-	 *
-	 * @param array   $defaults Defaults.
-	 * @param array   $settings Settings.
-	 * @param integer $depth    Depth.
-	 * @return array
-	 */
-	function seedprod_lite_merge_preserve_existing( $defaults, $settings, $depth = 0 ) {
-		// Guard clauses for safety.
-		if ( ! is_array( $defaults ) ) {
-			return $settings;
-		}
-		if ( ! is_array( $settings ) ) {
-			return $defaults;
-		}
-		// Prevent infinite recursion.
-		if ( $depth > 100 ) {
-			return $settings;
-		}
-
-		$result = $defaults;
-
-		foreach ( $settings as $key => $value ) {
-			// Special handling for 'items' array - preserve as-is without merging defaults.
-			if ( 'items' === $key || 'featuresList' === $key ) {
-				if ( is_array( $value ) ) {
-					$result[ $key ] = $value;
-				}
-				// If not an array (e.g. empty string), skip so the default from $result is used.
-				continue;
-			}
-
-			// If both values are arrays, merge recursively.
-			if ( isset( $result[ $key ] ) && is_array( $result[ $key ] ) && is_array( $value ) ) {
-				$result[ $key ] = seedprod_lite_merge_preserve_existing( $result[ $key ], $value, $depth + 1 );
-			} else {
-				// If the value is not an array, preserve the setting value.
-				$result[ $key ] = $value;
-			}
-		}
-
-		return $result;
-	}
-
-	function seedprod_lite_apply_defaults( &$element, $defaults ) {
-		$type                = $element['type'] ?? '';
-		$element_defaults    = $defaults[ $type ] ?? array();
-		$element['settings'] = seedprod_lite_merge_preserve_existing( $element_defaults, $element['settings'] ?? array() );
-	}
-
 	$document_defaults = $defaults['document'] ?? array();
 	$existing_document = ( isset( $object_to_hydrate['document'] ) && is_array( $object_to_hydrate['document'] ) )
 		? $object_to_hydrate['document']
--- a/coming-soon/resources/views/seedprod-preview.php
+++ b/coming-soon/resources/views/seedprod-preview.php
@@ -97,8 +97,7 @@
 	$lpage_uuid       = get_post_meta( $post->ID, '_seedprod_page_uuid', true );
 }

-// remove vue comment bug.
-$content = str_replace( 'function(e,n,r,i){return fn(t,e,n,r,i,!0)}', '', $content );
+$content = seedprod_lite_strip_vue_render_helpers( $content );

 $plugin_url = SEEDPROD_PLUGIN_URL;

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.