diff --git a/forms-bridge/addons/rocketchat/assets/logo.png b/forms-bridge/addons/rocketchat/assets/logo.png new file mode 100644 index 00000000..2d8bbe42 Binary files /dev/null and b/forms-bridge/addons/rocketchat/assets/logo.png differ diff --git a/forms-bridge/addons/rocketchat/class-rocketchat-addon.php b/forms-bridge/addons/rocketchat/class-rocketchat-addon.php new file mode 100644 index 00000000..f15558bb --- /dev/null +++ b/forms-bridge/addons/rocketchat/class-rocketchat-addon.php @@ -0,0 +1,84 @@ + '__rocketchat-' . time(), + 'endpoint' => '/api/v1/users.list', + 'method' => 'GET', + 'backend' => $backend, + ) + ); + + $response = $bridge->submit( array( 'status' => 'active' ) ); + + if ( is_wp_error( $response ) ) { + Logger::log( 'Rocket.Chat backend ping error response', Logger::ERROR ); + Logger::log( $response, Logger::ERROR ); + return false; + } + + return true; + } + + /** + * Performs an introspection of the backend endpoint and returns API fields. + * + * @param string $endpoint API endpoint. + * @param string $backend Backend name. + * @param string|null $method HTTP method. + * + * @return array List of fields and content type of the endpoint. + */ + public function get_endpoint_schema( $endpoint, $backend, $method = null ) { + return array(); + } +} + +Rocketchat_Addon::setup(); diff --git a/forms-bridge/addons/rocketchat/class-rocketchat-form-bridge.php b/forms-bridge/addons/rocketchat/class-rocketchat-form-bridge.php new file mode 100644 index 00000000..0b0ea594 --- /dev/null +++ b/forms-bridge/addons/rocketchat/class-rocketchat-form-bridge.php @@ -0,0 +1,78 @@ +backend(); + + $message_attachments = array(); + foreach ( $attachments as $name => $path ) { + $info = pathinfo( $path ); + $filename = $info['basename']; + + $response = $backend->post( + '/api/v1/rooms.media/' . $room_id, + array( + 'msg' => $name, + ), + array( + 'Content-Type' => 'multipart/form-data', + ), + array( + 'file' => $path, + ) + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + if ( ! $response['data']['success'] ) { + return new WP_Error( 'rocketchat_upload', __( 'Can not upload a file to Rocket.Chat', 'forms-bridge' ), $response['data'] ); + } + + unset( $payload[ $name ] ); + unset( $payload[ $name . '_filename' ] ); + + $message_attachments[] = array( + 'title' => $filename, + 'title_link' => $response['data']['file']['url'], + 'title_link_download' => true, + ); + } + + $payload['attachments'] = $message_attachments; + } + + return parent::submit( $payload, array() ); + } +} diff --git a/forms-bridge/addons/rocketchat/hooks.php b/forms-bridge/addons/rocketchat/hooks.php new file mode 100644 index 00000000..98b75086 --- /dev/null +++ b/forms-bridge/addons/rocketchat/hooks.php @@ -0,0 +1,260 @@ + array( + array( + 'ref' => '#backend', + 'name' => 'name', + 'description' => __( + 'Label of the Rocket.Chat API backend connection', + 'forms-bridge' + ), + 'default' => 'Rocket.Chat API', + ), + array( + 'ref' => '#backend/headers[]', + 'name' => 'X-Auth-Token', + 'label' => __( 'Personal Access Token', 'forms-bridge' ), + 'description' => __( + 'Use Personal Access Tokens to interact securely with the Rocket.Chat API', + 'forms-bridge', + ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#backend/headers[]', + 'name' => 'X-User-Id', + 'label' => __( 'User Id', 'forms-bridge' ), + 'description' => __( + 'Displayed when the Personal Access Token is created', + 'forms-bridge', + ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#bridge', + 'name' => 'method', + 'value' => 'POST', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'emoji', + 'label' => __( 'Emoji', 'forms-bridge' ), + 'description' => __( 'If provided, the avatar will be displayed as an emoji', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => ':smile:', + 'label' => '😄', + ), + array( + 'value' => ':grinning:', + 'label' => '😀', + ), + array( + 'value' => ':laughing:', + 'label' => '😂', + ), + array( + 'value' => ':wink:', + 'label' => '😉', + ), + array( + 'value' => ':blush:', + 'label' => '😊', + ), + array( + 'value' => ':heart_eyes:', + 'label' => '😍', + ), + array( + 'value' => ':sunglasses:', + 'label' => '😎', + ), + array( + 'value' => ':rocket:', + 'label' => '🚀', + ), + array( + 'value' => ':alien:', + 'label' => '👽', + ), + array( + 'value' => ':robot:', + 'label' => '🤖', + ), + array( + 'value' => ':ghost:', + 'label' => '👻', + ), + array( + 'value' => ':cat:', + 'label' => '🐱', + ), + array( + 'value' => ':dog:', + 'label' => '🐶', + ), + array( + 'value' => ':panda_face:', + 'label' => '🐼', + ), + array( + 'value' => ':owl:', + 'label' => '🦉', + ), + array( + 'value' => ':fox_face:', + 'label' => '🦊', + ), + array( + 'value' => ':fire:', + 'label' => '🔥', + ), + array( + 'value' => ':sparkles:', + 'label' => '✨', + ), + array( + 'value' => ':star:', + 'label' => '⭐', + ), + array( + 'value' => ':crescent_moon:', + 'label' => '🌙', + ), + array( + 'value' => ':rainbow:', + 'label' => '🌈', + ), + array( + 'value' => ':tada:', + 'label' => '🎉', + ), + array( + 'value' => ':confetti_ball:', + 'label' => '🎊', + ), + array( + 'value' => ':bulb:', + 'label' => '💡', + ), + array( + 'value' => ':gift:', + 'label' => '🎁', + ), + array( + 'value' => ':trophy:', + 'label' => '🏆', + ), + array( + 'value' => ':microphone:', + 'label' => '🎤', + ), + array( + 'value' => ':headphones:', + 'label' => '🎧', + ), + array( + 'value' => ':camera:', + 'label' => '📷', + ), + array( + 'value' => ':video_game:', + 'label' => '🎮', + ), + array( + 'value' => ':book:', + 'label' => '📖', + ), + array( + 'value' => ':coffee:', + 'label' => '☕', + ), + array( + 'value' => ':pizza:', + 'label' => '🍕', + ), + array( + 'value' => ':hamburger:', + 'label' => '🍔', + ), + array( + 'value' => ':fries:', + 'label' => '🍟', + ), + array( + 'value' => ':cookie:', + 'label' => '🍪', + ), + array( + 'value' => ':cake:', + 'label' => '🍰', + ), + array( + 'value' => ':icecream:', + 'label' => '🍦', + ), + array( + 'value' => ':beer:', + 'label' => '🍺', + ), + array( + 'value' => ':wine_glass:', + 'label' => '🍷', + ), + array( + 'value' => ':earth_americas:', + 'label' => '🌎', + ), + array( + 'value' => ':milky_way:', + 'label' => '🌌', + ), + ), + ), + ), + 'bridge' => array( + 'method' => 'POST', + ), + ), + $defaults, + $schema, + ); + }, + 10, + 3 +); + +add_filter( + 'forms_bridge_bridge_schema', + function ( $schema, $addon ) { + if ( 'rocketchat' !== $addon ) { + return $schema; + } + + $schema['properties']['method']['enum'] = array( 'GET', 'POST' ); + return $schema; + }, + 10, + 2 +); diff --git a/forms-bridge/addons/rocketchat/jobs/create-dm.php b/forms-bridge/addons/rocketchat/jobs/create-dm.php new file mode 100644 index 00000000..cdbc57c6 --- /dev/null +++ b/forms-bridge/addons/rocketchat/jobs/create-dm.php @@ -0,0 +1,57 @@ + __( 'Create DM', 'forms-bridge' ), + 'description' => __( 'Creates a direct message session with a user', 'forms-bridge' ), + 'method' => 'forms_bridge_rocketchat_create_dm', + 'input' => array( + array( + 'name' => 'username', + 'schema' => array( 'type' => 'string' ), + 'required' => true, + ), + ), + 'output' => array( + array( + 'name' => 'roomId', + 'schema' => array( 'type' => 'string' ), + ), + ), +); + +/** + * Open DM job method. + * + * @param array $payload Bridge payload. + * @param Form_Bridge $bridge Bridge object. + * + * @return array + */ +function forms_bridge_rocketchat_create_dm( $payload, $bridge ) { + $response = $bridge->patch( + array( + 'endpoint' => '/api/v1/dm.create', + 'method' => 'POST', + ) + )->submit( array( 'username' => $payload['username'] ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $room_id = $response['data']['room']['rid']; + $payload['roomId'] = $room_id; + + return $payload; +} diff --git a/forms-bridge/addons/rocketchat/templates/contact.php b/forms-bridge/addons/rocketchat/templates/contact.php new file mode 100644 index 00000000..23d70397 --- /dev/null +++ b/forms-bridge/addons/rocketchat/templates/contact.php @@ -0,0 +1,97 @@ + __( 'Contacts Channel', 'forms-bridge' ), + 'description' => __( + 'Contact form template. The resulting bridge will notify form submissions in a Slack channel', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => '/api/v1/chat.postMessage', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'roomId', + 'label' => __( 'Channel', 'forms-bridge' ), + 'description' => __( + 'Name of the channel where messages will be sent', + 'forms-bridge' + ), + 'type' => 'select', + 'options' => array( + 'endpoint' => '/api/v1/rooms.get', + 'finger' => array( + 'value' => 'update[]._id', + 'label' => 'update[].name', + ), + ), + 'required' => true, + ), + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Contacts', 'forms-bridge' ), + ), + ), + 'form' => array( + 'title' => __( 'Contacts', 'forms-bridge' ), + 'fields' => array( + array( + 'name' => 'your-name', + 'label' => __( 'Your name', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'name' => 'your-email', + 'label' => __( 'Your email', 'forms-bridge' ), + 'type' => 'email', + 'required' => true, + ), + array( + 'name' => 'comments', + 'label' => __( 'Comments', 'forms-bridge' ), + 'type' => 'textarea', + ), + ), + ), + 'bridge' => array( + 'endpoint' => '/api/v1/chat.postMessage', + 'mutations' => array( + array( + array( + 'from' => 'your-name', + 'to' => 'text.name', + 'cast' => 'string', + ), + array( + 'from' => 'your-email', + 'to' => 'text.email', + 'cast' => 'string', + ), + array( + 'from' => '?comments', + 'to' => 'text.comments', + 'cast' => 'string', + ), + array( + 'from' => 'text', + 'to' => 'text', + 'cast' => 'pretty_json', + ), + ), + ), + ), +); diff --git a/forms-bridge/addons/rocketchat/templates/direct-message.php b/forms-bridge/addons/rocketchat/templates/direct-message.php new file mode 100644 index 00000000..64011fee --- /dev/null +++ b/forms-bridge/addons/rocketchat/templates/direct-message.php @@ -0,0 +1,94 @@ + __( 'Direct Messages', 'forms-bridge' ), + 'description' => __( + 'Contact form template. The resulting bridge will send form submissions as direct messages in Rocket.Chat', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => '/api/v1/chat.postMessage', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'username', + 'label' => __( 'User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + 'endpoint' => '/api/v1/users.listByStatus', + 'finger' => array( + 'value' => 'users[].username', + 'label' => 'users[].name', + ), + ), + 'required' => true, + ), + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Direct Messages', 'forms-bridge' ), + ), + ), + 'form' => array( + 'title' => __( 'Direct Messages', 'forms-bridge' ), + 'fields' => array( + array( + 'name' => 'your-name', + 'label' => __( 'Your name', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'name' => 'your-email', + 'label' => __( 'Your email', 'forms-bridge' ), + 'type' => 'email', + 'required' => true, + ), + array( + 'name' => 'comments', + 'label' => __( 'Comments', 'forms-bridge' ), + 'type' => 'textarea', + ), + ), + ), + 'bridge' => array( + 'endpoint' => '/api/v1/chat.postMessage', + 'workflow' => array( 'create-dm' ), + 'mutations' => array( + array( + array( + 'from' => 'your-name', + 'to' => 'text.name', + 'cast' => 'string', + ), + array( + 'from' => 'your-email', + 'to' => 'text.email', + 'cast' => 'string', + ), + array( + 'from' => '?comments', + 'to' => 'text.comments', + 'cast' => 'string', + ), + array( + 'from' => 'text', + 'to' => 'text', + 'cast' => 'pretty_json', + ), + ), + ), + ), +); diff --git a/forms-bridge/addons/rocketchat/templates/support.php b/forms-bridge/addons/rocketchat/templates/support.php new file mode 100644 index 00000000..6a824706 --- /dev/null +++ b/forms-bridge/addons/rocketchat/templates/support.php @@ -0,0 +1,118 @@ + __( 'Support Channel', 'forms-bridge' ), + 'description' => __( + 'Support form template. The resulting bridge will notify form submissions in a Rocket.Chat channel', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => '/api/v1/chat.postMessage', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'roomId', + 'label' => __( 'Channel', 'forms-bridge' ), + 'description' => __( + 'Name of the channel where messages will be sent', + 'forms-bridge' + ), + 'type' => 'select', + 'options' => array( + 'endpoint' => '/api/v1/rooms.get', + 'finger' => array( + 'value' => 'update[]._id', + 'label' => 'update[].name', + ), + ), + 'required' => true, + ), + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Support', 'forms-bridge' ), + ), + ), + 'form' => array( + 'title' => __( 'Support', 'forms-bridge' ), + 'fields' => array( + array( + 'name' => 'your-name', + 'label' => __( 'Your name', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'name' => 'your-email', + 'label' => __( 'Your email', 'forms-bridge' ), + 'type' => 'email', + 'required' => true, + ), + array( + 'name' => 'subject', + 'label' => __( 'Subject', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Option 1', + 'label' => 'Option 1', + ), + array( + 'value' => 'Option 2', + 'label' => 'Option 2', + ), + ), + 'required' => true, + ), + array( + 'name' => 'comments', + 'label' => __( 'Comments', 'forms-bridge' ), + 'type' => 'textarea', + ), + ), + ), + 'bridge' => array( + 'endpoint' => '/api/v1/chat.postMessage', + 'mutations' => array( + array( + array( + 'from' => 'your-name', + 'to' => 'text.name', + 'cast' => 'string', + ), + array( + 'from' => 'your-email', + 'to' => 'text.email', + 'cast' => 'string', + ), + array( + 'from' => 'subject', + 'to' => 'text.subject', + 'cast' => 'string', + ), + array( + 'from' => '?comments', + 'to' => 'text.comments', + 'cast' => 'string', + ), + array( + 'from' => 'text', + 'to' => 'text', + 'cast' => 'pretty_json', + ), + ), + ), + ), +); diff --git a/forms-bridge/addons/slack/class-slack-form-bridge.php b/forms-bridge/addons/slack/class-slack-form-bridge.php index dc9fb18d..725d2cb7 100644 --- a/forms-bridge/addons/slack/class-slack-form-bridge.php +++ b/forms-bridge/addons/slack/class-slack-form-bridge.php @@ -85,19 +85,14 @@ public function submit( $payload = array(), $attachments = array() ) { foreach ( $attachments as $name => $file_id ) { $files[] = array( 'id' => $file_id, - 'title' => $name, + 'title' => $filename, ); } $response = $backend->post( '/api/files.completeUploadExternal', - array( - 'files' => wp_json_encode( $files ), - 'channel_id' => $payload['channel'] ?? $payload['channel_id'] ?? null, - ), - array( - 'Content-Type' => 'application/x-www-form-urlencoded', - ), + array( 'files' => wp_json_encode( $files ) ), + array( 'Content-Type' => 'application/x-www-form-urlencoded' ), ); if ( is_wp_error( $response ) ) { @@ -110,13 +105,17 @@ public function submit( $payload = array(), $attachments = array() ) { $annex = "\n\n----\n" . esc_html( __( 'Attachments', 'forms-bridge' ) ) . ":\n"; - foreach ( $response['data']['files'] as $upload ) { - $annex .= "* [{$upload['name']}]({$upload['permalink']})\n"; - } - if ( isset( $payload['markdown_text'] ) ) { + foreach ( $response['data']['files'] as $upload ) { + $annex .= "* [{$upload['name']}]({$upload['permalink']})\n"; + } + $payload['markdown_text'] .= $annex; } else { + foreach ( $response['data']['files'] as $upload ) { + $annex .= "* {$upload['name']}: {$upload['permalink']}\n"; + } + $payload['text'] .= $annex; } } diff --git a/forms-bridge/addons/slack/hooks.php b/forms-bridge/addons/slack/hooks.php index 38d9c854..589e168c 100644 --- a/forms-bridge/addons/slack/hooks.php +++ b/forms-bridge/addons/slack/hooks.php @@ -40,18 +40,20 @@ function ( $defaults, $addon, $schema ) { 'value' => 'https://slack.com/oauth/v2', ), array( - 'ref' => '#credential', - 'name' => 'client_id', - 'label' => __( 'Client ID', 'forms-bridge' ), - 'type' => 'text', - 'required' => true, + 'ref' => '#credential', + 'name' => 'client_id', + 'label' => __( 'Client ID', 'forms-bridge' ), + 'description' => __( 'Register Forms Bridge as an app on Slack API and get its Client ID', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, ), array( - 'ref' => '#credential', - 'name' => 'client_secret', - 'label' => __( 'Client secret', 'forms-bridge' ), - 'type' => 'text', - 'required' => true, + 'ref' => '#credential', + 'name' => 'client_secret', + 'label' => __( 'Client Secret', 'forms-bridge' ), + 'description' => __( 'Register Forms Bridge as an app on Slack API and get its Client Secret', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, ), array( 'ref' => '#credential', diff --git a/forms-bridge/addons/slack/templates/support.php b/forms-bridge/addons/slack/templates/support.php index e4470b16..551678a6 100644 --- a/forms-bridge/addons/slack/templates/support.php +++ b/forms-bridge/addons/slack/templates/support.php @@ -42,6 +42,22 @@ 'type' => 'email', 'required' => true, ), + array( + 'name' => 'subject', + 'label' => __( 'Subject', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Option 1', + 'label' => 'Option 1', + ), + array( + 'value' => 'Option 2', + 'label' => 'Option 2', + ), + ), + 'required' => true, + ), array( 'name' => 'comments', 'label' => __( 'Comments', 'forms-bridge' ), @@ -63,6 +79,11 @@ 'to' => 'text.email', 'cast' => 'string', ), + array( + 'from' => 'subject', + 'to' => 'text.subject', + 'cast' => 'string', + ), array( 'from' => '?comments', 'to' => 'text.comments', diff --git a/forms-bridge/addons/zulip/class-zulip-addon.php b/forms-bridge/addons/zulip/class-zulip-addon.php index b32f9098..39ac3ad5 100644 --- a/forms-bridge/addons/zulip/class-zulip-addon.php +++ b/forms-bridge/addons/zulip/class-zulip-addon.php @@ -100,7 +100,7 @@ public function get_endpoint_schema( $endpoint, $backend, $method = null ) { $oa_explorer = new OpenAPI( $data ); $method = strtolower( $method ?? 'post' ); - $path = preg_replace( '', '', $endpoint ); + $path = preg_replace( '/^\/api\/v1/', '', $endpoint ); $source = in_array( $method, array( 'post', 'put', 'patch' ), true ) ? 'body' : 'query'; $params = $oa_explorer->params( $path, $method, $source ); diff --git a/forms-bridge/addons/zulip/class-zulip-form-bridge.php b/forms-bridge/addons/zulip/class-zulip-form-bridge.php index c437ec51..7f27172b 100644 --- a/forms-bridge/addons/zulip/class-zulip-form-bridge.php +++ b/forms-bridge/addons/zulip/class-zulip-form-bridge.php @@ -46,6 +46,9 @@ public function submit( $payload = array(), $attachments = array() ) { $attachments = Forms_Bridge::attachments( $uploads ); foreach ( $attachments as $name => $path ) { + $info = pathinfo( $path ); + $filename = $info['basename']; + $response = $backend->post( '/api/v1/user_uploads', array(), array(), array( $name => $path ) ); if ( is_wp_error( $response ) ) { @@ -57,7 +60,7 @@ public function submit( $payload = array(), $attachments = array() ) { unset( $payload[ $name ] ); unset( $payload[ $name . '_filename' ] ); - $annex .= "* [{$name}]({$response['data']['url']})\n"; + $annex .= "* [{$filename}]({$response['data']['url']})\n"; } $payload['content'] .= $annex; diff --git a/forms-bridge/deps/http b/forms-bridge/deps/http index 803b7a4d..7218bf42 160000 --- a/forms-bridge/deps/http +++ b/forms-bridge/deps/http @@ -1 +1 @@ -Subproject commit 803b7a4de09d1a382792a5c1b857a4a3974186bb +Subproject commit 7218bf42d6b821abb7ea04dccdef686e7d6ee98d diff --git a/forms-bridge/includes/class-openapi.php b/forms-bridge/includes/class-openapi.php index 420d49c2..ea25a457 100644 --- a/forms-bridge/includes/class-openapi.php +++ b/forms-bridge/includes/class-openapi.php @@ -180,7 +180,7 @@ public function params( $path, $method = null, $source = null ) { $param = &$parameters[ $i ]; if ( 'body' === $param['in'] && isset( $param['schema'] ) ) { if ( isset( $param['schema']['$ref'] ) ) { - $param['schema'] = $this->get_ref( $param['schema']['$ref'] ); + $param['schema'] = array_merge( $param['schema'], $this->get_ref( $param['schema']['$ref'] ) ); } if ( isset( $param['schema']['properties'] ) && is_array( $param['schema']['properties'] ) ) { @@ -225,16 +225,22 @@ function ( $param ) use ( $source ) { for ( $i = 0; $i < $l; $i++ ) { $param = &$parameters[ $i ]; + if ( isset( $param['$ref'] ) ) { + $parameters[ $i ] = array_merge( $param, $this->get_ref( $param['$ref'] ) ); + } elseif ( isset( $param['schema']['$ref'] ) ) { + $param['schema'] = array_merge( $param['schema'], $this->get_ref( $param['schema']['$ref'] ) ); + } + + if ( isset( $param['anyOf'] ) ) { + $param['schema'] = $param['anyOf'][0]; + } elseif ( isset( $param['oneOf'] ) ) { + $param['schema'] = $param['oneOf'][0]; + } + if ( isset( $param['type'] ) && ! isset( $param['schema'] ) ) { $param['schema'] = array( 'type' => $param['type'] ); unset( $param['type'] ); } - - if ( isset( $param['$ref'] ) ) { - $parameters[ $i ] = $this->get_ref( $param['$ref'] ); - } elseif ( isset( $param['schema']['$ref'] ) ) { - $param['schema'] = $this->get_ref( $param['schema']['$ref'] ); - } } return $parameters; @@ -274,7 +280,7 @@ private function body_to_params( $body ) { foreach ( $body['content'] as $encoding => $obj ) { if ( isset( $obj['schema']['$ref'] ) ) { - $obj['schema'] = $this->get_ref( $obj['schema']['$ref'] ); + $obj['schema'] = array_merge( $obj['schema'], $this->get_ref( $obj['schema']['$ref'] ) ); } foreach ( $obj['schema']['properties'] as $name => $defn ) {