Skip to content

Commit 3992ee4

Browse files
committed
Implement SpamCop reporting feature with settings and AJAX handling
1 parent df3f9d4 commit 3992ee4

File tree

8 files changed

+409
-16
lines changed

8 files changed

+409
-16
lines changed

modules/core/functions.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,17 @@ function process_site_setting($type, $handler, $callback=false, $default=false,
276276
}
277277
}
278278
else {
279-
list($success, $form) = $handler->process_form(array('save_settings', $type));
279+
list($success, $form) = $handler->process_form(array('save_settings'));
280+
if ($success) {
281+
if (array_key_exists($type, $handler->request->post)) {
282+
$form[$type] = $handler->request->post[$type];
283+
}
284+
else {
285+
$form[$type] = '';
286+
}
287+
}
280288
}
289+
281290
$new_settings = $handler->get('new_user_settings', array());
282291
$settings = $handler->get('user_settings', array());
283292

@@ -291,7 +300,8 @@ function process_site_setting($type, $handler, $callback=false, $default=false,
291300
$new_settings[$type.'_setting'] = $result;
292301
}
293302
else {
294-
$settings[$type] = $handler->user_config->get($type.'_setting', $default);
303+
$value_from_config = $handler->user_config->get($type.'_setting', $default);
304+
$settings[$type] = $value_from_config;
295305
}
296306
$handler->out('new_user_settings', $new_settings, false);
297307
$handler->out('user_settings', $settings, false);
@@ -785,3 +795,23 @@ function isPageConfigured($page) {
785795
return in_array($page, $pages);
786796
}
787797

798+
/**
799+
* Get setting value with fallback to _setting suffix
800+
* @subpackage core/functions
801+
* @param array $settings User settings array
802+
* @param string $key Setting key without _setting suffix
803+
* @param mixed $default Default value
804+
* @return mixed Setting value
805+
*/
806+
if (!hm_exists('get_setting_value')) {
807+
function get_setting_value($settings, $key, $default = '') {
808+
if (array_key_exists($key, $settings)) {
809+
return $settings[$key];
810+
}
811+
if (array_key_exists($key . '_setting', $settings)) {
812+
return $settings[$key . '_setting'];
813+
}
814+
return $default;
815+
}
816+
}
817+

modules/core/handler_modules.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,32 @@ public function process() {
10101010
}
10111011
}
10121012

1013+
/**
1014+
* Process SpamCop reporting settings from the Report Spam section
1015+
* @subpackage core/handler
1016+
*/
1017+
class Hm_Handler_process_spam_report_settings extends Hm_Handler_Module {
1018+
public function process() {
1019+
list($success, $form) = $this->process_form(array('save_settings'));
1020+
if (!$success || !array_key_exists('spamcop_settings', $this->request->post)) {
1021+
return;
1022+
}
1023+
1024+
$new_settings = $this->get('new_user_settings', array());
1025+
$spamcop = $this->request->post['spamcop_settings'];
1026+
1027+
$set_email_setting = function($key, $value) use (&$new_settings) {
1028+
$new_settings[$key] = (!empty($value) && filter_var($value, FILTER_VALIDATE_EMAIL)) ? $value : '';
1029+
};
1030+
1031+
$new_settings['spamcop_enabled_setting'] = isset($spamcop['enabled']);
1032+
$set_email_setting('spamcop_submission_email_setting', $spamcop['submission_email'] ?? '');
1033+
$set_email_setting('spamcop_from_email_setting', $spamcop['from_email'] ?? '');
1034+
1035+
$this->out('new_user_settings', $new_settings, false);
1036+
}
1037+
}
1038+
10131039
/**
10141040
* Process warn for unsaved changes in the settings page
10151041
* @subpackage core/handler

modules/core/output_modules.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,6 +2315,70 @@ protected function output() {
23152315
}
23162316
}
23172317

2318+
/**
2319+
* Starts the Report Spam section on the settings page
2320+
* @subpackage core/output
2321+
*/
2322+
class Hm_Output_start_report_spam_settings extends Hm_Output_Module {
2323+
/**
2324+
* Settings in this section control spam reporting features
2325+
*/
2326+
protected function output() {
2327+
return '<tr><td data-target=".report_spam_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
2328+
'<i class="bi bi-shield-exclamation fs-5 me-2"></i>'.
2329+
$this->trans('Report Spam').'</td></tr>';
2330+
}
2331+
}
2332+
2333+
/**
2334+
* Option to enable/disable SpamCop reporting
2335+
* @subpackage core/output
2336+
*/
2337+
class Hm_Output_spamcop_enabled_setting extends Hm_Output_Module {
2338+
protected function output() {
2339+
$settings = $this->get('user_settings', array());
2340+
$enabled = get_setting_value($settings, 'spamcop_enabled', false);
2341+
$checked = $enabled ? ' checked="checked"' : '';
2342+
$reset = $enabled ? '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>' : '';
2343+
2344+
return '<tr class="report_spam_setting"><td><label class="form-check-label" for="spamcop_enabled">'.
2345+
$this->trans('Enable SpamCop reporting').'</label></td>'.
2346+
'<td><input class="form-check-input" type="checkbox" '.$checked.' id="spamcop_enabled" name="spamcop_settings[enabled]" data-default-value="false" value="1" />'.$reset.'</td></tr>';
2347+
}
2348+
}
2349+
2350+
/**
2351+
* Option for SpamCop submission email address
2352+
* @subpackage core/output
2353+
*/
2354+
class Hm_Output_spamcop_submission_email_setting extends Hm_Output_Module {
2355+
protected function output() {
2356+
$settings = $this->get('user_settings', array());
2357+
$email = get_setting_value($settings, 'spamcop_submission_email', '');
2358+
$reset = !empty($email) ? '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>' : '';
2359+
2360+
return '<tr class="report_spam_setting"><td><label for="spamcop_submission_email">'.
2361+
$this->trans('SpamCop submission email').'</label></td>'.
2362+
'<td class="d-flex"><input class="form-control form-control-sm" type="email" id="spamcop_submission_email" name="spamcop_settings[submission_email]" value="'.$this->html_safe($email).'" placeholder="[email protected]" />'.$reset.'</td></tr>';
2363+
}
2364+
}
2365+
2366+
/**
2367+
* Option for SpamCop from email address
2368+
* @subpackage core/output
2369+
*/
2370+
class Hm_Output_spamcop_from_email_setting extends Hm_Output_Module {
2371+
protected function output() {
2372+
$settings = $this->get('user_settings', array());
2373+
$email = get_setting_value($settings, 'spamcop_from_email', '');
2374+
$reset = !empty($email) ? '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>' : '';
2375+
2376+
return '<tr class="report_spam_setting"><td><label for="spamcop_from_email">'.
2377+
$this->trans('From email address (optional)').'</label></td>'.
2378+
'<td class="d-flex"><input class="form-control form-control-sm" type="email" id="spamcop_from_email" name="spamcop_settings[from_email]" value="'.$this->html_safe($email).'" placeholder="'.$this->trans('Uses your IMAP email if not set').'" />'.$reset.'</td></tr>';
2379+
}
2380+
}
2381+
23182382
/**
23192383
* Option to warn user when he has unsaved changes.
23202384
* @subpackage imap/output

modules/core/setup.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
add_handler('settings', 'process_trash_source_max_setting', true, 'core', 'date', 'after');
5757
add_handler('settings', 'process_drafts_since_setting', true, 'core', 'date', 'after');
5858
add_handler('settings', 'process_drafts_source_max_setting', true, 'core', 'date', 'after');
59+
add_handler('settings', 'process_spam_report_settings', true, 'core', 'save_user_settings', 'before');
5960
add_handler('settings', 'process_hide_folder_icons', true, 'core', 'date', 'after');
6061
add_handler('settings', 'process_delete_prompt_setting', true, 'core', 'date', 'after');
6162
add_handler('settings', 'process_delete_attachment_setting', true, 'core', 'date', 'after');
@@ -101,7 +102,11 @@
101102
add_output('settings', 'start_drafts_settings', true, 'core', 'trash_source_max_setting', 'after');
102103
add_output('settings', 'drafts_since_setting', true, 'core', 'start_drafts_settings', 'after');
103104
add_output('settings', 'drafts_source_max_setting', true, 'core', 'drafts_since_setting', 'after');
104-
add_output('settings', 'start_everything_settings', true, 'core', 'drafts_source_max_setting', 'after');
105+
add_output('settings', 'start_report_spam_settings', true, 'core', 'drafts_source_max_setting', 'after');
106+
add_output('settings', 'spamcop_enabled_setting', true, 'core', 'start_report_spam_settings', 'after');
107+
add_output('settings', 'spamcop_submission_email_setting', true, 'core', 'spamcop_enabled_setting', 'after');
108+
add_output('settings', 'spamcop_from_email_setting', true, 'core', 'spamcop_submission_email_setting', 'after');
109+
add_output('settings', 'start_everything_settings', true, 'core', 'spamcop_from_email_setting', 'after');
105110
add_output('settings', 'all_since_setting', true, 'core', 'start_everything_settings', 'after');
106111
add_output('settings', 'all_source_max_setting', true, 'core', 'all_since_setting', 'after');
107112
add_output('settings', 'start_all_email_settings', true, 'core', 'all_source_max_setting', 'after');
@@ -360,5 +365,6 @@
360365
'srv_setup_stepper_imap_hide_from_c_page' => FILTER_VALIDATE_BOOLEAN,
361366
'images_whitelist' => FILTER_UNSAFE_RAW,
362367
'update' => FILTER_VALIDATE_BOOLEAN,
368+
'spamcop_settings' => array('filter' => FILTER_UNSAFE_RAW, 'flags' => FILTER_FORCE_ARRAY),
363369
)
364370
);

modules/imap/functions.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,3 +1673,133 @@ function is_imap_archive_folder($server_id, $user_config, $current_folder) {
16731673

16741674
return false;
16751675
}}
1676+
1677+
/**
1678+
* Error messages from spam reporting services
1679+
* @subpackage imap/functions
1680+
* @param string $error_msg Raw error message from service
1681+
* @return string User-friendly error message
1682+
*/
1683+
if (!hm_exists('normalize_spam_report_error')) {
1684+
function normalize_spam_report_error($error_msg) {
1685+
$error_mappings = array(
1686+
'not enabled' => 'SpamCop reporting is not enabled. Please enable it in Settings.',
1687+
'not configured' => 'SpamCop submission email is not configured. Please configure it in Settings.',
1688+
'submission email' => 'SpamCop submission email is not configured. Please configure it in Settings.',
1689+
'sender email' => 'No sender email address configured. Please configure it in Settings.',
1690+
'No sender' => 'No sender email address configured. Please configure it in Settings.',
1691+
'Failed to send email' => 'Failed to send email to SpamCop. Please check your server mail configuration.',
1692+
'send email' => 'Failed to send email to SpamCop. Please check your server mail configuration.'
1693+
);
1694+
1695+
foreach ($error_mappings as $key => $message) {
1696+
if (strpos($error_msg, $key) !== false) {
1697+
return $message;
1698+
}
1699+
}
1700+
1701+
return $error_msg;
1702+
}}
1703+
1704+
/**
1705+
* Report spam message to SpamCop
1706+
*/
1707+
if (!hm_exists('report_spam_to_spamcop')) {
1708+
function report_spam_to_spamcop($message_source, $reasons, $user_config) {
1709+
$spamcop_enabled = $user_config->get('spamcop_enabled_setting', false);
1710+
if (!$spamcop_enabled) {
1711+
return array('success' => false, 'error' => 'SpamCop reporting is not enabled');
1712+
}
1713+
1714+
$spamcop_email = $user_config->get('spamcop_submission_email_setting', '');
1715+
if (empty($spamcop_email)) {
1716+
return array('success' => false, 'error' => 'SpamCop submission email not configured');
1717+
}
1718+
1719+
$sanitized_message = sanitize_message_for_spam_report($message_source, $user_config);
1720+
1721+
$from_email = $user_config->get('spamcop_from_email_setting', '');
1722+
if (empty($from_email)) {
1723+
// Try to get from IMAP servers
1724+
$imap_servers = $user_config->get('imap_servers', array());
1725+
if (!empty($imap_servers)) {
1726+
$first_server = reset($imap_servers);
1727+
$from_email = isset($first_server['user']) ? $first_server['user'] : '';
1728+
}
1729+
}
1730+
1731+
if (empty($from_email)) {
1732+
return array('success' => false, 'error' => 'No sender email address configured');
1733+
}
1734+
1735+
$reasons_text = implode(', ', $reasons);
1736+
1737+
$subject = 'Spam Report: ' . $reasons_text;
1738+
1739+
$body = "This email is being reported as spam for the following reasons:\n\n";
1740+
$body .= $reasons_text . "\n\n";
1741+
$body .= "--- Original Message ---\n\n";
1742+
$body .= $sanitized_message;
1743+
1744+
$timeout = 10; //dont foget to add it to UI
1745+
$old_timeout = ini_get('default_socket_timeout');
1746+
ini_set('default_socket_timeout', $timeout);
1747+
1748+
try {
1749+
$headers = array();
1750+
$headers[] = 'From: ' . $from_email;
1751+
$headers[] = 'Reply-To: ' . $from_email;
1752+
$headers[] = 'X-Mailer: Cypht Spam Reporter';
1753+
$headers[] = 'Content-Type: message/rfc822';
1754+
1755+
$mail_sent = @mail($spamcop_email, $subject, $body, implode("\r\n", $headers));
1756+
1757+
ini_set('default_socket_timeout', $old_timeout);
1758+
1759+
if ($mail_sent) {
1760+
return array('success' => true);
1761+
} else {
1762+
return array('success' => false, 'error' => 'Failed to send email to SpamCop');
1763+
}
1764+
} catch (Exception $e) {
1765+
ini_set('default_socket_timeout', $old_timeout);
1766+
return array('success' => false, 'error' => $e->getMessage());
1767+
}
1768+
}}
1769+
1770+
/**
1771+
* Sanitize message source for spam reporting
1772+
*/
1773+
if (!hm_exists('sanitize_message_for_spam_report')) {
1774+
function sanitize_message_for_spam_report($message_source, $user_config) {
1775+
$user_emails = array();
1776+
$imap_servers = $user_config->get('imap_servers', array());
1777+
foreach ($imap_servers as $server) {
1778+
if (isset($server['user'])) {
1779+
$user_emails[] = strtolower($server['user']);
1780+
}
1781+
}
1782+
1783+
// Split message into headers and body
1784+
$parts = explode("\r\n\r\n", $message_source, 2);
1785+
$headers = isset($parts[0]) ? $parts[0] : '';
1786+
$body = isset($parts[1]) ? $parts[1] : '';
1787+
1788+
if (!empty($user_emails)) {
1789+
foreach ($user_emails as $email) {
1790+
// Remove email from various headers
1791+
$headers = preg_replace('/\b' . preg_quote($email, '/') . '\b/i', '[REDACTED]', $headers);
1792+
}
1793+
}
1794+
1795+
// Remove sensitive headers
1796+
$sensitive_headers = array('X-Original-From', 'X-Forwarded-For', 'X-Real-IP');
1797+
foreach ($sensitive_headers as $header) {
1798+
$headers = preg_replace('/^' . preg_quote($header, '/') . ':.*$/mi', '', $headers);
1799+
}
1800+
1801+
// Clean up multiple blank lines
1802+
$headers = preg_replace('/\r\n\r\n+/', "\r\n\r\n", $headers);
1803+
1804+
return $headers . "\r\n\r\n" . $body;
1805+
}}

0 commit comments

Comments
 (0)