From f78d030e9259185b9df83cb39c9f4f4a2018a52f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Wed, 19 Nov 2025 03:26:35 +0100 Subject: [PATCH 1/3] feat: oauth grant redirection url from backend --- forms-bridge/addons/gsheets/hooks.php | 11 ++++------- forms-bridge/addons/zulip/class-zulip-addon.php | 1 - forms-bridge/addons/zulip/class-zulip-form-bridge.php | 1 - forms-bridge/deps/http | 2 +- src/components/Credential/AuthorizeButton.jsx | 4 ++-- .../Templates/Wizard/useAuthorizedCredential.js | 4 ++-- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/forms-bridge/addons/gsheets/hooks.php b/forms-bridge/addons/gsheets/hooks.php index 58f0b689..1bc263d5 100644 --- a/forms-bridge/addons/gsheets/hooks.php +++ b/forms-bridge/addons/gsheets/hooks.php @@ -87,8 +87,7 @@ function ( $defaults, $addon, $schema ) { 'name' => 'scope', 'label' => __( 'Scope', 'forms-bridge' ), 'type' => 'text', - 'value' => - 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets', + 'value' => 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets', 'required' => true, ), array( @@ -147,8 +146,7 @@ function ( $defaults, $addon, $schema ) { 'name' => '', 'schema' => 'Bearer', 'oauth_url' => 'https://accounts.google.com/o/oauth2/v2', - 'scope' => - 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets', + 'scope' => 'https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/spreadsheets', 'client_id' => '', 'client_secret' => '', 'access_token' => '', @@ -173,8 +171,7 @@ function ( $data, $template_id ) { return $data; } - $data['bridge']['endpoint'] = - '/v4/spreadsheets/' . $data['bridge']['endpoint']; + $data['bridge']['endpoint'] = '/v4/spreadsheets/' . $data['bridge']['endpoint']; return $data; }, 10, @@ -184,7 +181,7 @@ function ( $data, $template_id ) { add_filter( 'http_bridge_oauth_url', function ( $url, $verb ) { - if ( strpos( $url, 'accounts.google.com' ) === false ) { + if ( false === strpos( $url, 'accounts.google.com' ) ) { return $url; } diff --git a/forms-bridge/addons/zulip/class-zulip-addon.php b/forms-bridge/addons/zulip/class-zulip-addon.php index b9a3434c..92048ad9 100644 --- a/forms-bridge/addons/zulip/class-zulip-addon.php +++ b/forms-bridge/addons/zulip/class-zulip-addon.php @@ -20,7 +20,6 @@ * Zulip addon class */ class Zulip_Addon extends Addon { - /** * Holds the addon's title. * diff --git a/forms-bridge/addons/zulip/class-zulip-form-bridge.php b/forms-bridge/addons/zulip/class-zulip-form-bridge.php index 0e0a66b9..c437ec51 100644 --- a/forms-bridge/addons/zulip/class-zulip-form-bridge.php +++ b/forms-bridge/addons/zulip/class-zulip-form-bridge.php @@ -18,7 +18,6 @@ * Form bridge implamentation for the Zulip API. */ class Zulip_Form_Bridge extends Form_Bridge { - /** * Submits payload and attachments to the bridge's backend. * diff --git a/forms-bridge/deps/http b/forms-bridge/deps/http index 418b5932..803b7a4d 160000 --- a/forms-bridge/deps/http +++ b/forms-bridge/deps/http @@ -1 +1 @@ -Subproject commit 418b593220c9babdeeed18c7f78a57d9fadd3bfd +Subproject commit 803b7a4de09d1a382792a5c1b857a4a3974186bb diff --git a/src/components/Credential/AuthorizeButton.jsx b/src/components/Credential/AuthorizeButton.jsx index d949cb2c..554dcddf 100644 --- a/src/components/Credential/AuthorizeButton.jsx +++ b/src/components/Credential/AuthorizeButton.jsx @@ -47,11 +47,11 @@ export default function AuthorizeButton({ addon, data }) { method: "POST", data: { credential: data }, }) - .then(({ success }) => { + .then(({ success, redirect_url }) => { if (!success) throw "error"; const form = document.createElement("form"); - form.action = data.oauth_url + "/auth"; + form.action = redirect_url; form.method = "GET"; form.target = "_blank"; diff --git a/src/components/Templates/Wizard/useAuthorizedCredential.js b/src/components/Templates/Wizard/useAuthorizedCredential.js index d288b043..541690cc 100644 --- a/src/components/Templates/Wizard/useAuthorizedCredential.js +++ b/src/components/Templates/Wizard/useAuthorizedCredential.js @@ -107,12 +107,12 @@ export default function useAuthorizedCredential({ data = {}, fields = [] }) { method: "POST", data: { credential }, }) - .then(({ success }) => { + .then(({ success, redirect_url }) => { if (!success) throw "error"; const form = document.createElement("form"); form.method = "GET"; - form.action = credential.oauth_url + "/auth"; + form.action = redirect_url; form.target = "_blank"; let innerHTML = ` From 8a67e1ba64b9f65b0bdd53f26a9072a30e82571d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Wed, 19 Nov 2025 03:26:47 +0100 Subject: [PATCH 2/3] feat: slack addon --- forms-bridge/addons/slack/assets/logo.png | Bin 0 -> 16990 bytes .../addons/slack/class-slack-addon.php | 93 +++++++++++ .../addons/slack/class-slack-form-bridge.php | 153 ++++++++++++++++++ forms-bridge/addons/slack/hooks.php | 148 +++++++++++++++++ .../addons/slack/templates/support.php | 96 +++++++++++ 5 files changed, 490 insertions(+) create mode 100644 forms-bridge/addons/slack/assets/logo.png create mode 100644 forms-bridge/addons/slack/class-slack-addon.php create mode 100644 forms-bridge/addons/slack/class-slack-form-bridge.php create mode 100644 forms-bridge/addons/slack/hooks.php create mode 100644 forms-bridge/addons/slack/templates/support.php diff --git a/forms-bridge/addons/slack/assets/logo.png b/forms-bridge/addons/slack/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..03fb1f130949e860b5b7ebe56d467194734c40cf GIT binary patch literal 16990 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 '__slack-' . time(), + 'endpoint' => '/api/conversations.list', + 'method' => 'GET', + 'backend' => $backend, + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + Logger::log( 'Slack backend ping error response', Logger::ERROR ); + Logger::log( $response, Logger::ERROR ); + return false; + } + + return true; + } + + /** + * Performs a GET request against the backend endpoint and retrive the response data. + * + * @param string $endpoint API endpoint. + * @param string $backend Backend name. + * + * @return array|WP_Error + */ + public function fetch( $endpoint, $backend ) { + $bridge = new Slack_Form_Bridge( + array( + 'name' => '__slack-' . time(), + 'endpoint' => $endpoint, + 'method' => 'GET', + 'backend' => $backend, + ), + 'zulip' + ); + + return $bridge->submit(); + } +} + +Slack_Addon::setup(); diff --git a/forms-bridge/addons/slack/class-slack-form-bridge.php b/forms-bridge/addons/slack/class-slack-form-bridge.php new file mode 100644 index 00000000..dc9fb18d --- /dev/null +++ b/forms-bridge/addons/slack/class-slack-form-bridge.php @@ -0,0 +1,153 @@ +backend(); + + foreach ( $attachments as $name => $path ) { + $info = pathinfo( $path ); + $filename = $info['basename']; + + $response = $backend->post( + '/api/files.getUploadURLExternal', + array( + 'length' => filesize( $path ), + 'filename' => $filename, + ), + array( + 'Content-Type' => 'application/x-www-form-urlencoded', + ) + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + if ( isset( $response['data']['error'] ) ) { + return new WP_Error( 'slack_upload', __( 'Can not upload a file to Slack', 'forms-bridge' ), $response['data'] ); + } + + $file_id = $response['data']['file_id']; + $upload_url = $response['data']['upload_url']; + + $response = http_bridge_post( + $upload_url, + file_get_contents( $path ), + array( + 'Content-Type' => 'application/octet-stream', + ), + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $attachments[ $name ] = $file_id; + } + + $files = array(); + foreach ( $attachments as $name => $file_id ) { + $files[] = array( + 'id' => $file_id, + 'title' => $name, + ); + } + + $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', + ), + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + if ( isset( $response['data']['error'] ) ) { + return new WP_Error( 'slack_upload', __( 'Can not upload a file to Slack', 'forms-bridge' ), $response['data'] ); + } + + $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'] ) ) { + $payload['markdown_text'] .= $annex; + } else { + $payload['text'] .= $annex; + } + } + + add_filter( + 'http_bridge_request', + static function ( $request ) { + self::$request = $request; + return $request; + }, + 10, + 1 + ); + + $response = parent::submit( $payload, array() ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + if ( isset( $response['data']['error'] ) ) { + return new WP_Error( + 'slack_rpc_error', + 'Slack bridge response error', + array( + 'request' => self::$request, + 'response' => $response, + ) + ); + } + + return $response; + } +} diff --git a/forms-bridge/addons/slack/hooks.php b/forms-bridge/addons/slack/hooks.php new file mode 100644 index 00000000..0cc8834f --- /dev/null +++ b/forms-bridge/addons/slack/hooks.php @@ -0,0 +1,148 @@ + array( + array( + 'ref' => '#credential', + 'name' => 'name', + 'label' => __( 'Name', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#credential', + 'name' => 'schema', + 'type' => 'text', + 'value' => 'Bearer', + ), + array( + 'ref' => '#credential', + 'name' => 'oauth_url', + 'label' => __( 'Authorization URL', 'forms-bridge' ), + 'type' => 'text', + 'value' => 'https://slack.com/oauth/v2', + ), + array( + 'ref' => '#credential', + 'name' => 'client_id', + 'label' => __( 'Client ID', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#credential', + 'name' => 'client_secret', + 'label' => __( 'Client secret', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#credential', + 'name' => 'scope', + 'label' => __( 'Scope', 'forms-bridge' ), + 'type' => 'text', + 'value' => 'chat:write,channels:read,users:read,files:write', + 'required' => true, + ), + array( + 'ref' => '#backend', + 'name' => 'name', + 'description' => __( + 'Label of the Slack API backend connection', + 'forms-bridge' + ), + 'default' => 'Slack API', + ), + array( + 'ref' => '#backend', + 'name' => 'base_url', + 'type' => 'url', + 'value' => 'https://slack.com', + ), + array( + 'ref' => '#bridge', + 'name' => 'method', + 'value' => 'POST', + ), + ), + 'credential' => array( + 'name' => '', + 'schema' => 'Bearer', + 'oauth_url' => 'https://slack.com/oauth/v2', + 'scope' => 'chat:write,channels:read,users:read', + 'client_id' => '', + 'client_secret' => '', + 'access_token' => '', + 'expires_at' => 0, + 'refresh_token' => '', + ), + 'backend' => array( + 'base_url' => 'https://slack.com', + 'headers' => array( + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + ), + ), + ), + $defaults, + $schema, + ); + }, + 10, + 3 +); + +add_filter( + 'http_bridge_oauth_update_tokens', + function ( $tokens, $credential ) { + if ( false !== strstr( $credential->oauth_url, 'slack.com' ) ) { + $tokens['expires_at'] = time() + 60 * 60 * 24 * 365 * 10; + $tokens['refresh_token'] = $tokens['access_token']; + $tokens['refresh_token_expires_at'] = time() + 60 * 60 * 24 * 365 * 10; + } + + return $tokens; + }, + 10, + 2 +); + +add_filter( + 'http_bridge_oauth_url', + function ( $url, $verb ) { + if ( false === strstr( $url, 'slack.com' ) ) { + return $url; + } + + if ( 'auth' === $verb ) { + return $url .= 'orize'; + } + + if ( 'token/revoke' === $verb ) { + return 'https://slack.com/api/auth.revoke'; + } + + return 'https://slack.com/api/oauth.v2.access'; + }, + 10, + 2 +); diff --git a/forms-bridge/addons/slack/templates/support.php b/forms-bridge/addons/slack/templates/support.php new file mode 100644 index 00000000..d48d7db6 --- /dev/null +++ b/forms-bridge/addons/slack/templates/support.php @@ -0,0 +1,96 @@ + __( 'Support Channel', 'forms-bridge' ), + 'description' => __( + 'Support form template. The resulting bridge will notify form submissions in a Slack channel', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => '/api/chat.postMessage', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'channel', + 'label' => __( 'Channel', 'forms-bridge' ), + 'description' => __( + 'Name of the channel where notifications will be sent', + 'forms-bridge' + ), + 'type' => 'select', + 'options' => array( + 'endpoint' => '/api/conversations.list', + 'finger' => array( + 'value' => 'channels[].id', + 'label' => 'channels[].name', + ), + ), + ), + 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' => 'comments', + 'label' => __( 'Comments', 'forms-bridge' ), + 'type' => 'textarea', + ), + ), + ), + 'bridge' => array( + 'endpoint' => '/api/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', + ), + ), + ), + ), +); From 716a532b36f943f16b81aafdf540b04760968d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Wed, 19 Nov 2025 03:44:42 +0100 Subject: [PATCH 3/3] feat: slack templates --- .../addons/slack/templates/contact.php | 96 +++++++++++++++++++ .../addons/slack/templates/direct-message.php | 92 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 forms-bridge/addons/slack/templates/contact.php create mode 100644 forms-bridge/addons/slack/templates/direct-message.php diff --git a/forms-bridge/addons/slack/templates/contact.php b/forms-bridge/addons/slack/templates/contact.php new file mode 100644 index 00000000..594e18f3 --- /dev/null +++ b/forms-bridge/addons/slack/templates/contact.php @@ -0,0 +1,96 @@ + __( '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/chat.postMessage', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'channel', + 'label' => __( 'Channel', 'forms-bridge' ), + 'description' => __( + 'Name of the channel where notifications will be sent', + 'forms-bridge' + ), + 'type' => 'select', + 'options' => array( + 'endpoint' => '/api/conversations.list', + 'finger' => array( + 'value' => 'channels[].id', + 'label' => 'channels[].name', + ), + ), + ), + 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/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/slack/templates/direct-message.php b/forms-bridge/addons/slack/templates/direct-message.php new file mode 100644 index 00000000..b3727c07 --- /dev/null +++ b/forms-bridge/addons/slack/templates/direct-message.php @@ -0,0 +1,92 @@ + __( 'Direct Messages', 'forms-bridge' ), + 'description' => __( + 'Contact form template. The resulting bridge will send form submissions as direct messages on Slack', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => '/api/chat.postMessage', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'channel', + 'label' => __( 'User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + 'endpoint' => '/api/users.list', + 'finger' => array( + 'value' => 'members[].id', + 'label' => 'members[].name', + ), + ), + ), + 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/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', + ), + ), + ), + ), +);