$row Stored delivery. * * @return string The payload to send. */ public function restore_retry_payload( string $body, Webhook $webhook, array $row ): string { $key = (string) ( $row['webhook_key'] ?? '' ); if ( ! str_starts_with( $key, IntegrationRegistry::PREFIX ) ) { return $body; } $decoded = json_decode( $body, true ); if ( ! is_array( $decoded ) || ! Redact::is_mask( $decoded['secret'] ?? '' ) ) { return $body; } $form = $this->forms->find( (int) ( $row['form_id'] ?? 0 ) ); if ( ! $form instanceof Form ) { return $body; } $secret = $this->registry->secret_for( $form, $key ); if ( '' === $secret ) { return $body; } $decoded['secret'] = $secret; return (string) wp_json_encode( $decoded ); } /** * Rebuilds the endpoint of an integration delivery that is being retried. * * @param mixed $webhook Endpoint resolved so far, null when nothing matched. * @param Form $form Form the delivery belongs to. * @param string $key Key the delivery was recorded under. * * @return mixed The endpoint, or whatever was passed in when this is not ours. */ public function resolve_retry( mixed $webhook, Form $form, string $key ): mixed { if ( $webhook instanceof Webhook || ! str_starts_with( $key, IntegrationRegistry::PREFIX ) ) { return $webhook; } return $this->registry->endpoint( $form, $key ); } /** * Sends a stored submission to every connected service. * * @param int $submission_id Stored submission ID. * @param int $form_id Form the submission belongs to. * @param array $entries Stored values. */ public function on_submission_created( int $submission_id, int $form_id, array $entries ): void { $form = $this->forms->find( $form_id ); if ( ! $form instanceof Form ) { return; } $active = $this->registry->active( $form ); if ( array() === $active ) { return; } $values = array(); foreach ( $entries as $entry ) { $key = (string) ( $entry['key'] ?? '' ); if ( '' !== $key ) { $values[ $key ] = $entry['value'] ?? ''; } } $context = new IntegrationContext( $form, $submission_id, $values, $this->fields->for_form( $form_id ), $this->formatter ); foreach ( $active as $integration ) { $this->deliver( $integration, $context, $this->registry->config( $form, $integration->id() ) ); } } /** * Sends one submission to one service. * * @param Integration $integration Service to send to. * @param IntegrationContext $context Submission being delivered. * @param array $config Stored values for the form. */ private function deliver( Integration $integration, IntegrationContext $context, array $config ): void { if ( ! $integration->should_send( $context, $config ) ) { return; } $endpoint = $integration->endpoint( $context->form(), $config ); if ( ! $endpoint instanceof Webhook ) { return; } $this->sender->dispatch( $endpoint, $integration->payload( $context, $config ), $context->form()->id(), $context->submission_id() ); } /** * Sends a made up submission and reports what came back. * * Uses the saved configuration rather than what is on screen: an integration * is several fields including a credential, and testing something other than * what is stored would tell the person a reassuring thing about a setup that * is not the one their visitors will meet. */ public function handle_test(): void { if ( ! Nonce::verify_ajax( self::TEST_NONCE, 'nonce' ) || ! Capabilities::current_user_can( Capabilities::MANAGE_FORMS ) ) { wp_send_json_error( array( 'message' => __( 'This page has expired. Please reload it and try again.', 'devplugins-custom-form-builder' ) ), 403 ); } $form = $this->forms->find( Request::int( 'form_id' ) ); $integration = $this->registry->find( Request::key( 'integration' ) ); if ( ! $form instanceof Form || ! $integration instanceof Integration ) { wp_send_json_error( array( 'message' => __( 'Save the form before testing an integration.', 'devplugins-custom-form-builder' ) ), 404 ); } $config = $this->registry->config( $form, $integration->id() ); if ( ! $integration->is_configured( $config ) ) { wp_send_json_error( array( 'message' => __( 'Fill in the settings and save the form, then test.', 'devplugins-custom-form-builder' ) ), 400 ); } $endpoint = $integration->endpoint( $form, $config ); if ( ! $endpoint instanceof Webhook ) { wp_send_json_error( array( 'message' => __( 'These settings do not produce a usable address.', 'devplugins-custom-form-builder' ) ), 400 ); } $context = $this->sample_context( $form ); $result = $this->sender->probe( $endpoint, $integration->payload( $context, $config ) ); wp_send_json_success( array( 'ok' => $result['ok'], 'message' => $this->message( $result ), 'body' => $result['body'], ) ); } /** * Builds a submission with plausible answers. * * Plausible rather than obviously fake: a service that validates what it is * given will refuse "test" in an email column, and the refusal would look * like a configuration problem instead of the test data it is. * * @param Form $form Form being tested. */ private function sample_context( Form $form ): IntegrationContext { $fields = $this->fields->for_form( $form->id() ); $values = array(); foreach ( $fields as $field ) { if ( ! $field instanceof Field || FieldTypes::STEP === $field->type() ) { continue; } $values[ $field->field_key() ] = $this->sample_value( $field ); } return new IntegrationContext( $form, 0, $values, $fields, $this->formatter ); } /** * Returns a believable answer for one field. * * @param Field $field Field to answer. */ private function sample_value( Field $field ): string { $choices = (array) $field->setting( 'options', array() ); return match ( $field->type() ) { FieldTypes::EMAIL => 'test@example.com', FieldTypes::URL => 'https://example.com', FieldTypes::PHONE => '+15550100', FieldTypes::NUMBER => '42', FieldTypes::DATE => gmdate( 'Y-m-d' ), FieldTypes::TEXTAREA => __( 'This is a test delivery from FormPilot — Smart Form Builder.', 'devplugins-custom-form-builder' ), FieldTypes::CHECKBOX, FieldTypes::RADIO, FieldTypes::SELECT => (string) ( is_scalar( reset( $choices ) ) ? reset( $choices ) : '' ), default => __( 'Test', 'devplugins-custom-form-builder' ), }; } /** * Builds the sentence shown under the test button. * * @param array{ok: bool, code: int, message: string, body: string, ms: int} $result Response details. */ private function message( array $result ): string { if ( 0 === $result['code'] ) { return sprintf( /* translators: %s: Error returned by the HTTP client. */ __( 'The request never reached the service: %s', 'devplugins-custom-form-builder' ), $result['message'] ); } if ( $result['ok'] ) { return sprintf( /* translators: 1: HTTP status code. 2: Milliseconds the request took. */ __( 'The service accepted the test (HTTP %1$d, %2$d ms).', 'devplugins-custom-form-builder' ), $result['code'], $result['ms'] ); } return sprintf( /* translators: 1: HTTP status code. 2: HTTP status message. */ __( 'The service answered HTTP %1$d %2$s. Its reply is below.', 'devplugins-custom-form-builder' ), $result['code'], $result['message'] ); } }