$fields Submitted fields. * @return true|WP_Error */ public static function check( array $fields ) { $trap = $fields[ self::FIELD_TRAP ] ?? ''; $token = $fields[ self::FIELD_TOKEN ] ?? ''; if ( '' !== $trap || ! is_string( $token ) || ! self::token_is_valid( $token ) ) { return self::rejected(); } $captcha = apply_filters( self::FILTER_CAPTCHA, true, $fields ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- The constant holds the prefixed hook name. if ( is_wp_error( $captcha ) ) { return $captcha; } return true === $captcha ? true : self::rejected(); } /** * Asks spam services whether a submission looks like spam. * * @param array{title: string, description: string, name: string, email: string} $submission Submitted request. */ public static function is_spam( array $submission ): bool { return (bool) apply_filters( self::FILTER_SPAM, false, $submission ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- The constant holds the prefixed hook name. } /** * Whether a token is genuine and old enough. * * @param string $token Token from the form. */ private static function token_is_valid( string $token ): bool { $parts = explode( '.', $token, 2 ); if ( 2 !== count( $parts ) || ! ctype_digit( $parts[0] ) || ! hash_equals( self::sign( $parts[0] ), $parts[1] ) ) { return false; } return time() - (int) $parts[0] >= self::MIN_SECONDS; } /** * Signs a value with a secret only this site knows. * * @param string $value Value to sign. */ private static function sign( string $value ): string { return hash_hmac( 'sha256', $value, wp_salt( 'nonce' ) ); } /** * The error for a refused submission. It does not say which check failed. */ private static function rejected(): WP_Error { return new WP_Error( self::ERROR_REJECTED, __( 'Your request could not be sent. Reload the page and try again.', 'feature-request-system' ), [ 'status' => 400 ] ); } }