diff --git a/forms-bridge/addons/bigin/class-bigin-addon.php b/forms-bridge/addons/bigin/class-bigin-addon.php index e0c146a1..cd43dd6e 100644 --- a/forms-bridge/addons/bigin/class-bigin-addon.php +++ b/forms-bridge/addons/bigin/class-bigin-addon.php @@ -103,6 +103,38 @@ public function ping( $backend ) { return true; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $bridge = new Bigin_Form_Bridge( + array( + 'name' => '__bigin-' . time(), + 'endpoint' => '/bigin/v2/settings/modules', + 'method' => 'GET', + 'backend' => $backend, + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + return array(); + } + + return array_map( + function ( $module ) { + return '/bigin/v2/' . $module['api_name']; + }, + $response['data']['modules'], + ); + } + /** * Performs an introspection of the backend endpoint and returns API fields. * diff --git a/forms-bridge/addons/bigin/hooks.php b/forms-bridge/addons/bigin/hooks.php index c1f247e9..fce06ca9 100644 --- a/forms-bridge/addons/bigin/hooks.php +++ b/forms-bridge/addons/bigin/hooks.php @@ -43,12 +43,12 @@ function ( $defaults, $addon, $schema ) { 'ref' => '#credential', 'name' => 'scope', 'value' => - 'ZohoBigin.modules.ALL,ZohoBigin.settings.layouts.READ,ZohoBigin.users.READ', + 'ZohoBigin.modules.ALL,ZohoBigin.settings.modules.READ,ZohoBigin.settings.layouts.READ,ZohoBigin.users.READ', ), ), 'credential' => array( 'scope' => - 'ZohoBigin.modules.ALL,ZohoBigin.settings.layouts.READ,ZohoBigin.users.READ', + 'ZohoBigin.modules.ALL,ZohoBigin.settings.modules.READ,ZohoBigin.settings.layouts.READ,ZohoBigin.users.READ', ), ), $defaults, diff --git a/forms-bridge/addons/brevo/class-brevo-addon.php b/forms-bridge/addons/brevo/class-brevo-addon.php index d52cf4fa..99ff6be1 100644 --- a/forms-bridge/addons/brevo/class-brevo-addon.php +++ b/forms-bridge/addons/brevo/class-brevo-addon.php @@ -46,7 +46,8 @@ class Brevo_Addon extends Addon { * * @var string */ - public const OAS_URL = 'https://developers.brevo.com/reference/get_companies?json=on'; + // public const OAS_URL = 'https://developers.brevo.com/reference/get_companies?json=on'; + public const OAS_URL = 'https://api.brevo.com/v3/swagger_definition_v3.yml'; /** * Performs a request against the backend to check the connexion status. @@ -79,41 +80,51 @@ public function ping( $backend ) { /** * Fetch available models from the OAS spec. * - * @param Backend $backend HTTP backend object. + * @param string $backend Backend name. + * @param string|null $method HTTP method. * * @return array * * @todo Implementar el endpoint de consulta de endpoints disponibles. */ - public function get_endpoints( $backend ) { - $response = wp_remote_get( - self::OAS_URL, - array( - 'headers' => array( - 'Accept' => 'application/json', - 'Host' => 'developers.brevo.com', - 'Referer' => 'https://developers.brevo.com/reference/get_companies', - 'Alt-Used' => 'developers.brevo.com', - 'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0', - ), - ) - ); + public function get_endpoints( $backend, $method = null ) { + if ( function_exists( 'yaml_parse' ) ) { + $response = wp_remote_get( self::OAS_URL ); - if ( is_wp_error( $response ) ) { - return array(); - } + if ( ! is_wp_error( $response ) ) { + $data = yaml_parse( $response['body'] ); - $data = json_decode( $response['body'], true ); - $oa_explorer = new OpenAPI( $data['oasDefinition'] ); + if ( $data ) { + $oa_explorer = new OpenAPI( $data ); - $paths = $oa_explorer->paths(); + $paths = $oa_explorer->paths(); - return array_map( - function ( $path ) { - return '/v3' . $path; - }, - $paths, - ); + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return array_map( + function ( $path ) { + return '/v3' . $path; + }, + $paths, + ); + } + } + + return array( '/v3/contacts' ); + } } /** diff --git a/forms-bridge/addons/dolibarr/class-dolibarr-addon.php b/forms-bridge/addons/dolibarr/class-dolibarr-addon.php index 9f398437..8f606373 100644 --- a/forms-bridge/addons/dolibarr/class-dolibarr-addon.php +++ b/forms-bridge/addons/dolibarr/class-dolibarr-addon.php @@ -157,6 +157,62 @@ public function get_endpoint_schema( $endpoint, $backend, $method = null ) { return $fields; } + + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => '__dolibarr-' . time(), + 'endpoint' => self::SWAGGER_ENDPOINT, + 'backend' => $backend, + 'method' => 'GET', + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + return array(); + } + + $version = $response['data']['swagger'] ?? null; + if ( ! $version ) { + return array(); + } + + $oa_explorer = new OpenAPI( $response['data'] ); + + $paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return array_map( + function ( $path ) { + return '/api/index.php' . $path; + }, + $paths, + ); + } } Dolibarr_Addon::setup(); diff --git a/forms-bridge/addons/financoop/class-financoop-addon.php b/forms-bridge/addons/financoop/class-financoop-addon.php index cbde8b4e..42c1715c 100644 --- a/forms-bridge/addons/financoop/class-financoop-addon.php +++ b/forms-bridge/addons/financoop/class-financoop-addon.php @@ -69,6 +69,22 @@ public function ping( $backend ) { return true; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + return array( + '/api/campaign/{campaign_id}/subscription_request', + '/api/campaign/{campaign_id}/donation_request', + '/api/campaign/{campaign_id}/loan_request', + ); + } + /** * Performs an introspection of the backend endpoint and returns API fields * and accepted content type. diff --git a/forms-bridge/addons/gcalendar/class-gcalendar-addon.php b/forms-bridge/addons/gcalendar/class-gcalendar-addon.php index 5985589d..6cb32604 100644 --- a/forms-bridge/addons/gcalendar/class-gcalendar-addon.php +++ b/forms-bridge/addons/gcalendar/class-gcalendar-addon.php @@ -130,6 +130,29 @@ public function fetch( $endpoint, $backend ) { return $response; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $response = $this->fetch( null, $backend ); + + if ( is_wp_error( $response ) || empty( $response['data']['items'] ) ) { + return array(); + } + + return array_map( + function ( $calendar ) { + return '/calendar/v3/calendars/' . $calendar['id'] . '/events'; + }, + $response['data']['items'] + ); + } + /** * Performs an introspection of the backend endpoint and returns API fields * and accepted content type. diff --git a/forms-bridge/addons/gsheets/class-gsheets-addon.php b/forms-bridge/addons/gsheets/class-gsheets-addon.php index 9379e030..1cd1db2f 100644 --- a/forms-bridge/addons/gsheets/class-gsheets-addon.php +++ b/forms-bridge/addons/gsheets/class-gsheets-addon.php @@ -152,6 +152,29 @@ public function fetch( $endpoint, $backend ) { return $response; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $response = $this->fetch( null, $backend ); + + if ( is_wp_error( $response ) || empty( $response['data']['files'] ) ) { + return array(); + } + + return array_map( + function ( $file ) { + return '/v4/spreadsheets/' . $file['id']; + }, + $response['data']['files'] + ); + } + /** * Performs an introspection of the backend endpoint and returns API fields * and accepted content type. diff --git a/forms-bridge/addons/holded/class-holded-addon.php b/forms-bridge/addons/holded/class-holded-addon.php index 90cbe362..8ad44cba 100644 --- a/forms-bridge/addons/holded/class-holded-addon.php +++ b/forms-bridge/addons/holded/class-holded-addon.php @@ -91,6 +91,74 @@ public function ping( $backend ) { return true; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $paths = array(); + + foreach ( self::OAS_URLS as $module => $oas_path ) { + $oas_url = self::OAS_BASE_URL . $oas_path . '?dereference=false&reduce=false'; + + $response = wp_remote_get( + $oas_url, + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Host' => 'developers.holded.com', + 'Alt-Used' => 'developers.holded.com', + 'Referer' => 'https://developers.holded.com/reference/list-contacts-1', + 'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0', + ), + ), + ); + + if ( is_wp_error( $response ) ) { + continue; + } + + $data = json_decode( $response['body'], true ); + if ( ! $data ) { + continue; + } + + $oa_explorer = new OpenAPI( $data['data']['api']['schema'] ); + + $module_paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $module_paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $module_paths = $method_paths; + } + + $module_paths = array_map( + function ( $path ) use ( $module ) { + return '/api/' . $module . '/v1' . $path; + }, + $module_paths, + ); + + $paths = array_merge( $paths, $module_paths ); + } + + return $paths; + } + /** * Performs an introspection of the backend endpoint and returns API fields * and accepted content type. diff --git a/forms-bridge/addons/listmonk/class-listmonk-addon.php b/forms-bridge/addons/listmonk/class-listmonk-addon.php index 51903e95..c17a1112 100644 --- a/forms-bridge/addons/listmonk/class-listmonk-addon.php +++ b/forms-bridge/addons/listmonk/class-listmonk-addon.php @@ -78,34 +78,50 @@ public function ping( $backend ) { /** * Fetch available models from the OAS spec. * - * @param Backend $backend HTTP backend object. + * @param string $backend Backend name. + * @param string|null $method HTTP method. * * @return array * * @todo Implementar el endpoint de consulta de endpoints disponibles. */ - public function get_endpoints( $backend ) { + public function get_endpoints( $backend, $method = null ) { if ( function_exists( 'yaml_parse' ) ) { $response = wp_remote_get( self::OAS_URL ); if ( ! is_wp_error( $response ) ) { $data = yaml_parse( $response['body'] ); - $oa_explorer = new OpenAPI( $data ); - $paths = $oa_explorer->paths(); + if ( $data ) { + $oa_explorer = new OpenAPI( $data ); + $paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); - return array_map( - function ( $path ) { - return '/api' . $path; - }, - $paths - ); + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return array_map( + function ( $path ) { + return '/api' . $path; + }, + $paths + ); + } } } - return array( - '/api/subscribers', - ); + return array( '/api/subscribers' ); } /** diff --git a/forms-bridge/addons/mailchimp/class-mailchimp-addon.php b/forms-bridge/addons/mailchimp/class-mailchimp-addon.php index 1ad5ace3..ae208de6 100644 --- a/forms-bridge/addons/mailchimp/class-mailchimp-addon.php +++ b/forms-bridge/addons/mailchimp/class-mailchimp-addon.php @@ -76,6 +76,62 @@ public function ping( $backend ) { return true; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $response = wp_remote_get( + self::SWAGGER_URL, + array( + 'headers' => array( + 'Accept' => 'application/json', + 'Host' => 'mailchimp.com', + 'Referer' => 'https://mailchimp.com/developer/marketing/api/', + 'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0', + ), + ), + ); + + if ( ! is_wp_error( $response ) ) { + $data = json_decode( $response['body'], true ); + + if ( $data ) { + $oa_explorer = new OpenAPI( $data ); + + $paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return array_map( + function ( $path ) { + return '/3.0' . $path; + }, + $paths, + ); + } + } + + return array( '/3.0/lists/{list_id}/members' ); + } + /** * Performs an introspection of the backend endpoint and returns API fields * and accepted content type. diff --git a/forms-bridge/addons/odoo/class-odoo-addon.php b/forms-bridge/addons/odoo/class-odoo-addon.php index 824bcde2..3e80d536 100644 --- a/forms-bridge/addons/odoo/class-odoo-addon.php +++ b/forms-bridge/addons/odoo/class-odoo-addon.php @@ -93,13 +93,14 @@ public function fetch( $endpoint, $backend ) { /** * Fetch available models from the backend * - * @param Backend $backend HTTP backend object. + * @param string $backend Backend name. + * @param string|null $method RPC method. * * @return array * * @todo Implementar el endpoint de consulta de endpoints disponibles. */ - public function get_endpoints( $backend ) { + public function get_endpoints( $backend, $method = null ) { $bridge = new Odoo_Form_Bridge( array( 'name' => '__odoo-' . time(), diff --git a/forms-bridge/addons/rocketchat/class-rocketchat-addon.php b/forms-bridge/addons/rocketchat/class-rocketchat-addon.php index 10829b22..8a866b62 100644 --- a/forms-bridge/addons/rocketchat/class-rocketchat-addon.php +++ b/forms-bridge/addons/rocketchat/class-rocketchat-addon.php @@ -76,6 +76,50 @@ public function ping( $backend ) { return true; } + /** + * Fetch available models from the OAS spec. + * + * @param string $backend Backend name. + * @param string|null $method HTTP method. + * + * @return array + * + * @todo Implementar el endpoint de consulta de endpoints disponibles. + */ + public function get_endpoints( $backend, $method = null ) { + if ( function_exists( 'yaml_parse' ) ) { + $response = wp_remote_get( self::OAS_URL ); + + if ( ! is_wp_error( $response ) ) { + $data = yaml_parse( $response['body'] ); + + if ( $data ) { + $oa_explorer = new OpenAPI( $data ); + $paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return $paths; + } + } + } + + return array( '/api/v1/chat.postMessage' ); + } + /** * Performs an introspection of the backend endpoint and returns API fields. * diff --git a/forms-bridge/addons/slack/class-slack-addon.php b/forms-bridge/addons/slack/class-slack-addon.php index bcda565e..9f9dc55d 100644 --- a/forms-bridge/addons/slack/class-slack-addon.php +++ b/forms-bridge/addons/slack/class-slack-addon.php @@ -76,6 +76,53 @@ public function ping( $backend ) { return true; } + /** + * Fetch available models from the OAS spec. + * + * @param string $backend Backend name. + * @param string|null $method HTTP method. + * + * @return array + * + * @todo Implementar el endpoint de consulta de endpoints disponibles. + */ + public function get_endpoints( $backend, $method = null ) { + $response = wp_remote_get( self::OAS_URL ); + + if ( ! is_wp_error( $response ) ) { + $data = json_decode( $response['body'], true ); + + if ( $data ) { + $oa_explorer = new OpenAPI( $data ); + $paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return array_map( + function ( $path ) { + return '/api' . $path; + }, + $paths, + ); + } + } + + return array( '/api/chat.postMessage' ); + } + /** * Performs an introspection of the backend endpoint and returns API fields. * diff --git a/forms-bridge/addons/suitecrm/class-suitecrm-addon.php b/forms-bridge/addons/suitecrm/class-suitecrm-addon.php index f261def8..73e8ecda 100644 --- a/forms-bridge/addons/suitecrm/class-suitecrm-addon.php +++ b/forms-bridge/addons/suitecrm/class-suitecrm-addon.php @@ -94,11 +94,12 @@ public function fetch( $endpoint, $backend ) { /** * Fetch available modules from the backend. * - * @param Backend $backend HTTP backend object. + * @param string $backend Backend name. + * @param string|null $method API method. * * @return array */ - public function get_endpoints( $backend ) { + public function get_endpoints( $backend, $method = null ) { $bridge = new SuiteCRM_Form_Bridge( array( 'name' => '__suitecrm-' . time(), diff --git a/forms-bridge/addons/vtiger/class-vtiger-addon.php b/forms-bridge/addons/vtiger/class-vtiger-addon.php index a0f213f2..ab0453b7 100644 --- a/forms-bridge/addons/vtiger/class-vtiger-addon.php +++ b/forms-bridge/addons/vtiger/class-vtiger-addon.php @@ -98,11 +98,12 @@ public function fetch( $endpoint, $backend ) { /** * Fetch available modules from the backend. * - * @param Backend $backend HTTP backend object. + * @param string $backend HTTP backend object. + * @param string|null $method API method. * * @return array */ - public function get_endpoints( $backend ) { + public function get_endpoints( $backend, $method = null ) { $bridge = new Vtiger_Form_Bridge( array( 'name' => '__vtiger-' . time(), diff --git a/forms-bridge/addons/zoho/class-zoho-addon.php b/forms-bridge/addons/zoho/class-zoho-addon.php index 70c127b5..5ddee35b 100644 --- a/forms-bridge/addons/zoho/class-zoho-addon.php +++ b/forms-bridge/addons/zoho/class-zoho-addon.php @@ -102,6 +102,38 @@ public function ping( $backend ) { return true; } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + $bridge = new Zoho_Form_Bridge( + array( + 'name' => '__zoho-' . time(), + 'endpoint' => '/crm/v7/settings/modules', + 'method' => 'GET', + 'backend' => $backend, + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + return array(); + } + + return array_map( + function ( $module ) { + return '/crm/v7/' . $module['api_name']; + }, + $response['data']['modules'], + ); + } + /** * Performs an introspection of the backend endpoint and returns API fields. * diff --git a/forms-bridge/addons/zoho/hooks.php b/forms-bridge/addons/zoho/hooks.php index bd270dca..a2303bb9 100644 --- a/forms-bridge/addons/zoho/hooks.php +++ b/forms-bridge/addons/zoho/hooks.php @@ -107,7 +107,7 @@ function ( $defaults, $addon, $schema ) { 'name' => 'scope', 'label' => __( 'Scope', 'forms-bridge' ), 'type' => 'text', - 'value' => 'ZohoCRM.modules.ALL,ZohoCRM.settings.layouts.READ,ZohoCRM.users.READ', + 'value' => 'ZohoCRM.modules.ALL,ZohoCRM.settings.modules.READ,ZohoCRM.settings.layouts.READ,ZohoCRM.users.READ', 'required' => true, ), array( @@ -174,7 +174,7 @@ function ( $defaults, $addon, $schema ) { 'name' => '', 'schema' => 'Bearer', 'oauth_url' => 'https://accounts.{region}/oauth/v2', - 'scope' => 'ZohoCRM.modules.ALL,ZohoCRM.settings.layouts.READ,ZohoCRM.users.READ', + 'scope' => 'ZohoCRM.modules.ALL,ZohoCRM.settings.modules.READ,ZohoCRM.settings.layouts.READ,ZohoCRM.users.READ', 'client_id' => '', 'client_secret' => '', 'access_token' => '', diff --git a/forms-bridge/addons/zulip/class-zulip-addon.php b/forms-bridge/addons/zulip/class-zulip-addon.php index b37d6fc2..8643804c 100644 --- a/forms-bridge/addons/zulip/class-zulip-addon.php +++ b/forms-bridge/addons/zulip/class-zulip-addon.php @@ -77,6 +77,55 @@ public function ping( $backend ) { return true; } + /** + * Fetch available models from the OAS spec. + * + * @param string $backend Backend name. + * @param string|null $method HTTP method. + * + * @return array + * + * @todo Implementar el endpoint de consulta de endpoints disponibles. + */ + public function get_endpoints( $backend, $method = null ) { + if ( function_exists( 'yaml_parse' ) ) { + $response = wp_remote_get( self::OAS_URL ); + + if ( ! is_wp_error( $response ) ) { + $data = yaml_parse( $response['body'] ); + + if ( $data ) { + $oa_explorer = new OpenAPI( $data ); + $paths = $oa_explorer->paths(); + + if ( $method ) { + $method = strtolower( $method ); + $method_paths = array(); + + foreach ( $paths as $path ) { + $path_obj = $oa_explorer->path_obj( $path ); + + if ( $path_obj && isset( $path_obj[ $method ] ) ) { + $method_paths[] = $path; + } + } + + $paths = $method_paths; + } + + return array_map( + function ( $path ) { + return '/api/v1' . $path; + }, + $paths, + ); + } + } + } + + return array( '/api/v1/messages' ); + } + /** * Performs an introspection of the backend endpoint and returns API fields. * diff --git a/forms-bridge/includes/class-addon.php b/forms-bridge/includes/class-addon.php index 93092be9..a722cb9d 100644 --- a/forms-bridge/includes/class-addon.php +++ b/forms-bridge/includes/class-addon.php @@ -595,6 +595,18 @@ public function get_endpoint_schema( $endpoint, $backend, $method = null ) { return array(); } + /** + * Performs an introspection of the backend API and returns a list of available endpoints. + * + * @param string $backend Target backend name. + * @param string|null $method HTTP method. + * + * @return array|WP_Error + */ + public function get_endpoints( $backend, $method = null ) { + return array(); + } + /** * Get posts from the database based on a post type and an addon name. * diff --git a/forms-bridge/includes/class-rest-settings-controller.php b/forms-bridge/includes/class-rest-settings-controller.php index 789896b1..3bced50e 100644 --- a/forms-bridge/includes/class-rest-settings-controller.php +++ b/forms-bridge/includes/class-rest-settings-controller.php @@ -332,10 +332,7 @@ private static function register_backend_routes() { 'callback' => static function ( $request ) use ( $addon ) { return self::ping_backend( $addon, $request ); }, - 'permission_callback' => array( - self::class, - 'permission_callback', - ), + 'permission_callback' => array( self::class, 'permission_callback' ), 'args' => array( 'backend' => FBAPI::get_backend_schema(), 'credential' => FBAPI::get_credential_schema(), @@ -344,6 +341,27 @@ private static function register_backend_routes() { ) ); + register_rest_route( + 'forms-bridge/v1', + "/{$addon}/backend/endpoints", + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => static function ( $request ) use ( $addon ) { + return self::get_backend_endpoints( $addon, $request ); + }, + 'permission_callback' => array( self::class, 'permission_callback' ), + 'args' => array( + 'backend' => FBAPI::get_backend_schema(), + 'method' => array( + 'description' => __( 'HTTP method used to filter the list of endpoints', 'forms-bridge' ), + 'type' => 'string', + ), + ), + ), + ), + ); + register_rest_route( 'forms-bridge/v1', "/{$addon}/backend/endpoint/schema", @@ -353,25 +371,16 @@ private static function register_backend_routes() { 'callback' => static function ( $request ) use ( $addon ) { return self::get_endpoint_schema( $addon, $request ); }, - 'permission_callback' => array( - self::class, - 'permission_callback', - ), + 'permission_callback' => array( self::class, 'permission_callback' ), 'args' => array( 'backend' => FBAPI::get_backend_schema(), 'endpoint' => array( - 'description' => __( - 'Target endpoint name', - 'forms-bridge' - ), + 'description' => __( 'Target endpoint name', 'forms-bridge' ), 'type' => 'string', 'required' => true, ), 'method' => array( - 'description' => __( - 'HTTP method', - 'forms-bridge', - ), + 'description' => __( 'HTTP method', 'forms-bridge' ), 'type' => 'string', ), ), @@ -818,6 +827,39 @@ private static function ping_backend( $addon, $request ) { return array( 'success' => $result ); } + /** + * Backend endpoints route callback. + * + * @param string $addon Addon name. + * @param WP_REST_Request $request Request object. + * + * @return array|WP_Error + */ + private static function get_backend_endpoints( $addon, $request ) { + $handler = self::prepare_addon_backend_request_handler( $addon, $request ); + + if ( is_wp_error( $handler ) ) { + return $handler; + } + + [$addon, $backend] = $handler; + + $endpoints = $addon->get_endpoints( $backend, $request['method'] ); + + if ( is_wp_error( $endpoints ) ) { + $error = self::internal_server_error(); + $error->add( + $endpoints->get_error_code(), + $endpoints->get_error_message(), + $endpoints->get_error_data() + ); + + return $error; + } + + return $endpoints; + } + /** * Backend endpoint schema route callback. * @@ -827,10 +869,7 @@ private static function ping_backend( $addon, $request ) { * @return array|WP_Error */ private static function get_endpoint_schema( $addon, $request ) { - $handler = self::prepare_addon_backend_request_handler( - $addon, - $request - ); + $handler = self::prepare_addon_backend_request_handler( $addon, $request ); if ( is_wp_error( $handler ) ) { return $handler; diff --git a/src/components/Bridge/Fields.jsx b/src/components/Bridge/Fields.jsx index 102e1180..a4815138 100644 --- a/src/components/Bridge/Fields.jsx +++ b/src/components/Bridge/Fields.jsx @@ -2,8 +2,9 @@ import { useBackends } from "../../hooks/useHttp"; import { useForms } from "../../providers/Forms"; import { isset, prependEmptyOption } from "../../lib/utils"; import FieldWrapper from "../FieldWrapper"; +import { useApiEndpoints } from "../../providers/ApiSchema"; -const { TextControl, SelectControl } = wp.components; +const { BaseControl, SelectControl } = wp.components; const { useEffect, useMemo } = wp.element; export const INTERNALS = [ @@ -45,6 +46,8 @@ export default function BridgeFields({ data, setData, schema, errors = {} }) { }); }, [forms]); + const endpoints = useApiEndpoints(); + const fields = useMemo(() => { if (!schema) return []; @@ -135,6 +138,7 @@ export default function BridgeFields({ data, setData, schema, errors = {} }) { label={field.label} value={data[field.name] || ""} setValue={(value) => setData({ ...data, [field.name]: value })} + datalist={field.name === "endpoint" ? endpoints : []} /> ); case "select": @@ -152,18 +156,41 @@ export default function BridgeFields({ data, setData, schema, errors = {} }) { }); } -export function StringField({ label, value, setValue, error, disabled }) { +export function StringField({ + label, + value, + setValue, + error, + disabled, + datalist = [], +}) { return ( - + + setValue(ev.target.value)} + disabled={disabled} + style={{ + height: "40px", + paddingRight: "12px", + paddingLeft: "12px", + borderColor: "var(--wp-components-color-gray-600,#949494)", + fontSize: "13px", + width: "100%", + border: "1px solid #949494", + borderRadius: "2px", + }} + /> + + {datalist.map((endpoint) => ( + + ))} + + ); } diff --git a/src/providers/ApiSchema.jsx b/src/providers/ApiSchema.jsx index ef2e9ca2..4e5e2085 100644 --- a/src/providers/ApiSchema.jsx +++ b/src/providers/ApiSchema.jsx @@ -11,32 +11,78 @@ export default function ApiSchemaProvider({ children, bridge }) { const [addon] = useTab(); const [backends] = useBackends(); - const [loading, setLoading] = useState(false); + const [loadingEndpoints, setLoadingEndpoints] = useState(false); + const [loadingSchema, setLoadingSchema] = useState(false); + + const endpoints = useRef(new Map()).current; const schemas = useRef(new Map()).current; - const [, updates] = useState(0); + + const [, endpointUpdates] = useState(0); + const [, schemaUpdates] = useState(0); const backend = useMemo( () => backends.find(({ name }) => bridge?.backend === name), [backends, bridge] ); - const key = useMemo( - () => - JSON.stringify({ - method: bridge?.method, - endpoint: bridge?.endpoint, - backend, - }), - [bridge?.endpoint, bridge?.method, backend] - ); + const endpointsKey = useMemo(() => { + if (!backend?.name) return ""; + + return JSON.stringify({ + addon, + method: bridge?.method || "", + backend: backend?.name, + }); + }, [addon, bridge?.method, backend?.name]); + + const addEndpoints = (key, list) => { + endpoints.set(key, list); + endpointUpdates((i) => i + 1); + }; + + const fetchEndpoints = (key, method, backend) => { + setLoadingEndpoints(true); + + apiFetch({ + path: `forms-bridge/v1/${addon}/backend/endpoints`, + method: "POST", + data: { method, backend }, + }) + .then((endpoints) => addEndpoints(key, endpoints)) + .catch(() => addEndpoints(key, [])) + .finally(() => setLoadingEndpoints(false)); + }; + + const endpointsTimeout = useRef(); + useEffect(() => { + clearTimeout(endpointsTimeout.current); + + if (!bridge || loadingEndpoints || endpoints.get(endpointsKey)) return; + + endpointsTimeout.current = setTimeout( + () => fetchEndpoints(endpointsKey, bridge.method, backend), + 400 + ); + }, [endpointsKey, bridge, backend]); + + const schemaKey = useMemo(() => { + if (!bridge?.method || !backend?.name) return ""; + + return JSON.stringify({ + addon, + method: bridge.method, + endpoint: bridge.endpoint || "/", + backend: backend.name, + }); + }, [addon, bridge?.method, bridge?.endpoint, backend?.name]); const addSchema = (key, schema) => { schemas.set(key, schema); - updates((i) => i + 1); + schemaUpdates((i) => i + 1); }; - const fetch = (key, { endpoint, method }, backend) => { - setLoading(true); + const fetchSchema = (key, endpoint, method, backend) => { + setLoadingSchema(true); apiFetch({ path: `forms-bridge/v1/${addon}/backend/endpoint/schema`, @@ -45,27 +91,40 @@ export default function ApiSchemaProvider({ children, bridge }) { }) .then((schema) => addSchema(key, schema)) .catch(() => addSchema(key, [])) - .finally(() => setLoading(false)); + .finally(() => setLoadingSchema(false)); }; - const timeout = useRef(); + const schemaTimeout = useRef(); useEffect(() => { - clearTimeout(timeout.current); + clearTimeout(schemaTimeout.current); - if (!backend || !bridge?.endpoint || loading || schemas.get(key)) return; + if (!bridge || !backend || loadingSchema || schemas.get(schemaKey)) return; - timeout.current = setTimeout(() => fetch(key, bridge, backend), 400); - }, [key, bridge, backend]); + schemaTimeout.current = setTimeout( + () => + fetchSchema(schemaKey, bridge.endpoint || "/", bridge.method, backend), + 400 + ); + }, [schemaKey, bridge, backend]); - const schema = schemas.get(key); return ( - + {children} ); } export function useApiFields() { - const schema = useContext(ApiSchemaContext); + const { schema } = useContext(ApiSchemaContext); return schema || []; } + +export function useApiEndpoints() { + const { endpoints } = useContext(ApiSchemaContext); + return endpoints || []; +}