true ), 'names' ); foreach ( $public_post_types as $post_type ) { if ( $post_type === 'attachment' ) { continue; } register_rest_field( $post_type, 'meta_fields', array( 'get_callback' => array( $this, 'get_meta_fields' ), 'update_callback' => array( $this, 'update_meta_fields' ), 'schema' => array( 'description' => __( 'MetaForge Engine custom field values, auto-formatted by field type.', 'metaforge-engine' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), ), ) ); } } /** * Callback: reads all MetaForge field groups assigned to this post's type, * formats each value through the existing API pipeline, and returns the result. * * @param array $post Post data array from REST response. * @param string $field_name 'meta_fields' * @param WP_REST_Request $request The request object. * @return array Keyed array of formatted field values. */ public function get_meta_fields( array $post, $field_name, WP_REST_Request $request ) { $post_id = $post['id']; $post_type = get_post_type( $post_id ); if ( ! $post_type ) { return array(); } // Use a static singleton to avoid re-instantiating (and re-registering AJAX hooks) // on every REST response — the transient cache inside FieldManager is shared. static $field_manager = null; if ( $field_manager === null ) { $field_manager = new MetaForge_FieldManager(); } $groups = $field_manager->get_groups_by_post_type( $post_type, $post_id ); if ( empty( $groups ) ) { return array(); } $all_meta = get_post_meta( $post_id ); $result = array(); foreach ( $groups as $group ) { if ( empty( $group['fields'] ) || ! is_array( $group['fields'] ) ) { continue; } // Skip inactive groups if ( isset( $group['active'] ) && $group['active'] == 0 ) { continue; } $this->collect_fields_recursive( $group['fields'], $all_meta, $result ); } return $result; } /** * Recursively collect formatted field values. * Handles top-level fields, group sub-fields, and repeater rows. * * @param array $fields Array of field config arrays. * @param array $all_meta Prefetched array of all post meta keys/values. * @param array &$output Output array to populate. */ private function collect_fields_recursive( array $fields, array $all_meta, array &$output ) { foreach ( $fields as $field ) { if ( empty( $field['name'] ) ) { continue; } $field_name = $field['name']; $raw_value = isset( $all_meta[ $field_name ][0] ) ? maybe_unserialize( $all_meta[ $field_name ][0] ) : ''; // Use the existing metaforge_format_value pipeline (images, repeaters, groups, etc.) $formatted = metaforge_format_value( $raw_value, $field ); $output[ $field_name ] = $formatted; } } /** * Update callback: accepts an associative array of meta fields and saves them using MetaForge_Loader. */ public function update_meta_fields( $value, $post, $field_name ) { if ( ! is_array( $value ) ) { return false; } $post_id = is_object( $post ) ? $post->ID : intval( $post['id'] ?? 0 ); if ( ! $post_id ) { return false; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Permission denied.', 'metaforge-engine' ), array( 'status' => 403 ) ); } static $field_manager = null; if ( $field_manager === null ) { $field_manager = new MetaForge_FieldManager(); } $groups = $field_manager->get_groups_by_post_type( get_post_type( $post_id ), $post_id ); if ( empty( $groups ) ) { return true; } global $metaforge_loader; if ( empty( $metaforge_loader ) ) { $metaforge_loader = new MetaForge_Loader(); } $_POST['metaforge'] = $value; $metaforge_loader->save_fields( $post_id, 'post', $groups ); return true; } }