*/ private $values; /** * @param array $values Normalised values. */ private function __construct( array $values ) { $this->values = $values; } /** * Build from a decoded tracker cookie. * * @param mixed $decoded Decoded `__sov_ft` / `__sov_lt` payload. * @return self|null Null when the cookie held nothing describing an acquisition. */ public static function from_cookie( $decoded ): ?self { if ( ! is_array( $decoded ) ) { return null; } $values = array(); foreach ( self::FIELDS as $field ) { if ( isset( $decoded[ $field ] ) && is_scalar( $decoded[ $field ] ) ) { $value = sanitize_text_field( (string) $decoded[ $field ] ); if ( '' !== $value ) { $values[ $field ] = $value; } } } // The collector treats an empty touch as "unknown" rather than "direct", which is the honest // reading — so a cookie with only a timestamp in it is not a touch at all. if ( ! array_filter( $values ) ) { return null; } // `ts` (cookie, milliseconds) becomes `timestamp` (contract). The collector accepts a numeric // millisecond value or an RFC3339 string; milliseconds round-trip without a timezone guess. $ts = $decoded['ts'] ?? ( $decoded['timestamp'] ?? null ); if ( is_numeric( $ts ) && $ts > 0 ) { $values['timestamp'] = (int) $ts; } return new self( $values ); } /** * The contract shape. * * @return array */ public function to_array(): array { return $this->values; } }