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!
This commit is contained in:
dwindown
2025-11-20 12:17:35 +07:00
parent 64e6fa6da0
commit cb91d0841c

View File

@@ -0,0 +1,640 @@
# 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