consent` to choose an endpoint - see the doc comment * on Api_Client::upsert_contact() for why that branch exists at all. * * Deliberately provider-agnostic: nothing here knows about Event Tickets. * * @package Emails404 */ namespace Emails404; if ( ! defined( 'ABSPATH' ) ) { exit; } class Sync_Service { /** @var Api_Client */ private $api_client; public function __construct( Api_Client $api_client ) { $this->api_client = $api_client; } /** * The first attempt for a freshly-fired hook: dispatches, logs, and - * on a retryable failure - creates a *new* Retry_Queue row. Never call * this for an attempt Retry_Queue itself is driving (see attempt() * below) - that would create a second queue row for the same retry * and defeat the bounded-attempts guarantee. * * @param Signup_Dto $dto * @param string $provider_slug For Logger attribution only. * @return Api_Response */ public function send( Signup_Dto $dto, $provider_slug ) { $response = $this->attempt( $dto, $provider_slug ); if ( ! $response->success && $response->retryable ) { Retry_Queue::enqueue( $dto, $provider_slug ); } return $response; } /** * Dispatches and logs, without ever touching the retry queue itself - * the primitive both send() (fresh attempts) and Retry_Queue::attempt_row() * (queued retries, which own their own row's lifecycle) build on. * * @param Signup_Dto $dto * @param string $provider_slug * @return Api_Response */ public function attempt( Signup_Dto $dto, $provider_slug ) { if ( ! $dto->is_valid() ) { Logger::record( $provider_slug, $dto->idempotency_key, Logger::OUTCOME_FAILED, null, 'invalid_dto' ); return Api_Response::failure( null, 'invalid_dto', false ); } $response = $this->dispatch( $dto ); if ( $response->success ) { Logger::record( $provider_slug, $dto->idempotency_key, Logger::OUTCOME_SUCCESS, $response->status ); Settings::set_connection_result( Settings::CONNECTION_CONNECTED ); } elseif ( $response->retryable ) { Logger::record( $provider_slug, $dto->idempotency_key, Logger::OUTCOME_QUEUED, $response->status, $response->error_category ); } else { Logger::record( $provider_slug, $dto->idempotency_key, Logger::OUTCOME_FAILED, $response->status, $response->error_category ); } return $response; } private function dispatch( Signup_Dto $dto ) { if ( $dto->consent && $dto->destination_form_public_id ) { $body = array( 'email' => $dto->email, 'firstName' => $dto->first_name, 'lastName' => $dto->last_name, 'consent' => true, ); if ( null !== $dto->consent_text ) { // Agreed to on another plugin's form - see Form_Connections. $body['consentText'] = $dto->consent_text; } return $this->api_client->submit_form( $dto->destination_form_public_id, self::without_nulls( $body ), $dto->idempotency_key ); } // No consent, or no destination form configured - contact-only via // the Contacts endpoint, which is the one path that supports // marketingPermission=false without fabricating consent. See // Api_Client::upsert_contact()'s doc comment. return $this->api_client->upsert_contact( self::without_nulls( array( 'email' => $dto->email, 'firstName' => $dto->first_name, 'lastName' => $dto->last_name, 'marketingPermission' => $dto->consent, 'sourceReference' => $dto->source_reference, ) ), $dto->idempotency_key ); } /** * Leaves out what isn't known (e.g. no name on the form): 404 Emails * treats a missing field as "not given" but refuses `null`. */ public static function without_nulls( array $body ) { return array_filter( $body, static function ( $value ) { return null !== $value; } ); } }