feat: Page Editor v1.0 - canonical schema, SSR parity, and migration
Major improvements to WooNooW Page Editor system: Schema & Architecture: - Canonical section schema with unified sectionSchema.ts - Normalized feature-grid to use items (not features) - Standardized default values across all section types - Schema versioning with automatic migration on read Backend (PHP): - Enhanced PlaceholderRenderer with typed output contracts - Added fallback behavior for empty/invalid dynamic sources - Added caching support for post data resolution - New SchemaMigration class for backward compatibility - New Features class for feature flags - Enhanced PageSSR with full style support - Removed controller-level special-casing for related_posts Frontend (Admin SPA): - Updated CanvasRenderer with schema-aware transformation - Enhanced InspectorPanel with canonical schema metadata - Added new section renderers Frontend (Customer SPA): - New section components: BentoCategoryGrid, MarqueeBanner, ProductCarousel, ShoppableImage - Updated FeatureGridSection for items prop contract Testing: - Add PHP tests: SchemaMigrationTest, PlaceholderRendererTest, PageSSRTest - Add TypeScript tests: schema-integration, feature-grid-regression - Add parity tests for React vs SSR content matching - Add CI script: check-schema-drift.mjs - Add VERIFICATION_CHECKLIST.md Documentation: - RELEASE_NOTES-v1.0.md with full release notes - docs/PAGE_EDITOR_SECTION_SCHEMA_V1.md - docs/PAGE_EDITOR_SSR_COVERAGE_AUDIT.md
This commit is contained in:
@@ -527,9 +527,11 @@ class ProductsController
|
||||
|
||||
// Virtual and downloadable
|
||||
if (array_key_exists('virtual', $data)) {
|
||||
error_log("Setting virtual to: " . ($data['virtual'] ? 'true' : 'false') . " for product ID: " . $product->get_id());
|
||||
$product->set_virtual((bool) $data['virtual']);
|
||||
}
|
||||
if (array_key_exists('downloadable', $data)) {
|
||||
error_log("Setting downloadable to: " . ($data['downloadable'] ? 'true' : 'false') . " for product ID: " . $product->get_id());
|
||||
$product->set_downloadable((bool) $data['downloadable']);
|
||||
}
|
||||
if (array_key_exists('featured', $data)) {
|
||||
@@ -887,6 +889,62 @@ class ProductsController
|
||||
}
|
||||
$data['gallery'] = $gallery;
|
||||
|
||||
// Video / rich media - stored in product meta
|
||||
$video_url = get_post_meta($product->get_id(), '_woonoow_video_url', true) ?: '';
|
||||
$data['video_url'] = $video_url;
|
||||
|
||||
// Detect embed type (youtube / vimeo / direct mp4)
|
||||
$data['video_type'] = '';
|
||||
if ($video_url) {
|
||||
if (strpos($video_url, 'youtube.com') !== false || strpos($video_url, 'youtu.be') !== false) {
|
||||
$data['video_type'] = 'youtube';
|
||||
} elseif (strpos($video_url, 'vimeo.com') !== false) {
|
||||
$data['video_type'] = 'vimeo';
|
||||
} else {
|
||||
$data['video_type'] = 'mp4';
|
||||
}
|
||||
}
|
||||
|
||||
// Media attachments - allow plugins/themes to inject additional media (e.g. 3D models, extra videos)
|
||||
$media_attachments = apply_filters('woonoow_product_media_attachments', [], $product->get_id(), $product);
|
||||
$data['media_attachments'] = is_array($media_attachments) ? $media_attachments : [];
|
||||
|
||||
// Cross-sells (shown in mini-cart / cart page)
|
||||
$cross_sell_ids = $product->get_cross_sell_ids();
|
||||
$cross_sells = [];
|
||||
foreach (array_slice($cross_sell_ids, 0, 4) as $cs_id) {
|
||||
$cs = wc_get_product($cs_id);
|
||||
if (!$cs || !$cs->is_visible()) continue;
|
||||
$cs_image_id = $cs->get_image_id();
|
||||
$cross_sells[] = [
|
||||
'id' => $cs->get_id(),
|
||||
'name' => $cs->get_name(),
|
||||
'slug' => $cs->get_slug(),
|
||||
'price' => $cs->get_price(),
|
||||
'regular_price' => $cs->get_regular_price(),
|
||||
'image' => $cs_image_id ? wp_get_attachment_url($cs_image_id) : '',
|
||||
];
|
||||
}
|
||||
$data['cross_sells'] = $cross_sells;
|
||||
|
||||
// Upsells (shown on product page below main content)
|
||||
$upsell_ids = $product->get_upsell_ids();
|
||||
$upsells = [];
|
||||
foreach (array_slice($upsell_ids, 0, 4) as $up_id) {
|
||||
$up = wc_get_product($up_id);
|
||||
if (!$up || !$up->is_visible()) continue;
|
||||
$up_image_id = $up->get_image_id();
|
||||
$upsells[] = [
|
||||
'id' => $up->get_id(),
|
||||
'name' => $up->get_name(),
|
||||
'slug' => $up->get_slug(),
|
||||
'price' => $up->get_price(),
|
||||
'regular_price' => $up->get_regular_price(),
|
||||
'image' => $up_image_id ? wp_get_attachment_url($up_image_id) : '',
|
||||
];
|
||||
}
|
||||
$data['upsells'] = $upsells;
|
||||
|
||||
// Variable product specifics
|
||||
if ($product->is_type('variable')) {
|
||||
$data['attributes'] = self::get_product_attributes($product);
|
||||
@@ -993,6 +1051,8 @@ class ProductsController
|
||||
'stock_status' => $variation->get_stock_status(),
|
||||
'stock_quantity' => $variation->get_stock_quantity(),
|
||||
'manage_stock' => $variation->get_manage_stock(),
|
||||
'virtual' => $variation->is_virtual(),
|
||||
'downloadable' => $variation->is_downloadable(),
|
||||
'attributes' => $formatted_attributes,
|
||||
'image_id' => $variation->get_image_id(),
|
||||
'image_url' => $image_url,
|
||||
@@ -1118,9 +1178,11 @@ class ProductsController
|
||||
$variation->set_image_id($var_data['image_id']);
|
||||
}
|
||||
|
||||
// Inherit virtual status from parent if parent is virtual
|
||||
if ($product->is_virtual()) {
|
||||
$variation->set_virtual(true);
|
||||
if (array_key_exists('virtual', $var_data)) {
|
||||
$variation->set_virtual((bool) $var_data['virtual']);
|
||||
}
|
||||
if (array_key_exists('downloadable', $var_data)) {
|
||||
$variation->set_downloadable((bool) $var_data['downloadable']);
|
||||
}
|
||||
|
||||
// Save variation first
|
||||
|
||||
Reference in New Issue
Block a user