' . '' . ''; echo ''; } /** True when the silent checks indicate a bot. */ private function looks_like_bot() { if ( ! empty( $_POST[ self::HP ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return true; } $token = isset( $_POST[ self::TOKEN ] ) ? sanitize_text_field( wp_unslash( $_POST[ self::TOKEN ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification if ( '' === $token || false === strpos( $token, '|' ) ) { return true; // token missing/malformed → almost certainly a bot. } list( $ts, $sig ) = explode( '|', $token, 2 ); if ( ! hash_equals( hash_hmac( 'sha256', $ts, wp_salt( 'auth' ) ), $sig ) ) { return true; // forged token. } $min = max( 1, (int) Helper::get_setting( 'antispam_min_seconds', 3 ) ); return ( time() - (int) $ts ) < $min; // submitted too fast. } /** * @param array $commentdata * @return array */ public function check_comment( $commentdata ) { // Only guard anonymous submissions. if ( ! is_user_logged_in() && $this->looks_like_bot() ) { Logger::log( 'antispam_comment_blocked', sprintf( 'Blocked spam comment from %s.', Helper::get_ip_address() ), Logger::SEVERITY_NOTICE, [ 'user_id' => 0 ] ); wp_die( esc_html__( 'Your comment was blocked as spam.', 'gemsecurity' ), esc_html__( 'Comment blocked', 'gemsecurity' ), [ 'response' => 403, 'back_link' => true ] ); } return $commentdata; } /** * @param \WP_Error $errors * @return \WP_Error */ public function check_registration( $errors ) { if ( $this->looks_like_bot() ) { $errors->add( 'gemsec_antispam', __( 'Registration blocked. Please try again.', 'gemsecurity' ) ); Logger::log( 'antispam_register_blocked', sprintf( 'Blocked spam registration from %s.', Helper::get_ip_address() ), Logger::SEVERITY_NOTICE, [ 'user_id' => 0 ] ); } return $errors; } /** * Heuristic comment scoring — flag as spam on too many links or blocklisted * words/domains. * * @param int|string $approved * @param array $commentdata * @return int|string */ public function score_comment( $approved, $commentdata ) { if ( 'spam' === $approved || 'trash' === $approved ) { return $approved; } $content = isset( $commentdata['comment_content'] ) ? strtolower( $commentdata['comment_content'] ) : ''; $author = isset( $commentdata['comment_author'] ) ? strtolower( $commentdata['comment_author'] ) : ''; $max_links = max( 0, (int) Helper::get_setting( 'antispam_max_links', 3 ) ); $links = preg_match_all( '#https?://#i', $content ); if ( $links > $max_links ) { return 'spam'; } $keywords = (array) Helper::get_setting( 'antispam_keywords', [] ); foreach ( $keywords as $kw ) { $kw = strtolower( trim( (string) $kw ) ); if ( '' !== $kw && ( false !== strpos( $content, $kw ) || false !== strpos( $author, $kw ) ) ) { return 'spam'; } } // Reuse the link blocklist from the link scanner if present. $blocklist = (array) Helper::get_setting( 'link_blocklist', [] ); foreach ( $blocklist as $domain ) { $domain = strtolower( trim( (string) $domain ) ); if ( '' !== $domain && false !== strpos( $content, $domain ) ) { return 'spam'; } } return $approved; } }