> */ private $data; /** * Constructor. * * @param array $data Event data. */ private function __construct( array $data ) { $this->data = array( 'event_id' => $this->sanitize_key_like_value( $this->get_scalar_string( $data, 'event_id' ), 80 ), 'created_at' => $this->sanitize_timestamp( isset( $data['created_at'] ) && is_scalar( $data['created_at'] ) ? (int) $data['created_at'] : time() ), 'source_key' => $this->sanitize_key_like_value( $this->get_scalar_string( $data, 'source_key' ), 80 ), 'form_id' => $this->sanitize_key_like_value( $this->get_scalar_string( $data, 'form_id' ), 128 ), 'action' => $this->sanitize_action( $this->get_scalar_string( $data, 'action' ) ), 'score' => $this->sanitize_score( isset( $data['score'] ) && is_scalar( $data['score'] ) ? (int) $data['score'] : 0 ), 'reason_codes' => $this->sanitize_reason_codes( isset( $data['reason_codes'] ) && is_array( $data['reason_codes'] ) ? $data['reason_codes'] : array() ), 'ip_hash' => $this->sanitize_hash( $this->get_scalar_string( $data, 'ip_hash' ) ), 'email_hash' => $this->sanitize_hash( $this->get_scalar_string( $data, 'email_hash' ) ), 'user_agent_hash' => $this->sanitize_hash( $this->get_scalar_string( $data, 'user_agent_hash' ) ), 'context_hash' => $this->sanitize_hash( $this->get_scalar_string( $data, 'context_hash' ) ), ); if ( '' === $this->data['event_id'] ) { $this->data['event_id'] = hash( 'sha256', implode( '|', array( (string) $this->data['created_at'], (string) $this->data['source_key'], (string) $this->data['form_id'], (string) $this->data['action'] ) ) ); } } /** * Create an event from array data. * * @param array $data Event data. */ public static function from_array( array $data ): self { return new self( $data ); } /** * Convert to storage array. * * @return array> */ public function to_array(): array { return $this->data; } /** * Get event ID. */ public function get_event_id(): string { return (string) $this->data['event_id']; } /** * Get created timestamp. */ public function get_created_at(): int { return (int) $this->data['created_at']; } /** * Get source key. */ public function get_source_key(): string { return (string) $this->data['source_key']; } /** * Get form ID. */ public function get_form_id(): string { return (string) $this->data['form_id']; } /** * Get action. */ public function get_action(): string { return (string) $this->data['action']; } /** * Get score. */ public function get_score(): int { return (int) $this->data['score']; } /** * Get reason codes. * * @return array */ public function get_reason_codes(): array { return is_array( $this->data['reason_codes'] ) ? $this->data['reason_codes'] : array(); } /** * Get a scalar string field. * * @param array $data Event data. * @param string $key Field key. */ private function get_scalar_string( array $data, string $key ): string { if ( ! isset( $data[ $key ] ) || ! is_scalar( $data[ $key ] ) ) { return ''; } return (string) $data[ $key ]; } /** * Sanitize a key-like value. * * @param string $value Value. * @param int $length Max length. */ private function sanitize_key_like_value( string $value, int $length ): string { $value = strtolower( preg_replace( '/[^a-zA-Z0-9_.:-]/', '', $value ) ?? '' ); return $this->limit_string( $value, $length ); } /** * Sanitize action. * * @param string $action Action. */ private function sanitize_action( string $action ): string { if ( in_array( $action, array( self::ACTION_ALLOWED, self::ACTION_SUSPICIOUS, self::ACTION_BLOCKED ), true ) ) { return $action; } return self::ACTION_SUSPICIOUS; } /** * Sanitize timestamp. * * @param int $timestamp Timestamp. */ private function sanitize_timestamp( int $timestamp ): int { return max( 1, $timestamp ); } /** * Sanitize score. * * @param int $score Score. */ private function sanitize_score( int $score ): int { return max( 0, min( 100, $score ) ); } /** * Sanitize reason codes. * * @param array $reason_codes Reason codes. * * @return array */ private function sanitize_reason_codes( array $reason_codes ): array { $sanitized = array(); foreach ( $reason_codes as $reason_code ) { if ( ! is_scalar( $reason_code ) ) { continue; } $reason_code = strtolower( (string) $reason_code ); if ( preg_match( '/^[a-z0-9_]+$/', $reason_code ) ) { $sanitized[] = $this->limit_string( $reason_code, 80 ); } } return array_values( array_unique( $sanitized ) ); } /** * Sanitize a stored hash. * * @param string $hash Hash. */ private function sanitize_hash( string $hash ): string { if ( ! preg_match( '/^[a-f0-9]{64}$/', $hash ) ) { return ''; } return $hash; } /** * Limit a string safely. * * @param string $value Value. * @param int $length Max length. */ private function limit_string( string $value, int $length ): string { $value = trim( str_replace( "\0", '', $value ) ); if ( function_exists( 'mb_substr' ) ) { return mb_substr( $value, 0, $length ); } return substr( $value, 0, $length ); } }