*/ private $order = array(); /** * @var LineItem[] */ private $items = array(); /** * @var array */ private $properties = array(); /** * @var array */ private $context = array(); /** * Unix timestamp in seconds, or zero for "whenever this arrives". * * @var int */ private $occurred_at = 0; /** * @param string $name Event name. * @param string $event_id Idempotency key. * @param Identity $identity Who the event is about. */ private function __construct( string $name, string $event_id, Identity $identity ) { $this->name = $name; $this->event_id = $event_id; $this->identity = $identity; } /** * Start a new event. * * @param string $name Event name, e.g. purchase. * @param string $event_id Idempotency key. * @return self */ public static function create( string $name, string $event_id ): self { return new self( sanitize_key( $name ), sanitize_text_field( $event_id ), Identity::anonymous() ); } /** * An idempotency key that is the same every time the same thing is described. * * Two deliveries of one order status change must collapse into one event, including across a * retry queue that has no memory of what it sent last time — so the key is derived from what the * event is about rather than from when it was created. * * @param string ...$parts Stable identifying parts. * @return string */ public static function stable_id( string ...$parts ): string { return 'wp-' . md5( implode( ':', $parts ) ); } /** * Attach the person. * * @param Identity $identity Identity. * @return self */ public function with_identity( Identity $identity ): self { $clone = clone $this; $clone->identity = $identity; return $clone; } /** * Attach the browser's anonymous id, which is what joins this call to the visit that earned it. * * @param string|null $anonymous_id Tracker anonymous id. * @return self */ public function with_anonymous_id( ?string $anonymous_id ): self { $clone = clone $this; $clone->anonymous_id = null !== $anonymous_id ? $anonymous_id : ''; return $clone; } /** * Mark the event as being about an order. * * @param string $order_id Order id in the shop's own numbering. * @param float $value Order value. * @param string $currency ISO 4217 code. * @param string $status Order status. * @return self */ public function with_order( string $order_id, float $value, string $currency, string $status = '' ): self { $clone = clone $this; $clone->order = array( 'order_id' => sanitize_text_field( $order_id ), 'value' => round( $value, 4 ), // The collector rejects an unrecognised currency outright when a value is present, so an // empty code is safer than a guess. 'currency' => strtoupper( substr( preg_replace( '/[^A-Za-z]/', '', $currency ), 0, 3 ) ), 'status' => sanitize_text_field( $status ), ); return $clone; } /** * Attach line items. * * @param LineItem[] $items Line items. * @return self */ public function with_items( array $items ): self { $clone = clone $this; $clone->items = array_values( array_filter( $items, static function ( $item ) { return $item instanceof LineItem; } ) ); return $clone; } /** * Attach free-form properties. * * @param array $properties Properties. * @return self */ public function with_properties( array $properties ): self { $clone = clone $this; $clone->properties = array_merge( $this->properties, $properties ); return $clone; } /** * Attach the visitor context. * * @param array $context Context object. * @return self */ public function with_context( array $context ): self { $clone = clone $this; $clone->context = $context; return $clone; } /** * Set when the event happened. * * @param int $timestamp Unix timestamp in seconds. * @return self */ public function at( int $timestamp ): self { $clone = clone $this; $clone->occurred_at = $timestamp > 0 ? $timestamp : 0; return $clone; } /** * The event name. * * @return string */ public function name(): string { return $this->name; } /** * The idempotency key. * * @return string */ public function event_id(): string { return $this->event_id; } /** * Whether the collector has enough to resolve a person. * * An order placed by a guest with no email is the case this catches: there is no identity and no * cookie, so the event could only ever land on a user created for it and never seen again. * * @return bool */ public function is_identifiable(): bool { return ! $this->identity->is_empty() || '' !== $this->anonymous_id; } /** * The request body. * * @return array */ public function to_array(): array { $payload = array_merge( array( 'event' => $this->name, 'event_id' => $this->event_id, ), $this->identity->to_array(), $this->order ); if ( '' !== $this->anonymous_id ) { $payload['anonymous_id'] = $this->anonymous_id; } if ( array() !== $this->items ) { $payload['items'] = array_map( static function ( LineItem $item ) { return $item->to_array(); }, $this->items ); } if ( array() !== $this->properties ) { $payload['properties'] = $this->properties; } if ( array() !== $this->context ) { $payload['context'] = $this->context; } if ( $this->occurred_at > 0 ) { // RFC3339 is the only format the collector parses for an event timestamp; anything else is // read as "no timestamp" and the event silently takes its arrival time instead. $payload['timestamp'] = gmdate( DATE_RFC3339, $this->occurred_at ); } /** * Filters an outgoing server-side event payload. * * @since 1.0.0 * * @param array $payload Request body. * @param Event $event The event object. */ return (array) apply_filters( 'adpix_analytics_event_payload', $payload, $this ); } }