Files
WooCow/includes/class-woocow-admin.php

546 lines
23 KiB
PHP
Raw Normal View History

<?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',
];
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
);
add_submenu_page( 'woocow', 'Dashboard', 'Dashboard', 'manage_woocommerce', 'woocow', [ $this, 'page_dashboard' ] );
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' ] );
}
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 &rarr; Access &rarr; Edit administrator details &rarr; 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 &rarr; 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>
<!-- 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
}
// ── 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.' );
}
$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();
}
}