feat: comment spam content checks (URL-in-email, link limits)
- check_comment_content(): new method called from validate_comment() - Detects URL in email field (binance.info/register?ref=... pattern) - Blocks comments with more URLs than max_links threshold - Blocks any link from first-time commenters (0 approved comments) - New options: block_url_in_email, block_links_new_commenters, max_links - Admin: new "Comment Spam Content" card in Honeypot tab with toggles and max_links numeric input Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -131,8 +131,10 @@ class ITK_Admin {
|
|||||||
break;
|
break;
|
||||||
case 'save_settings_honeypot':
|
case 'save_settings_honeypot':
|
||||||
$this->save_settings_form('itk_honeypot', [
|
$this->save_settings_form('itk_honeypot', [
|
||||||
'min_time', 'max_time', 'retain_days',
|
'min_time', 'max_time', 'retain_days', 'max_links',
|
||||||
], []);
|
], [
|
||||||
|
'block_url_in_email', 'block_links_new_commenters',
|
||||||
|
]);
|
||||||
$this->redirect(['tab' => 'honeypot', 'saved' => 1]);
|
$this->redirect(['tab' => 'honeypot', 'saved' => 1]);
|
||||||
break;
|
break;
|
||||||
case 'save_settings_waf':
|
case 'save_settings_waf':
|
||||||
@@ -847,6 +849,34 @@ class ITK_Admin {
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="itk-card">
|
||||||
|
<h2>Comment Spam Content</h2>
|
||||||
|
<p class="description" style="margin-bottom:16px">Content-based rules that catch spam comments that pass the honeypot and timing checks.</p>
|
||||||
|
<?php
|
||||||
|
$content_toggles = [
|
||||||
|
'block_url_in_email' => ['Block URL in Email Field', 'Block comments where the email field contains a URL instead of a real address'],
|
||||||
|
'block_links_new_commenters' => ['Block Links from New Commenters', 'Block any comment with a URL or website from an author with zero approved comments'],
|
||||||
|
];
|
||||||
|
foreach ($content_toggles as $key => [$label, $desc]):
|
||||||
|
$this->render_toggle('itk_honeypot', $key, $label, $desc, $opts);
|
||||||
|
endforeach;
|
||||||
|
?>
|
||||||
|
<form method="post" action="options-general.php?page=<?= self::MENU_SLUG ?>&tab=honeypot" style="margin-top:16px">
|
||||||
|
<?php wp_nonce_field(self::NONCE_ACTION); ?>
|
||||||
|
<input type="hidden" name="itk_action" value="save_settings_honeypot">
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th>Max Links per Comment</th>
|
||||||
|
<td>
|
||||||
|
<input type="number" name="itk_honeypot[max_links]" value="<?= (int)($opts['max_links'] ?? 2) ?>" min="0" max="20">
|
||||||
|
<p class="description">Block comments with more than this many URLs. Set 0 to disable.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php submit_button('Save Content Rules'); ?>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
/* ── Central Honeypot API card ────────────────── */
|
/* ── Central Honeypot API card ────────────────── */
|
||||||
$hp_api = ITK_HP_API::settings();
|
$hp_api = ITK_HP_API::settings();
|
||||||
|
|||||||
@@ -193,9 +193,55 @@ class ITK_Honeypot {
|
|||||||
if (!$this->check_honeypot('comment')) {
|
if (!$this->check_honeypot('comment')) {
|
||||||
wp_die('Spam detected. Please go back and try again.', 'Spam Blocked', ['response' => 403]);
|
wp_die('Spam detected. Please go back and try again.', 'Spam Blocked', ['response' => 403]);
|
||||||
}
|
}
|
||||||
|
if (!$this->check_comment_content($comment_data)) {
|
||||||
|
wp_die('Your comment was flagged as spam. Please remove any links and try again.', 'Spam Blocked', ['response' => 403]);
|
||||||
|
}
|
||||||
return $comment_data;
|
return $comment_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Comment content spam checks ─────────────────────────── */
|
||||||
|
|
||||||
|
private function check_comment_content(array $data): bool {
|
||||||
|
if (ITK_Whitelist::allowed()) return true;
|
||||||
|
|
||||||
|
$email = trim($data['comment_author_email'] ?? '');
|
||||||
|
$content = $data['comment_content'] ?? '';
|
||||||
|
$url = trim($data['comment_author_url'] ?? '');
|
||||||
|
|
||||||
|
// 1. URL in email field — real emails never contain / or ?
|
||||||
|
if (!empty(self::$opts['block_url_in_email']) && $email !== '') {
|
||||||
|
$is_url_in_email = !filter_var($email, FILTER_VALIDATE_EMAIL)
|
||||||
|
&& preg_match('#(https?://|[a-z0-9-]+\.[a-z]{2,}/|[?&]ref=)#i', $email);
|
||||||
|
if ($is_url_in_email) {
|
||||||
|
$this->log_block('comment', 'URL in email field: ' . substr($email, 0, 100));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Count URLs in comment body
|
||||||
|
$url_count = preg_match_all('#https?://\S+#i', $content);
|
||||||
|
$max_links = (int)(self::$opts['max_links'] ?? 2);
|
||||||
|
if ($max_links > 0 && $url_count > $max_links) {
|
||||||
|
$this->log_block('comment', "Too many URLs in comment ({$url_count})");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Any URL from a first-time commenter (0 approved comments)
|
||||||
|
if (!empty(self::$opts['block_links_new_commenters']) && ($url_count > 0 || $url !== '')) {
|
||||||
|
$approved = (int) get_comments([
|
||||||
|
'author_email' => $email,
|
||||||
|
'status' => 'approve',
|
||||||
|
'count' => true,
|
||||||
|
]);
|
||||||
|
if ($approved === 0) {
|
||||||
|
$this->log_block('comment', 'Link from first-time commenter');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public function validate_login($user, string $username, string $password) {
|
public function validate_login($user, string $username, string $password) {
|
||||||
if (empty(self::$opts['protect_login'])) return $user;
|
if (empty(self::$opts['protect_login'])) return $user;
|
||||||
if (!empty($username) && !$this->check_honeypot('login')) {
|
if (!empty($username) && !$this->check_honeypot('login')) {
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ class InformatiQ_Toolkit {
|
|||||||
'min_time' => 3,
|
'min_time' => 3,
|
||||||
'max_time' => 7200,
|
'max_time' => 7200,
|
||||||
'retain_days' => 90,
|
'retain_days' => 90,
|
||||||
|
'block_url_in_email' => 1,
|
||||||
|
'block_links_new_commenters' => 1,
|
||||||
|
'max_links' => 2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user