*/ private $field_values; /** * Current timestamp. * * @var int */ private $timestamp; /** * Constructor. * * @param string $form_id Generic form identifier. * @param string $honeypot_value Honeypot value. * @param int|null $completion_time_seconds Completion time. * @param string $token Submitted token. * @param string $rate_limit_identifier Identifier used only for hashing. * @param array $field_values In-memory field values. * @param int $timestamp Current timestamp. */ private function __construct( string $form_id, string $honeypot_value, ?int $completion_time_seconds, string $token, string $rate_limit_identifier, array $field_values, int $timestamp ) { $this->form_id = $this->sanitize_key_like_value( $form_id ); $this->honeypot_value = $this->limit_string( $honeypot_value, 1000 ); $this->completion_time_seconds = null === $completion_time_seconds ? null : max( 0, $completion_time_seconds ); $this->token = $this->limit_string( $token, 2048 ); $this->rate_limit_identifier = $this->limit_string( $rate_limit_identifier, 512 ); $this->field_values = $field_values; $this->timestamp = max( 1, $timestamp ); } /** * Create a context from raw data. * * @param array $data Raw data. */ public static function from_array( array $data ): self { $field_values = array(); if ( isset( $data['field_values'] ) && is_array( $data['field_values'] ) ) { foreach ( $data['field_values'] as $value ) { if ( is_scalar( $value ) ) { $field_values[] = self::static_limit_string( (string) $value, 5000 ); } } } return new self( isset( $data['form_id'] ) && is_scalar( $data['form_id'] ) ? (string) $data['form_id'] : 'generic', isset( $data['honeypot_value'] ) && is_scalar( $data['honeypot_value'] ) ? (string) $data['honeypot_value'] : '', isset( $data['completion_time_seconds'] ) && is_scalar( $data['completion_time_seconds'] ) ? (int) $data['completion_time_seconds'] : null, isset( $data['token'] ) && is_scalar( $data['token'] ) ? (string) $data['token'] : '', isset( $data['rate_limit_identifier'] ) && is_scalar( $data['rate_limit_identifier'] ) ? (string) $data['rate_limit_identifier'] : '', $field_values, isset( $data['timestamp'] ) && is_scalar( $data['timestamp'] ) ? (int) $data['timestamp'] : time() ); } /** * Get form ID. */ public function get_form_id(): string { return $this->form_id; } /** * Get honeypot value. */ public function get_honeypot_value(): string { return $this->honeypot_value; } /** * Get completion time. */ public function get_completion_time_seconds(): ?int { return $this->completion_time_seconds; } /** * Get submitted token. */ public function get_token(): string { return $this->token; } /** * Get rate-limit identifier for immediate hashing only. */ public function get_rate_limit_identifier(): string { return $this->rate_limit_identifier; } /** * Get in-memory field values. * * @return array */ public function get_field_values(): array { return $this->field_values; } /** * Get timestamp. */ public function get_timestamp(): int { return $this->timestamp; } /** * Sanitize a key-like value without relying on WordPress runtime. * * @param string $value Raw value. */ private function sanitize_key_like_value( string $value ): string { $sanitized = strtolower( preg_replace( '/[^a-zA-Z0-9_.:-]/', '', $value ) ?? '' ); if ( '' === $sanitized ) { return 'generic'; } return $this->limit_string( $sanitized, 128 ); } /** * Limit string length. * * @param string $value Raw value. * @param int $length Maximum length. */ private function limit_string( string $value, int $length ): string { return self::static_limit_string( $value, $length ); } /** * Limit string length without needing an instance. * * @param string $value Raw value. * @param int $length Maximum length. */ private static function static_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 ); } }