From e53b8320e401180b7b2a541c96a68bc715f5a8d0 Mon Sep 17 00:00:00 2001 From: dwindown Date: Thu, 20 Nov 2025 12:22:01 +0700 Subject: [PATCH] feat: Phase 1 - Backend API meta compatibility (Level 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Implemented: Backend API Enhancement for Level 1 Compatibility** Following IMPLEMENTATION_PLAN_META_COMPAT.md Phase 1 **OrdersController.php:** ✅ Added get_order_meta_data() - Expose meta in API responses ✅ Added update_order_meta_data() - Update meta from API ✅ Modified show() - Include meta in response ✅ Modified update() - Handle meta updates ✅ Added filter: woonoow/order_allowed_private_meta ✅ Added filter: woonoow/order_updatable_meta ✅ Added filter: woonoow/order_api_data ✅ Added action: woonoow/order_updated **ProductsController.php:** ✅ Added get_product_meta_data() - Expose meta in API responses ✅ Added update_product_meta_data() - Update meta from API ✅ Modified format_product_full() - Include meta in response ✅ Modified update_product() - Handle meta updates ✅ Added filter: woonoow/product_allowed_private_meta ✅ Added filter: woonoow/product_updatable_meta ✅ Added filter: woonoow/product_api_data ✅ Added action: woonoow/product_updated **Meta Filtering Logic:** - Skip internal WooCommerce meta (_wc_*) - Skip WooNooW internal meta (_woonoow_*) - Public meta (no underscore) - always expose - Private meta (starts with _) - check allowed list - Plugins can add to allowed list via filters **Default Allowed Meta (Orders):** - _tracking_number - _tracking_provider - _tracking_url - _shipment_tracking_items - _wc_shipment_tracking_items - _transaction_id - _payment_method_title **How It Works:** 1. Plugin stores: update_post_meta($order_id, '_tracking_number', '123') 2. WooNooW API exposes: GET /orders/123 returns meta._tracking_number 3. Frontend can read/write via API 4. Plugin works WITHOUT any extra effort **Next Steps:** - Phase 2: Frontend components (MetaFields, useMetaFields) - Phase 3: PHP MetaFieldsRegistry system - Testing with popular plugins **Status:** Backend API ready for Level 1 compatibility! 🎉 --- includes/Api/OrdersController.php | 107 ++++++++++++++++++++++++++++ includes/Api/ProductsController.php | 99 +++++++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/includes/Api/OrdersController.php b/includes/Api/OrdersController.php index b7dd208..437ab98 100644 --- a/includes/Api/OrdersController.php +++ b/includes/Api/OrdersController.php @@ -522,8 +522,12 @@ class OrdersController { 'notes' => $notes, 'coupons' => $coupon_codes, 'customer_note' => $order->get_customer_note(), + 'meta' => self::get_order_meta_data( $order ), ]; + // Allow plugins to modify response (Level 1 compatibility) + $data = apply_filters( 'woonoow/order_api_data', $data, $order, $req ); + return new WP_REST_Response( $data, 200 ); } @@ -682,6 +686,11 @@ class OrdersController { } } + // Update custom meta fields (Level 1 compatibility) + if ( isset( $p['meta'] ) && is_array( $p['meta'] ) ) { + self::update_order_meta_data( $order, $p['meta'] ); + } + // SOLUTION: Block WooCommerce analytics tracking (pixel.wp.com) during save // This was causing 30s timeout on every order status change add_filter( 'pre_http_request', function( $preempt, $args, $url ) { @@ -694,6 +703,9 @@ class OrdersController { $order->save(); + // Allow plugins to perform additional updates (Level 1 compatibility) + do_action( 'woonoow/order_updated', $order, $p, $req ); + // Clean up remove_all_filters( 'pre_http_request' ); @@ -2172,4 +2184,99 @@ class OrdersController { 'expiry_date' => $coupon->get_date_expires() ? $coupon->get_date_expires()->date( 'Y-m-d' ) : null, ], 200 ); } + + /** + * Get order meta data for API exposure (Level 1 compatibility) + * Filters out internal meta unless explicitly allowed + * + * @param \WC_Order $order + * @return array + */ + 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; + } + + // Skip WooNooW internal meta + if ( strpos( $key, '_woonoow_' ) === 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', + + // Payment gateway meta + '_transaction_id', + '_payment_method_title', + + // Allow plugins to add their meta via filter + ], $order ); + + if ( in_array( $key, $allowed_private, true ) ) { + $meta_data[ $key ] = $value; + } + } + + return $meta_data; + } + + /** + * Update order meta data from API (Level 1 compatibility) + * + * @param \WC_Order $order + * @param array $meta_updates + */ + private static function update_order_meta_data( $order, $meta_updates ) { + // Get allowed updatable meta keys + $allowed = apply_filters( 'woonoow/order_updatable_meta', [ + // Common shipping tracking fields + '_tracking_number', + '_tracking_provider', + '_tracking_url', + + // Allow plugins to add their meta via filter + ], $order ); + + foreach ( $meta_updates as $key => $value ) { + // Skip internal WooCommerce meta + if ( strpos( $key, '_wc_' ) === 0 ) { + continue; + } + + // Skip WooNooW internal meta + if ( strpos( $key, '_woonoow_' ) === 0 ) { + continue; + } + + // 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 ); + } + } + } } \ No newline at end of file diff --git a/includes/Api/ProductsController.php b/includes/Api/ProductsController.php index 472be24..86b80ee 100644 --- a/includes/Api/ProductsController.php +++ b/includes/Api/ProductsController.php @@ -371,8 +371,16 @@ class ProductsController { $product->set_gallery_image_ids($data['gallery_image_ids']); } + // Update custom meta fields (Level 1 compatibility) + if (isset($data['meta']) && is_array($data['meta'])) { + self::update_product_meta_data($product, $data['meta']); + } + $product->save(); + // Allow plugins to perform additional updates (Level 1 compatibility) + do_action('woonoow/product_updated', $product, $data, $request); + // Handle variations for variable products if ($product->is_type('variable')) { if (isset($data['attributes'])) { @@ -565,6 +573,12 @@ class ProductsController { $data['variations'] = self::get_product_variations($product); } + // Expose meta data (Level 1 compatibility) + $data['meta'] = self::get_product_meta_data($product); + + // Allow plugins to modify response (Level 1 compatibility) + $data = apply_filters('woonoow/product_api_data', $data, $product); + return $data; } @@ -697,4 +711,89 @@ class ProductsController { } } } + + /** + * Get product meta data for API exposure (Level 1 compatibility) + * Filters out internal meta unless explicitly allowed + * + * @param \WC_Product $product + * @return array + */ + private static function get_product_meta_data($product) { + $meta_data = []; + + foreach ($product->get_meta_data() as $meta) { + $key = $meta->key; + $value = $meta->value; + + // Skip internal WooCommerce meta (starts with _wc_) + if (strpos($key, '_wc_') === 0) { + continue; + } + + // Skip WooNooW internal meta + if (strpos($key, '_woonoow_') === 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/product_allowed_private_meta', [ + // Common custom fields + '_custom_field', + + // Allow plugins to add their meta via filter + ], $product); + + if (in_array($key, $allowed_private, true)) { + $meta_data[$key] = $value; + } + } + + return $meta_data; + } + + /** + * Update product meta data from API (Level 1 compatibility) + * + * @param \WC_Product $product + * @param array $meta_updates + */ + private static function update_product_meta_data($product, $meta_updates) { + // Get allowed updatable meta keys + $allowed = apply_filters('woonoow/product_updatable_meta', [ + // Common custom fields + '_custom_field', + + // Allow plugins to add their meta via filter + ], $product); + + foreach ($meta_updates as $key => $value) { + // Skip internal WooCommerce meta + if (strpos($key, '_wc_') === 0) { + continue; + } + + // Skip WooNooW internal meta + if (strpos($key, '_woonoow_') === 0) { + continue; + } + + // Public meta (no underscore) - always allow + if (strpos($key, '_') !== 0) { + $product->update_meta_data($key, $value); + continue; + } + + // Private meta - check if allowed + if (in_array($key, $allowed, true)) { + $product->update_meta_data($key, $value); + } + } + } }