**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)
15 KiB
15 KiB
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:
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:
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():
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():
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():
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
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
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
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
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
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.tsxcomponent - Create
useMetaFields.tshook - Update
Orders/Edit.tsxto include meta fields - Update
Orders/View.tsxto display meta fields (read-only) - Update
Products/Edit.tsxto include meta fields - Add meta fields to Order/Product detail pages
Phase 3: Plugin Integration ✅
- Create
MetaFieldsRegistry.php - Add
woonoow/register_meta_fieldsaction - 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
// 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:
- Breaks compatibility with popular plugins (Shipment Tracking, ACF, etc.)
- Users cannot see/edit custom fields added by other plugins
- Data exists in database but not accessible in SPA admin
- 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)