feat: domains, transports, logs, quarantine, spam filter, i18n + UX fixes
Features added: - Admin > Domains: add domains to Mailcow servers, auto-generate DKIM, display full DNS record set (MX, SPF, DMARC, DKIM, autoconfig CNAMEs) with one-click copy per record - Admin > Transports: manage sender-dependent relay hosts (add/delete) - Admin > Logs: view Postfix, Dovecot, Rspamd, Ratelimit, API and other server logs in a dark scrollable panel - My Account: per-domain Quarantine panel — view score, sender, subject, date; permanently delete quarantined messages - My Account: per-mailbox Spam Filter slider (1–15 threshold) saved via API - My Account: Aliases & Forwarders (alias creation doubles as forwarder to any external address) UX fixes: - Quota 0 now displays ∞ (unlimited) in both admin and account views - Admin mailbox action buttons replaced with Dashicon icon buttons (lock, chart-bar, trash) with title tooltips i18n: - load_plugin_textdomain registered on init hook - All user-facing PHP strings wrapped in __() / esc_html__() - Translated strings array passed to account JS via wp_localize_script - woocow-es_ES.po/.mo — Spanish translation - woocow-ro_RO.po/.mo — Romanian translation (with correct plural forms) - English remains the fallback Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,9 @@ class WooCow_Account {
|
||||
'woocow_acct_aliases',
|
||||
'woocow_acct_alias_create',
|
||||
'woocow_acct_alias_delete',
|
||||
'woocow_acct_quarantine',
|
||||
'woocow_acct_quarantine_delete',
|
||||
'woocow_acct_spam_score',
|
||||
];
|
||||
foreach ( $actions as $action ) {
|
||||
add_action( 'wp_ajax_' . $action, [ $this, 'ajax_' . $action ] );
|
||||
@@ -61,6 +64,32 @@ class WooCow_Account {
|
||||
wp_localize_script( 'woocow-account', 'woocowAcct', [
|
||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'woocow_account' ),
|
||||
'i18n' => [
|
||||
'load_mailboxes' => __( 'Load Mailboxes', 'woocow' ),
|
||||
'refresh' => __( 'Refresh', 'woocow' ),
|
||||
'loading' => __( 'Loading…', 'woocow' ),
|
||||
'no_mailboxes' => __( 'No mailboxes yet. Create one below.', 'woocow' ),
|
||||
'change_password' => __( 'Change Password', 'woocow' ),
|
||||
'aliases_forwarders' => __( 'Aliases & Forwarders', 'woocow' ),
|
||||
'spam_filter' => __( 'Spam Filter', 'woocow' ),
|
||||
'webmail' => __( 'Webmail', 'woocow' ),
|
||||
'add_alias' => __( 'Add Alias / Forwarder', 'woocow' ),
|
||||
'no_aliases' => __( 'No aliases for this domain yet.', 'woocow' ),
|
||||
'add_mailbox' => __( 'Add Mailbox', 'woocow' ),
|
||||
'create_mailbox' => __( 'Create Mailbox', 'woocow' ),
|
||||
'cancel' => __( 'Cancel', 'woocow' ),
|
||||
'save' => __( 'Save', 'woocow' ),
|
||||
'delete' => __( 'Delete', 'woocow' ),
|
||||
'quarantine' => __( 'Quarantine', 'woocow' ),
|
||||
'no_quarantine' => __( 'No quarantined messages for this domain.', 'woocow' ),
|
||||
'q_release_note' => __( 'To release a message to your inbox, use the link in your quarantine notification email or via Webmail.', 'woocow' ),
|
||||
'q_delete_confirm' => __( 'Permanently delete this quarantined message?', 'woocow' ),
|
||||
'spam_threshold' => __( 'Spam Filter Threshold', 'woocow' ),
|
||||
'spam_help' => __( 'Lower = stricter. Default is 5. Emails above this score go to spam/quarantine.', 'woocow' ),
|
||||
'update_password' => __( 'Update Password', 'woocow' ),
|
||||
'passwords_mismatch' => __( 'Passwords do not match or are empty.', 'woocow' ),
|
||||
'unlimited' => __( 'Unlimited', 'woocow' ),
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
@@ -94,7 +123,13 @@ class WooCow_Account {
|
||||
<a href="<?php echo esc_url( $assignment->webmail_url ); ?>" target="_blank" rel="noopener" class="woocow-btn woocow-btn-sm">
|
||||
Open Webmail
|
||||
</a>
|
||||
<button class="woocow-btn woocow-btn-sm woocow-btn-outline woocow-load-mailboxes">Load Mailboxes</button>
|
||||
<button class="woocow-btn woocow-btn-sm woocow-btn-outline woocow-load-mailboxes"><?php esc_html_e( 'Load Mailboxes', 'woocow' ); ?></button>
|
||||
<button class="woocow-btn woocow-btn-sm woocow-btn-ghost woocow-load-quarantine"><?php esc_html_e( 'Quarantine', 'woocow' ); ?></button>
|
||||
</div>
|
||||
|
||||
<!-- Quarantine panel -->
|
||||
<div class="woocow-quarantine-wrap" style="display:none">
|
||||
<div class="woocow-quarantine-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="woocow-mailboxes-wrap" style="display:none">
|
||||
@@ -416,6 +451,98 @@ class WooCow_Account {
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
// ── AJAX: Quarantine ──────────────────────────────────────────────────────
|
||||
|
||||
public function ajax_woocow_acct_quarantine(): void {
|
||||
$this->account_verify();
|
||||
|
||||
$server_id = absint( $_POST['server_id'] ?? 0 );
|
||||
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
||||
|
||||
if ( ! $this->verify_ownership( $server_id, $domain ) ) {
|
||||
wp_send_json_error( 'Access denied.', 403 );
|
||||
}
|
||||
|
||||
$server = $this->get_server( $server_id );
|
||||
if ( ! $server ) {
|
||||
wp_send_json_error( 'Server unavailable.' );
|
||||
}
|
||||
|
||||
$api = WooCow_API::from_server( $server );
|
||||
$result = $api->get_quarantine();
|
||||
|
||||
if ( ! $result['success'] ) {
|
||||
wp_send_json_error( $result['error'] ?? 'Could not load quarantine.' );
|
||||
}
|
||||
|
||||
// Filter to messages where the recipient belongs to this domain.
|
||||
$messages = array_values( array_filter( (array) $result['data'], function ( $m ) use ( $domain ) {
|
||||
return isset( $m['rcpt'] ) && str_ends_with( $m['rcpt'], '@' . $domain );
|
||||
} ) );
|
||||
|
||||
wp_send_json_success( $messages );
|
||||
}
|
||||
|
||||
public function ajax_woocow_acct_quarantine_delete(): void {
|
||||
$this->account_verify();
|
||||
|
||||
$server_id = absint( $_POST['server_id'] ?? 0 );
|
||||
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
||||
$qid = absint( $_POST['qid'] ?? 0 );
|
||||
|
||||
if ( ! $this->verify_ownership( $server_id, $domain ) ) {
|
||||
wp_send_json_error( 'Access denied.', 403 );
|
||||
}
|
||||
|
||||
$server = $this->get_server( $server_id );
|
||||
if ( ! $server ) {
|
||||
wp_send_json_error( 'Server unavailable.' );
|
||||
}
|
||||
|
||||
$api = WooCow_API::from_server( $server );
|
||||
$result = $api->delete_quarantine( $qid );
|
||||
|
||||
if ( ! $result['success'] ) {
|
||||
wp_send_json_error( $result['error'] ?? 'Failed to delete quarantine message.' );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
// ── AJAX: Spam score ──────────────────────────────────────────────────────
|
||||
|
||||
public function ajax_woocow_acct_spam_score(): void {
|
||||
$this->account_verify();
|
||||
|
||||
$server_id = absint( $_POST['server_id'] ?? 0 );
|
||||
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
||||
$email = sanitize_email( $_POST['email'] ?? '' );
|
||||
$spam_score = floatval( $_POST['spam_score'] ?? 5 );
|
||||
|
||||
if ( ! $this->verify_ownership( $server_id, $domain ) ) {
|
||||
wp_send_json_error( 'Access denied.', 403 );
|
||||
}
|
||||
if ( ! str_ends_with( $email, '@' . $domain ) ) {
|
||||
wp_send_json_error( 'Email does not belong to your domain.' );
|
||||
}
|
||||
|
||||
$server = $this->get_server( $server_id );
|
||||
if ( ! $server ) {
|
||||
wp_send_json_error( 'Server unavailable.' );
|
||||
}
|
||||
|
||||
$api = WooCow_API::from_server( $server );
|
||||
$result = $api->request( 'POST', '/api/v1/edit/spam-score/' . rawurlencode( $email ), [
|
||||
'spam_score' => $spam_score,
|
||||
] );
|
||||
|
||||
if ( ! $result['success'] ) {
|
||||
wp_send_json_error( $result['error'] ?? 'Failed to update spam score.' );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
// ── WP password change sync ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user