docs: Critical metabox & custom fields compatibility gap identified

**Issue: Third-Party Plugin Compatibility**

Current Status:  NOT IMPLEMENTED
Priority: 🔴 CRITICAL - Blocks production readiness

**Problem:**
Our SPA admin does NOT expose:
- Custom meta fields from third-party plugins
- WordPress metaboxes (add_meta_box)
- WooCommerce custom fields
- ACF/CMB2/Pods fields
- Plugin-injected data (e.g., Tracking Number)

**Example Use Case:**
Plugin: WooCommerce Shipment Tracking
- Adds 'Tracking Number' metabox to order edit page
- Stores: _tracking_number meta field
- Current:  NOT visible in WooNooW admin
- Expected:  Should be visible and editable

**Impact:**
1. Breaks compatibility with popular plugins
2. Users cannot see/edit custom fields
3. Data exists but not accessible in SPA
4. Forces users back to classic admin
5. BLOCKS production readiness

**Solution Architecture:**

Phase 1: API Layer (2-3 days)
- Expose meta_data in OrdersController::show()
- Expose meta_data in ProductsController::get_product()
- Add filters: woonoow/order_api_data, woonoow/product_api_data
- Add filters: woonoow/order_allowed_private_meta
- Add actions: woonoow/order_updated, woonoow/product_updated

Phase 2: Frontend Components (3-4 days)
- Create MetaFields.tsx component
- Create useMetaFields.ts hook
- Update Orders/Edit.tsx to include meta fields
- Update Products/Edit.tsx to include meta fields
- Add meta fields to detail pages

Phase 3: Plugin Integration (2-3 days)
- Create MetaFieldsRegistry.php
- Add woonoow/register_meta_fields action
- Localize fields to JavaScript
- Create example: ShipmentTracking.php integration
- Document integration pattern

**Documentation Created:**
- METABOX_COMPAT.md - Complete implementation guide
- Includes code examples for all phases
- Includes third-party integration guide
- Includes testing checklist

**Updated:**
- PROJECT_SOP.md - Added metabox compat reference
- Marked as CRITICAL requirement
- Noted as blocking production readiness

**Timeline:**
Total: 1-2 weeks implementation

**Blocking:**
-  Coupons CRUD (can proceed)
-  Customers CRUD (can proceed)
-  Production readiness (BLOCKED)

**Next Steps:**
1. Review METABOX_COMPAT.md
2. Prioritize implementation
3. Start with Phase 1 (API layer)
4. Test with popular plugins (Shipment Tracking, ACF)
This commit is contained in:
dwindown
2025-11-20 11:11:06 +07:00
parent 316cee846d
commit f7dca7bc28
2 changed files with 558 additions and 0 deletions

552
METABOX_COMPAT.md Normal file
View File

@@ -0,0 +1,552 @@
# WooNooW Metabox & Custom Fields Compatibility
## Current Status: ❌ NOT IMPLEMENTED
**Critical Gap:** Our SPA admin does NOT currently expose custom meta fields, metaboxes, or third-party plugin data injected via WordPress/WooCommerce hooks.
### Example Use Case:
```
Plugin: WooCommerce Shipment Tracking
- Adds "Tracking Number" metabox to order edit page
- Uses: add_meta_box('wc_shipment_tracking', ...)
- Stores: update_post_meta($order_id, '_tracking_number', $value)
Current Behavior: ❌ Field NOT visible in WooNooW admin
Expected Behavior: ✅ Field should be visible and editable
```
---
## Problem Analysis
### 1. Orders API (`OrdersController.php`)
**Current Implementation:**
```php
public static function show(WP_REST_Request $req) {
$order = wc_get_order($id);
$data = [
'id' => $order->get_id(),
'status' => $order->get_status(),
'billing' => [...],
'shipping' => [...],
'items' => [...],
// ... hardcoded fields only
];
return new WP_REST_Response($data, 200);
}
```
**Missing:**
- ❌ No `get_meta_data()` exposure
- ❌ No `apply_filters('woonoow/order_data', $data, $order)`
- ❌ No metabox hook listening
- ❌ No custom field groups
### 2. Products API (`ProductsController.php`)
**Current Implementation:**
```php
public static function get_product(WP_REST_Request $request) {
$product = wc_get_product($id);
return new WP_REST_Response([
'id' => $product->get_id(),
'name' => $product->get_name(),
// ... hardcoded fields only
], 200);
}
```
**Missing:**
- ❌ No custom product meta exposure
- ❌ No `apply_filters('woonoow/product_data', $data, $product)`
- ❌ No ACF/CMB2/Pods integration
- ❌ No custom tabs/panels
---
## Solution Architecture
### Phase 1: Meta Data Exposure (API Layer)
#### 1.1 Orders API Enhancement
**Add to `OrdersController::show()`:**
```php
public static function show(WP_REST_Request $req) {
$order = wc_get_order($id);
// ... existing data ...
// Expose all meta data
$meta_data = [];
foreach ($order->get_meta_data() as $meta) {
$key = $meta->key;
// Skip internal/private meta (starts with _)
// unless explicitly allowed
if (strpos($key, '_') === 0) {
$allowed_private = apply_filters('woonoow/order_allowed_private_meta', [
'_tracking_number',
'_tracking_provider',
'_shipment_tracking_items',
'_wc_shipment_tracking_items',
// Add more as needed
], $order);
if (!in_array($key, $allowed_private, true)) {
continue;
}
}
$meta_data[$key] = $meta->value;
}
$data['meta'] = $meta_data;
// Allow plugins to add/modify data
$data = apply_filters('woonoow/order_api_data', $data, $order, $req);
return new WP_REST_Response($data, 200);
}
```
**Add to `OrdersController::update()`:**
```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
if (isset($data['meta']) && is_array($data['meta'])) {
foreach ($data['meta'] as $key => $value) {
// Validate meta key is allowed
$allowed = apply_filters('woonoow/order_updatable_meta', [
'_tracking_number',
'_tracking_provider',
// Add more as needed
], $order);
if (in_array($key, $allowed, true)) {
$order->update_meta_data($key, $value);
}
}
}
$order->save();
// Allow plugins to perform additional updates
do_action('woonoow/order_updated', $order, $data, $req);
return new WP_REST_Response(['success' => true], 200);
}
```
#### 1.2 Products API Enhancement
**Add to `ProductsController::get_product()`:**
```php
public static function get_product(WP_REST_Request $request) {
$product = wc_get_product($id);
// ... existing data ...
// Expose all meta data
$meta_data = [];
foreach ($product->get_meta_data() as $meta) {
$key = $meta->key;
// Skip internal meta unless allowed
if (strpos($key, '_') === 0) {
$allowed_private = apply_filters('woonoow/product_allowed_private_meta', [
'_custom_field_example',
// Add more as needed
], $product);
if (!in_array($key, $allowed_private, true)) {
continue;
}
}
$meta_data[$key] = $meta->value;
}
$data['meta'] = $meta_data;
// Allow plugins to add/modify data
$data = apply_filters('woonoow/product_api_data', $data, $product, $request);
return new WP_REST_Response($data, 200);
}
```
---
### Phase 2: Frontend Rendering (React Components)
#### 2.1 Dynamic Meta Fields Component
**Create: `admin-spa/src/components/MetaFields.tsx`**
```tsx
interface MetaField {
key: string;
label: string;
type: 'text' | 'textarea' | 'number' | 'select' | 'date';
options?: Array<{value: string; label: string}>;
section?: string; // Group fields into sections
}
interface MetaFieldsProps {
meta: Record<string, any>;
fields: MetaField[];
onChange: (key: string, value: any) => void;
readOnly?: boolean;
}
export function MetaFields({ meta, fields, onChange, readOnly }: MetaFieldsProps) {
// Group fields by section
const sections = fields.reduce((acc, field) => {
const section = field.section || 'Other';
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>{field.label}</Label>
{field.type === 'text' && (
<Input
value={meta[field.key] || ''}
onChange={(e) => onChange(field.key, e.target.value)}
disabled={readOnly}
/>
)}
{field.type === 'textarea' && (
<Textarea
value={meta[field.key] || ''}
onChange={(e) => onChange(field.key, e.target.value)}
disabled={readOnly}
/>
)}
{/* Add more field types as needed */}
</div>
))}
</CardContent>
</Card>
))}
</div>
);
}
```
#### 2.2 Hook System for Field Registration
**Create: `admin-spa/src/hooks/useMetaFields.ts`**
```tsx
interface MetaFieldsRegistry {
orders: MetaField[];
products: MetaField[];
}
// Global registry (can be extended by plugins via window object)
declare global {
interface Window {
WooNooWMetaFields?: MetaFieldsRegistry;
}
}
export function useMetaFields(type: 'orders' | 'products'): MetaField[] {
const [fields, setFields] = useState<MetaField[]>([]);
useEffect(() => {
// Get fields from global registry
const registry = window.WooNooWMetaFields || { orders: [], products: [] };
setFields(registry[type] || []);
}, [type]);
return fields;
}
```
#### 2.3 Integration in Order Edit Form
**Update: `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 orderQ = useQuery({
queryKey: ['order', id],
queryFn: () => api.get(`/orders/${id}`),
});
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>
{/* Existing order form fields */}
{/* Custom meta fields */}
{metaFields.length > 0 && (
<MetaFields
meta={formData.meta}
fields={metaFields}
onChange={handleMetaChange}
/>
)}
</div>
);
}
```
---
### Phase 3: Plugin Integration Layer
#### 3.1 PHP Hook for Field Registration
**Create: `includes/Compat/MetaFieldsRegistry.php`**
```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
*/
public static function register_order_field($key, $args = []) {
$defaults = [
'key' => $key,
'label' => ucfirst(str_replace('_', ' ', $key)),
'type' => 'text',
'section' => 'Other',
];
self::$order_fields[$key] = array_merge($defaults, $args);
}
/**
* Register product meta field
*/
public static function register_product_field($key, $args = []) {
$defaults = [
'key' => $key,
'label' => ucfirst(str_replace('_', ' ', $key)),
'type' => 'text',
'section' => 'Other',
];
self::$product_fields[$key] = array_merge($defaults, $args);
}
/**
* Localize fields to JavaScript
*/
public static function localize_fields() {
if (!is_admin()) return;
wp_localize_script('woonoow-admin', 'WooNooWMetaFields', [
'orders' => array_values(self::$order_fields),
'products' => array_values(self::$product_fields),
]);
}
}
```
#### 3.2 Example: Shipment Tracking Integration
**Create: `includes/Compat/Integrations/ShipmentTracking.php`**
```php
<?php
namespace WooNooW\Compat\Integrations;
use WooNooW\Compat\MetaFieldsRegistry;
class ShipmentTracking {
public static function init() {
// Only load if WC Shipment Tracking is active
if (!class_exists('WC_Shipment_Tracking')) {
return;
}
add_action('woonoow/register_meta_fields', [__CLASS__, 'register_fields']);
add_filter('woonoow/order_allowed_private_meta', [__CLASS__, 'allow_meta']);
add_filter('woonoow/order_updatable_meta', [__CLASS__, 'allow_meta']);
}
public static function register_fields() {
MetaFieldsRegistry::register_order_field('_tracking_number', [
'label' => __('Tracking Number', 'woonoow'),
'type' => 'text',
'section' => 'Shipment Tracking',
]);
MetaFieldsRegistry::register_order_field('_tracking_provider', [
'label' => __('Tracking Provider', 'woonoow'),
'type' => 'select',
'section' => 'Shipment Tracking',
'options' => [
['value' => 'jne', 'label' => 'JNE'],
['value' => 'jnt', 'label' => 'J&T'],
['value' => 'sicepat', 'label' => 'SiCepat'],
],
]);
}
public static function allow_meta($allowed) {
$allowed[] = '_tracking_number';
$allowed[] = '_tracking_provider';
$allowed[] = '_shipment_tracking_items';
return $allowed;
}
}
```
---
## Implementation Checklist
### Phase 1: API Layer ✅
- [ ] Add meta data exposure to `OrdersController::show()`
- [ ] Add meta data update to `OrdersController::update()`
- [ ] Add meta data exposure to `ProductsController::get_product()`
- [ ] Add meta data update to `ProductsController::update_product()`
- [ ] Add filters: `woonoow/order_api_data`, `woonoow/product_api_data`
- [ ] Add filters: `woonoow/order_allowed_private_meta`, `woonoow/order_updatable_meta`
- [ ] Add actions: `woonoow/order_updated`, `woonoow/product_updated`
### Phase 2: Frontend Components ✅
- [ ] 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 Order/Product detail pages
### Phase 3: Plugin Integration ✅
- [ ] Create `MetaFieldsRegistry.php`
- [ ] Add `woonoow/register_meta_fields` action
- [ ] Localize fields to JavaScript
- [ ] Create example integration: `ShipmentTracking.php`
- [ ] Document integration pattern for third-party devs
### Phase 4: Testing ✅
- [ ] Test with WooCommerce Shipment Tracking plugin
- [ ] Test with ACF (Advanced Custom Fields)
- [ ] Test with CMB2 (Custom Metaboxes 2)
- [ ] Test with custom metabox plugins
- [ ] Test meta data save/update
- [ ] Test meta data display in detail view
---
## Third-Party Plugin Integration Guide
### For Plugin Developers:
**Example: Adding custom fields to WooNooW admin**
```php
// In your plugin file
add_action('woonoow/register_meta_fields', function() {
// Register order field
WooNooW\Compat\MetaFieldsRegistry::register_order_field('_my_custom_field', [
'label' => __('My Custom Field', 'my-plugin'),
'type' => 'text',
'section' => 'My Plugin',
]);
// Register product field
WooNooW\Compat\MetaFieldsRegistry::register_product_field('_my_product_field', [
'label' => __('My Product Field', 'my-plugin'),
'type' => 'textarea',
'section' => 'My Plugin',
]);
});
// Allow meta to be read/written
add_filter('woonoow/order_allowed_private_meta', function($allowed) {
$allowed[] = '_my_custom_field';
return $allowed;
});
add_filter('woonoow/order_updatable_meta', function($allowed) {
$allowed[] = '_my_custom_field';
return $allowed;
});
```
---
## Priority
**Status:** 🔴 **CRITICAL - MUST IMPLEMENT**
**Why:**
1. Breaks compatibility with popular plugins (Shipment Tracking, ACF, etc.)
2. Users cannot see/edit custom fields added by other plugins
3. Data exists in database but not accessible in SPA admin
4. Forces users to switch back to classic admin for custom fields
**Timeline:**
- Phase 1 (API): 2-3 days
- Phase 2 (Frontend): 3-4 days
- Phase 3 (Integration): 2-3 days
- **Total: ~1-2 weeks**
**Blocking:**
- Coupons CRUD (can proceed)
- Customers CRUD (can proceed)
- **Production readiness** (BLOCKED until this is done)