Files
WooCow/assets/js/woocow-account.js
Malin 2ee81efacf feat: initial WooCow plugin — Mailcow/WooCommerce integration
- Mailcow API client wrapping domains, mailboxes, aliases endpoints
- Admin backend: server management, customer-domain assignments, mailbox overview
- WooCommerce My Account: email hosting tab with mailbox/alias management
- Per-mailbox password change (independent of WP account password)
- Optional WP account password sync to all customer mailboxes
- Installer creates wp_woocow_servers and wp_woocow_assignments DB tables
- Full nonce + capability + ownership verification on all AJAX endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 08:06:22 +01:00

282 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WooCow My Account frontend JavaScript
*/
(function ($) {
'use strict';
if (!$('#woocow-account').length) return;
const ajax = (action, data) =>
$.post(woocowAcct.ajax_url, { action, nonce: woocowAcct.nonce, ...data });
const notice = (msg, type = 'success') => {
const $n = $('#woocow-acct-notices');
$n.html(`<div class="woocommerce-${type === 'success' ? 'message' : 'error'}">${msg}</div>`);
setTimeout(() => $n.find('> div').fadeOut(400, function () { $(this).remove(); }), 5000);
};
function esc(str) {
return String(str).replace(/[&<>"']/g, m => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
})[m]);
}
function formatMB(bytes) {
if (!bytes) return '0 MB';
const mb = bytes / 1024 / 1024;
return mb >= 1024 ? (mb / 1024).toFixed(1) + ' GB' : mb.toFixed(0) + ' MB';
}
// ── Load Mailboxes ────────────────────────────────────────────────────────
$(document).on('click', '.woocow-load-mailboxes', function () {
const $panel = $(this).closest('.woocow-domain-panel');
const sid = $panel.data('server-id');
const domain = $panel.data('domain');
const $wrap = $panel.find('.woocow-mailboxes-wrap');
const $list = $panel.find('.woocow-mailboxes-list');
$(this).prop('disabled', true).text('Loading…');
$wrap.show();
$list.html('<p class="woocow-loading">Fetching mailboxes…</p>');
ajax('woocow_acct_mailboxes', { server_id: sid, domain }).done(res => {
$(this).prop('disabled', false).text('Refresh');
if (!res.success) {
$list.html(`<p class="woocow-error">${esc(res.data)}</p>`);
return;
}
const boxes = res.data.mailboxes || [];
const webmail = res.data.webmail_url;
if (!boxes.length) {
$list.html('<p class="woocow-muted">No mailboxes yet. Create one below.</p>');
return;
}
let html = '';
boxes.forEach(m => {
const pct = parseFloat(m.quota_used_in_percent || 0);
const used = formatMB(m.quota_used);
const max = formatMB(m.quota);
const col = pct > 85 ? '#e74c3c' : pct > 60 ? '#f39c12' : '#27ae60';
html += `<div class="woocow-mailbox-row" data-email="${esc(m.username)}">
<div class="woocow-mbox-main">
<div class="woocow-mbox-address">
<span class="woocow-mbox-icon">✉</span>
<strong>${esc(m.username)}</strong>
${m.name ? `<span class="woocow-mbox-name">(${esc(m.name)})</span>` : ''}
</div>
<div class="woocow-quota-wrap">
<div class="woocow-quota-bar-outer">
<div class="woocow-quota-bar-inner" style="width:${pct}%;background:${col}"></div>
</div>
<span class="woocow-quota-text">${used} / ${max} (${pct}%)</span>
</div>
</div>
<div class="woocow-mbox-actions">
<button class="woocow-btn woocow-btn-sm wc-change-pw"
data-server="${esc(sid)}" data-domain="${esc(domain)}" data-email="${esc(m.username)}">
Change Password
</button>
<button class="woocow-btn woocow-btn-sm woocow-btn-outline wc-toggle-aliases"
data-server="${esc(sid)}" data-domain="${esc(domain)}">
Aliases
</button>
<a href="${esc(webmail)}" target="_blank" rel="noopener" class="woocow-btn woocow-btn-sm woocow-btn-ghost">
Webmail ↗
</a>
</div>
<div class="woocow-aliases-wrap" style="display:none">
<div class="woocow-aliases-list"></div>
<div class="woocow-alias-create-form" style="display:none">
<div class="woocow-alias-fields">
<input type="email" class="wc-alias-addr woocow-input" placeholder="alias@${esc(domain)}">
<span class="woocow-arrow">→</span>
<input type="email" class="wc-alias-goto woocow-input" placeholder="destination@example.com" value="${esc(m.username)}">
<button class="woocow-btn woocow-btn-primary woocow-btn-sm wc-alias-save"
data-server="${esc(sid)}" data-domain="${esc(domain)}">Add</button>
<button class="woocow-btn woocow-btn-sm wc-alias-cancel">✕</button>
</div>
</div>
<button class="woocow-btn woocow-btn-sm woocow-btn-outline wc-alias-add-btn">+ Add Alias</button>
</div>
</div>`;
});
$list.html(html);
});
});
// ── Aliases ───────────────────────────────────────────────────────────────
$(document).on('click', '.wc-toggle-aliases', function () {
const $row = $(this).closest('.woocow-mailbox-row');
const $wrap = $row.find('.woocow-aliases-wrap');
const sid = $(this).data('server');
const domain = $(this).data('domain');
if ($wrap.is(':visible')) {
$wrap.slideUp();
return;
}
const $list = $row.find('.woocow-aliases-list').html('<p class="woocow-muted">Loading aliases…</p>');
$wrap.slideDown();
ajax('woocow_acct_aliases', { server_id: sid, domain }).done(res => {
if (!res.success) {
$list.html(`<p class="woocow-error">${esc(res.data)}</p>`);
return;
}
const aliases = res.data;
if (!aliases.length) {
$list.html('<p class="woocow-muted">No aliases for this domain yet.</p>');
return;
}
let html = '<ul class="woocow-alias-list">';
aliases.forEach(a => {
html += `<li>
<span class="woocow-alias-addr">${esc(a.address)}</span>
<span class="woocow-arrow">→</span>
<span class="woocow-alias-goto">${esc(a.goto)}</span>
<button class="woocow-btn woocow-btn-danger woocow-btn-xs wc-alias-del"
data-id="${esc(a.id)}" data-server="${esc(sid)}" data-domain="${esc(domain)}">✕</button>
</li>`;
});
html += '</ul>';
$list.html(html);
});
});
$(document).on('click', '.wc-alias-add-btn', function () {
$(this).closest('.woocow-aliases-wrap').find('.woocow-alias-create-form').slideToggle();
});
$(document).on('click', '.wc-alias-cancel', function () {
$(this).closest('.woocow-alias-create-form').slideUp();
});
$(document).on('click', '.wc-alias-save', function () {
const $form = $(this).closest('.woocow-alias-create-form');
const sid = $(this).data('server');
const domain = $(this).data('domain');
const addr = $form.find('.wc-alias-addr').val().trim();
const goto_ = $form.find('.wc-alias-goto').val().trim();
if (!addr || !goto_) { alert('Both alias and destination are required.'); return; }
ajax('woocow_acct_alias_create', { server_id: sid, domain, address: addr, goto: goto_ }).done(res => {
if (res.success) {
// Refresh alias list
$(this).closest('.woocow-mailbox-row').find('.wc-toggle-aliases').trigger('click');
setTimeout(() => { $(this).closest('.woocow-mailbox-row').find('.wc-toggle-aliases').trigger('click'); }, 300);
$form.slideUp();
} else {
alert('Error: ' + res.data);
}
});
});
$(document).on('click', '.wc-alias-del', function () {
if (!confirm('Delete this alias?')) return;
const $li = $(this).closest('li');
const sid = $(this).data('server');
const domain = $(this).data('domain');
const id = $(this).data('id');
ajax('woocow_acct_alias_delete', { server_id: sid, domain, alias_id: id }).done(res => {
if (res.success) $li.fadeOut(300, function () { $(this).remove(); });
else alert('Delete failed: ' + res.data);
});
});
// ── Create Mailbox ────────────────────────────────────────────────────────
$(document).on('click', '.woocow-create-mbox-btn', function () {
const $panel = $(this).closest('.woocow-domain-panel');
$panel.find('.woocow-create-mbox-form').slideToggle();
});
$(document).on('click', '.wc-mbox-cancel', function () {
$(this).closest('.woocow-create-mbox-form').slideUp();
});
$(document).on('click', '.wc-mbox-submit', function () {
const $panel = $(this).closest('.woocow-domain-panel');
const sid = $panel.data('server-id');
const domain = $panel.data('domain');
const $form = $(this).closest('.woocow-create-mbox-form');
const $note = $form.find('.wc-mbox-notice');
const local = $form.find('.wc-mbox-local').val().trim();
const name = $form.find('.wc-mbox-name').val().trim();
const pass = $form.find('.wc-mbox-pass').val();
const pass2 = $form.find('.wc-mbox-pass2').val();
const quota = $form.find('.wc-mbox-quota').val();
if (!local || !pass) { $note.html('<span class="woocow-error">Username and password required.</span>'); return; }
if (pass !== pass2) { $note.html('<span class="woocow-error">Passwords do not match.</span>'); return; }
$note.text('Creating…');
ajax('woocow_acct_mailbox_create', { server_id: sid, domain, local_part: local, name, password: pass, password2: pass2, quota }).done(res => {
if (res.success) {
$note.html('<span style="color:green">✓ Mailbox created!</span>');
$form.slideUp();
// Refresh mailbox list
$panel.find('.woocow-load-mailboxes').trigger('click');
} else {
$note.html(`<span class="woocow-error">${esc(res.data)}</span>`);
}
});
});
// ── Change Password Modal ─────────────────────────────────────────────────
$(document).on('click', '.wc-change-pw', function () {
const sid = $(this).data('server');
const domain = $(this).data('domain');
const email = $(this).data('email');
$('#woocow-pw-server-id').val(sid);
$('#woocow-pw-mailbox').val(email);
$('#woocow-pw-email').text(email);
$('#woocow-pw-new, #woocow-pw-new2').val('');
$('#woocow-pw-notice').text('');
$('#woocow-pw-modal').fadeIn(200);
// Store domain on modal for verification
$('#woocow-pw-modal').data('domain', domain);
});
$('#woocow-pw-cancel').on('click', () => $('#woocow-pw-modal').fadeOut(200));
$(document).on('click', '#woocow-pw-modal', function (e) {
if ($(e.target).is('#woocow-pw-modal')) $(this).fadeOut(200);
});
$('#woocow-pw-save').on('click', function () {
const sid = $('#woocow-pw-server-id').val();
const email = $('#woocow-pw-mailbox').val();
const domain = $('#woocow-pw-modal').data('domain');
const pass = $('#woocow-pw-new').val();
const pass2 = $('#woocow-pw-new2').val();
const $note = $('#woocow-pw-notice');
if (!pass || pass !== pass2) {
$note.html('<span class="woocow-error">Passwords do not match or are empty.</span>');
return;
}
$note.text('Updating…');
ajax('woocow_acct_mailbox_password', { server_id: sid, domain, email, password: pass, password2: pass2 }).done(res => {
if (res.success) {
$note.html('<span style="color:green">✓ Password updated!</span>');
setTimeout(() => $('#woocow-pw-modal').fadeOut(200), 1500);
} else {
$note.html(`<span class="woocow-error">${esc(res.data)}</span>`);
}
});
});
})(jQuery);