settings = $settings; $this->queue = $queue; $this->visitor = $visitor; $this->consent = $consent; $this->logger = $logger; } /** * Queue an event. * * @param Event $event The event. * @param string $category Consent category it falls under. * @param VisitorContextInterface|null $visitor Context to attribute it to; defaults to this * request's. An order passes the context frozen at * checkout so it is not attributed to whoever * happened to trigger the status change. * @return bool Whether it was accepted for delivery. */ public function record( Event $event, string $category = ConsentGate::CATEGORY_STATISTICS, ?VisitorContextInterface $visitor = null ): bool { $visitor = null !== $visitor ? $visitor : $this->visitor; if ( ! $this->settings->load()->is_connected() ) { return false; } if ( ! $this->consent->allows( $category, $visitor ) ) { return false; } $enriched = $event ->with_anonymous_id( $visitor->anonymous_id() ) ->with_context( $visitor->to_array() ); if ( ! $enriched->is_identifiable() ) { // No identity and no cookie. The collector would accept it and create a user that can never // be matched to anything, which is worse than not sending it: it inflates the user count and // attributes the conversion to nobody. $this->logger->debug( 'Skipped an event with no way to identify the visitor', array( 'event' => $event->name() ) ); return false; } return $this->enqueue( 'events', $enriched->to_array() ); } /** * Queue an identify call, which attaches an email or phone number to the visitor's profile. * * @param Identity $identity Who the visitor is. * @param string $category Consent category it falls under. * @param VisitorContextInterface|null $visitor Context to attribute it to. * @return bool */ public function identify( Identity $identity, string $category = ConsentGate::CATEGORY_STATISTICS, ?VisitorContextInterface $visitor = null ): bool { $visitor = null !== $visitor ? $visitor : $this->visitor; $settings = $this->settings->load(); if ( ! $settings->is_connected() ) { return false; } // The collector rejects an identify with no external id, email or phone, so there is no point // spending a queued request and a retry cycle discovering that. if ( $identity->is_empty() ) { return false; } if ( ! $this->consent->allows( $category, $visitor ) ) { return false; } $payload = $identity->to_array(); $anonymous = $visitor->anonymous_id(); if ( null !== $anonymous ) { // This is the whole point of the call: it is what merges the anonymous browsing that led up // to the sign-up with the person who signed up. $payload['anonymous_id'] = $anonymous; } $traits = $identity->traits(); if ( array() !== $traits ) { $payload['traits'] = $traits; } $context = $visitor->to_array(); if ( array() !== $context ) { $payload['context'] = $context; } $default_cc = $settings->str( 'default_cc' ); if ( '' !== $default_cc ) { // Lets a national phone number such as 09121234567 resolve to the same person as its // international form, on properties that have not set a calling code of their own. $payload['default_cc'] = $default_cc; } return $this->enqueue( 'identify', $payload ); } /** * Put a payload on the queue. * * @param string $endpoint Endpoint suffix. * @param array $payload Request body. * @return bool */ private function enqueue( string $endpoint, array $payload ): bool { $accepted = $this->queue->push( new Job( $endpoint, $payload ) ); if ( ! $accepted ) { $this->logger->error( 'The delivery queue is full; an event was dropped', array( 'endpoint' => $endpoint, 'event' => isset( $payload['event'] ) && is_scalar( $payload['event'] ) ? (string) $payload['event'] : $endpoint, ) ); } return $accepted; } }