= 0 && $elapsed <= self::MAX_AGE_SECONDS; } /** * Derive the per-render honeypot field name. * * Scoped to the timestamp + form + post so two forms on the same render * don't share a honeypot name, and a bot that scraped one form can't reuse * the name against another. * * @param int $timestamp The verified timestamp. * @param int $form_id The form post ID. * @param int $post_id The post ID. * @return string The expected honeypot field name (e.g. "gb_a1b2c3"). */ public static function honeypot_field_name( $timestamp, $form_id = 0, $post_id = 0 ) { $payload = 'hp:' . self::sign_payload( (string) $timestamp, $form_id, $post_id ); $hash = hash_hmac( 'sha256', $payload, wp_salt() ); return 'gb_' . substr( $hash, 0, 6 ); } /** * Build a canonical payload string for HMAC signing. * * Pipe-delimited and segment values are absint'd, so collisions between * (form_id=12, post_id=345) and (form_id=1, post_id=2345) etc. are not * possible — integers can't contain pipes. * * @param string $time Timestamp string. * @param int $form_id Form post ID. * @param int $post_id Host page ID. * @return string */ private static function sign_payload( $time, $form_id, $post_id ) { return $time . '|' . absint( $form_id ) . '|' . absint( $post_id ); } }