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" ); ?>

WooCow v

Active Servers Manage
Customers with Email Manage
Domain Assignments View Mailboxes

WooCow – Servers

Add your Mailcow server instances here. The API key must be a read-write key from Configuration → Access → Edit administrator details → API.

Loading servers…

WooCow – Domain Assignments

Assign one or more Mailcow domains to a WooCommerce customer. The customer can then manage mailboxes for those domains from My Account → Email Hosting.

Assign Domain to Customer

Current Assignments

Loading…
get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" ); ?>

WooCow – Mailboxes

get_results( "SELECT id, name, url FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" ); ?>

WooCow – Domains

Add and manage domains on your Mailcow servers. After adding a domain, DNS records (including DKIM) are generated automatically.

get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" ); ?>

WooCow – Sender-Dependent Transports

Configure relay hosts for outbound mail delivery. Each transport can be associated with one or more domains in Mailcow.

get_results( "SELECT id, name FROM {$wpdb->prefix}woocow_servers WHERE active=1 ORDER BY name" ); $log_types = [ 'postfix', 'dovecot', 'rspamd', 'ratelimit', 'api', 'acme', 'autodiscover', 'sogo', 'netfilter', 'watchdog' ]; ?>

WooCow – Server Logs

Select a server and log type above.

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.' ); } $domains = array_map( fn( $d ) => [ 'domain' => $d['domain_name'], 'active' => $d['active'] ], (array) $result['data'] ); $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(); } 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(); } // ── 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' ], ], ] ); } // ── 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' ); $allowed = [ 'postfix', 'dovecot', 'rspamd', 'ratelimit', 'api', 'acme', 'autodiscover', 'sogo', 'netfilter', 'watchdog' ]; 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 ); $result = $api->request( 'GET', '/api/v1/get/logs/' . $log_type ); if ( ! $result['success'] ) { $this->json_err( $result['error'] ?? 'Failed to fetch logs.' ); } $this->json_ok( $result['data'] ?? [] ); } }