2026-02-27 08:06:22 +01:00
|
|
|
|
<?php
|
|
|
|
|
|
/**
|
|
|
|
|
|
* WooCow Admin – menu pages + AJAX handlers for the backend.
|
|
|
|
|
|
*/
|
|
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
|
|
|
|
|
|
|
|
class WooCow_Admin {
|
|
|
|
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
|
|
add_action( 'admin_menu', [ $this, 'register_menu' ] );
|
|
|
|
|
|
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
|
|
|
|
|
|
|
|
|
|
|
|
$ajax_actions = [
|
|
|
|
|
|
'woocow_servers_list',
|
|
|
|
|
|
'woocow_server_save',
|
|
|
|
|
|
'woocow_server_delete',
|
|
|
|
|
|
'woocow_server_test',
|
|
|
|
|
|
'woocow_server_domains',
|
|
|
|
|
|
'woocow_assignments_list',
|
|
|
|
|
|
'woocow_assignment_save',
|
|
|
|
|
|
'woocow_assignment_delete',
|
|
|
|
|
|
'woocow_customers_search',
|
|
|
|
|
|
'woocow_admin_mailboxes',
|
|
|
|
|
|
'woocow_admin_mailbox_create',
|
|
|
|
|
|
'woocow_admin_mailbox_delete',
|
2026-02-27 08:21:46 +01:00
|
|
|
|
'woocow_admin_mailbox_edit',
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
'woocow_admin_domain_add',
|
|
|
|
|
|
'woocow_admin_domain_dns',
|
2026-02-27 08:53:11 +01:00
|
|
|
|
'woocow_admin_domain_edit',
|
|
|
|
|
|
'woocow_admin_domain_delete',
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
'woocow_admin_relayhosts_list',
|
|
|
|
|
|
'woocow_admin_relayhost_save',
|
|
|
|
|
|
'woocow_admin_relayhost_delete',
|
|
|
|
|
|
'woocow_admin_logs',
|
2026-02-27 08:53:11 +01:00
|
|
|
|
'woocow_admin_quarantine',
|
|
|
|
|
|
'woocow_admin_quarantine_delete',
|
|
|
|
|
|
'woocow_admin_quarantine_block',
|
2026-02-27 08:06:22 +01:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
foreach ( $ajax_actions as $action ) {
|
|
|
|
|
|
add_action( 'wp_ajax_' . $action, [ $this, 'ajax_' . $action ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Menu ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function register_menu(): void {
|
|
|
|
|
|
add_menu_page(
|
|
|
|
|
|
'WooCow',
|
|
|
|
|
|
'WooCow',
|
|
|
|
|
|
'manage_woocommerce',
|
|
|
|
|
|
'woocow',
|
|
|
|
|
|
[ $this, 'page_dashboard' ],
|
|
|
|
|
|
'dashicons-email-alt2',
|
|
|
|
|
|
56
|
|
|
|
|
|
);
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
add_submenu_page( 'woocow', 'Dashboard', 'Dashboard', 'manage_woocommerce', 'woocow', [ $this, 'page_dashboard' ] );
|
2026-02-27 08:06:22 +01:00
|
|
|
|
add_submenu_page( 'woocow', 'Servers', 'Servers', 'manage_woocommerce', 'woocow-servers', [ $this, 'page_servers' ] );
|
|
|
|
|
|
add_submenu_page( 'woocow', 'Assignments','Assignments', 'manage_woocommerce', 'woocow-assignments', [ $this, 'page_assignments' ] );
|
|
|
|
|
|
add_submenu_page( 'woocow', 'Mailboxes', 'Mailboxes', 'manage_woocommerce', 'woocow-mailboxes', [ $this, 'page_mailboxes' ] );
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
add_submenu_page( 'woocow', 'Domains', 'Domains', 'manage_woocommerce', 'woocow-domains', [ $this, 'page_domains' ] );
|
|
|
|
|
|
add_submenu_page( 'woocow', 'Transports','Transports', 'manage_woocommerce', 'woocow-transports', [ $this, 'page_transports' ] );
|
2026-02-27 08:53:11 +01:00
|
|
|
|
add_submenu_page( 'woocow', 'Logs', 'Logs', 'manage_woocommerce', 'woocow-logs', [ $this, 'page_logs' ] );
|
|
|
|
|
|
add_submenu_page( 'woocow', 'Quarantine', 'Quarantine', 'manage_woocommerce', 'woocow-quarantine', [ $this, 'page_quarantine' ] );
|
2026-02-27 08:06:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function enqueue_assets( string $hook ): void {
|
|
|
|
|
|
if ( strpos( $hook, 'woocow' ) === false ) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
wp_enqueue_style( 'woocow-admin', WOOCOW_PLUGIN_URL . 'assets/css/woocow.css', [], WOOCOW_VERSION );
|
|
|
|
|
|
wp_enqueue_script( 'woocow-admin', WOOCOW_PLUGIN_URL . 'assets/js/woocow-admin.js', [ 'jquery' ], WOOCOW_VERSION, true );
|
|
|
|
|
|
wp_localize_script( 'woocow-admin', 'woocow', [
|
|
|
|
|
|
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
|
|
|
|
'nonce' => wp_create_nonce( 'woocow_admin' ),
|
|
|
|
|
|
] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page: Dashboard ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_dashboard(): void {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$servers = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocow_servers WHERE active=1" );
|
|
|
|
|
|
$assignments = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocow_assignments" );
|
|
|
|
|
|
$customers = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT customer_id) FROM {$wpdb->prefix}woocow_assignments" );
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow <span class="woocow-version">v<?php echo esc_html( WOOCOW_VERSION ); ?></span></h1>
|
|
|
|
|
|
<div class="woocow-dashboard-cards">
|
|
|
|
|
|
<div class="woocow-card">
|
|
|
|
|
|
<span class="woocow-card-number"><?php echo esc_html( $servers ); ?></span>
|
|
|
|
|
|
<span class="woocow-card-label">Active Servers</span>
|
|
|
|
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woocow-servers' ) ); ?>" class="button button-secondary">Manage</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="woocow-card">
|
|
|
|
|
|
<span class="woocow-card-number"><?php echo esc_html( $customers ); ?></span>
|
|
|
|
|
|
<span class="woocow-card-label">Customers with Email</span>
|
|
|
|
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woocow-assignments' ) ); ?>" class="button button-secondary">Manage</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="woocow-card">
|
|
|
|
|
|
<span class="woocow-card-number"><?php echo esc_html( $assignments ); ?></span>
|
|
|
|
|
|
<span class="woocow-card-label">Domain Assignments</span>
|
|
|
|
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woocow-mailboxes' ) ); ?>" class="button button-secondary">View Mailboxes</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page: Servers ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_servers(): void {
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Servers</h1>
|
|
|
|
|
|
<p>Add your Mailcow server instances here. The API key must be a read-write key from <strong>Configuration → Access → Edit administrator details → API</strong>.</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-toolbar">
|
|
|
|
|
|
<button class="button button-primary" id="wc-add-server">+ Add Server</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="wc-notices"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Add / Edit form (hidden by default) -->
|
|
|
|
|
|
<div id="wc-server-form" class="woocow-card woocow-form" style="display:none">
|
|
|
|
|
|
<h3 id="wc-server-form-title">Add Server</h3>
|
|
|
|
|
|
<input type="hidden" id="wc-server-id" value="">
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-server-name">Name</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-server-name" class="regular-text" placeholder="My Mailcow Server"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-server-url">Server URL</label></th>
|
|
|
|
|
|
<td><input type="url" id="wc-server-url" class="regular-text" placeholder="https://mail.example.com"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-server-key">API Key</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-server-key" class="regular-text" placeholder="Your read-write API key"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-server-active">Active</label></th>
|
|
|
|
|
|
<td><input type="checkbox" id="wc-server-active" checked></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div class="woocow-form-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-server-save">Save Server</button>
|
|
|
|
|
|
<button class="button" id="wc-server-test">Test Connection</button>
|
|
|
|
|
|
<button class="button" id="wc-server-cancel">Cancel</button>
|
|
|
|
|
|
<span id="wc-server-test-result"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Servers table -->
|
|
|
|
|
|
<div id="wc-servers-table-wrap">
|
|
|
|
|
|
<p id="wc-servers-loading">Loading servers…</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page: Assignments ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_assignments(): void {
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Domain Assignments</h1>
|
|
|
|
|
|
<p>Assign one or more Mailcow domains to a WooCommerce customer. The customer can then manage mailboxes for those domains from <em>My Account → Email Hosting</em>.</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-card woocow-form" id="wc-assign-form">
|
|
|
|
|
|
<h3>Assign Domain to Customer</h3>
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label>Customer</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<input type="text" id="wc-cust-search" class="regular-text" placeholder="Search by name or email…" autocomplete="off">
|
|
|
|
|
|
<div id="wc-cust-results" class="woocow-autocomplete"></div>
|
|
|
|
|
|
<input type="hidden" id="wc-cust-id">
|
|
|
|
|
|
<span id="wc-cust-selected" class="woocow-selected-badge"></span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-assign-server">Server</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<select id="wc-assign-server" class="regular-text">
|
|
|
|
|
|
<option value="">— Select a server —</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr id="wc-domain-row" style="display:none">
|
|
|
|
|
|
<th><label for="wc-assign-domain">Domain</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<select id="wc-assign-domain" class="regular-text">
|
|
|
|
|
|
<option value="">— Loading domains —</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div class="woocow-form-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-assign-save">Assign Domain</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="wc-assign-notice"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<h2>Current Assignments</h2>
|
|
|
|
|
|
<div id="wc-assignments-loading">Loading…</div>
|
|
|
|
|
|
<div id="wc-assignments-table-wrap"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page: Mailboxes ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_mailboxes(): void {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$servers = $wpdb->get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Mailboxes</h1>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-toolbar woocow-flex">
|
|
|
|
|
|
<select id="wc-mb-server" class="regular-text">
|
|
|
|
|
|
<option value="">— Select server —</option>
|
|
|
|
|
|
<?php foreach ( $servers as $s ) : ?>
|
|
|
|
|
|
<option value="<?php echo esc_attr( $s->id ); ?>"><?php echo esc_html( $s->name ); ?></option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<select id="wc-mb-domain" class="regular-text" style="display:none">
|
|
|
|
|
|
<option value="">— Select domain —</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="button" id="wc-mb-load" disabled>Load Mailboxes</button>
|
|
|
|
|
|
<button class="button button-primary" id="wc-mb-create" style="display:none">+ Create Mailbox</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-mb-notices"></div>
|
|
|
|
|
|
<div id="wc-mb-table-wrap"></div>
|
|
|
|
|
|
|
2026-02-27 08:21:46 +01:00
|
|
|
|
<!-- Edit Mailbox Modal (password / quota) -->
|
|
|
|
|
|
<div id="wc-mb-edit-modal" class="woocow-modal" style="display:none">
|
|
|
|
|
|
<div class="woocow-modal-box">
|
|
|
|
|
|
<h3 id="wc-mb-edit-title">Edit Mailbox</h3>
|
|
|
|
|
|
<p id="wc-mb-edit-subtitle" class="woocow-modal-subtitle"></p>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-mb-edit-pw-section" style="display:none">
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-edit-pass">New Password</label></th>
|
|
|
|
|
|
<td><input type="password" id="wc-mb-edit-pass" class="regular-text" autocomplete="new-password"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-edit-pass2">Confirm Password</label></th>
|
|
|
|
|
|
<td><input type="password" id="wc-mb-edit-pass2" class="regular-text" autocomplete="new-password"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-mb-edit-quota-section" style="display:none">
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-edit-quota">Max Quota (MB)</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<input type="number" id="wc-mb-edit-quota" class="small-text" min="1" step="1">
|
|
|
|
|
|
<p class="description">Current usage is shown in the mailbox list. 1024 MB = 1 GB.</p>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-modal-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-mb-edit-save">Save</button>
|
|
|
|
|
|
<button class="button" id="wc-mb-edit-cancel">Cancel</button>
|
|
|
|
|
|
<span id="wc-mb-edit-notice"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-27 08:06:22 +01:00
|
|
|
|
<!-- Create Mailbox Modal -->
|
|
|
|
|
|
<div id="wc-mb-modal" class="woocow-modal" style="display:none">
|
|
|
|
|
|
<div class="woocow-modal-box">
|
|
|
|
|
|
<h3>Create Mailbox</h3>
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-local">Local Part</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<div class="woocow-flex-inline">
|
|
|
|
|
|
<input type="text" id="wc-mb-local" placeholder="user">
|
|
|
|
|
|
<span class="woocow-at">@</span>
|
|
|
|
|
|
<span id="wc-mb-domain-label" class="woocow-domain-label"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-fullname">Full Name</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-mb-fullname" class="regular-text" placeholder="Jane Doe"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-pass">Password</label></th>
|
|
|
|
|
|
<td><input type="password" id="wc-mb-pass" class="regular-text"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-pass2">Confirm Password</label></th>
|
|
|
|
|
|
<td><input type="password" id="wc-mb-pass2" class="regular-text"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-mb-quota">Quota (MB)</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-mb-quota" value="1024" min="1"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div class="woocow-modal-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-mb-modal-save">Create</button>
|
|
|
|
|
|
<button class="button" id="wc-mb-modal-cancel">Cancel</button>
|
|
|
|
|
|
<span id="wc-mb-modal-notice"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
// ── Page: Domains ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_domains(): void {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$servers = $wpdb->get_results( "SELECT id, name, url FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Domains</h1>
|
|
|
|
|
|
<p>Add and manage domains on your Mailcow servers. After adding a domain, DNS records (including DKIM) are generated automatically.</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-toolbar woocow-flex">
|
|
|
|
|
|
<select id="wc-dom-server" class="regular-text">
|
|
|
|
|
|
<option value="">— Select server —</option>
|
|
|
|
|
|
<?php foreach ( $servers as $s ) : ?>
|
|
|
|
|
|
<option value="<?php echo esc_attr( $s->id ); ?>"
|
|
|
|
|
|
data-url="<?php echo esc_attr( $s->url ); ?>">
|
|
|
|
|
|
<?php echo esc_html( $s->name ); ?>
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="button" id="wc-dom-load" disabled>Load Domains</button>
|
|
|
|
|
|
<button class="button button-primary" id="wc-dom-add-btn" style="display:none">+ Add Domain</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-dom-notices"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Add Domain form -->
|
|
|
|
|
|
<div id="wc-dom-form" class="woocow-card woocow-form" style="display:none">
|
|
|
|
|
|
<h3>Add Domain</h3>
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-name">Domain</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-dom-name" class="regular-text" placeholder="example.com"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-desc">Description</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-dom-desc" class="regular-text" placeholder="Optional"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-mailboxes">Max Mailboxes</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-mailboxes" value="10" min="1"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-aliases">Max Aliases</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-aliases" value="400" min="0"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-quota">Total Quota (MB)</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-quota" value="10240" min="1"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-defquota">Default Mailbox Quota (MB)</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-defquota" value="3072" min="1"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-dkim-size">DKIM Key Size</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<select id="wc-dom-dkim-size">
|
|
|
|
|
|
<option value="2048" selected>2048 (recommended)</option>
|
|
|
|
|
|
<option value="1024">1024</option>
|
|
|
|
|
|
<option value="4096">4096</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div class="woocow-form-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-dom-save">Add Domain & Generate DKIM</button>
|
|
|
|
|
|
<button class="button" id="wc-dom-cancel">Cancel</button>
|
|
|
|
|
|
<span id="wc-dom-form-notice"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Domain list -->
|
|
|
|
|
|
<div id="wc-dom-table-wrap"></div>
|
|
|
|
|
|
|
2026-02-27 08:53:11 +01:00
|
|
|
|
<!-- Edit Domain Modal -->
|
|
|
|
|
|
<div id="wc-dom-edit-modal" class="woocow-modal" style="display:none">
|
|
|
|
|
|
<div class="woocow-modal-box" style="max-width:600px">
|
|
|
|
|
|
<h3>Edit Domain: <span id="wc-dom-edit-name"></span></h3>
|
|
|
|
|
|
<input type="hidden" id="wc-dom-edit-domain">
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-desc">Description</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-dom-edit-desc" class="regular-text"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-mboxes">Max Mailboxes</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-edit-mboxes" class="small-text" min="1"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-aliases">Max Aliases</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-edit-aliases" class="small-text" min="0"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-quota">Total Quota (MB)</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-edit-quota" class="small-text" min="1">
|
|
|
|
|
|
<p class="description">Sum of all mailbox quotas for this domain.</p></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-defquota">Default Mailbox Quota (MB)</label></th>
|
|
|
|
|
|
<td><input type="number" id="wc-dom-edit-defquota" class="small-text" min="1"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label>Rate Limit</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<div class="woocow-flex-inline">
|
|
|
|
|
|
<input type="number" id="wc-dom-edit-rl-value" class="small-text" min="0" placeholder="0 = off">
|
|
|
|
|
|
<span>msgs per</span>
|
|
|
|
|
|
<select id="wc-dom-edit-rl-frame">
|
|
|
|
|
|
<option value="s">Second</option>
|
|
|
|
|
|
<option value="m">Minute</option>
|
|
|
|
|
|
<option value="h">Hour</option>
|
|
|
|
|
|
<option value="d">Day</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<span class="description">(0 = disabled)</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-relayhost">Outbound Transport</label></th>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<select id="wc-dom-edit-relayhost" class="regular-text">
|
|
|
|
|
|
<option value="0">— Direct delivery (no relay) —</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<p class="description">Route outbound mail for this domain through a sender-dependent transport.</p>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-dom-edit-active">Active</label></th>
|
|
|
|
|
|
<td><input type="checkbox" id="wc-dom-edit-active"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div class="woocow-modal-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-dom-edit-save">Save Changes</button>
|
|
|
|
|
|
<button class="button" id="wc-dom-edit-cancel">Cancel</button>
|
|
|
|
|
|
<span id="wc-dom-edit-notice"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
<!-- DNS Records panel -->
|
|
|
|
|
|
<div id="wc-dom-dns-panel" class="woocow-card" style="display:none">
|
|
|
|
|
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
|
|
|
|
|
|
<h3 style="margin:0" id="wc-dom-dns-title">DNS Records</h3>
|
|
|
|
|
|
<button class="button" id="wc-dom-dns-close">Close</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="description">Add these records to your DNS provider for <strong id="wc-dom-dns-domain"></strong>.</p>
|
|
|
|
|
|
<div id="wc-dom-dns-content"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page: Transports ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_transports(): void {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$servers = $wpdb->get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Sender-Dependent Transports</h1>
|
|
|
|
|
|
<p>Configure relay hosts for outbound mail delivery. Each transport can be associated with one or more domains in Mailcow.</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-toolbar woocow-flex">
|
|
|
|
|
|
<select id="wc-tr-server" class="regular-text">
|
|
|
|
|
|
<option value="">— Select server —</option>
|
|
|
|
|
|
<?php foreach ( $servers as $s ) : ?>
|
|
|
|
|
|
<option value="<?php echo esc_attr( $s->id ); ?>"><?php echo esc_html( $s->name ); ?></option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="button" id="wc-tr-load" disabled>Load Transports</button>
|
|
|
|
|
|
<button class="button button-primary" id="wc-tr-add-btn" style="display:none">+ Add Transport</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-tr-notices"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-tr-form" class="woocow-card woocow-form" style="display:none">
|
|
|
|
|
|
<h3>Add Transport (Relay Host)</h3>
|
|
|
|
|
|
<table class="form-table">
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-tr-hostname">Hostname:Port</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-tr-hostname" class="regular-text" placeholder="smtp.relay.com:587"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-tr-user">SMTP Username</label></th>
|
|
|
|
|
|
<td><input type="text" id="wc-tr-user" class="regular-text"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-tr-pass">SMTP Password</label></th>
|
|
|
|
|
|
<td><input type="password" id="wc-tr-pass" class="regular-text"></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th><label for="wc-tr-active">Active</label></th>
|
|
|
|
|
|
<td><input type="checkbox" id="wc-tr-active" checked></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
<div class="woocow-form-actions">
|
|
|
|
|
|
<button class="button button-primary" id="wc-tr-save">Add Transport</button>
|
|
|
|
|
|
<button class="button" id="wc-tr-cancel">Cancel</button>
|
|
|
|
|
|
<span id="wc-tr-form-notice"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-tr-table-wrap"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page: Logs ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_logs(): void {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$servers = $wpdb->get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
|
2026-02-27 08:53:11 +01:00
|
|
|
|
$log_types = [
|
|
|
|
|
|
'postfix' => 'Postfix',
|
|
|
|
|
|
'dovecot' => 'Dovecot',
|
|
|
|
|
|
'rspamd-history' => 'Rspamd',
|
|
|
|
|
|
'ratelimited' => 'Rate Limit',
|
|
|
|
|
|
'api' => 'API',
|
|
|
|
|
|
'acme' => 'ACME',
|
|
|
|
|
|
'autodiscover' => 'Autodiscover',
|
|
|
|
|
|
'sogo' => 'SOGo',
|
|
|
|
|
|
'netfilter' => 'Netfilter',
|
|
|
|
|
|
'watchdog' => 'Watchdog',
|
|
|
|
|
|
];
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Server Logs</h1>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-toolbar woocow-flex">
|
|
|
|
|
|
<select id="wc-log-server" class="regular-text">
|
|
|
|
|
|
<option value="">— Select server —</option>
|
|
|
|
|
|
<?php foreach ( $servers as $s ) : ?>
|
|
|
|
|
|
<option value="<?php echo esc_attr( $s->id ); ?>"><?php echo esc_html( $s->name ); ?></option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<select id="wc-log-type">
|
2026-02-27 08:53:11 +01:00
|
|
|
|
<?php foreach ( $log_types as $slug => $label ) : ?>
|
|
|
|
|
|
<option value="<?php echo esc_attr( $slug ); ?>"><?php echo esc_html( $label ); ?></option>
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="button" id="wc-log-load" disabled>Load Logs</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-log-wrap">
|
|
|
|
|
|
<p class="description">Select a server and log type above.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 08:53:11 +01:00
|
|
|
|
// ── Page: Quarantine ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function page_quarantine(): void {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$servers = $wpdb->get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" );
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="wrap woocow-wrap">
|
|
|
|
|
|
<h1>WooCow – Quarantine</h1>
|
|
|
|
|
|
<p>View and manage quarantined messages across all servers. You can permanently delete messages or blacklist senders by domain.</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="woocow-toolbar woocow-flex">
|
|
|
|
|
|
<select id="wc-quar-server" class="regular-text">
|
|
|
|
|
|
<option value="">— Select server —</option>
|
|
|
|
|
|
<?php foreach ( $servers as $s ) : ?>
|
|
|
|
|
|
<option value="<?php echo esc_attr( $s->id ); ?>"><?php echo esc_html( $s->name ); ?></option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="button" id="wc-quar-load" disabled>Load Quarantine</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="wc-quar-notices"></div>
|
|
|
|
|
|
<div id="wc-quar-wrap">
|
|
|
|
|
|
<p class="description">Select a server above to view quarantined messages.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 08:06:22 +01:00
|
|
|
|
// ── AJAX helpers ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
private function verify(): void {
|
|
|
|
|
|
check_ajax_referer( 'woocow_admin', 'nonce' );
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
|
wp_send_json_error( 'Insufficient permissions.', 403 );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function get_server( int $id ): ?object {
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
return $wpdb->get_row( $wpdb->prepare(
|
|
|
|
|
|
"SELECT * FROM {$wpdb->prefix}woocow_servers WHERE id = %d",
|
|
|
|
|
|
$id
|
|
|
|
|
|
) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function json_ok( $data = null ): void {
|
|
|
|
|
|
wp_send_json_success( $data );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function json_err( string $msg ): void {
|
|
|
|
|
|
wp_send_json_error( $msg );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Servers ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_servers_list(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$rows = $wpdb->get_results( "SELECT id, name, url, active, created_at FROM {$wpdb->prefix}woocow_servers ORDER BY name" );
|
|
|
|
|
|
$this->json_ok( $rows );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_server_save(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
|
|
|
|
|
|
$id = absint( $_POST['id'] ?? 0 );
|
|
|
|
|
|
$name = sanitize_text_field( $_POST['name'] ?? '' );
|
|
|
|
|
|
$url = esc_url_raw( $_POST['url'] ?? '' );
|
|
|
|
|
|
$key = sanitize_text_field( $_POST['api_key'] ?? '' );
|
|
|
|
|
|
$active = absint( $_POST['active'] ?? 1 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $name || ! $url || ! $key ) {
|
|
|
|
|
|
$this->json_err( 'Name, URL, and API key are required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$data = compact( 'name', 'url', 'active' ) + [ 'api_key' => $key ];
|
|
|
|
|
|
|
|
|
|
|
|
if ( $id ) {
|
|
|
|
|
|
$wpdb->update( "{$wpdb->prefix}woocow_servers", $data, [ 'id' => $id ] );
|
|
|
|
|
|
$this->json_ok( [ 'id' => $id ] );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$wpdb->insert( "{$wpdb->prefix}woocow_servers", $data );
|
|
|
|
|
|
$this->json_ok( [ 'id' => $wpdb->insert_id ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_server_delete(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$id = absint( $_POST['id'] ?? 0 );
|
|
|
|
|
|
$wpdb->delete( "{$wpdb->prefix}woocow_servers", [ 'id' => $id ] );
|
|
|
|
|
|
$wpdb->delete( "{$wpdb->prefix}woocow_assignments", [ 'server_id' => $id ] );
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_server_test(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$id = absint( $_POST['id'] ?? 0 );
|
|
|
|
|
|
|
|
|
|
|
|
// Allow testing before saving (pass url+key directly)
|
|
|
|
|
|
if ( $id ) {
|
|
|
|
|
|
$server = $this->get_server( $id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$url = esc_url_raw( $_POST['url'] ?? '' );
|
|
|
|
|
|
$key = sanitize_text_field( $_POST['api_key'] ?? '' );
|
|
|
|
|
|
if ( ! $url || ! $key ) {
|
|
|
|
|
|
$this->json_err( 'URL and API key required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
$api = new WooCow_API( $url, $key );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$result = $api->test_connection();
|
|
|
|
|
|
if ( $result['success'] ) {
|
|
|
|
|
|
$version = $result['data']['version'] ?? $result['data'][0]['version'] ?? 'unknown';
|
|
|
|
|
|
$this->json_ok( [ 'version' => $version ] );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Connection failed.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_server_domains(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
$id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$server = $this->get_server( $id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->get_domains();
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to fetch domains.' );
|
|
|
|
|
|
}
|
2026-02-27 08:53:11 +01:00
|
|
|
|
$domains = array_map( function ( $d ) {
|
|
|
|
|
|
$rl = is_array( $d['rl'] ?? false ) ? $d['rl'] : [];
|
|
|
|
|
|
return [
|
|
|
|
|
|
'domain' => $d['domain_name'],
|
|
|
|
|
|
'active' => $d['active'],
|
|
|
|
|
|
'description' => $d['description'] ?? '',
|
|
|
|
|
|
'mailboxes' => $d['max_num_mboxes_for_domain'] ?? 0,
|
|
|
|
|
|
'mboxes_in' => $d['mboxes_in_domain'] ?? 0,
|
|
|
|
|
|
'aliases' => $d['max_num_aliases_for_domain'] ?? 0,
|
|
|
|
|
|
'aliases_in' => $d['aliases_in_domain'] ?? 0,
|
|
|
|
|
|
'quota' => (int) round( ( $d['max_quota_for_domain'] ?? 0 ) / 1024 / 1024 ),
|
|
|
|
|
|
'defquota' => (int) round( ( $d['def_new_mailbox_quota'] ?? 0 ) / 1024 / 1024 ),
|
|
|
|
|
|
'quota_used' => (int) round( ( $d['quota_used_in_domain'] ?? 0 ) / 1024 / 1024 ),
|
|
|
|
|
|
'relayhost' => $d['relayhost'] ?? '0',
|
|
|
|
|
|
'rl_value' => $rl['value'] ?? 0,
|
|
|
|
|
|
'rl_frame' => $rl['frame'] ?? 's',
|
|
|
|
|
|
'gal' => $d['gal'] ?? '0',
|
|
|
|
|
|
'backupmx' => $d['backupmx'] ?? '0',
|
|
|
|
|
|
];
|
|
|
|
|
|
}, (array) $result['data'] );
|
2026-02-27 08:06:22 +01:00
|
|
|
|
$this->json_ok( $domains );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Assignments ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_assignments_list(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$rows = $wpdb->get_results( "
|
|
|
|
|
|
SELECT a.id, a.customer_id, a.domain, a.created_at,
|
|
|
|
|
|
s.name AS server_name, s.url AS server_url,
|
|
|
|
|
|
u.display_name, u.user_email
|
|
|
|
|
|
FROM {$wpdb->prefix}woocow_assignments a
|
|
|
|
|
|
JOIN {$wpdb->prefix}woocow_servers s ON s.id = a.server_id
|
|
|
|
|
|
JOIN {$wpdb->users} u ON u.ID = a.customer_id
|
|
|
|
|
|
ORDER BY u.display_name, a.domain
|
|
|
|
|
|
" );
|
|
|
|
|
|
$this->json_ok( $rows );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_assignment_save(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
|
|
|
|
|
|
$customer_id = absint( $_POST['customer_id'] ?? 0 );
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $customer_id || ! $server_id || ! $domain ) {
|
|
|
|
|
|
$this->json_err( 'Customer, server, and domain are all required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$existing = $wpdb->get_var( $wpdb->prepare(
|
|
|
|
|
|
"SELECT id FROM {$wpdb->prefix}woocow_assignments WHERE customer_id=%d AND domain=%s",
|
|
|
|
|
|
$customer_id, $domain
|
|
|
|
|
|
) );
|
|
|
|
|
|
if ( $existing ) {
|
|
|
|
|
|
$this->json_err( 'This domain is already assigned to this customer.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$wpdb->insert( "{$wpdb->prefix}woocow_assignments", compact( 'customer_id', 'server_id', 'domain' ) );
|
|
|
|
|
|
$this->json_ok( [ 'id' => $wpdb->insert_id ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_assignment_delete(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
$id = absint( $_POST['id'] ?? 0 );
|
|
|
|
|
|
$wpdb->delete( "{$wpdb->prefix}woocow_assignments", [ 'id' => $id ] );
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Customer search ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_customers_search(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$term = sanitize_text_field( $_POST['term'] ?? '' );
|
|
|
|
|
|
if ( strlen( $term ) < 2 ) {
|
|
|
|
|
|
$this->json_ok( [] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$users = get_users( [
|
|
|
|
|
|
'search' => '*' . $term . '*',
|
|
|
|
|
|
'search_columns' => [ 'user_login', 'user_email', 'display_name' ],
|
|
|
|
|
|
'role__in' => [ 'customer', 'subscriber', 'administrator', 'shop_manager' ],
|
|
|
|
|
|
'number' => 15,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
$out = array_map( fn( $u ) => [
|
|
|
|
|
|
'id' => $u->ID,
|
|
|
|
|
|
'label' => sprintf( '%s (%s)', $u->display_name, $u->user_email ),
|
|
|
|
|
|
], $users );
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( $out );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Admin Mailboxes ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_mailboxes(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
|
|
|
|
|
|
$result = $domain
|
|
|
|
|
|
? $api->get_domain_mailboxes( $domain )
|
|
|
|
|
|
: $api->get_all_mailboxes();
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to fetch mailboxes.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( [
|
|
|
|
|
|
'mailboxes' => $result['data'] ?? [],
|
|
|
|
|
|
'webmail_url' => $api->get_webmail_url(),
|
|
|
|
|
|
] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_mailbox_create(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
$local_part = sanitize_text_field( $_POST['local_part'] ?? '' );
|
|
|
|
|
|
$name = sanitize_text_field( $_POST['name'] ?? '' );
|
|
|
|
|
|
$password = $_POST['password'] ?? '';
|
|
|
|
|
|
$password2 = $_POST['password2'] ?? '';
|
|
|
|
|
|
$quota = absint( $_POST['quota'] ?? 1024 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $domain || ! $local_part || ! $password ) {
|
|
|
|
|
|
$this->json_err( 'Domain, local part, and password are required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $password !== $password2 ) {
|
|
|
|
|
|
$this->json_err( 'Passwords do not match.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->create_mailbox( [
|
|
|
|
|
|
'local_part' => $local_part,
|
|
|
|
|
|
'domain' => $domain,
|
|
|
|
|
|
'name' => $name ?: $local_part,
|
|
|
|
|
|
'password' => $password,
|
|
|
|
|
|
'password2' => $password2,
|
|
|
|
|
|
'quota' => $quota,
|
|
|
|
|
|
'active' => 1,
|
|
|
|
|
|
'force_pw_update' => 0,
|
|
|
|
|
|
'tls_enforce_in' => 0,
|
|
|
|
|
|
'tls_enforce_out' => 0,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to create mailbox.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( [ 'email' => $local_part . '@' . $domain ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_mailbox_delete(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$email = sanitize_email( $_POST['email'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->delete_mailbox( $email );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to delete mailbox.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
2026-02-27 08:21:46 +01:00
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_mailbox_edit(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$email = sanitize_email( $_POST['email'] ?? '' );
|
|
|
|
|
|
$type = sanitize_text_field( $_POST['type'] ?? '' ); // 'password' or 'quota'
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $email ) {
|
|
|
|
|
|
$this->json_err( 'Email address is required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$attr = [];
|
|
|
|
|
|
|
|
|
|
|
|
if ( $type === 'password' ) {
|
|
|
|
|
|
$pass = $_POST['password'] ?? '';
|
|
|
|
|
|
$pass2 = $_POST['password2'] ?? '';
|
|
|
|
|
|
if ( ! $pass ) {
|
|
|
|
|
|
$this->json_err( 'Password cannot be empty.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $pass !== $pass2 ) {
|
|
|
|
|
|
$this->json_err( 'Passwords do not match.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
$attr = [ 'password' => $pass, 'password2' => $pass2 ];
|
|
|
|
|
|
|
|
|
|
|
|
} elseif ( $type === 'quota' ) {
|
|
|
|
|
|
$quota = absint( $_POST['quota'] ?? 0 );
|
|
|
|
|
|
if ( $quota < 1 ) {
|
|
|
|
|
|
$this->json_err( 'Quota must be at least 1 MB.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
$attr = [ 'quota' => $quota ];
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->json_err( 'Unknown edit type.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$result = $api->edit_mailbox( [ $email ], $attr );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to update mailbox.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Domains ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_domain_add(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
$desc = sanitize_text_field( $_POST['description'] ?? '' );
|
|
|
|
|
|
$mailboxes = absint( $_POST['mailboxes'] ?? 10 );
|
|
|
|
|
|
$aliases = absint( $_POST['aliases'] ?? 400 );
|
|
|
|
|
|
$quota = absint( $_POST['quota'] ?? 10240 );
|
|
|
|
|
|
$defquota = absint( $_POST['defquota'] ?? 3072 );
|
|
|
|
|
|
$dkim_size = absint( $_POST['dkim_size'] ?? 2048 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $domain ) {
|
|
|
|
|
|
$this->json_err( 'Domain name is required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
|
|
|
|
|
|
// Add domain
|
|
|
|
|
|
$result = $api->create_domain( [
|
|
|
|
|
|
'domain' => $domain,
|
|
|
|
|
|
'description' => $desc,
|
|
|
|
|
|
'mailboxes' => $mailboxes,
|
|
|
|
|
|
'aliases' => $aliases,
|
|
|
|
|
|
'quota' => $quota,
|
|
|
|
|
|
'defquota' => $defquota,
|
|
|
|
|
|
'maxquota' => $quota,
|
|
|
|
|
|
'active' => 1,
|
|
|
|
|
|
'restart_sogo'=> 1,
|
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to add domain.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Auto-generate DKIM
|
|
|
|
|
|
$api->generate_dkim( $domain, 'dkim', $dkim_size );
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( [ 'domain' => $domain ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_domain_dns(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$mail_host = parse_url( $server->url, PHP_URL_HOST );
|
|
|
|
|
|
|
|
|
|
|
|
// Fetch DKIM
|
|
|
|
|
|
$dkim_result = $api->get_dkim( $domain );
|
|
|
|
|
|
$dkim_txt = '';
|
|
|
|
|
|
$dkim_sel = 'dkim';
|
|
|
|
|
|
if ( $dkim_result['success'] && ! empty( $dkim_result['data']['dkim_txt'] ) ) {
|
|
|
|
|
|
$dkim_txt = $dkim_result['data']['dkim_txt'];
|
|
|
|
|
|
$dkim_sel = $dkim_result['data']['dkim_selector'] ?? 'dkim';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( [
|
|
|
|
|
|
'domain' => $domain,
|
|
|
|
|
|
'mail_host' => $mail_host,
|
|
|
|
|
|
'dkim_sel' => $dkim_sel,
|
|
|
|
|
|
'dkim_txt' => $dkim_txt,
|
|
|
|
|
|
'records' => [
|
|
|
|
|
|
[ 'type' => 'MX', 'host' => $domain, 'value' => $mail_host . '.', 'prio' => '10', 'ttl' => '3600' ],
|
|
|
|
|
|
[ 'type' => 'TXT', 'host' => $domain, 'value' => 'v=spf1 mx ~all', 'prio' => '', 'ttl' => '3600', 'note' => 'SPF' ],
|
|
|
|
|
|
[ 'type' => 'TXT', 'host' => '_dmarc.' . $domain, 'value' => 'v=DMARC1; p=quarantine; rua=mailto:postmaster@' . $domain, 'prio' => '', 'ttl' => '3600', 'note' => 'DMARC' ],
|
|
|
|
|
|
[ 'type' => 'TXT', 'host' => $dkim_sel . '._domainkey.' . $domain, 'value' => $dkim_txt ?: '(generate DKIM first)', 'prio' => '', 'ttl' => '3600', 'note' => 'DKIM' ],
|
|
|
|
|
|
[ 'type' => 'CNAME','host' => 'autoconfig.' . $domain, 'value' => $mail_host . '.', 'prio' => '', 'ttl' => '3600', 'note' => 'Autoconfig' ],
|
|
|
|
|
|
[ 'type' => 'CNAME','host' => 'autodiscover.' . $domain, 'value' => $mail_host . '.', 'prio' => '', 'ttl' => '3600', 'note' => 'Autodiscover' ],
|
|
|
|
|
|
],
|
|
|
|
|
|
] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 08:53:11 +01:00
|
|
|
|
public function ajax_woocow_admin_domain_edit(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $domain ) {
|
|
|
|
|
|
$this->json_err( 'Domain name is required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$attr = [];
|
|
|
|
|
|
|
|
|
|
|
|
if ( isset( $_POST['description'] ) ) {
|
|
|
|
|
|
$attr['description'] = sanitize_text_field( $_POST['description'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['mailboxes'] ) ) {
|
|
|
|
|
|
$attr['mailboxes'] = absint( $_POST['mailboxes'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['aliases'] ) ) {
|
|
|
|
|
|
$attr['aliases'] = absint( $_POST['aliases'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['quota'] ) ) {
|
|
|
|
|
|
$attr['quota'] = absint( $_POST['quota'] );
|
|
|
|
|
|
$attr['maxquota'] = absint( $_POST['quota'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['defquota'] ) ) {
|
|
|
|
|
|
$attr['defquota'] = absint( $_POST['defquota'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['rl_value'] ) ) {
|
|
|
|
|
|
$attr['rl_value'] = absint( $_POST['rl_value'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['rl_frame'] ) ) {
|
|
|
|
|
|
$frame = sanitize_text_field( $_POST['rl_frame'] );
|
|
|
|
|
|
if ( in_array( $frame, [ 's', 'm', 'h', 'd' ], true ) ) {
|
|
|
|
|
|
$attr['rl_frame'] = $frame;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['relayhost'] ) ) {
|
|
|
|
|
|
$attr['relayhost'] = absint( $_POST['relayhost'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $_POST['active'] ) ) {
|
|
|
|
|
|
$attr['active'] = absint( $_POST['active'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->edit_domain( [ $domain ], $attr );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to update domain.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_domain_delete(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $domain ) {
|
|
|
|
|
|
$this->json_err( 'Domain name is required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->delete_domain( $domain );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to delete domain.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
// ── AJAX: Transports ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_relayhosts_list(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->get_relayhosts();
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to fetch transports.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( $result['data'] ?? [] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_relayhost_save(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$hostname = sanitize_text_field( $_POST['hostname'] ?? '' );
|
|
|
|
|
|
$username = sanitize_text_field( $_POST['username'] ?? '' );
|
|
|
|
|
|
$password = $_POST['password'] ?? '';
|
|
|
|
|
|
$active = absint( $_POST['active'] ?? 1 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $hostname || ! $username ) {
|
|
|
|
|
|
$this->json_err( 'Hostname and username are required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->create_relayhost( compact( 'hostname', 'username', 'password', 'active' ) );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to add transport.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_relayhost_delete(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$id = absint( $_POST['id'] ?? 0 );
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->delete_relayhost( $id );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to delete transport.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Logs ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_logs(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$log_type = sanitize_key( $_POST['log_type'] ?? 'postfix' );
|
|
|
|
|
|
|
2026-02-27 08:53:11 +01:00
|
|
|
|
$allowed = [ 'postfix', 'dovecot', 'rspamd-history', 'ratelimited', 'api', 'acme', 'autodiscover', 'sogo', 'netfilter', 'watchdog' ];
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
if ( ! in_array( $log_type, $allowed, true ) ) {
|
|
|
|
|
|
$this->json_err( 'Invalid log type.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
2026-02-27 08:53:11 +01:00
|
|
|
|
$result = $api->request( 'GET', '/api/v1/get/logs/' . $log_type . '/100' );
|
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>
2026-02-27 08:38:52 +01:00
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to fetch logs.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( $result['data'] ?? [] );
|
|
|
|
|
|
}
|
2026-02-27 08:53:11 +01:00
|
|
|
|
|
|
|
|
|
|
// ── AJAX: Admin Quarantine ─────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_quarantine(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->get_quarantine();
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to fetch quarantine.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok( $result['data'] ?? [] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_quarantine_delete(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$qid = absint( $_POST['qid'] ?? 0 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $qid ) {
|
|
|
|
|
|
$this->json_err( 'Message ID is required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->delete_quarantine( $qid );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to delete quarantine message.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ajax_woocow_admin_quarantine_block(): void {
|
|
|
|
|
|
$this->verify();
|
|
|
|
|
|
|
|
|
|
|
|
$server_id = absint( $_POST['server_id'] ?? 0 );
|
|
|
|
|
|
$domain = sanitize_text_field( $_POST['domain'] ?? '' );
|
|
|
|
|
|
$object_from = sanitize_text_field( $_POST['object_from'] ?? '' );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $domain || ! $object_from ) {
|
|
|
|
|
|
$this->json_err( 'Domain and sender address are required.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$server = $this->get_server( $server_id );
|
|
|
|
|
|
if ( ! $server ) {
|
|
|
|
|
|
$this->json_err( 'Server not found.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$api = WooCow_API::from_server( $server );
|
|
|
|
|
|
$result = $api->add_domain_policy( $domain, $object_from, 'bl' );
|
|
|
|
|
|
|
|
|
|
|
|
if ( ! $result['success'] ) {
|
|
|
|
|
|
$this->json_err( $result['error'] ?? 'Failed to blacklist sender.' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->json_ok();
|
|
|
|
|
|
}
|
2026-02-27 08:06:22 +01:00
|
|
|
|
}
|