['value' => ..., 'label' => ..., 'type' => ...]). * @param array $form_settings The form block settings. * @param array $context Request context (post_id, page_url). * @return true|WP_Error True on success, WP_Error on failure. */ public static function send( $sanitized_data, $form_settings, $context = [] ) { $webhook_url = self::get_valid_webhook_url( $form_settings ); if ( is_wp_error( $webhook_url ) ) { return $webhook_url; } return self::send_to_url( $webhook_url, $sanitized_data, $form_settings, $context ); } /** * Validate email-signup webhook provider settings. * * @param array $settings Email signup settings. * @return true|WP_Error */ public static function validate_signup_settings( $settings ) { $url = self::get_valid_url( $settings['url'] ?? '' ); return is_wp_error( $url ) ? $url : true; } /** * Send an email signup submission to a webhook provider URL. * * @param array $sanitized_data The sanitized form data. * @param array $settings Email signup settings. * @param array $context Request context. * @return true|WP_Error */ public static function subscribe( $sanitized_data, $settings, $context = [] ) { $webhook_url = self::get_valid_url( $settings['url'] ?? '' ); if ( is_wp_error( $webhook_url ) ) { return $webhook_url; } $form_settings = [ 'actionSettings' => [ 'email-signup' => $settings, 'webhook' => [ 'url' => $settings['url'] ?? '', ], ], ]; return self::send_to_url( $webhook_url, $sanitized_data, $form_settings, $context ); } /** * Send sanitized form data to a validated webhook URL. * * @param string $webhook_url Validated webhook URL. * @param array $sanitized_data The sanitized form data. * @param array $form_settings Form settings passed to filters. * @param array $context Request context. * @return true|WP_Error */ private static function send_to_url( $webhook_url, $sanitized_data, $form_settings, $context = [] ) { // Build a clean payload — field name => value (flat), plus metadata. $fields = []; foreach ( $sanitized_data as $field_name => $field ) { if ( ! is_array( $field ) ) { continue; } $fields[ $field_name ] = (string) ( $field['value'] ?? '' ); } /** * Filter the webhook payload before sending. * * Field values are sent flat at the top level — receivers like * FluentCRM only map top-level keys (e.g. `email` is required there) — * and repeated under `fields` for consumers that map structured paths. * On a key collision a field value wins the top-level slot. * * @since 2.6.0 * @param array $payload The webhook payload. * @param array $sanitized_data The full sanitized form data with labels and types. * @param array $form_settings The form block settings. * @param array $context Request context. */ $payload = apply_filters( 'generateblocks_form_webhook_payload', array_merge( [ 'form_name' => ! empty( $context['form_id'] ) ? get_the_title( $context['form_id'] ) : '', 'fields' => $fields, 'page_url' => $context['page_url'] ?? '', 'timestamp' => gmdate( 'c' ), ], $fields ), $sanitized_data, $form_settings, $context ); // wp_safe_remote_post blocks requests to internal/private IPs (127.0.0.1, // 10.x.x.x, 169.254.169.254, etc.) via wp_http_validate_url(). This prevents // SSRF since the webhook URL is user-supplied. Redirection is disabled so a // 302 to an internal IP can't bypass the validation. $response = wp_safe_remote_post( $webhook_url, [ 'body' => wp_json_encode( $payload ), 'headers' => [ 'Content-Type' => 'application/json' ], 'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT, 'redirection' => 0, 'data_format' => 'body', ] ); if ( is_wp_error( $response ) ) { return new WP_Error( 'webhook_failed', 'Webhook request failed: ' . $response->get_error_message() ); } $status_code = wp_remote_retrieve_response_code( $response ); // Accept any 2xx response. if ( $status_code < 200 || $status_code >= 300 ) { return new WP_Error( 'webhook_failed', 'Webhook returned status ' . $status_code . '.' ); } return true; } /** * Get the validated webhook URL. * * @param array $form_settings The form block settings. * @return string|WP_Error */ private static function get_valid_webhook_url( $form_settings ) { $webhook_url = $form_settings['actionSettings']['webhook']['url'] ?? ''; return self::get_valid_url( $webhook_url ); } /** * Validate a webhook URL. * * @param mixed $webhook_url Raw webhook URL. * @return string|WP_Error */ private static function get_valid_url( $webhook_url ) { if ( empty( $webhook_url ) ) { return new WP_Error( 'missing_webhook_url', 'Webhook URL is not configured.', [ 'status' => 400 ] ); } $webhook_url = esc_url_raw( $webhook_url, [ 'https', 'http' ] ); if ( empty( $webhook_url ) || ! wp_http_validate_url( $webhook_url ) ) { return new WP_Error( 'invalid_webhook_url', 'Invalid webhook URL.', [ 'status' => 400 ] ); } return $webhook_url; } }