Files
WooNooW/IMPLEMENTATION_PLAN_META_COMPAT.md
dwindown cb91d0841c plan: Complete implementation plan for Level 1 meta compatibility
**Implementation Plan Created: IMPLEMENTATION_PLAN_META_COMPAT.md**

Following all documentation guidelines:
- ADDON_BRIDGE_PATTERN.md (3-level strategy)
- ADDON_DEVELOPMENT_GUIDE.md (hook system)
- ADDON_REACT_INTEGRATION.md (React exposure)
- METABOX_COMPAT.md (compatibility requirements)

**Key Principles:**
1.  Zero addon dependencies in core
2.  Listen to WP/WooCommerce hooks (NOT WooNooW-specific)
3.  Community does NOTHING extra
4.  Do NOT support specific plugins
5.  Do NOT integrate plugins into core

**3 Phases:**

Phase 1: Backend API Enhancement (2-3 days)
- Add get_order_meta_data() / get_product_meta_data()
- Add update_order_meta_data() / update_product_meta_data()
- Expose meta in API responses
- Add filters: woonoow/order_allowed_private_meta
- Add filters: woonoow/order_updatable_meta
- Add filters: woonoow/order_api_data
- Add actions: woonoow/order_updated

Phase 2: Frontend Components (3-4 days)
- Create MetaFields.tsx component (generic field renderer)
- Create useMetaFields.ts hook (registry access)
- Update Orders/Edit.tsx to include meta fields
- Update Products/Edit.tsx to include meta fields
- Support all field types: text, textarea, number, select, checkbox

Phase 3: PHP Registry System (2-3 days)
- Create MetaFieldsRegistry.php
- Add action: woonoow/register_meta_fields
- Auto-register fields to allowed meta lists
- Localize to JavaScript (window.WooNooWMetaFields)
- Initialize in Plugin.php

**Testing Plan:**
- WooCommerce Shipment Tracking plugin
- Advanced Custom Fields (ACF)
- Custom metabox plugins
- Meta data save/update
- Field registration

**Timeline:** 8-12 days (1.5-2 weeks)

**Success Criteria:**
 Plugins using standard WP/WooCommerce meta work automatically
 No special integration needed
 Meta fields visible and editable
 Zero coupling with specific plugins
 Community does NOTHING extra

Ready to start implementation!
2025-11-20 12:17:35 +07:00

641 lines
18 KiB
Markdown

# Implementation Plan: Level 1 Meta Compatibility
## Objective
Make WooNooW listen to ALL standard WordPress/WooCommerce hooks for custom meta fields automatically.
## Principles (From Documentation Review)
### From ADDON_BRIDGE_PATTERN.md:
1. ✅ WooNooW Core = Zero addon dependencies
2. ✅ We listen to WP/WooCommerce hooks (NOT WooNooW-specific)
3. ✅ Community does NOTHING extra
4. ❌ We do NOT support specific plugins
5. ❌ We do NOT integrate plugins into core
### From ADDON_DEVELOPMENT_GUIDE.md:
1. ✅ Hook system for functional extensions
2. ✅ Zero coupling with core
3. ✅ WordPress-style filters and actions
### From ADDON_REACT_INTEGRATION.md:
1. ✅ Expose React runtime on window
2. ✅ Support vanilla JS/jQuery addons
3. ✅ No build process required for simple addons
---
## Implementation Strategy
### Phase 1: Backend API Enhancement (2-3 days)
#### 1.1 OrdersController - Expose Meta Data
**File:** `includes/Api/OrdersController.php`
**Changes:**
```php
public static function show(WP_REST_Request $req) {
$order = wc_get_order($id);
// ... existing data ...
// Expose meta data (Level 1 compatibility)
$meta_data = self::get_order_meta_data($order);
$data['meta'] = $meta_data;
// Allow plugins to modify response
$data = apply_filters('woonoow/order_api_data', $data, $order, $req);
return new WP_REST_Response($data, 200);
}
/**
* Get order meta data for API exposure
* Filters out internal meta unless explicitly allowed
*/
private static function get_order_meta_data($order) {
$meta_data = [];
foreach ($order->get_meta_data() as $meta) {
$key = $meta->key;
$value = $meta->value;
// Skip internal WooCommerce meta (starts with _wc_)
if (strpos($key, '_wc_') === 0) {
continue;
}
// Public meta (no underscore) - always expose
if (strpos($key, '_') !== 0) {
$meta_data[$key] = $value;
continue;
}
// Private meta (starts with _) - check if allowed
$allowed_private = apply_filters('woonoow/order_allowed_private_meta', [
// Common shipping tracking fields
'_tracking_number',
'_tracking_provider',
'_tracking_url',
'_shipment_tracking_items',
'_wc_shipment_tracking_items',
// Allow plugins to add their meta
], $order);
if (in_array($key, $allowed_private, true)) {
$meta_data[$key] = $value;
}
}
return $meta_data;
}
```
**Update Method:**
```php
public static function update(WP_REST_Request $req) {
$order = wc_get_order($id);
$data = $req->get_json_params();
// ... existing update logic ...
// Update custom meta fields (Level 1 compatibility)
if (isset($data['meta']) && is_array($data['meta'])) {
self::update_order_meta_data($order, $data['meta']);
}
$order->save();
// Allow plugins to perform additional updates
do_action('woonoow/order_updated', $order, $data, $req);
return new WP_REST_Response(['success' => true], 200);
}
/**
* Update order meta data from API
*/
private static function update_order_meta_data($order, $meta_updates) {
// Get allowed updatable meta keys
$allowed = apply_filters('woonoow/order_updatable_meta', [
'_tracking_number',
'_tracking_provider',
'_tracking_url',
// Allow plugins to add their meta
], $order);
foreach ($meta_updates as $key => $value) {
// Public meta (no underscore) - always allow
if (strpos($key, '_') !== 0) {
$order->update_meta_data($key, $value);
continue;
}
// Private meta - check if allowed
if (in_array($key, $allowed, true)) {
$order->update_meta_data($key, $value);
}
}
}
```
#### 1.2 ProductsController - Expose Meta Data
**File:** `includes/Api/ProductsController.php`
**Changes:** (Same pattern as OrdersController)
```php
public static function get_product(WP_REST_Request $request) {
$product = wc_get_product($id);
// ... existing data ...
// Expose meta data (Level 1 compatibility)
$meta_data = self::get_product_meta_data($product);
$data['meta'] = $meta_data;
// Allow plugins to modify response
$data = apply_filters('woonoow/product_api_data', $data, $product, $request);
return new WP_REST_Response($data, 200);
}
private static function get_product_meta_data($product) {
// Same logic as orders
}
public static function update_product(WP_REST_Request $request) {
// ... existing logic ...
if (isset($data['meta']) && is_array($data['meta'])) {
self::update_product_meta_data($product, $data['meta']);
}
do_action('woonoow/product_updated', $product, $data, $request);
}
```
---
### Phase 2: Frontend Components (3-4 days)
#### 2.1 MetaFields Component
**File:** `admin-spa/src/components/MetaFields.tsx`
**Purpose:** Generic component to display/edit meta fields
```tsx
interface MetaField {
key: string;
label: string;
type: 'text' | 'textarea' | 'number' | 'select' | 'date' | 'checkbox';
options?: Array<{value: string; label: string}>;
section?: string;
description?: string;
placeholder?: string;
}
interface MetaFieldsProps {
meta: Record<string, any>;
fields: MetaField[];
onChange: (key: string, value: any) => void;
readOnly?: boolean;
}
export function MetaFields({ meta, fields, onChange, readOnly }: MetaFieldsProps) {
if (fields.length === 0) return null;
// Group fields by section
const sections = fields.reduce((acc, field) => {
const section = field.section || 'Additional Fields';
if (!acc[section]) acc[section] = [];
acc[section].push(field);
return acc;
}, {} as Record<string, MetaField[]>);
return (
<div className="space-y-6">
{Object.entries(sections).map(([section, sectionFields]) => (
<Card key={section}>
<CardHeader>
<CardTitle>{section}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{sectionFields.map(field => (
<div key={field.key}>
<Label htmlFor={field.key}>
{field.label}
{field.description && (
<span className="text-xs text-muted-foreground ml-2">
{field.description}
</span>
)}
</Label>
{field.type === 'text' && (
<Input
id={field.key}
value={meta[field.key] || ''}
onChange={(e) => onChange(field.key, e.target.value)}
disabled={readOnly}
placeholder={field.placeholder}
/>
)}
{field.type === 'textarea' && (
<Textarea
id={field.key}
value={meta[field.key] || ''}
onChange={(e) => onChange(field.key, e.target.value)}
disabled={readOnly}
placeholder={field.placeholder}
rows={4}
/>
)}
{field.type === 'number' && (
<Input
id={field.key}
type="number"
value={meta[field.key] || ''}
onChange={(e) => onChange(field.key, e.target.value)}
disabled={readOnly}
placeholder={field.placeholder}
/>
)}
{field.type === 'select' && field.options && (
<Select
value={meta[field.key] || ''}
onValueChange={(value) => onChange(field.key, value)}
disabled={readOnly}
>
<SelectTrigger id={field.key}>
<SelectValue placeholder={field.placeholder || 'Select...'} />
</SelectTrigger>
<SelectContent>
{field.options.map(opt => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{field.type === 'checkbox' && (
<div className="flex items-center space-x-2">
<Checkbox
id={field.key}
checked={!!meta[field.key]}
onCheckedChange={(checked) => onChange(field.key, checked)}
disabled={readOnly}
/>
<label htmlFor={field.key} className="text-sm cursor-pointer">
{field.placeholder || 'Enable'}
</label>
</div>
)}
</div>
))}
</CardContent>
</Card>
))}
</div>
);
}
```
#### 2.2 useMetaFields Hook
**File:** `admin-spa/src/hooks/useMetaFields.ts`
**Purpose:** Hook to get registered meta fields from global registry
```tsx
interface MetaFieldsRegistry {
orders: MetaField[];
products: MetaField[];
}
// Global registry exposed by PHP
declare global {
interface Window {
WooNooWMetaFields?: MetaFieldsRegistry;
}
}
export function useMetaFields(type: 'orders' | 'products'): MetaField[] {
const [fields, setFields] = useState<MetaField[]>([]);
useEffect(() => {
// Get fields from global registry (set by PHP)
const registry = window.WooNooWMetaFields || { orders: [], products: [] };
setFields(registry[type] || []);
// Listen for dynamic field registration
const handleFieldsUpdated = (e: CustomEvent) => {
if (e.detail.type === type) {
setFields(e.detail.fields);
}
};
window.addEventListener('woonoow:meta_fields_updated', handleFieldsUpdated as EventListener);
return () => {
window.removeEventListener('woonoow:meta_fields_updated', handleFieldsUpdated as EventListener);
};
}, [type]);
return fields;
}
```
#### 2.3 Integration in Order Edit
**File:** `admin-spa/src/routes/Orders/Edit.tsx`
```tsx
import { MetaFields } from '@/components/MetaFields';
import { useMetaFields } from '@/hooks/useMetaFields';
export default function OrderEdit() {
const { id } = useParams();
const metaFields = useMetaFields('orders');
const [formData, setFormData] = useState({
// ... existing fields ...
meta: {},
});
useEffect(() => {
if (orderQ.data) {
setFormData(prev => ({
...prev,
meta: orderQ.data.meta || {},
}));
}
}, [orderQ.data]);
const handleMetaChange = (key: string, value: any) => {
setFormData(prev => ({
...prev,
meta: {
...prev.meta,
[key]: value,
},
}));
};
return (
<div className="space-y-6">
{/* Existing order form fields */}
<OrderForm data={formData} onChange={setFormData} />
{/* Custom meta fields (Level 1 compatibility) */}
{metaFields.length > 0 && (
<MetaFields
meta={formData.meta}
fields={metaFields}
onChange={handleMetaChange}
/>
)}
</div>
);
}
```
---
### Phase 3: PHP Registry System (2-3 days)
#### 3.1 MetaFieldsRegistry Class
**File:** `includes/Compat/MetaFieldsRegistry.php`
**Purpose:** Allow plugins to register meta fields for display in SPA
```php
<?php
namespace WooNooW\Compat;
class MetaFieldsRegistry {
private static $order_fields = [];
private static $product_fields = [];
public static function init() {
add_action('admin_enqueue_scripts', [__CLASS__, 'localize_fields']);
// Allow plugins to register fields
do_action('woonoow/register_meta_fields');
}
/**
* Register order meta field
*
* @param string $key Meta key (e.g., '_tracking_number')
* @param array $args Field configuration
*/
public static function register_order_field($key, $args = []) {
$defaults = [
'key' => $key,
'label' => self::format_label($key),
'type' => 'text',
'section' => 'Additional Fields',
'description' => '',
'placeholder' => '',
];
self::$order_fields[$key] = array_merge($defaults, $args);
// Auto-add to allowed meta lists
add_filter('woonoow/order_allowed_private_meta', function($allowed) use ($key) {
if (!in_array($key, $allowed, true)) {
$allowed[] = $key;
}
return $allowed;
});
add_filter('woonoow/order_updatable_meta', function($allowed) use ($key) {
if (!in_array($key, $allowed, true)) {
$allowed[] = $key;
}
return $allowed;
});
}
/**
* Register product meta field
*/
public static function register_product_field($key, $args = []) {
$defaults = [
'key' => $key,
'label' => self::format_label($key),
'type' => 'text',
'section' => 'Additional Fields',
'description' => '',
'placeholder' => '',
];
self::$product_fields[$key] = array_merge($defaults, $args);
// Auto-add to allowed meta lists
add_filter('woonoow/product_allowed_private_meta', function($allowed) use ($key) {
if (!in_array($key, $allowed, true)) {
$allowed[] = $key;
}
return $allowed;
});
add_filter('woonoow/product_updatable_meta', function($allowed) use ($key) {
if (!in_array($key, $allowed, true)) {
$allowed[] = $key;
}
return $allowed;
});
}
/**
* Format meta key to human-readable label
*/
private static function format_label($key) {
// Remove leading underscore
$label = ltrim($key, '_');
// Replace underscores with spaces
$label = str_replace('_', ' ', $label);
// Capitalize words
$label = ucwords($label);
return $label;
}
/**
* Localize fields to JavaScript
*/
public static function localize_fields() {
if (!is_admin()) return;
// Allow plugins to modify fields before localizing
$order_fields = apply_filters('woonoow/meta_fields_orders', array_values(self::$order_fields));
$product_fields = apply_filters('woonoow/meta_fields_products', array_values(self::$product_fields));
wp_localize_script('woonoow-admin', 'WooNooWMetaFields', [
'orders' => $order_fields,
'products' => $product_fields,
]);
}
}
```
#### 3.2 Initialize Registry
**File:** `includes/Core/Plugin.php`
```php
// Add to init() method
\WooNooW\Compat\MetaFieldsRegistry::init();
```
---
## Testing Plan
### Test Case 1: WooCommerce Shipment Tracking
```php
// Plugin stores tracking number
update_post_meta($order_id, '_tracking_number', '1234567890');
// Expected: Field visible in WooNooW order edit
// Expected: Can edit and save tracking number
```
### Test Case 2: Advanced Custom Fields (ACF)
```php
// ACF stores custom field
update_post_meta($product_id, 'custom_field', 'value');
// Expected: Field visible in WooNooW product edit
// Expected: Can edit and save custom field
```
### Test Case 3: Custom Metabox Plugin
```php
// Plugin registers field
add_action('woonoow/register_meta_fields', function() {
\WooNooW\Compat\MetaFieldsRegistry::register_order_field('_custom_field', [
'label' => 'Custom Field',
'type' => 'text',
'section' => 'My Plugin',
]);
});
// Expected: Field appears in "My Plugin" section
// Expected: Can edit and save
```
---
## Implementation Checklist
### Backend (PHP)
- [ ] Add `get_order_meta_data()` to OrdersController
- [ ] Add `update_order_meta_data()` to OrdersController
- [ ] Add `get_product_meta_data()` to ProductsController
- [ ] Add `update_product_meta_data()` to ProductsController
- [ ] Add filters: `woonoow/order_allowed_private_meta`
- [ ] Add filters: `woonoow/order_updatable_meta`
- [ ] Add filters: `woonoow/product_allowed_private_meta`
- [ ] Add filters: `woonoow/product_updatable_meta`
- [ ] Add filters: `woonoow/order_api_data`
- [ ] Add filters: `woonoow/product_api_data`
- [ ] Add actions: `woonoow/order_updated`
- [ ] Add actions: `woonoow/product_updated`
- [ ] Create `MetaFieldsRegistry.php`
- [ ] Add action: `woonoow/register_meta_fields`
- [ ] Initialize registry in Plugin.php
### Frontend (React/TypeScript)
- [ ] Create `MetaFields.tsx` component
- [ ] Create `useMetaFields.ts` hook
- [ ] Update `Orders/Edit.tsx` to include meta fields
- [ ] Update `Orders/View.tsx` to display meta fields (read-only)
- [ ] Update `Products/Edit.tsx` to include meta fields
- [ ] Add meta fields to Product detail page
### Testing
- [ ] Test with WooCommerce Shipment Tracking
- [ ] Test with ACF (Advanced Custom Fields)
- [ ] Test with custom metabox plugin
- [ ] Test meta data save/update
- [ ] Test meta data display in detail view
- [ ] Test field registration via `woonoow/register_meta_fields`
---
## Timeline
- **Phase 1 (Backend):** 2-3 days
- **Phase 2 (Frontend):** 3-4 days
- **Phase 3 (Registry):** 2-3 days
- **Testing:** 1-2 days
**Total:** 8-12 days (1.5-2 weeks)
---
## Success Criteria
✅ Plugins using standard WP/WooCommerce meta storage work automatically
✅ No special integration needed from plugin developers
✅ Meta fields visible and editable in WooNooW admin
✅ Data saved correctly to WooCommerce database
✅ Compatible with popular plugins (Shipment Tracking, ACF, etc.)
✅ Follows 3-level compatibility strategy
✅ Zero coupling with specific plugins
✅ Community does NOTHING extra for Level 1 compatibility